新的方法

ESES5 开始就有一个设计意图:避免创建新的全局函数,避免在 Object 对象的原型上添加新方法,而是尝试寻找哪些对象应该被添加新方法。因此,对其他对象不适用的新方法就被添加到全局的 Object 对象上。ES6Object 对象上引入了两个新方法,以便让特定任务更易完成。

Object.is() 方法

当在 JS 中要比较两个值时,你可能会使用相等运算符(==)或严格相等运算符(===)。为了避免在比较时发生强制类型转换,许多开发者更倾向于使用后者。但严格相等运算符也并不完全准确,例如,它认为 +0-0 相等,即使这两者在 JS 引擎中有不同的表示;另外 NaN === NaN 会返回 false,因此有必要使用 isNaN() 函数来正确检测 NaN

ES6 引入了 Object.is() 方法来弥补严格相等运算符残留的怪异点。此方法接受两个参数,并会在二者的值相等时返回 true,此时要求二者类型相同并且值也相等。这有个例子:

console.log(+0 == -0);              // true
console.log(+0 === -0);             // true
console.log(Object.is(+0, -0));     // false

console.log(NaN == NaN);            // false
console.log(NaN === NaN);           // false
console.log(Object.is(NaN, NaN));   // true

console.log(5 == 5);                // true
console.log(5 == "5");              // true
console.log(5 === 5);               // true
console.log(5 === "5");             // false
console.log(Object.is(5, 5));       // true
console.log(Object.is(5, "5"));     // false

在许多情况下,Object.is() 的结果与 === 运算符是相同的,仅有的例外是:它会认为 +0-0 不相等,而且 NaN 等于 NaN。不过仍然没必要停止使用严格相等运算符,选择 Object.is(),还是选择 =====,取决于代码的实际情况。

Object.assign() 方法

混入(Mixin)是在 JS 中组合对象时最流行的模式。在一次混入中,一个对象会从另一个对象中接收属性与方法。很多 JS 的库中都有类似下面的混入方法:

function mixin(receiver, supplier) {
    Object.keys(supplier).forEach(function(key) {
        receiver[key] = supplier[key];
    });

    return receiver;
}

mixin() 函数在 supplier 对象的自有属性上进行迭代,并将这些属性复制到 receiver 对象(浅复制,当属性值为对象时,仅复制其引用)。这样 receiver 对象就能获得新的属性而无须使用继承,正如下面代码:

function EventTarget() { /*...*/ }
EventTarget.prototype = {
    constructor: EventTarget,
    emit: function() { /*...*/ },
    on: function() { /*...*/ }
};

var myObject = {};
mixin(myObject, EventTarget.prototype);

myObject.emit("somethingChanged");

此处 myObject 对象接收了 EventTarget.prototype 对象的行为,这给了它分别使用 emit()on() 方法来发布事件与订阅事件的能力。

此模式已经足够流行,于是 ES6 就添加了 Object.assign() 方法来完成同样的行为。该方法接受一个接收者,以及任意数量的供应者,并会返回接收者。方法名称从 mixin() 变更为 assign() 更能反映出实际发生的操作。由于 mixin() 函数使用了赋值运算符(=),它就无法将访问器属性复制到接收者上Object.assign() 体现了这种区别。

各式各样的库中都有相似但名称不同的方法,其基本功能相同,流行的替代方法包括 extend()mix()。而 ES6 也曾在 Object.assign() 之外短暂存在一个 Object.mixin() 方法,二者的主要差异在于 Object.mixin() 也会复制访问器属性,但考虑到 super 的使用(详见本章的 “使用 super 引用的简单原型访问” 小节),此方法最终被移除了。

你可以在任意曾使用 mixin() 函数的地方使用 Object.assign(),此处有个例子:

function EventTarget() { /*...*/ }
EventTarget.prototype = {
    constructor: EventTarget,
    emit: function() { /*...*/ },
    on: function() { /*...*/ }
};

var myObject = {};
mixin(myObject, EventTarget.prototype);

myObject.emit("somethingChanged");

Object.assign() 方法接受任意数量的供应者,而接收者会按照供应者在参数中的顺序来依次接收它们的属性。这意味着在接收者中,第二个供应者的属性可能会覆盖第一个供应者的,这在下面的代码片段中就发生了:

var receiver = {};

Object.assign(receiver,
    {
        type: "js",
        name: "file.js"
    },
    {
        type: "css"
    }
);

console.log(receiver.type);     // "css"
console.log(receiver.name);     // "file.js"

receiver.type 的值为 "css",这是因为第二个供应者覆盖了第一个供应者的值。

Object.assign() 方法并不是 ES6 的一项重大扩展,但它确实将很多 JS 库中的一个公共方法标准化了。

操作访问器属性

需要记住 Object.assign() 并未在接收者上创建访问器属性,即使供应者拥有访问器属性。由于 Object.assign() 使用赋值运算符,供应者的访问器属性就会转变成接收者的数据属性,例如:

var receiver = {},
supplier = {
    get name() {
        return "file.js"
    }
};

Object.assign(receiver, supplier);

var descriptor = Object.getOwnPropertyDescriptor(receiver, "name");

console.log(descriptor.value); // "file.js"
console.log(descriptor.get); // undefined

此代码中的 supplier 对象拥有一个名为 name 的访问器属性。在使用了 Object.assign() 方法后,receiver.name 就作为一个数据属性存在了,其值为 "file.js",这是因为在调用 Object.assign() 时,supplier.name 返回的值是 "file.js" 。