物件特性簡化與增強


在 JavaScript 中,使用底下的方式建立物件實字應該是屢見不鮮了:

var x;
var y;

// 經過一些計算得到 x、y 的值...

var o = {
    x : x,
    y : y
};

在 ES6 中現在可以直接寫:

let x;
let y;

// 經過一些計算得到 x、y 的值...

let o = {
    x,
    y
};

變數名稱會成為特性名稱,而變數值會成為特性值。當特性實際參考函式時,在過去會這麼寫:

var o = {
    doSome : function() {
        //...
    },
    doOther : function(param) {
        // ...
    }
};

ES6 可以簡單地寫為:

let o = {
    doSome() {
        //...
    },
    doOther(param) {
        // ...
    }
};

然而,簡單的寫法看似只有簡化,與傳統的寫法還有點不同,簡單的寫法中可以使用 super 關鍵字:

> let o = {
...       doABC() {
.....         return super.toString();
.....     }
... };
undefined
> o.doABC();
'[object Object]'
> var o = {
...       doABC : function() {
.....         return super.toString();
        return super.toString();
               ^^^^^

SyntaxError: 'super' keyword unexpected here

>  

super 是個關鍵字,不是個參考名稱,在可以使用 super 的場合,super 代表著物件的 __proto__,這之後會再討論。

在過去,如果打算讓特性名稱是計算後的結果,必須透過 [],例如:

var prefix = 'doSome';
var n = 1;
var o = {};
o[prefix + n] = function() {
    // ...
};

在 ES6 中,可以直接這麼作了:

let prefix = 'doSome';
let n = 1;
let o = {
    [prefix + n] : function() {
    }
};

或者是直接採用簡便模式:

let prefix = 'doSome';
let n = 1;
let o = {
    [prefix + n]() {
    }
};

在〈符號〉中曾經談過,Symbol 值是獨一無二的,如果物件上需要有個獨一無二的特性,可以使用 Symbol 值作為特性,這時就要搭配 [] 來指定。例如:

> let hook = Symbol('some method hook');
undefined
> let o = {
...     [hook]() {
.....       console.log('do some ....');
.....   }
... };
undefined
> o[hook];
[Function: [some method hook]]
> o[hook]();
do some ....
undefined
>

會使用 Symbol 作為特性,通常是作為物件間某個特定的協定,例如〈符號〉就曾看過,物件若要實現迭代器,會使用 Symbol.iterator 作為特性,因而其他想想要取得迭代器,就可以透過 Symbol.iterator

Symbol.toStringTag 也是個例子,在〈檢驗物件〉中看過,呼叫物件的 toString(),要傳回 '[object class]' 格式的字串,例如:

> let o = {};
undefined
> o.toString();
'[object Object]'
>

Object 實例會傳回 [object Object]、陣列會傳回 [object Array]、函式會傳回 [object Function] 等,在 ES6 中,若想指定 class 部份的描述,可以使用 Symbol.toStringTag 特性來指定:

> let o = {
...     [Symbol.toStringTag] : 'Foo'
... };
undefined
> o.toString();
'[object Foo]'
>

在〈不只是加減乘除的運算子〉中看過,如果運算過程牽涉到基本型態與物件,可以定義物件的 valueOf 方法,使之傳回可用於計算的基本型態,不過 valueOf 嚴格來說,並不是專用於轉換為基本型態時的物件協定,而是定義型態轉換發生時,應該傳回哪個值,因此它可以傳回任何型態。

如果你想要規範物件轉換為基本型態時,應當如何轉換,在 ES6 中有了個專門的符號 Symbol.toPrimitive 可用於設定特性,例如:

> let o = {
...     [Symbol.toPrimitive]() {
.....       return 10;
.....   }
... };
undefined
> 2 + o;
12
> o.valueOf();
{ [Symbol(Symbol.toPrimitive)]: [Function: [Symbol.toPrimitive]] }
>

可以看到,定義了 Symbol.toPrimitive 特性的物件,在必須轉換為基本型態的場合,就會使用該特性而不是 valueOf,而就算定義了 Symbol.toPrimitive 特性,該物件的 valueOf 預設仍是輸出 toString 的結果)。

當你定義一個建構式,用該建構式 new 出來的物件,在 instanceof 判斷時會是 true,而在〈檢驗物件〉中看過,ES5 中 instanceof 是根據原型鏈來查找。

而在 ES6 中,instanceof 是否為 true,還可藉由建構式的 Symbol.hasInstance 特性來決定:

> function Foo() {}
undefined
> let foo = new Foo();
undefined
> foo instanceof Foo;
true
> Foo[Symbol.hasInstance](foo);
true
>

建構式的 Symbol.hasInstance 特性是個函式,接受一個物件,判定該物件是否為此建構式的一個實例,建構式的 Symbol.hasInstance 特性之 writablefalse,如果想定義自己的 Symbol.hasInstance 特性,可以透過 Object.defineProperty

> function ArrayLike() {}
undefined
> Object.defineProperty(ArrayLike, Symbol.hasInstance, {
...     value : function(obj) {
.....     return obj.length !== undefined;
.....   }
... });
[Function: ArrayLike]
> let o1 = {};
undefined
> let o2 = {length : 0};
undefined
> o1 instanceof ArrayLike;
false
> o2 instanceof ArrayLike;
true
>

使用 Symbol 作為特性,無法使用 for (var prop in obj) 的方式迭代,然而,透過 Object.getOwnPropertyDescriptor 取得的特性描述中,enumerabletrue

> Object.getOwnPropertyDescriptor(o, Symbol.toStringTag);
{ value: 'Foo',
  writable: true,
  enumerable: true,
  configurable: true }
> for(let p in o) {
...     console.log(p);
... }
undefined
>

想要一次取得物件上使用 Symbol 的特性,可以透過 Object.getOwnPropertySymbols,例如:

> Object.getOwnPropertySymbols(o);
[ Symbol(Symbol.toStringTag) ]
> Object.getOwnPropertySymbols(Array.prototype);
[ Symbol(Symbol.iterator), Symbol(Symbol.unscopables) ]
>