14.3 新的 Object 方法
14.3.1 Object.assign(target, source_1, source_2, ···)
该方法将源数据( source )合并到目标数据( target )中去:会修改 target
,第一步拷贝 source_1
上所有的可枚举自有属性到 target
,然后是 source_2
,以此类推。最后,返回目标数据。
let obj = { foo: 123 };
Object.assign(obj, { bar: true });
console.log(JSON.stringify(obj));
// {"foo":123,"bar":true}
让我们更近距离地看下 Object.assign()
是如何运作的:
- 两种属性键:
Object.assign()
支持字符串和 Symbol 作为属性键。 - 仅操作可枚举的自有属性:
Object.assign()
忽略继承的属性和不可枚举的属性。 - 通过赋值来拷贝:在目标对象中的属性通过赋值(内部操作 [[Put]] )来创建。这意味着如果
target
上有(自有的或继承的) setters ,在拷贝的时候会被调用。另一种可选的定义新属性的方式就是始终创建新的属性,而不调用 setters 。本来有人提议让Object.assign()
也支持使用定义而不是赋值的方式,这个建议最终在 ECMAScript 6 中被拒绝了,但是可能在后面的版本中会重新考虑。 - 不能移动使用
super
的方法:这样的方法有一个内部属性[[HomeObject]]
,该属性将该方法与创建该方法的时候所在的对象绑定起来。如果通过Object.assign()
移动这个方法,该方法将会保持和原对象的这种绑定。详细内容在本章关于类的那一节讲解。
14.3.1.1 Object.assign()
的使用场景
让我们看几个使用场景。
14.3.1.1.1 给 this
添加属性
在构造器中可以使用 Object.assign()
给 this
添加属性:
class Point {
constructor(x, y) {
Object.assign(this, {x, y});
}
}
14.3.1.1.2 给对象属性提供默认值
Object.assign()
在给缺失的属性填充默认值的时候也很有用。在下面的例子中,有一个带有默认值的对象 DEFAULTS
和一个带有数据的对象 options
。
const DEFAULTS = {
logLevel: 0,
outputFormat: 'html'
};
function processContent(options) {
options = Object.assign({}, DEFAULTS, options); // (A)
···
}
在行 A ,创建了一个新的对象,把默认属性拷贝过来,然后把 options
上面的属性拷贝过来,覆盖默认值。 Object.assign()
返回这些操作的结果,然后赋值给 options
。
14.3.1.1.3 给对象添加方法
另一个使用场景就是给对象添加方法:
Object.assign(SomeClass.prototype, {
someMethod(arg1, arg2) {
···
},
anotherMethod() {
···
}
});
也可以手动地将函数赋值上去,但是这样写出来的代码就没那么漂亮了,并且每次都需要写一遍 SomeClass.prototype
:
SomeClass.prototype.someMethod = function (arg1, arg2) {
···
};
SomeClass.prototype.anotherMethod = function () {
···
};
14.3.1.1.4 克隆对象
最后一个 Object.assign()
的使用场景就是快速克隆对象:
function clone(orig) {
return Object.assign({}, orig);
}
这种克隆方式某种程度上来讲是不完美的,因为并不会保持 orig
的属性特性(可写、可枚举等)。如果这是你想要的,就必须使用属性描述器( property descriptors )了。
如果想让克隆出来的对象和原对象有相同的原型,可以使用 Object.getPrototypeOf()
和 Object.create()
:
function clone(orig) {
let origProto = Object.getPrototypeOf(orig);
return Object.assign(Object.create(origProto), orig);
}
14.3.2 Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols(obj)
获取 obj
上所有自有的为 Symbol 的键。它补充了 Object.getOwnPropertyNames()
,这个方法获取自有的字符串形式的键。后面的一节中详细讲解了迭代属性键的知识。
14.3.3 Object.is(value1, value2)
严格相等操作符(===)处理两个值的结果可能不是想象中的那样。
首先, NaN
和自身不相等。
> NaN === NaN
false
这很不幸,因为这经常阻止我们检测 NaN
:
> [0,NaN,2].indexOf(NaN)
-1
其次, JavaScript 有两种零值,但是在严格模式下处理结果看起来似乎是同一个值:
> -0 === +0
true
这样做通常情况下是好的。
Object.is()
提供了一种相对于 ===
明确一点的比较值的方式。如下所示:
> Object.is(NaN, NaN)
true
> Object.is(-0, +0)
false
其它所有的比较结果都和 ===
一样。
14.3.3.1 使用 Object.is()
查找数组元素
如果将 Object.is()
和新的 ES6 数组方法 findIndex()
结合起来,可以找到数组中的 NaN
:
function myIndexOf(arr, elem) {
return arr.findIndex(x => Object.is(x, elem));
}
myIndexOf([0,NaN,2], NaN); // 1
相比之下, indexOf()
并不能很好地处理 NaN
:
> [0,NaN,2].indexOf(NaN)
-1
14.3.4 Object.setPrototypeOf(obj, proto)
该方法将 obj
的原型设置为 proto
。在 ECMAScript 5 中有一种非标准的方式,这种方式被很多引擎支持,就是通过给特殊的属性 __proto__
赋值。推荐的设置原型的方式还是与在 ECMAScript 5 中使用的一样:通过 Object.create()
创建对象。这总是比先创建对象再设置原型的方式快。很明显,这种方式在想要改变已有对象的原型的时候是不行的。