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