ES6中的Metaprogramming: Symbols 為什么令人驚嘆

ecfire 8年前發布 | 9K 次閱讀 JavaScript開發 ECMAScript

你聽說過es6對吧?他是在很多方面令人驚嘆的 javascript 新版本。我常常興高采烈地談論我在 ES6 中發現的令人難以置信的新功能,這使很多同事感到懊惱 (因為似乎并不是每個人都喜歡,別人消費自己的午餐休息來談論 ES6 模塊的)。

ES6中一個很不錯的新功能是帶來了一組大量新的元編程工具形式,提供了一組低級 hooks 到代碼的方法。因為我的同事名們已經在寫(ES6)的并不多,所以我想我會做一個很細致的帖子給他們(順便說一下:因為我很懶,這篇文章已經在我的草稿箱里呆了三個月,完成 90% ,從和他們說了以后,我寫的稍微多了些, 已經寫的 ):

第一部分: Symbols (這個帖子) 第二部分: Reflect 第三部分: Proxies

元編程( Metaprogramming)

首先, 讓我們快速帶過,發現元編程的奇妙世界。元編程是所有關于機器語言的基本機制,而不是“高級”數據建模或業務邏輯。如果編程可以被形容為為“制造程序”, 元編程就應該被描述為“讓程序制作程序” 或者其他東西。你也許每天都在使用元編程,只是可能沒有注意到它。

元編程有一些“亞種” , 一個是 代碼生成 (Code Generation) , 又名 eval , JavaScript自從成立以來就存在 (JS 在ES1就有 eval , 甚至在 try / catch 或 switch 聲明之前)。 幾乎所有你今天可以使用的其他語言都有 Code Generation 功能。

元編程的另一個特點是 反射 (Reflection) :找出并調整應用程序的語義和結構。JavaScript 有相當多與反射相關的方法。如 Functions 中的 Function#name 、 Function#length , 以及 Function#bind 、 Function#call 和 Function#apply . Object上的所有可用方法都是 Reflection , 例如: Object.getOwnProperties (另外,不修改代碼但反而收集有關它的信息的 Reflection 工具通常稱為 Introspection ).我們也有 Reflection / Introspection 操作符, 比如 typeof 、 instanceof 和 delete .

反射是元編程中很酷的一部分,因為他允許你改變應用內部的工作原理。以Ruby舉個例子,在Ruby中,你可以指定運算符作為方法,它允許你覆蓋這些運算符在類中使用時的工作方式(有時稱為“運算符重載”)。

class BoringClass
end
class CoolClass
  def ==(other_object)
   other_object.is_a? CoolClass
  end
end
BoringClass.new == BoringClass.new #=> false
CoolClass.new == CoolClass.new #=> true!

與其他語言(如Ruby或Python)相比,Javascript的元編程特性是不夠高級的,尤其是在操作符重載等工具方面,但ES6開始確實在這方面有所改進。

ES6 中的元編程

ES6中有三種新類型的API: Symbol , Reflect 和 Proxy 。 第一眼看可能有點迷惑,三個單獨的API都用于元編程的么?但是當你分開看每一個的時候,確實會讓你有這種感覺。

  • Symbols 都是關于 Reflection within implementation -- 把它們用在你現有的類和對象上來改變行為。
  • Reflect 都是關于 Reflection through introspection -- 用于發現你代碼中非常低級的信息。
  • Proxy 都是關于 Reflection through intercession -- 包裝對象和通過“圈套”截取他們的行為。

所以它們是怎樣工作的呢?它們怎么有用?這篇文章將講述 Symbols ,而其余的兩篇文章將分別講述 Reflect 和 Proxy 。

Symbols - Reflection within Implementation

Symbols 是一個新的基本類型. 就像 Number , String , 和 Boolean 基本類型, Symbols 有一個 Symbol 方法可以用于創建它。不同于其他基本類型,Symbols 沒有字符串語法(例如字符換有“"”),唯一的使用方法是Symbol構造函數,而且不能創建實例。

Symbol(); // symbol
console.log(Symbol()); // prints "Symbol()" to the console
assert(typeof Symbol() === 'symbol')
new Symbol(); // TypeError: Symbol is not a constructor

Symbols 具有內置的可調試性

Symbols 可以給出一個描述,這實際上只是用于調試,這樣打印日志到控制臺會使開發更容易。

console.log(Symbol('foo')); // prints "Symbol(foo)" to the console.
assert(Symbol('foo').toString() === 'Symbol(foo)');

Symbols 可以作為 Object的keys 使用

Symbols與對象密切相關。 他們可以作為keys 分配給對象(類似于String 的keys),這意味著你可以分配無數個唯一的 Symbols 到一個對象,并保證這些 Symbols 不會與字符串鍵或其他唯一符號沖突,這是 Symbols 真正有意思的地方。

var myObj = {};
var fooSym = Symbol('foo');
var otherSym = Symbol('bar');
myObj['foo'] = 'bar';
myObj[fooSym] = 'baz';
myObj[otherSym] = 'bing';
assert(myObj.foo === 'bar');
assert(myObj[fooSym] === 'baz');
assert(myObj[otherSym] === 'bing');

除此之外, Symbols 不會展示在使用 for in 、 for of 或者 Object.getOwnPropertyNames 的對象上,在對象中獲取符號的唯一方法是 Object.getOwnPropertySymbols :

var fooSym = Symbol('foo');
var myObj = {};
myObj['foo'] = 'bar';
myObj[fooSym] = 'baz';
Object.keys(myObj); // -> [ 'foo' ]
Object.getOwnPropertyNames(myObj); // -> [ 'foo' ]
Object.getOwnPropertySymbols(myObj); // -> [ Symbol(foo) ]
assert(Object.getOwnPropertySymbols(myObj)[0] === fooSym);

這意味著Symbol給對象一個全新的感覺,它們為對象提供了一種隱藏屬性,不能迭代,不能使用已經存在的反射工具獲取,并保證不與對象中的其他屬性沖突!

Symbols 是完全唯一的

默認情況下,每個新的符號具有完全唯一的值。如果你創建一個了 symbol( var mysym = Symbol() ),它會在 JavaScript引擎創建一個全新的值。如果你沒有Symbol的引用,你就不能使用它。這也意味著兩個symbols 將永遠不會等于相同的值,即時他們有相同的描述。

assert.notEqual(Symbol(), Symbol());
assert.notEqual(Symbol('foo'), Symbol('foo'));
assert.notEqual(Symbol('foo'), Symbol('bar'));

var foo1 = Symbol('foo');
var foo2 = Symbol('foo');
var object = {
    [foo1]: 1,
    [foo2]: 2,
};
assert(object[foo1] === 1);
assert(object[foo2] === 2);

除非他們不是

嗯,這有一個小小的警告,因為還有另一種方法使 Symbols,可以很容易抓取和重新使用: Symbol.for() 。這個方法在 global Symbol registry 創建了一個 Symbols 。順便說一下:這個 registry 是跨域的,這意味著來自同一個框架或者服務工作的 Symbol 將與從現有框架生成的 Symbol 相同。

assert.notEqual(Symbol('foo'), Symbol('foo'));
assert.equal(Symbol.for('foo'), Symbol.for('foo'));

// Not unique:
var myObj = {};
var fooSym = Symbol.for('foo');
var otherSym = Symbol.for('foo');
myObj[fooSym] = 'baz';
myObj[otherSym] = 'bing';
assert(fooSym === otherSym);
assert(myObj[fooSym] === 'bing');
assert(myObj[otherSym] === 'bing');

// Cross-Realm
iframe = document.createElement('iframe');
iframe.src = String(window.location);
document.body.appendChild(iframe);
assert.notEqual(iframe.contentWindow.Symbol, Symbol);
assert(iframe.contentWindow.Symbol.for('foo') === Symbol.for('foo')); // true!

擁有全局 Symbols 使事情更復雜,但好的方面是,我們會(容易)獲得它。現在你們有些人可能會說“哎呀!?我怎么知道哪個 Symbols 是唯一的Symbols,哪個不是?對于這個問題,我覺得“沒關系,不會有什么壞事情發生,因為我們有 Symbol.keyFor() ”

var localFooSymbol = Symbol('foo');
var globalFooSymbol = Symbol.for('foo');

assert(Symbol.keyFor(localFooSymbol) === undefined);
assert(Symbol.keyFor(globalFooSymbol) === 'foo');
assert(Symbol.for(Symbol.keyFor(globalFooSymbol)) === Symbol.for('foo'));

Symbols 是什么,Symbols 不是什么

所以我們對于什么是 Symbols得到了一個不錯的概念,并且了解了它是如何工作的。但同樣重要的是知道對于Symbols 什么是好的,什么是不好的,這樣很容易假定他們不是什么:

  • Symbols永遠不會與對象字符串鍵沖突.這讓他們非常適合集成你已經給出的對象(例如 作為函數的 param)而不會以明顯的方式影響對象。

  • Symbols 不能使用已有的反射工具讀取。你需要新的 Object.getOwnPopertySymbols() 來存取一個對象的Symbols,這讓Symbols 更好的存儲一塊你不想讓別人通過正常運行得到的信息。使用 Object.getOwnPropertySymbols() 是一個非常特殊的用例。

  • Symbols 不是私有的。另一方面,所有的對象中的Symbols,都可以使用 Object.getOwnSymbols() 獲取到,對于真正私有的值并不是非常有效。不要嘗試在對象中使用 symbol存儲你想要真正私有的信息,他是可以被獲取的。

  • 能使用像 Object.assign 這樣的新方法把可枚舉的 Symbols復制給其他對象 。如果你嘗試調用 Object.assign(newObject, objectWithSymbols) ,第二個參數( objectWithSymbols )中所有的(可枚舉)的Symbols 將被復制到第一個參數 ( newObject )。如果過你不想這種情況發生,用 Object.defineProperty 使他們不可枚舉,

  • Symbols 不強制轉換為原始值。如果你嘗試轉換一個Symbol變成原始值( +Symbol() , ''+Symbol() , Symbol() + 'foo' ),將會拋出一個異常,這可以防止在將其設置為屬性名稱時意外對其進行字符串化。

  • Symbols 通常不是唯一的。如上所述, Symbol.for() 返回一個非唯一的Symbol。不要總是假設你擁有的符號是獨一無二的,除非你自己做。

  • *Symbols 不像Ruby Symbols。他們有一些相似之處,比如有一個中央Symbol 注冊表,只是這樣而已。但是他們被當做 Rumby symbols 一樣使用。

好吧,但什么是Symbols真正好的呢

實際上,Symbols 只是稍微不同的方式來將屬性附加到對象上。你可以輕松地使用內置的Symbols作為標準方法,就像 Object.prototype.hasOwnProperty ,它出現在從 Object 繼承的所有東西(基本上是一切)。事實上,其他語言,比如在 Python中: Symbol.iterator 等價于 __iter__ 、 Symbol.hasInstance 等價于 __instancecheck__ ,我猜 Symbol.toPrimitive 大概與 __cmp__ 類似。Python的方式可以說是一個更糟糕的方法,雖然,因為JavaScript中的 Symbols 不需要任何奇怪的語法,用戶不會莫名其妙地與這些特殊方法沖突。

Symbols,在我看來,可以在以下兩種情況使用:

1. 通常用在一個正常的 String 或 Integer 類型的唯一值使用的地方:

假設你有一個日志類庫,它包含多個像日志級別,比如 logger.levels.DEBUG 、 logger.levels.INFO 、 logger.levels.WARN 等等。在 ES5的代碼中,你需要使這些作為字符串(所以 logger.levels.DEBUG ==='debug' )或數字( logger.levels.DEBUG === 10 )。這兩個都不理想,因為這些值不是唯一的值,但Symbols 是!所以 logger.levels 很容易改成這樣:

log.levels = {
    DEBUG: Symbol('debug'),
    INFO: Symbol('info'),
    WARN: Symbol('warn'),
};
log(log.levels.DEBUG, 'debug message');
log(log.levels.INFO, 'info message');

2. 用在一個對象中需要存放元數據的地方

你也可以用它們來存儲一些對實際對象來說不那么重要的自定義元數據屬性。這可以被認為是一個額外的不可枚舉的層(畢竟,不可枚舉的鍵仍然出現在 Object.getOwnProperty 中)。下面讓我們新建一個可信賴的 Collection 類,并且添加一個隱藏在Symbol語句中的size 引用(只要記住 Symbols 不是私有的 ,你可以,而且應該,只在你不介意被其他應用改變的地方使用。)

var size = Symbol('size');
class Collection {
    constructor() {
        this[size] = 0;
    }

    add(item) {
        this[this[size]] = item;
        this[size]++;
    }

    static sizeOf(instance) {
        return instance[size];
    }

}

var x = new Collection();
assert(Collection.sizeOf(x) === 0);
x.add('foo');
assert(Collection.sizeOf(x) === 1);
assert.deepEqual(Object.keys(x), ['0']);
assert.deepEqual(Object.getOwnPropertyNames(x), ['0']);
assert.deepEqual(Object.getOwnPropertySymbols(x), [size]);

3. 賦予開發者通過你的API為他們的對象添加 hooks (鉤子的)能力

好吧,這聽起來可能有點怪異,但是聽我說。讓我們假設我們有一個 console.log 樣式的實用函數 ,這個函數可以接受任何對象,并將其記錄到控制臺。 對于如何在控制臺中顯示給定的對象,這個函數有自己的規則。 但作為一名使用這個API的開發人員,可以通過使用 inspect Symbol(檢查常量) 在 hook 下提供一個方法來覆蓋原有的規則:

// 從API的 Symbol 常量中獲取一個 魔法  inspect Symbols
var inspect = console.Symbols.INSPECT;

var myVeryOwnObject = {};
console.log(myVeryOwnObject); // logs out `{}`

myVeryOwnObject[inspect] = function () { return 'DUUUDE'; };
console.log(myVeryOwnObject); // logs out `DUUUDE`

按照這個想法實現的 inspect hook 看起來可能像這樣:

console.log = function (…items) {
    var output = '';
    for(const item of items) {
        if (typeof item[console.Symbols.INSPECT] === 'function') {
            output += item[console.Symbols.INSPECT](item);
        } else {
            output += console.inspect[typeof item](item);
        }
        output += '  ';
    }
    process.stdout.write(output + '\n');
}

澄清一下,這并是說你應該修改代碼給對象提供什么擴展。這樣肯定是不對的,為此,請查看 WeakMaps ,它可以提供輔助對象,以便您在對象上收集您自己的元數據。 Node.js 的 已經為 console.log 提供了類似的實現 。它使用了 String ( 'inspect' )而不是一個 Symbol,這意味著你可以設置 x.inspect = function(){} ,但這可能與你的類本身的方法發生沖突,并發生意外。因而使用 Symbols 是實現這種行為非常有效的一種方式。 這種使用Symbols方式的意義是深遠的,以至于隨著我們Symbols 越來越了解,使之成為 javascript 語言的一部分。

內置的Symbols

使 Symbols 有用的關鍵部分是一組 Symbols 常量,稱為“內置的 Symbols”。這些實際上是在Symbol類上的一組靜態屬性,它們在其他本地對象(如Arrays,Strings)和JavaScript引擎內部實現。這是真正的“實現中的反射”部分發生的地方,因為這些內置的 Symbols 改變了JavaScript內部的行為。 下面我詳細介紹他們都是干嘛的 ?為什么他們讓人如此驚嘆!

Symbol.hasInstance: instanceof

Symbol.hasInstance 是一個驅動 instanceof 行為的Symbol。當一個符合ES6的引擎在表達式中看到 instanceof 運算符時,它調用 Symbol.hasInstance 。例如: lho instanceof rho 將調用 rho[Symbol.hasInstance](lho) (這里的 rho 是right hand operand(右邊的操作),而 lho 是 the left hand operand(左邊的操作))。然后根據方法來確定它是否繼承自該特定實例,你的實現可能像這樣:

class MyClass {
    static [Symbol.hasInstance](lho) {
        return Array.isArray(lho);
    }
}
assert([] instanceof MyClass);

(上面代碼中, MyClass 是一個類, new MyClass() 會返回一個實例。該實例的 Symbol.hasInstance 方法,會在進行 instanceof 運算時自動調用,判斷左側的運算子是否為 Array 的實例。)

Symbol.iterator

如果你聽說過關于 Symbols 的事情,你可能聽說過 Symbol.iterator ,在 ES6 帶來了新的語法, for of 循環,它調用了右邊操作的Symbol.iterator以獲取值進行迭代,換句話說使用 iterator 和 for of 是等價的。

var myArray = [1,2,3];

//有 `for of`
for(var value of myArray) {
    console.log(value);
}

// 沒有 `for of`
var _myArray = myArray[Symbol.iterator]();
while(var _iteration = _myArray.next()) {
    if (_iteration.done) {
        break;
    }
    var value = _iteration.value;
    console.log(value);
}

Symbol.iterator 允許你重寫 of 操作,這意味著你讓一個 library 使用這個方法,開發者會很愛你:

class Collection {
  *[Symbol.iterator]() {
    var i = 0;
    while(this[i] !== undefined) {
      yield this[i];
      ++i;
    }
  }

}
var myCollection = new Collection();
myCollection[0] = 1;
myCollection[1] = 2;
for(var value of myCollection) {
    console.log(value); // 1, then 2
}

Symbol. isConcatSpreadable

Symbol.isConcatSpreadable 是一個非常特殊的 Symbol ,它驅動 Array#concat 的行為。你可以看到, Array#concat 能接受多個參數,如果是數組, 則本身將作為concat操作的一部分被展開(或擴展)。(數組的默認行為是可以展開)。參考下面的代碼:

x = [1, 2].concat([3, 4], [5, 6], 7, 8);
assert.deepEqual(x, [1, 2, 3, 4, 5, 6, 7, 8]);  //true

ES6 將使用 Symbol.isConcatSpreadable 判定是否每個參數都是可以被展開的。這更常用于說,你繼承于 Array 的類, 對 Array#concat 不會友好(不會被 Array#concat 展開),而不是被展開:

class ArrayIsh extends Array {
    get [Symbol.isConcatSpreadable]() {
        return true;
    }
}
class Collection extends Array {
    get [Symbol.isConcatSpreadable]() {
        return false;
    }
}
arrayIshInstance = new ArrayIsh();
arrayIshInstance[0] = 3;
arrayIshInstance[1] = 4;
collectionInstance = new Collection();
collectionInstance[0] = 5;
collectionInstance[1] = 6;
spreadableTest = [1,2].concat(arrayInstance).concat(collectionInstance);
assert.deepEqual(spreadableTest, [1, 2, 3, 4, <Collection>]);

Symbol.unscopables

這個 Symbol 有一點有趣的歷史。大體上,在開發ES6的時候,TC(譯者注: tc39 ?不太確定是不是這個)在流行的JS庫中發現了一些舊代碼,這些庫執行了這樣的事情:

var keys = [];
with(Array.prototype) {
    keys.push('foo');
}

這段代碼在ES5及之前的老代碼中工作的很好,但是ES6 現在有 Array#keys ,這意味著當你執行 with(Array.prototype) , keys 現在是 Array#keys 方法,不是你設置的變量。所以這有三個解決方案:

  1. 嘗試獲取所有網站中使用這段代碼的庫,并改變/更新他們的代碼。(這不可能)。

  2. 移除 Array#keys 方法,然后祈禱刪除以后不會出現其他的bug(這不是真正的解決了問題)

  3. 寫一個hack包住這些代碼,防止一定范圍內的 with 語句。

TC 選擇了第三個方案,所以 Symbol.unscopables 就誕生了,它定義了一個對象中不可見的值,當在 with 語句中使用時不應該設置。你可能永遠都不需要使用它,也不會再日常 Javascript 中遇到它,但它演示了一些 Symbols 的實用功具,下面是完整的使用:

Object.keys(Array.prototype[Symbol.unscopables]); // -> ['copyWithin', 'entries', 'fill', 'find', 'findIndex', 'keys']

// Without unscopables:
class MyClass {
    foo() { return 1; }
}
var foo = function () { return 2; };
with (MyClass.prototype) {
    foo(); // 1!!
}

// Using unscopables:
class MyClass {
    foo() { return 1; }
    get [Symbol.unscopables]() {
        return { foo: true };
    }
}
var foo = function () { return 2; };
with (MyClass.prototype) {
    foo(); // 2!!
}

Symbol.match

這是另一個特定于函數的 Symbol。 String#match 函數現在將使用它來確定給定的值是否可以用來匹配它。 所以,你可以提供自己的匹配實現來使用,而不是使用正則表達式:

class MyMatcher {
    constructor(value) {
        this.value = value;
    }
    [Symbol.match](string) {
        var index = string.indexOf(this.value);
        if (index === -1) {
            return null;
        }
        return [this.value];
    }
}
var fooMatcher = 'foobar'.match(new MyMatcher('foo'));
var barMatcher = 'foobar'.match(new MyMatcher('bar'));
assert.deepEqual(fooMatcher, ['foo']);
assert.deepEqual(barMatcher, ['bar']);

Symbol.replace

和 Symbol.match 一樣, Symbol.replace 也被允許添加自定義類到 String#replace 中你通常使用正則表達式的地方:

class MyReplacer {
    constructor(value) {
        this.value = value;
    }
    [Symbol.replace](string,) {
        var index = string.indexOf(this.value);
        if (index === -1) {
            return string;
        }
        if (typeof replacer === 'function') {
            replacer = replacer.call(undefined, this.value, string);
        }
        return `${string.slice(0, index)}${replacer}${string.slice(index + this.value.length)}`;
    }
}
var fooReplaced = 'foobar'.replace(new MyReplacer('foo'), 'baz');
var barMatcher = 'foobar'.replace(new MyReplacer('bar'), function () { return 'baz' });
assert.equal(fooReplaced, 'bazbar');
assert.equal(barReplaced, 'foobaz');

Symbol.search

是的,就像 Symbol.match 和 Symbol.replace , Symbol.search 的存在是為了支持 String#search ,允許自定義類替代正則表達式:

class MySearch {
    constructor(value) {
        this.value = value;
    }
    [Symbol.search](string) {
        return string.indexOf(this.value);
    }
}
var fooSearch = 'foobar'.search(new MySearch('foo'));
var barSearch = 'foobar'.search(new MySearch('bar'));
var bazSearch = 'foobar'.search(new MySearch('baz'));
assert.equal(fooSearch, 0);
assert.equal(barSearch, 3);
assert.equal(bazSearch, -1);

Symbol.split

好的,最后一個 String symbols, Symbol.split 是為了支持 String#split ,像這樣使用:

class MySplitter {
    constructor(value) {
        this.value = value;
    }
    [Symbol.split](string) {
        var index = string.indexOf(this.value);
        if (index === -1) {
            return string;
        }
        return [string.substr(0, index), string.substr(index + this.value.length)];
    }
}
var fooSplitter = 'foobar'.split(new MySplitter('foo'));
var barSplitter = 'foobar'.split(new MySplitter('bar'));
assert.deepEqual(fooSplitter, ['', 'bar']);
assert.deepEqual(barSplitter, ['foo', '']);

Symbol.species

Symbol.species 是一個非常聰明的Symbol,它指向一個類的構造函數值,它允許類在方法中創建一個屬于自己的新版本。以 Array#map 為例,他從每一個返回值的回調中創建了一個新的數組,在 ES5 中 Array#map 的代碼可能看起來像這樣:

Array.prototype.map = function (callback) {
    var returnValue = new Array(this.length);
    this.forEach(function (item, index, array) {
        returnValue[index] = callback(item, index, array);
    });
    return returnValue;
}

在ES6 中 Array#map 隨著所有其沒有改變的數組方法已經升級,使用 Symbol.species 屬性創建對象,因此ES6 中 Array#map 的代碼看起來更像這樣:

Array.prototype.map = function (callback) {
    var Species = this.constructor[Symbol.species];
    var returnValue = new Species(this.length);
    this.forEach(function (item, index, array) {
        returnValue[index] = callback(item, index, array);
    });
    return returnValue;
}

如果你要一個類 Foo 繼承 Array( class Foo extends Array ), 當你每當調用 Foo#map 時,他將返回一個新的數組(無聊),你必須編寫自己的Map實現 Foo's 而不是 Array's ,現在 Foo#map 返回一個 Foo ,感謝 Symbol.species :

class Foo extends Array {
    static get [Symbol.species]() {
        return this;
    }
}

class Bar extends Array {
    static get [Symbol.species]() {
        return Array;
    }
}

assert(new Foo().map(function(){}) instanceof Foo);
assert(new Bar().map(function(){}) instanceof Bar);
assert(new Bar().map(function(){}) instanceof Array);

你可能會問“為什么不使用 this.constructor 替代 this.constructor[Symbol.species] 呢?” 好吧, Symbol.species 提供了一個 可定制的 入口點來創建類型 , 你可能不總是想要子類,并且有方法創建你的子類 - 比如下面的例子:

class TimeoutPromise extends Promise {
    static get [Symbol.species]() {
        return Promise;
    }
}

可以創建此 TimeoutPromise 以執行超時的操作,但是你當然不想一個Promise的超時影響到一整條 Promise 鏈,所以 Symbol.species 可以用來告訴 TimeoutPromise 從他的原型方法返回 Promise 。相當方便。

Symbol.toPrimitive

這個 Symbol 是我們最重要的事情,我們必須重載抽象等式運算符(簡寫 == )。基本上, Symbol.toPrimitive 被用在當Javascript 引擎需要轉換你的對象為原始值的時候。比如

  • 如果你使用 +object ,那么js將調用 object[Symbol.toPrimitive]('number');
  • 如果你使用 ''+object' ,那么js將調用 object[Symbol.toPrimitive]('string')
  • 如果你執行 if(object) 這樣的東西之后會調用 object[Symbol.toPrimitive]('default') 在這之前,我們有 valueOf 和 toString 來改變他們,但都這都是愚蠢的,你應該永遠不會得到他們想要的行為。 Symbol.toPrimitive 可以像這樣實現:
class AnswerToLifeAndUniverseAndEverything {
    [Symbol.toPrimitive](hint) {
        if (hint === 'string') {
            return 'Like, 42, man';
        } else if (hint === 'number') {
            return 42;
        } else {
            // when pushed, most classes (except Date)
            // default to returning a number primitive
            return 42;
        }
    }
}

var answer = new AnswerToLifeAndUniverseAndEverything();
+answer === 42;
Number(answer) === 42;
''+answer === 'Like, 42, man';
String(answer) === 'Like, 42, man';

Symbol.toStringTag

好的,這是內置的Symbol值的最后一個。繼續吧,你都看了這么多了,你可以的! Symbol.toStringTag 其實是非常酷的一個內置的Symbol值。如果你曾經試圖為 typeof 運算符實現自己的替換,你可能會碰到 Object#toString() ,并且如何返回這個古怪的 '[object Object]' 或 '[object Array]' 字符串。在ES6之前,這種行為是在你規范的縫隙中定義的,但是今天,在“富饒”的ES6大陸上我們有了專門為此的Symbol!任何對象通過 Object#toString() 將被檢查他們是否有 [Symbol.toStringTag] ,他應該是一個字符串,如果他是在,則會被用于生成字符串,例如:

class Collection {

  get [Symbol.toStringTag]() {
    return 'Collection';
  }

}
var x = new Collection();
Object.prototype.toString.call(x) === '[object Collection]'

為此,如果你使用 Chai 進行測試,現在支持使用下面的符號類型檢測,所以你可以用在你的測試(提供 x 在有 Symbol.toStringTag 屬性像上面,哦,你正在瀏覽器運行 Symbol.toStringTag 代碼)中使用 expect(x).to.be.a('Collection') ,

缺失的內置Symbol:Symbol.isAbstractEqual

你現在可能想,還是算了吧,但我真的喜歡 Symbols 中有關反射的想法。對于我來說,它還是缺失了一個讓我很興奮的Symbol, Symbol.isAbstractEqual 。有了 Symbol.isAbstractEqual 這個內置 Symbol 可以使抽象等式運算符( == )回到主流的使用。這個內置Symbol可以讓你的類以自己的方式使用 == ,就像在Ruby、Python和co 中一樣。允許類重寫 == 意味著當你看到類似于 lho == rho 這樣的代碼,可以轉化成 rho[Symbol.isAbstractEqual](lho) 。這可以通過定義所有當前原始原型(例如 Number.prototype )的默認值,并整理規范的一部分,同時給開發人員一個理由使 == 替換回來的方式完成向后兼容。

結論

你對Symbols有什么看法?仍然困惑?還是說想罵誰?我的推ter @keithamu ,感覺不錯就和我聯系,也許有一天我可能會占用你的整個午飯時間告訴你很多關于ES6中很我喜歡的新功能。

現在你已經通讀了所有關于Symbols的說明,你應該看 第二部分 - Reflect 了。

 

來自:http://www.zcfy.cc/article/metaprogramming-in-es6-symbols-and-why-they-re-awesome-1883.html

 

 本文由用戶 ecfire 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!