ES6 系列之模仿完成 Symbol 範例

媒介

實際上,Symbol 的許多特徵都沒法模仿完成……所以先讓我們回憶下有哪些特徵,然後挑點能完成的……固然在看的過程當中,你也能夠思索這個特徵是否能完成,假如能夠完成,該怎樣完成。

回憶

ES6 引入了一種新的原始數據範例 Symbol,示意舉世無雙的值。

1. Symbol 值經由過程 Symbol 函數天生,運用 typeof,結果為 “symbol”

var s = Symbol();
console.log(typeof s); // "symbol"

2. Symbol 函數前不能運用 new 敕令,不然會報錯。這是由於天生的 Symbol 是一個原始範例的值,不是對象。

3. instanceof 的結果為 false

var s = Symbol('foo');
console.log(s instanceof Symbol); // false

4. Symbol 函數能夠接收一個字符串作為參數,示意對 Symbol 實例的形貌,重要是為了在控制台顯現,或許轉為字符串時,比較輕易辨別。

var s1 = Symbol('foo');
console.log(s1); // Symbol(foo)

5. 假如 Symbol 的參數是一個對象,就會挪用該對象的 toString 要領,將其轉為字符串,然後才天生一個 Symbol 值。

const obj = {
  toString() {
    return 'abc';
  }
};
const sym = Symbol(obj);
console.log(sym); // Symbol(abc)

6. Symbol 函數的參數只是示意對當前 Symbol 值的形貌,雷同參數的 Symbol 函數的返回值是不相稱的。

// 沒有參數的狀況
var s1 = Symbol();
var s2 = Symbol();

console.log(s1 === s2); // false

// 有參數的狀況
var s1 = Symbol('foo');
var s2 = Symbol('foo');

console.log(s1 === s2); // false

7. Symbol 值不能與其他範例的值舉行運算,會報錯。

var sym = Symbol('My symbol');

console.log("your symbol is " + sym); // TypeError: can't convert symbol to string

8. Symbol 值能夠顯式轉為字符串。

var sym = Symbol('My symbol');

console.log(String(sym)); // 'Symbol(My symbol)'
console.log(sym.toString()); // 'Symbol(My symbol)'

9. Symbol 值能夠作為標識符,用於對象的屬性名,能夠保證不會湧現同名的屬性。

var mySymbol = Symbol();

// 第一種寫法
var a = {};
a[mySymbol] = 'Hello!';

// 第二種寫法
var a = {
  [mySymbol]: 'Hello!'
};

// 第三種寫法
var a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });

// 以上寫法都獲得一樣結果
console.log(a[mySymbol]); // "Hello!"

10. Symbol 作為屬性名,該屬性不會湧現在 for…in、for…of 循環中,也不會被 Object.keys()、Object.getOwnPropertyNames()、JSON.stringify() 返回。然則,它也不是私有屬性,有一個 Object.getOwnPropertySymbols 要領,能夠獵取指定對象的一切 Symbol 屬性名。

var obj = {};
var a = Symbol('a');
var b = Symbol('b');

obj[a] = 'Hello';
obj[b] = 'World';

var objectSymbols = Object.getOwnPropertySymbols(obj);

console.log(objectSymbols);
// [Symbol(a), Symbol(b)]

11. 假如我們願望運用同一個 Symbol 值,能夠運用 Symbol.for。它接收一個字符串作為參數,然後搜刮有無以該參數作為稱號的 Symbol 值。假如有,就返回這個 Symbol 值,不然就新建並返回一個以該字符串為稱號的 Symbol 值。

var s1 = Symbol.for('foo');
var s2 = Symbol.for('foo');

console.log(s1 === s2); // true

12. Symbol.keyFor 要領返回一個已登記的 Symbol 範例值的 key。

var s1 = Symbol.for("foo");
console.log(Symbol.keyFor(s1)); // "foo"

var s2 = Symbol("foo");
console.log(Symbol.keyFor(s2) ); // undefined

剖析

看完以上的特徵,你以為哪些特徵是能夠模仿完成的呢?

假如我們要模仿完成一個 Symbol 的話,基礎的思緒就是構建一個 Symbol 函數,然後直接返回一個舉世無雙的值。

不過在此之前,我們先看看範例中挪用 Symbol 時究竟做了哪些事情:

Symbol ( [ description ] )

When Symbol is called with optional argument description, the following steps are taken:

  1. If NewTarget is not undefined, throw a TypeError exception.
  2. If description is undefined, var descString be undefined.
  3. Else, var descString be ToString(description).
  4. ReturnIfAbrupt(descString).
  5. Return a new unique Symbol value whose [[Description]] value is descString.

當挪用 Symbol 的時刻,會採納以下步驟:

  1. 假如運用 new ,就報錯
  2. 假如 description 是 undefined,讓 descString 為 undefined
  3. 不然 讓 descString 為 ToString(description)
  4. 假如報錯,就返回
  5. 返回一個新的唯一的 Symbol 值,它的內部屬性 [[Description]] 值為 descString

斟酌到還須要定義一個 [[Description]] 屬性,假如直接返回一個基礎範例的值,是沒法做到這一點的,所以我們終究照樣返回一個對象。

初版

參照着範例,實在我們已能夠最先寫起來了:

// 初版
(function() {
    var root = this;

    var SymbolPolyfill = function Symbol(description) {

        // 完成特徵第 2 點:Symbol 函數前不能運用 new 敕令
        if (this instanceof SymbolPolyfill) throw new TypeError('Symbol is not a constructor');

        // 完成特徵第 5 點:假如 Symbol 的參數是一個對象,就會挪用該對象的 toString 要領,將其轉為字符串,然後才天生一個 Symbol 值。
        var descString = description === undefined ? undefined : String(description)

        var symbol = Object.create(null)

        Object.defineProperties(symbol, {
            '__Description__': {
                value: descString,
                writable: false,
                enumerable: false,
                configurable: false
            }
        });

        // 完成特徵第 6 點,由於挪用該要領,返回的是一個新對象,兩個對象之間,只需援用差別,就不會雷同
        return symbol;
    }

    root.SymbolPolyfill = SymbolPolyfill;
})();

只是參照着範例,我們已完成了特徵的第 2、5、6 點。

第二版

我們來看看其他的特徵該怎樣完成:

1. 運用 typeof,結果為 “symbol”。

應用 ES5,我們並不能修正 typeof 操作符的結果,所以這個沒法完成。

3. instanceof 的結果為 false

由於不是經由過程 new 的體式格局完成的,所以 instanceof 的結果自然是 false。

4. Symbol 函數能夠接收一個字符串作為參數,示意對 Symbol 實例的形貌。重要是為了在控制台顯現,或許轉為字符串時,比較輕易辨別。

當我們打印一個原生 Symbol 值的時刻:

console.log(Symbol('1')); // Symbol(1)

但是我們模仿完成的時刻返回的倒是一個對象,所以這個也是沒法完成的,固然你修正 console.log 這個要領是另講。

8. Symbol 值能夠顯式轉為字符串。

var sym = Symbol('My symbol');

console.log(String(sym)); // 'Symbol(My symbol)'
console.log(sym.toString()); // 'Symbol(My symbol)'

當挪用 String 要領的時刻,假如該對象有 toString 要領,就會挪用該 toString 要領,所以我們只需給返回的對象增加一個 toString 要領,即可完成這兩個結果。

// 第二版

// 前面面代碼雷同 ……

var symbol = Object.create({
    toString: function() {
        return 'Symbol(' + this.__Description__ + ')';
    },
});

// 背面代碼雷同 ……

第三版

9. Symbol 值能夠作為標識符,用於對象的屬性名,能夠保證不會湧現同名的屬性。

看着彷佛沒什麼,這點實在和第 8 點是爭執的,這是由於當我們模仿的所謂 Symbol 值實際上是一個有着 toString 要領的 對象,當對象作為對象的屬性名的時刻,就會舉行隱式範例轉換,照樣會挪用我們增加的 toString 要領,關於 Symbol(‘foo’) 和 Symbol(‘foo’)兩個 Symbol 值,雖然形貌一樣,然則由於是兩個對象,所以並不相稱,然則當作為對象的屬性名的時刻,都邑隱式轉換為 Symbol(foo) 字符串,這個時刻就會形成同名的屬性。舉個例子:

var a = SymbolPolyfill('foo');
var b = SymbolPolyfill('foo');

console.log(a ===  b); // false

var o = {};
o[a] = 'hello';
o[b] = 'hi';

console.log(o); // {Symbol(foo): 'hi'}

為了防備不會湧現同名的屬性,畢竟這是一個非常重要的特徵,必不得已,我們須要修正 toString 要領,讓它返回一個唯一值,所以第 8 點就沒法完成了,而且我們還須要再寫一個用來天生 唯一值的要領,就命名為 generateName,我們將該唯一值增加到返回對象的 __Name__ 屬性中保存下來。

// 第三版
(function() {
    var root = this;

    var generateName = (function(){
        var postfix = 0;
        return function(descString){
            postfix++;
            return '@@' + descString + '_' + postfix
        }
    })()

    var SymbolPolyfill = function Symbol(description) {

        if (this instanceof SymbolPolyfill) throw new TypeError('Symbol is not a constructor');

        var descString = description === undefined ? undefined : String(description)

        var symbol = Object.create({
            toString: function() {
                return this.__Name__;
            }
        })

        Object.defineProperties(symbol, {
            '__Description__': {
                value: descString,
                writable: false,
                enumerable: false,
                configurable: false
            },
            '__Name__': {
                value: generateName(descString),
                writable: false,
                enumerable: false,
                configurable: false
            }
        });

        return symbol;
    }


    root.SymbolPolyfill = SymbolPolyfill;

})()

此時再看下這個例子:

var a = SymbolPolyfill('foo');
var b = SymbolPolyfill('foo');

console.log(a ===  b); // false

var o = {};
o[a] = 'hello';
o[b] = 'hi';

console.log(o); // Object { "@@foo_1": "hello", "@@foo_2": "hi" }

第四版

我們再看看接下來的特徵。

7.Symbol 值不能與其他範例的值舉行運算,會報錯。

+ 操作符為例,當舉行隱式範例轉換的時刻,會先挪用對象的 valueOf 要領,假如沒有返回基礎值,就會再挪用 toString 要領,所以我們斟酌在 valueOf 要領中舉行報錯,比方:

var symbol = Object.create({
    valueOf: function() {
        throw new Error('Cannot convert a Symbol value')
    }
})

console.log('1' + symbol); // 報錯

看着很簡單的處置懲罰了這個題目,但是假如我們是顯式挪用 valueOf 要領呢?關於一個原生的 Symbol 值:

var s1 = Symbol('foo')
console.log(s1.valueOf()); // Symbol(foo)

是的,關於原生 Symbol,顯式挪用 valueOf 要領,會直接返回該 Symbol 值,而我們又沒法推斷是顯式照樣隱式的挪用,所以這個我們就只能完成一半,要不然完成隱式挪用報錯,要不然完成顯式挪用返回該值,那……我們挑選不報錯的誰人吧,即後者。

我們必不得已的修正 valueOf 函數:

// 第四版
// 前面面代碼雷同 ……

var symbol = Object.create({
    toString: function() {
        return this.__Name__;
    },
    valueOf: function() {
        return this;
    }
});
// 背面代碼雷同 ……

第五版

10. Symbol 作為屬性名,該屬性不會湧現在 for…in、for…of 循環中,也不會被 Object.keys()、Object.getOwnPropertyNames()、JSON.stringify() 返回。然則,它也不是私有屬性,有一個 Object.getOwnPropertySymbols 要領,能夠獵取指定對象的一切 Symbol 屬性名。

嗯,沒法完成。

11. 偶然,我們願望從新運用同一個Symbol值,Symbol.for要領能夠做到這一點。它接收一個字符串作為參數,然後搜刮有無以該參數作為稱號的Symbol值。假如有,就返回這個Symbol值,不然就新建並返回一個以該字符串為稱號的Symbol值。

這個完成類似於函數影象,我們豎立一個對象,用來貯存已建立的 Symbol 值即可。

12. Symbol.keyFor 要領返回一個已登記的 Symbol 範例值的 key。

遍歷 forMap,查找該值對應的鍵值即可。

// 第五版
// 前面代碼雷同 ……
var SymbolPolyfill = function() { ... }

var forMap = {};

Object.defineProperties(SymbolPolyfill, {
    'for': {
        value: function(description) {
            var descString = description === undefined ? undefined : String(description)
            return forMap[descString] ? forMap[descString] : forMap[descString] = SymbolPolyfill(descString);
        },
        writable: true,
        enumerable: false,
        configurable: true
    },
    'keyFor': {
        value: function(symbol) {
            for (var key in forMap) {
                if (forMap[key] === symbol) return key;
            }
        },
        writable: true,
        enumerable: false,
        configurable: true
    }
});
// 背面代碼雷同 ……

完全完成

綜上所述:

沒法完成的特徵有:1、4、7、8、10

能夠完成的特徵有:2、3、5、6、9、11、12

末了的完成以下:

(function() {
    var root = this;

    var generateName = (function(){
        var postfix = 0;
        return function(descString){
            postfix++;
            return '@@' + descString + '_' + postfix
        }
    })()

    var SymbolPolyfill = function Symbol(description) {

        if (this instanceof SymbolPolyfill) throw new TypeError('Symbol is not a constructor');

        var descString = description === undefined ? undefined : String(description)

        var symbol = Object.create({
            toString: function() {
                return this.__Name__;
            },
            valueOf: function() {
                return this;
            }
        })

        Object.defineProperties(symbol, {
            '__Description__': {
                value: descString,
                writable: false,
                enumerable: false,
                configurable: false
            },
            '__Name__': {
                value: generateName(descString),
                writable: false,
                enumerable: false,
                configurable: false
            }
        });

        return symbol;
    }

    var forMap = {};

    Object.defineProperties(SymbolPolyfill, {
        'for': {
            value: function(description) {
                var descString = description === undefined ? undefined : String(description)
                return forMap[descString] ? forMap[descString] : forMap[descString] = SymbolPolyfill(descString);
            },
            writable: true,
            enumerable: false,
            configurable: true
        },
        'keyFor': {
            value: function(symbol) {
                for (var key in forMap) {
                    if (forMap[key] === symbol) return key;
                }
            },
            writable: true,
            enumerable: false,
            configurable: true
        }
    });

    root.SymbolPolyfill = SymbolPolyfill;

})()

ES6 系列

ES6 系列目次地點:https://github.com/mqyqingfeng/Blog

ES6 系列估計寫二十篇擺布,旨在加深 ES6 部份知識點的明白,重點解說塊級作用域、標籤模板、箭頭函數、Symbol、Set、Map 以及 Promise 的模仿完成、模塊加載計劃、異步處置懲罰等內容。

假如有毛病或許不嚴謹的處所,請務必賦予斧正,非常謝謝。假如喜好或許有所啟示,迎接 star,對作者也是一種勉勵。

    原文作者:冴羽
    原文地址: https://segmentfault.com/a/1190000015262174
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞