[譯] Object.assign 和 Object Spread 之爭, 用誰?

藍色的秋風發表於2019-03-03

原文連結 thecodebarbarian.com/object-assi…

在 2018 年 Object Rest/Spread Proposal 達到了 stage 4,這意味著在未來它會將入到 ECMAScript 標準中。它也被加入到Node LTS. Node.js 8 以後的版本你可以使用它,所以你可以放心地開始使用它。

Object Spread 也可以叫做物件展開符,下文都以 Object Spread 來進行描述。

$ node -v
v8.9.4
$ node
> const obj = { foo: 1, bar: 1 };
undefined
> ({ ...obj, baz: 1 });
{ foo: 1, bar: 1, baz: 1 }
複製程式碼

Object Spread 和 Object.assign 在功能上很相似。你應該使用哪一個? 事實證明,答案比你想象的要微妙許多。

Object Spread 概論

Object Spread 運算子的基本思想是使用現有物件的自身屬性來建立新的普通物件。 所以{...obj} 建立一個和 obj 具有相同屬性的物件。 對於普通的舊 JavaScript 物件,你實際上是在建立一個obj副本。

const obj = { foo: 'bar' };
const clone = { ...obj }; // `{ foo: 'bar' }`
obj.foo = 'baz';
clone.foo; // 'bar'
複製程式碼

與object .assign()類似,Object spread 操作符不復制繼承的屬性或類的屬性。但是它會複製 ES6 的 symbols 屬性。

class BaseClass {
  foo() { return 1; }
}

class MyClass extends BaseClass {
  bar() { return 2; }
}

const obj = new MyClass();
obj.baz = function() { return 3; };
obj[Symbol.for('test')] = 4;

// Does _not_ copy any properties from `MyClass` or `BaseClass`
const clone = { ...obj };

console.log(clone); // { baz: [Function], [Symbol(test)]: 4 }
console.log(clone.constructor.name); // Object
console.log(clone instanceof MyClass); // false
複製程式碼

還可以使用 Object spread 操作符混合其他屬性。

順序問題: Object spread 操作符將覆蓋在它之前定義的屬性。

const obj = { a: 'a', b: 'b', c: 'c' };
{ a: 1, b: null, c: void 0, ...obj }; // { a: 'a', b: 'b', c: 'c' }
{ a: 1, b: null, ...obj, c: void 0 }; // { a: 'a', b: 'b', c: undefined }
{ a: 1, ...obj, b: null, c: void 0 }; // { a: 'a', b: null, c: undefined }
{ ...obj, a: 1, b: null, c: void 0 }; // { a: 1, b: null, c: undefined }
複製程式碼

和 Object.assign() 的區別

對於上面的例子,Object.assign()函式基本上可以與 Object spread 操作符互換。事實上,object spread spec 明確指出{... obj}等同於Object.assign({},obj)

const obj = { a: 'a', b: 'b', c: 'c' };
Object.assign({ a: 1, b: null, c: void 0 }, obj); // { a: 'a', b: 'b', c: 'c' }
Object.assign({ a: 1, b: null }, obj, { c: void 0 }); // { a: 'a', b: 'b', c: undefined }
Object.assign({ a: 1 }, obj, { b: null, c: void 0 }); // { a: 'a', b: null, c: undefined }
Object.assign({}, obj, { a: 1, b: null, c: void 0 }); // { a: 1, b: null, c: undefined }
複製程式碼

那麼你為什麼要使用其中一個呢?一個關鍵的區別是 Object spread 操作符總是給你一個POJO(Plain Ordinary JavaScript Object)。而Object.assign()函式卻修改其第一個傳入物件obj

class MyClass {
  set val(v) {
    console.log('Setter called', v);
    return v;
  }
}
const obj = new MyClass();

Object.assign(obj, { val: 42 }); // Prints "Setter called 42"
複製程式碼

換句話說,Object.assign()修改了一個物件,因此它可以觸發 ES6 setter。如果你更喜歡使用immutable技術,那麼 Object spread 操作符就是你更好的選擇。使用 Object.assign(),你必須確保始終將空物件{​​}作為第一個引數傳遞。

2019.02.12 補充說明,當一個 Object 使用了 Object.defineProperty 修改了 set 方法,因為呼叫 Object.assign 會觸發 setter 方法,會觸發意想不到的錯誤。

效能怎麼樣? 這是一些簡單的基準測試。如果將空物件作為第一個引數傳遞給Object.assign(),看起來 Object spread 會更快,但除此之外它們是可互換的。

下面是一個使用Object.assign()和in-place賦值的基準測試:

const Benchmark = require('benchmark');

const suite = new Benchmark.Suite;

const obj = { foo: 1, bar: 2 };

suite.
  add('Object spread', function() {
    ({ baz: 3, ...obj });
  }).
  add('Object.assign()', function() {
    Object.assign({ baz: 3 }, obj);
  }).
  on('cycle', function(event) {
    console.log(String(event.target));
  }).
  on('complete', function() {
    console.log('Fastest is ' + this.filter('fastest').map('name'));
  }).
  run({ 'async': true });

複製程式碼

在這種情況下,兩者是相似的:

Object spread x 3,170,111 ops/sec +-1.50% (90 runs sampled)
Object.assign() x 3,290,165 ops/sec +-1.86% (88 runs sampled)
Fastest is Object.assign()
複製程式碼

但是,一旦向Object.assign()輸入一個空物件引數,物件擴充套件運算子就會更快

suite.
  add('Object spread', function() {
    ({ baz: 3, ...obj });
  }).
  add('Object.assign()', function() {
    Object.assign({}, obj, { baz: 3 });
  })
複製程式碼

這是輸出:

Object spread x 3,065,831 ops/sec +-2.12% (85 runs sampled)
Object.assign() x 2,461,926 ops/sec +-1.52% (88 runs sampled)
Fastest is Object spread

複製程式碼

ESLint 配置

預設情況下,ESLint在解析層面禁止物件rest / spread運算子你需要在.eslintrc.yml中將parserOptions.ecmaVersion選項設定為至少9,否則你將得到一個解析錯誤。

parserOptions:
  # Otherwise object spread causes 'Parsing error: Unexpected token ..'
  ecmaVersion: 9
複製程式碼

ESLint新增了一個新的規則prefer-object-spread,它會強制你使用 Object spread 操作符 而不是Object.assign()。 要啟用此規則,請使用:

parserOptions:
  ecmaVersion: 9
rules:
  prefer-object-spread: error
複製程式碼

現在,如果您使用object .assign()而不是Object spread, ESLint將報告一個錯誤。

Use an object spread instead of `Object.assign` eg: `{ ...foo }`  prefer-object-spread
複製程式碼

最後

Object rest / spread運算子在語法更加簡潔,並且比Object.assign()提供了效能優勢。 如果你執行的是Node.js 8或更高版本,請嘗試使用這些新運算子,使程式碼更簡潔。

更多請關注

友情連結: huayifeng.top/

[譯] Object.assign 和 Object Spread 之爭, 用誰?

相關文章