摘要:值可以作為標(biāo)識符,用于對象的屬性名,可以保證不會出現(xiàn)同名的屬性。的結(jié)果為因為不是通過的方式實現(xiàn)的,所以的結(jié)果自然是。這個實現(xiàn)類似于函數(shù)記憶,我們建立一個對象,用來儲存已經(jīng)創(chuàng)建的值即可。方法返回一個已登記的類型值的。
前言
實際上,Symbol 的很多特性都無法模擬實現(xiàn)……所以先讓我們回顧下有哪些特性,然后挑點能實現(xiàn)的……當(dāng)然在看的過程中,你也可以思考這個特性是否能實現(xiàn),如果可以實現(xiàn),該如何實現(xiàn)。
回顧ES6 引入了一種新的原始數(shù)據(jù)類型 Symbol,表示獨一無二的值。
1. Symbol 值通過 Symbol 函數(shù)生成,使用 typeof,結(jié)果為 "symbol"
var s = Symbol(); console.log(typeof s); // "symbol"
2. Symbol 函數(shù)前不能使用 new 命令,否則會報錯。這是因為生成的 Symbol 是一個原始類型的值,不是對象。
3. instanceof 的結(jié)果為 false
var s = Symbol("foo"); console.log(s instanceof Symbol); // false
4. Symbol 函數(shù)可以接受一個字符串作為參數(shù),表示對 Symbol 實例的描述,主要是為了在控制臺顯示,或者轉(zhuǎn)為字符串時,比較容易區(qū)分。
var s1 = Symbol("foo"); console.log(s1); // Symbol(foo)
5. 如果 Symbol 的參數(shù)是一個對象,就會調(diào)用該對象的 toString 方法,將其轉(zhuǎn)為字符串,然后才生成一個 Symbol 值。
const obj = { toString() { return "abc"; } }; const sym = Symbol(obj); console.log(sym); // Symbol(abc)
6. Symbol 函數(shù)的參數(shù)只是表示對當(dāng)前 Symbol 值的描述,相同參數(shù)的 Symbol 函數(shù)的返回值是不相等的。
// 沒有參數(shù)的情況 var s1 = Symbol(); var s2 = Symbol(); console.log(s1 === s2); // false // 有參數(shù)的情況 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 值可以顯式轉(zhuǎn)為字符串。
var sym = Symbol("My symbol"); console.log(String(sym)); // "Symbol(My symbol)" console.log(sym.toString()); // "Symbol(My symbol)"
9. Symbol 值可以作為標(biāo)識符,用于對象的屬性名,可以保證不會出現(xiàn)同名的屬性。
var mySymbol = Symbol(); // 第一種寫法 var a = {}; a[mySymbol] = "Hello!"; // 第二種寫法 var a = { [mySymbol]: "Hello!" }; // 第三種寫法 var a = {}; Object.defineProperty(a, mySymbol, { value: "Hello!" }); // 以上寫法都得到同樣結(jié)果 console.log(a[mySymbol]); // "Hello!"
10. Symbol 作為屬性名,該屬性不會出現(xiàn)在 for...in、for...of 循環(huán)中,也不會被 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。它接受一個字符串作為參數(shù),然后搜索有沒有以該參數(shù)作為名稱的 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分析
看完以上的特性,你覺得哪些特性是可以模擬實現(xiàn)的呢?
如果我們要模擬實現(xiàn)一個 Symbol 的話,基本的思路就是構(gòu)建一個 Symbol 函數(shù),然后直接返回一個獨一無二的值。
不過在此之前,我們先看看規(guī)范中調(diào)用 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.
當(dāng)調(diào)用 Symbol 的時候,會采用以下步驟:
如果使用 new ,就報錯
如果 description 是 undefined,讓 descString 為 undefined
否則 讓 descString 為 ToString(description)
如果報錯,就返回
返回一個新的唯一的 Symbol 值,它的內(nèi)部屬性 [[Description]] 值為 descString
考慮到還需要定義一個 [[Description]] 屬性,如果直接返回一個基本類型的值,是無法做到這一點的,所以我們最終還是返回一個對象。
第一版參照著規(guī)范,其實我們已經(jīng)可以開始寫起來了:
// 第一版 (function() { var root = this; var SymbolPolyfill = function Symbol(description) { // 實現(xiàn)特性第 2 點:Symbol 函數(shù)前不能使用 new 命令 if (this instanceof SymbolPolyfill) throw new TypeError("Symbol is not a constructor"); // 實現(xiàn)特性第 5 點:如果 Symbol 的參數(shù)是一個對象,就會調(diào)用該對象的 toString 方法,將其轉(zhuǎn)為字符串,然后才生成一個 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 } }); // 實現(xiàn)特性第 6 點,因為調(diào)用該方法,返回的是一個新對象,兩個對象之間,只要引用不同,就不會相同 return symbol; } root.SymbolPolyfill = SymbolPolyfill; })();
只是參照著規(guī)范,我們已經(jīng)實現(xiàn)了特性的第 2、5、6 點。
第二版我們來看看其他的特性該如何實現(xiàn):
1. 使用 typeof,結(jié)果為 "symbol"。
利用 ES5,我們并不能修改 typeof 操作符的結(jié)果,所以這個無法實現(xiàn)。
3. instanceof 的結(jié)果為 false
因為不是通過 new 的方式實現(xiàn)的,所以 instanceof 的結(jié)果自然是 false。
4. Symbol 函數(shù)可以接受一個字符串作為參數(shù),表示對 Symbol 實例的描述。主要是為了在控制臺顯示,或者轉(zhuǎn)為字符串時,比較容易區(qū)分。
當(dāng)我們打印一個原生 Symbol 值的時候:
console.log(Symbol("1")); // Symbol(1)
可是我們模擬實現(xiàn)的時候返回的卻是一個對象,所以這個也是無法實現(xiàn)的,當(dāng)然你修改 console.log 這個方法是另講。
8. Symbol 值可以顯式轉(zhuǎn)為字符串。
var sym = Symbol("My symbol"); console.log(String(sym)); // "Symbol(My symbol)" console.log(sym.toString()); // "Symbol(My symbol)"
當(dāng)調(diào)用 String 方法的時候,如果該對象有 toString 方法,就會調(diào)用該 toString 方法,所以我們只要給返回的對象添加一個 toString 方法,即可實現(xiàn)這兩個效果。
// 第二版 // 前面面代碼相同 …… var symbol = Object.create({ toString: function() { return "Symbol(" + this.__Description__ + ")"; }, }); // 后面代碼相同 ……第三版
9. Symbol 值可以作為標(biāo)識符,用于對象的屬性名,可以保證不會出現(xiàn)同名的屬性。
看著好像沒什么,這點其實和第 8 點是沖突的,這是因為當(dāng)我們模擬的所謂 Symbol 值其實是一個有著 toString 方法的 對象,當(dāng)對象作為對象的屬性名的時候,就會進行隱式類型轉(zhuǎn)換,還是會調(diào)用我們添加的 toString 方法,對于 Symbol("foo") 和 Symbol("foo")兩個 Symbol 值,雖然描述一樣,但是因為是兩個對象,所以并不相等,但是當(dāng)作為對象的屬性名的時候,都會隱式轉(zhuǎn)換為 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"}
為了防止不會出現(xiàn)同名的屬性,畢竟這是一個非常重要的特性,迫不得已,我們需要修改 toString 方法,讓它返回一個唯一值,所以第 8 點就無法實現(xiàn)了,而且我們還需要再寫一個用來生成 唯一值的方法,就命名為 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 值不能與其他類型的值進行運算,會報錯。
以 + 操作符為例,當(dāng)進行隱式類型轉(zhuǎn)換的時候,會先調(diào)用對象的 valueOf 方法,如果沒有返回基本值,就會再調(diào)用 toString 方法,所以我們考慮在 valueOf 方法中進行報錯,比如:
var symbol = Object.create({ valueOf: function() { throw new Error("Cannot convert a Symbol value") } }) console.log("1" + symbol); // 報錯
看著很簡單的解決了這個問題,可是如果我們是顯式調(diào)用 valueOf 方法呢?對于一個原生的 Symbol 值:
var s1 = Symbol("foo") console.log(s1.valueOf()); // Symbol(foo)
是的,對于原生 Symbol,顯式調(diào)用 valueOf 方法,會直接返回該 Symbol 值,而我們又無法判斷是顯式還是隱式的調(diào)用,所以這個我們就只能實現(xiàn)一半,要不然實現(xiàn)隱式調(diào)用報錯,要不然實現(xiàn)顯式調(diào)用返回該值,那……我們選擇不報錯的那個吧,即后者。
我們迫不得已的修改 valueOf 函數(shù):
// 第四版 // 前面面代碼相同 …… var symbol = Object.create({ toString: function() { return this.__Name__; }, valueOf: function() { return this; } }); // 后面代碼相同 ……第五版
10. Symbol 作為屬性名,該屬性不會出現(xiàn)在 for...in、for...of 循環(huán)中,也不會被 Object.keys()、Object.getOwnPropertyNames()、JSON.stringify() 返回。但是,它也不是私有屬性,有一個 Object.getOwnPropertySymbols 方法,可以獲取指定對象的所有 Symbol 屬性名。
嗯,無法實現(xiàn)。
11. 有時,我們希望重新使用同一個Symbol值,Symbol.for方法可以做到這一點。它接受一個字符串作為參數(shù),然后搜索有沒有以該參數(shù)作為名稱的Symbol值。如果有,就返回這個Symbol值,否則就新建并返回一個以該字符串為名稱的Symbol值。
這個實現(xiàn)類似于函數(shù)記憶,我們建立一個對象,用來儲存已經(jīng)創(chuàng)建的 Symbol 值即可。
12. Symbol.keyFor 方法返回一個已登記的 Symbol 類型值的 key。
遍歷 forMap,查找該值對應(yīng)的鍵值即可。
// 第五版 // 前面代碼相同 …… 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 } }); // 后面代碼相同 ……完整實現(xiàn)
綜上所述:
無法實現(xiàn)的特性有:1、4、7、8、10
可以實現(xiàn)的特性有:2、3、5、6、9、11、12
最后的實現(xiàn)如下:
(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 系列預(yù)計寫二十篇左右,旨在加深 ES6 部分知識點的理解,重點講解塊級作用域、標(biāo)簽?zāi)0濉⒓^函數(shù)、Symbol、Set、Map 以及 Promise 的模擬實現(xiàn)、模塊加載方案、異步處理等內(nèi)容。
如果有錯誤或者不嚴(yán)謹?shù)牡胤剑垊?wù)必給予指正,十分感謝。如果喜歡或者有所啟發(fā),歡迎 star,對作者也是一種鼓勵。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/95381.html
摘要:基本介紹提供了新的數(shù)據(jù)結(jié)構(gòu)。初始化本身是一個構(gòu)造函數(shù),用來生成數(shù)據(jù)結(jié)構(gòu)。函數(shù)可以接受一個數(shù)組或者具有接口的其他數(shù)據(jù)結(jié)構(gòu)作為參數(shù),用來初始化。返回一個布爾值,表示該值是否為的成員。清除所有成員,無返回值。 基本介紹 ES6 提供了新的數(shù)據(jù)結(jié)構(gòu) Set。 它類似于數(shù)組,但是成員的值都是唯一的,沒有重復(fù)的值。 初始化 Set 本身是一個構(gòu)造函數(shù),用來生成 Set 數(shù)據(jù)結(jié)構(gòu)。 let set ...
摘要:使用指定的參數(shù)調(diào)用構(gòu)造函數(shù),并將綁定到新創(chuàng)建的對象。由構(gòu)造函數(shù)返回的對象就是表達式的結(jié)果。情況返回以外的基本類型實例中只能訪問到構(gòu)造函數(shù)中的屬性,和情況完全相反,結(jié)果相當(dāng)于沒有返回值。 定義 new 運算符創(chuàng)建一個用戶定義的對象類型的實例或具有構(gòu)造函數(shù)的內(nèi)置對象的實例。 ——(來自于MDN) 舉個栗子 function Car(color) { this.color = co...
摘要:前言在閱讀入門的時候,零散的看到有私有變量的實現(xiàn),所以在此總結(jié)一篇。構(gòu)造函數(shù)應(yīng)該只做對象初始化的事情,現(xiàn)在為了實現(xiàn)私有變量,必須包含部分方法的實現(xiàn),代碼組織上略不清晰。 前言 在閱讀 《ECMAScript 6 入門》的時候,零散的看到有私有變量的實現(xiàn),所以在此總結(jié)一篇。 1. 約定 實現(xiàn) class Example { constructor() { this...
摘要:注意這里因為添加完元素之后返回的是該對象,所以可以鏈?zhǔn)秸{(diào)用結(jié)果是,但是中只會存一個模擬實現(xiàn)的整體結(jié)構(gòu)除此之外我們還需要二個輔助方法模擬行為對迭代器對象進行遍歷操作。 更多系列文章請看 在實現(xiàn)之前我們可以通過阮一峰的ECMAScript 6 入門了解一下Set的基本信息 1、Set的基本語法 new Set([ iterable ]) 可以傳遞一個可迭代對象,它的所有元素將被添加到新的 ...
摘要:返回的綁定函數(shù)也能使用操作符創(chuàng)建對象這種行為就像把原函數(shù)當(dāng)成構(gòu)造器,提供的值被忽略,同時調(diào)用時的參數(shù)被提供給模擬函數(shù)。 bind() bind() 方法會創(chuàng)建一個新函數(shù),當(dāng)這個新函數(shù)被調(diào)用時,它的 this 值是傳遞給 bind() 的第一個參數(shù),傳入bind方法的第二個以及以后的參數(shù)加上綁定函數(shù)運行時本身的參數(shù)按照順序作為原函數(shù)的參數(shù)來調(diào)用原函數(shù)。bind返回的綁定函數(shù)也能使用 n...
閱讀 668·2021-11-23 09:51
閱讀 3327·2021-10-11 10:58
閱讀 15496·2021-09-29 09:47
閱讀 3590·2021-09-01 11:42
閱讀 1297·2019-08-29 16:43
閱讀 1842·2019-08-29 15:37
閱讀 2124·2019-08-29 12:56
閱讀 1734·2019-08-28 18:21