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() 创建对象。这总是比先创建对象再设置原型的方式快。很明显,这种方式在想要改变已有对象的原型的时候是不行的。

results matching ""

    No results matching ""