ECMAScript 6 中最主要的 OOP 新特性是類,除此之外,也包含了一些物件字面量(object literals)的新特性和物件新的可用方法。本文將主要描述這些內容。
物件字面量的新特性
方法定義
在 ECMAScript 5中,方法是函式值的屬性:
1 2 3 4 5 |
var obj = { myMethod: function () { ··· } }; |
在 ECMAScript 6中,方法依舊是函式值的屬性,但現在定義方式更簡潔了:
1 2 3 4 5 |
let obj = { myMethod() { ··· } }; |
Getters 和 setters 依舊像 ECMAScript 5 那樣使用(注意方法定義的語法相似度)
1 2 3 4 5 6 7 8 9 10 |
let obj = { get foo() { console.log('GET foo'); return 123; }, set bar(value) { console.log('SET bar to '+value); // return value is ignored } }; |
讓我們使用obj:
1 2 3 4 5 6 |
> obj.foo GET foo 123 > obj.bar = true SET bar to true true |
還有一種簡明方式來定義生成函式值的屬性:
1 2 3 4 5 |
let obj = { * myGeneratorMethod() { ··· } }; |
這段程式碼相當於:
1 2 3 4 5 |
let obj = { myGeneratorMethod: function* () { ··· } }; |
屬性值簡寫
屬性值簡寫能讓你將屬性定義簡寫成物件字面量:如果變數的名稱指定的屬性值也是屬性鍵,你就可以省略鍵,如下所示:
1 2 3 |
let x = 4; let y = 1; let obj = { x, y }; |
最後一行相當於:
1 |
let obj = { x: x, y: y }; |
屬性值簡寫在重構方面也非常有用:
1 2 3 4 |
let obj = { x: 4, y: 1 }; let {x,y} = obj; console.log(x); // 4 console.log(y); // 1 |
屬性值簡寫的另外一個例子就是多值返回。
可計算的屬性值
這裡有兩種設定屬性指定屬性鍵的方式:
- 通過一個固定的名字:obj.foo = true
- 通過一個表示式:
obj['b'+'ar'] = 123
使用物件字面量時, ECMAScript 5只能選擇第一種,而 ECMAScript 6則增加了第二種的選擇:
1 2 3 4 5 |
let propKey = 'foo'; let obj = { [propKey]: true, ['b'+'ar']: 123 }; |
這個新語法同樣也可以結合方法定義:
1 2 3 4 5 6 |
let obj = { ['h'+'ello']() { return 'hi'; } }; console.log(obj.hello()); // hi |
可計算的屬性值的主要用法就是標誌( symbols):你可以定義一個公共的標誌 (symbols)並將其作為一個特殊的屬性鍵。一個典型的例子就是將標誌(symbols)儲存在Symbol.iterator,如果物件有關於這個鍵的方法,就能成為可迭代的。這個方法返還一個用來迭代物件並使用 for-of 迴圈構造的迭代器。就像以下程式碼這樣:
1 2 3 4 5 6 7 8 9 10 11 12 |
let obj = { * [Symbol.iterator]() { // (A) yield 'hello'; yield 'world'; } }; for (let x of obj) { console.log(x); } // Output: // hello // world |
A行是生成方法定義了一個可計算的鍵(標誌(symbols)儲存在Symbol.iterator)。
物件的新方法
Object.assign(target, source_1, source_2, ···)
這個方法將source合併到target:這會改變target的值,首先複製列出所有source_1的屬性,再到source_2的屬性,等等,最後將返回target。
1 2 3 4 |
let obj = { foo: 123 }; Object.assign(obj, { bar: true }); console.log(JSON.stringify(obj)); // {"foo":123,"bar":true} |
讓我們仔細看看 Object.assign()
是怎麼運作的:
- 支援兩種型別的屬性鍵:
Object.assign()支援將 strings 和標誌(symbols)作為屬性鍵
- 只能列舉自己的屬性:Object.assign()忽略屬性繼承和不列舉屬性。
- 通過賦值來複制:target物件中的屬性是通過賦值建立的(內部操作[[put]])這意味著如果target有(自己的或者繼承的)setters,將在複製的時候被呼叫。另一種方式是定義新屬性,可以不用呼叫setter直接建立自己的新屬性,最初是建議使用Object.assign()的變體來定義以取代賦值,這個提議已經被ECMAScript 6否決,但也有可能在更後面的版本中重新考慮。
Object.assign()的使用案例
讓我們來看看幾個使用案例,你可以使用Object.assign()在構造器中給this新增屬性:
1 2 3 4 5 |
class Point { constructor(x, y) { Object.assign(this, {x, y}); } } |
Object.assign()在為缺失屬性補充預設值也同樣有用,在以下例子中,DEFAULTS 物件有屬性的預設值和物件選項:
1 2 3 4 5 6 7 8 |
const DEFAULTS = { logLevel: 0, outputFormat: 'html' }; function processContent(options) { let options = Object.assign({}, DEFAULTS, options); // (A) ··· } |
在A行,我們建立了一個新的物件,先複製defaults再複製options,並覆蓋defaults, Object.assign()將返回我們賦給options的值來作為操作的結果。
另一個使用案例是給物件新增方法:
1 2 3 4 5 6 7 8 |
Object.assign(SomeClass.prototype, { someMethod(arg1, arg2) { ··· }, anotherMethod() { ··· } }); |
你也可以給函式賦值,但這不是好的方法定義語法,因為每次都需要使用 SomeClass.prototype。
1 2 3 4 5 6 |
SomeClass.prototype.someMethod = function (arg1, arg2) { ··· }; SomeClass.prototype.anotherMethod = function () { ··· }; |
最後一個Object.assign()的使用案例是拷貝( cloning)物件一種快捷的方式。
1 2 3 |
function clone(orig) { return Object.assign({}, orig); } |
這種拷貝方式比較糟糕的一點是不能儲存oirg的屬性值,如果你需要這些值的話,你需要使用 property descriptors。
如果你希望拷貝的時候有原始物件一樣的原型,你可以使用 useObject.getPrototypeOf 和
Object.create()
1 2 3 4 |
function clone(orig) { let origProto = Object.getPrototypeOf(orig); return Object.assign(Object.create(origProto), orig); } |
Object.getOwnPropertySymbols(obj)
在ECMAScript 6,屬性鍵可以是 string 或者標誌(symbol),現在有五個工具方法來獲取obj物件的屬性鍵:
Object.keys(obj)
→Array<string>
獲取可列舉的自己屬性的string屬性鍵的全部值Object.getOwnPropertyNames(obj)
→Array<string>
獲取自己屬性的string屬性鍵的全部值Object.getOwnPropertySymbols(obj)
→Array<symbol>
獲取自己屬性的標誌(symbol)屬性鍵的全部值Reflect.ownKeys(obj)
→Array<string|symbol>
獲取自己屬性的屬性鍵的全部值Reflect.enumerate(obj)
→Iterator
獲取可列舉的string屬性鍵的全部值
Object.is(value1, value2)
嚴格等於操作符(===)比較兩個不同的值的時候有時候會與你預期的不同。
首先,NaN不等於NaN
1 2 |
> NaN === NaN false |
不幸的是,這會阻礙我們檢測到NaN
1 2 |
> [0,NaN,2].indexOf(NaN) -1 |
其次,JavaScript 有兩個0,但是等於操作符將其看做相等的,
1 2 |
> -0 === +0 true |
這樣做通常是件好事:
Object.is()提供一個比===更精確的比較值的方式,如下所示:
1 2 3 4 |
> Object.is(NaN, NaN) true > Object.is(-0, +0) false |
其他的將使用===來比較
如果我們將Object.is()和 ECMAScript 6的新陣列方法findIndex()結合使用,我們將在陣列中找到NaN:
1 2 |
> [0,NaN,2].findIndex(x => Object.is(x, NaN)) 1 |
Object.setPrototypeOf(obj, proto)
這個方法是將obj的原型設定為proto,在ECMAScript 5標準中不提供這樣的做法,但這個做法得到一些引擎的支援,並通過一個特定的屬性 __proto__ 來賦值。推薦設定原型的方法跟 ECMAScript 5是相同的:在建立物件的過程中,使用Object.create()將更快的建立的物件和設定原型,但很明顯的是,他不能改變已存在物件的原型。