摘要:值可以作為標識符,用于對象的屬性名,可以保證不會出現同名的屬性。的結果為因為不是通過的方式實現的,所以的結果自然是。這個實現類似于函數記憶,我們建立一個對象,用來儲存已經創建的值即可。方法返回一個已登記的類型值的。
前言
實際上,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:
If NewTarget is not undefined, throw a TypeError exception.
If description is undefined, var descString be undefined.
Else, var descString be ToString(description).
ReturnIfAbrupt(descString).
Return a new unique Symbol value whose [[Description]] value is descString.
當調用 Symbol 的時候,會采用以下步驟:
如果使用 new ,就報錯
如果 description 是 undefined,讓 descString 為 undefined
否則 讓 descString 為 ToString(description)
如果報錯,就返回
返回一個新的唯一的 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,對作者也是一種鼓勵。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/95381.html
摘要:基本介紹提供了新的數據結構。初始化本身是一個構造函數,用來生成數據結構。函數可以接受一個數組或者具有接口的其他數據結構作為參數,用來初始化。返回一個布爾值,表示該值是否為的成員。清除所有成員,無返回值。 基本介紹 ES6 提供了新的數據結構 Set。 它類似于數組,但是成員的值都是唯一的,沒有重復的值。 初始化 Set 本身是一個構造函數,用來生成 Set 數據結構。 let set ...
摘要:使用指定的參數調用構造函數,并將綁定到新創建的對象。由構造函數返回的對象就是表達式的結果。情況返回以外的基本類型實例中只能訪問到構造函數中的屬性,和情況完全相反,結果相當于沒有返回值。 定義 new 運算符創建一個用戶定義的對象類型的實例或具有構造函數的內置對象的實例。 ——(來自于MDN) 舉個栗子 function Car(color) { this.color = co...
摘要:前言在閱讀入門的時候,零散的看到有私有變量的實現,所以在此總結一篇。構造函數應該只做對象初始化的事情,現在為了實現私有變量,必須包含部分方法的實現,代碼組織上略不清晰。 前言 在閱讀 《ECMAScript 6 入門》的時候,零散的看到有私有變量的實現,所以在此總結一篇。 1. 約定 實現 class Example { constructor() { this...
摘要:注意這里因為添加完元素之后返回的是該對象,所以可以鏈式調用結果是,但是中只會存一個模擬實現的整體結構除此之外我們還需要二個輔助方法模擬行為對迭代器對象進行遍歷操作。 更多系列文章請看 在實現之前我們可以通過阮一峰的ECMAScript 6 入門了解一下Set的基本信息 1、Set的基本語法 new Set([ iterable ]) 可以傳遞一個可迭代對象,它的所有元素將被添加到新的 ...
摘要:返回的綁定函數也能使用操作符創建對象這種行為就像把原函數當成構造器,提供的值被忽略,同時調用時的參數被提供給模擬函數。 bind() bind() 方法會創建一個新函數,當這個新函數被調用時,它的 this 值是傳遞給 bind() 的第一個參數,傳入bind方法的第二個以及以后的參數加上綁定函數運行時本身的參數按照順序作為原函數的參數來調用原函數。bind返回的綁定函數也能使用 n...
閱讀 658·2021-11-23 09:51
閱讀 3258·2021-10-11 10:58
閱讀 15407·2021-09-29 09:47
閱讀 3528·2021-09-01 11:42
閱讀 1281·2019-08-29 16:43
閱讀 1832·2019-08-29 15:37
閱讀 2089·2019-08-29 12:56
閱讀 1718·2019-08-28 18:21