「Object.assign」详解 之 源码彻底吃透
# Object.assign是什么?
我们先了解一下Object.assign是什么?
MDN的定义:Object.assign() 方法将所有可枚举和自有属性从一个或多个源对象复制到目标对象,返回目标对象。
# Object.assign的使用
Object.assign(target, ...sources)
参数
target
目标对象,接收源对象属性的对象,也是修改后的返回值。
sources
源对象,包含将被合并的属性。
返回值
- 目标对象 (返回的是目标对象,即第一个参数!)
# 关于深拷贝
如果需要深拷贝,则需要使用其他办法,因为 Object.assign()
对于源对象里的引用数据类型是无法进行深拷贝的。假如源对象里是一个对象的引用,它仅仅会复制其引用值,并不会重新开辟一个新的内存地址。
可以理解为,合并之后源对象里的引用数据类型是浅拷贝,基本数据类型是深拷贝。
let obj1 = { a: 0 , b: { c: 0}};
let obj2 = Object.assign({}, obj1);
console.log(JSON.stringify(obj2)); // { "a": 0, "b": { "c": 0}}
obj1.a = 1;//改变对象中的基本数据类型 双方不影响
console.log(JSON.stringify(obj1)); // { "a": 1, "b": { "c": 0}}
console.log(JSON.stringify(obj2)); // { "a": 0, "b": { "c": 0}}
obj2.b.c = 3;//改变对象中的引用数据类型 双方都变了
console.log(JSON.stringify(obj1)); // { "a": 1, "b": { "c": 3}}
console.log(JSON.stringify(obj2)); // { "a": 2, "b": { "c": 3}}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
所以说Object.assign()
并不适用于深拷贝;可以用JSON.parse(JSON.stringify())
或者 递归的方式进行数据的深拷贝。
# 合并对象
const o1 = { a: 1 };
const o2 = { b: 2 };
const o3 = { c: 3 };
const obj = Object.assign(o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 } -- 返回目标对象o1
console.log(o1); // { a: 1, b: 2, c: 3 } -- o1目标对象已经被改变了,不再是{ a: 1 };
console.log(obj == o1); // true
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
如果我们想不改变源对象可以这样合并对象
const o1 = { a: 1 };
const o2 = { b: 2 };
const obj = Object.assign({},o1, o2);
console.log(obj); // { a: 1, b: 2 } -- 返回合并过后的目标对象
console.log(o1); // { a: 1} -- 源对象o1没有发生变化
console.log(obj == o1); // false
1
2
3
4
5
6
7
2
3
4
5
6
7
# 合并具有相同属性的对象
- 合并相同属性的源对象,合并过程中后面的源对象属性会替换前面源对象的属性
const o1 = { a: 1, b: 1, c: 1 };
const o2 = { b: 2, c: 2 };
const obj = Object.assign({}, o1, o2);
console.log(obj); // { a: 1, b: 2, c: 2 }
1
2
3
4
5
2
3
4
5
# 原型链上的属性和不可枚举属性不能被复制
const obj = Object.create({ foo: 1 }, { // foo 是 obj 的原型链
bar: {
value: 2 // bar 是不可枚举属性,因为没有设置enumerable,默认为不可枚举
},
baz: {
value: 3,
enumerable: true // baz是可枚举的
}
});
const copy = Object.assign({}, obj);
console.log(copy); // { baz: 3 } bar没有合并过来
//什么是不可枚举?简单理解为是不能够一个个地列举
let keys = []
for (let key in obj) { //不可枚举的属性则不能遍历
keys.push(key)
}
console.log(keys);//['bat', 'foo'] bar则没有遍历出来
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 基本类型会被包装为对象
const v1 = 'abc';
const v2 = true;
const v3 = 10;
const v4 = Symbol('foo');
const obj = Object.assign({}, v1, null, v2, undefined, v3, v4);
// null 和 undefined 会被忽略
// 只有字符串包装器才能有自己的可枚举属性
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 手写源码
function objectAssign(target) {
if (arguments.length === 1) return target
let source = Array.prototype.slice.call(arguments, 1)
source.map(object => {
for (const key in object) {
if (Object.hasOwnProperty.call(object, key)) {
target[key] = object[key];
}
}
})
return target
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# Object.assign的兼容性
# Object.assign的注意事项
- 合并时,目标对象自身也会改变
- 拷贝是浅拷贝,不能作为深拷贝
- 继承属性和不可枚举属性不能拷贝
- 如果有同名属性的话,后面的属性值会覆盖前面的属性值
- 异常会打断后续拷贝任务
如有小伙伴发现错误之处欢迎指正🤚
上次更新: 2022/12/02, 09:00:55