Object.assign

assign

在ES6中新增的对象的扩展方法中,该方法可以实现复制一个或者多个对象到target对象中。

函数原型

关于该函数的定义是:

第一个参数为目标对象,第二个是源对象(可以是多个对象)。通过调用爱函数可以复制所有可被枚举的自有属性到目标对象中。

1
Object.assign(target,...source);

关于该函数,强调三点:

  1. 可以被枚举的属性;
  2. 自有属性;
  3. string或Symbol类型是可以直接分配的;

如果只有一个参数,那么Object.assign将会直接返回该参数。如果该参数不是对象,则转换为对象后返回。

1
console.log(typeof(Object.assign(2))); //"object"

由于undefinednull无法被包装成为对象,所以不能作为首参数使用。但是可以作为源对象使用。

其他基本类型的值(数值、字符串和布尔值)不在首参数时,不会报错。但是只有字符串会以数组形式复制进入目标对象,其他的基本类型值不会产生这样的效果。

1
2
3
4
5
6
var v1 = 'abc';
var v2 = true;
var v3 = 10;

var obj = Object.assign({}, v1, v2, v3);
console.log(obj);// { "0": "a", "1": "b", "2": "c" }

因为只有字符串类型的包装对象可以产生可枚举属性,布尔值与数值的包装对象无法经过包装对象生成可枚举属性。

1
2
3
Object(true) // {[[PrimitiveValue]]: true}
Object(10) // {[[PrimitiveValue]]: 10}
Object('abc') // {0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"}

布尔值、数值的转换为对应的包装对象后,原始值都在包装对象的内部属性[[PrimitiveValue]]中,该属性不会被Object.assign拷贝。只有字符串的包装对象会产生可枚举的实义属性。

对于属性名为Symbol止的属性,也可以被Object.assign拷贝。

1
Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' }) // { a: 'b', Symbol(c): 'd' }

Object.assign拷贝的属性是有限制的,不可以复制不可枚举属性,只会拷贝源对象的自身属性(不拷贝继承属性)。

注意:通过Object.defineProperty定义的对象属性,默认是不可枚举的,即enumerable:false。对于可枚举的对象,可以直接使用Object.keys()获得,或者使用for-in循环遍历。

对于不可枚举的属性,使用Object.assign复制时会被直接忽略。

1
2
3
4
5
6
7
8
9
10
11
12
var obj = Object.create({ foo: 1 }, { // foo is an inherit property.
bar: {
value: 2 // bar is a non-enumerable property.
},
baz: {
value: 3,
enumerable: true // baz is an own enumerable property.
}
});

var copy = Object.assign({}, obj);
console.log(copy); // { baz: 3 }

只可浅复制

复制对象

如果源对象某个属性值是对象,那么目标对象复制得到的是这个对象的引用。

1
2
3
4
5
6
7
8
9
10
11
var objC = {
a:{
b:1,
c:[{
ca:"12"
},9,92]
}
};
var objD = Object.assign({},objC);
objC.a.b = 29;
console.log(objD.a.b);//29

对于复制源对象的任何变化,都会反映到目标对象中。对于嵌套的对象,一旦遇到同名属性Object.assign的处理方法是直接替换,而不是添加。所以在以上的代码中,objD.a.b的属性值发生了变化。

复制数组

Object.assign可以处理数组,但是会把数组视为对象。

1
Object.assign([1,2,3],[4,5]);//[4,5,3]

这是因为数组视为属性名为0,1,2的对象。

如果一定要使用它来实现深复制,就只能借助其他的函数库,比如:deep-merge

常见用途

1. 为对象添加属性

1
2
3
4
5
6
class Point {
constructor(x, y) {
Object.assign(this, {x, y});
}
}
console.log(new Point(1,909));

通过该方法可以将x属性和y属性添加到Point类的对象实例。

2. 为对象添加方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Object.assign(SomeClass.prototype,{
someMethod(arg1,arg2){
//...
},
annotherMethod(){
//...
}
});

//等同于下面的写法
SomeClass.prototype.someMethod = function(arg1,arg2){
//...
};
SomeClass.prototype.annotherMethod = function(){
//...
};

3. 克隆对象

1
2
3
4
5
6
7
8
9
function clone(orign){
return Object.assign({},origin);
}


function clone(origin){
let originProto = Object.getPrototypeOf(origin);
return Object.assign(Object.create(originProto),origin);
}

采用第一种方法只能复制原始对象自身的值,不能复制继承的值。如果要保持继承链,可以使用第二种方法,getPropertyOf可以获取到原型链上的属性,包括不可枚举的属性。

4. 合并对个对象

将多个对象合并到某个对象

1
2
3
4
5
6
let objR = {
b : 2,
c : [12,9,3,3]
};
const merge = (target,...source) => Object.assign(target,...source);
console.log(merge(objR,{a:9,b:"oi"}));

如果希望合并后返回一个新对象,可以改写上面的函数,对一个空对象合并。

1
2
3
4
5
6
let objR = {
b : 2,
c : [12,9,3,3]
};
const merge = (...source) => Object.assign({},...source);
console.log(merge(objR,{a:9,b:"oi"}));

5. 为属性指定默认值

1
2
3
4
5
6
7
8
9
const DEFAULTS = {
logLevel : 0,
outputFormat : "html"
};
function processContent(options){
options = Object.assign({},DEFAULTS,options);
console.log(options);
}
processContent({logLevel : 9});//logLevel : 9,outputFormat : "html"

DEFAULTS是默认值,options是用户提供的参数,Object.assign将两个对象合并为新对象,如果具有同名属性,那么options的属性会覆盖默认属性值。

注意:由于浅复制的问题,DEFAULTS对象和options对象的所有属性值最好都是简单类型。不要指向另一个对象,否则默认属性值可能会失效。

1
2
3
4
5
6
7
8
9
10
11
const DEFAULTS = {
url: {
host: 'example.com',
port: 7070
}
};
function processContent(options){
options = Object.assign({},DEFAULTS,options);
console.log(options);
}
processContent({ url: {port: 8000} });

此时得到的options只有url: {port: 8000},url的host不存在了。