vscode工具函數(shù)Symbol使用深入解析
什么是Symbol?
符號(Symbol
)是JavaScript中的一個原始數(shù)據(jù)類型,是ECMAScript 6標準引入的新特性。符號是一種類似于字符串的數(shù)據(jù)類型,但與字符串不同的是,符號是唯一的并且不可變的。
Symbol
的定義方法如下:
const mySymbol = Symbol('my symbol');
每次調(diào)用Symbol
創(chuàng)建的值都是唯一的,即使對同一個參數(shù)調(diào)用兩遍Symbol
它們的值還是不一樣的:
Symbol("foo") === Symbol("foo"); // false
js的第六種基本數(shù)據(jù)類型
在Symbol
出現(xiàn)之前,Javascript
已經(jīng)有五種內(nèi)置的基本數(shù)據(jù)類型:
- 布爾值(
Boolean
):表示真或假,只有兩個取值:true和false。 - 數(shù)字(
Number
):表示整數(shù)或浮點數(shù),可以使用十進制、十六進制、八進制、科學計數(shù)法等多種表示方式。 - 字符串(
String
):表示文本字符串,可以使用單引號、雙引號、反引號等方式表示。 - 空值(
Null
):表示一個空值或不存在的對象。 - 未定義(
Undefined
):表示一個未定義的值或未聲明的變量。
Symbol
則作為第六種基本數(shù)據(jù)類型加入到語言中:
- 符號(
Symbol
):表示唯一的、不可變的值,用于保護屬性名、實現(xiàn)私有屬性或方法等場景。
Symbol的起源
在JavaScript誕生之初,對象屬性只能使用字符串作為鍵,這導致了一些問題。例如,當兩個不同的對象試圖使用相同的字符串作為屬性名時,可能會導致屬性名沖突。此外,JavaScript
中沒有一種簡單的方法來實現(xiàn)私有屬性或方法。
其實對于Symbol
的追溯早在Lisp
語言中就有體現(xiàn):
(setq x (intern "my-symbol"))
這里其實就是創(chuàng)建了一個名為my-symbol
的符號對象,并將其賦值給變量x
。
另外,ES6引入Symbol
其實離不開Ruby
的身影,在Ruby
中,可以使用冒號(:
)來創(chuàng)建符號。冒號后面跟著符號的名稱,如:
:my_symbol
可以看到其實Ruby
的語法更加簡潔,定義和使用都是用冒號區(qū)分:
person = { 'name' => 'John', 'age' => 30, :gender => 'Male' } puts person[:gender] # 輸出:'Male'
所以,在這樣的需求背景下,ES6
在首批特性中包含了Symbol
也不足為奇了。
Symbol的基本知識
定義與使用
在JavaScript
中,可以使用Symbol()
函數(shù)來創(chuàng)建一個符號,如下所示:
const mySymbol = Symbol();
Symbol
函數(shù)可以接受一個描述性字符串作為參數(shù),用于標識符號的含義,如下所示:
const mySymbol = Symbol('my symbol');
需要注意的是,每個Symbol()
函數(shù)調(diào)用都會返回一個唯一的符號,即使描述性字符串相同,它們也是不同的符號。
Symbol
類型的值可以用作對象的屬性名,如下所示:
const mySymbol = Symbol('my symbol'); const myObject = { [mySymbol]: 'hello' }; console.log(myObject[mySymbol]); // 輸出:'hello'
在上面的代碼中,我們使用符號mySymbol
作為對象myObject
的屬性名,并將其值設(shè)置為'hello'
。使用符號作為屬性名的好處是它們不會與其他屬性名沖突,并且對外不可見,因此可以用于實現(xiàn)私有屬性或方法等場景。
另外,JavaScript中的Symbol類型有兩個特殊的方法Symbol.for()
和Symbol.keyFor()
,用于創(chuàng)建全局符號和獲取已經(jīng)存在的全局符號。
Symbol.for()
: 用于創(chuàng)建或獲取一個全局符號,如果全局符號已經(jīng)存在,則返回已經(jīng)存在的符號,否則創(chuàng)建一個新的全局符號。例如:
const mySymbol = Symbol.for('my symbol'); const sameSymbol = Symbol.for('my symbol'); console.log(mySymbol === sameSymbol); // 輸出:true
在上面的代碼中,我們使用Symbol.for()
方法來創(chuàng)建一個全局符號'my symbol'
,并將其賦值給mySymbol
變量。然后,我們再次使用Symbol.for()
方法來獲取同一個全局符號,賦值給sameSymbol
變量。由于全局符號已經(jīng)存在,因此sameSymbol
變量的值等于mySymbol
變量的值,輸出true
。
Symbol的重要屬性
1. Symbol.iterator: 用于指定對象的默認迭代器,例如:
const myObject = { *[Symbol.iterator]() { yield 1; yield 2; yield 3; } }; for (const value of myObject) { console.log(value); } // 輸出:1 2 3
在上面的代碼中,我們?yōu)?code>myObject對象設(shè)置了Symbol.iterator
符號,并指定了一個生成器函數(shù)作為迭代器的實現(xiàn)。然后,我們可以使用for...of
循環(huán)迭代myObject
對象,并輸出其中的值。
2. Symbol.hasInstance: 用于定義一個對象是否為某個構(gòu)造函數(shù)的實例。
Symbol.hasInstance
方法接受一個參數(shù),表示要檢查的對象。該方法需要返回一個布爾值,表示該對象是否為該構(gòu)造函數(shù)的實例。例如:
class MyClass { static [Symbol.hasInstance](obj) { return obj instanceof Array; } } console.log([] instanceof MyClass); // 輸出:true console.log({} instanceof MyClass); // 輸出:false
在上面的代碼中,我們定義了一個MyClass
類,并使用Symbol.hasInstance
方法自定義了instanceof
運算符的行為,使其檢查對象是否為數(shù)組。當檢查[]對象時,instanceof
運算符返回true
,因為[]是Array
的實例;當檢查{}
對象時,instanceof
運算符返回false
,因為{}不是Array
的實例。
需要注意的是,Symbol.hasInstance
方法是一個靜態(tài)方法,需要定義在構(gòu)造函數(shù)的靜態(tài)屬性中。另外,Symbol.hasInstance
方法不能被繼承,因此子類需要重新定義該方法。
3. Symbol.toStringTag: 用于自定義對象的默認字符串描述。
當調(diào)用Object.prototype.toString()
方法時,會使用該對象的Symbol.toStringTag
屬性作為默認的字符串描述,例如:
class MyObject { get [Symbol.toStringTag]() { return 'MyObject'; } } const obj = new MyObject(); console.log(Object.prototype.toString.call(obj)); // 輸出:'[object MyObject]'
在上面的代碼中,我們定義了一個MyObject
類,并使用Symbol.toStringTag
屬性自定義了該類的默認字符串描述。然后,我們創(chuàng)建了一個obj
對象,并使用Object.prototype.toString()
方法獲取其字符串描述,輸出'[object MyObject]'
。
需要注意的是,Symbol.toStringTag
屬性只有在調(diào)用Object.prototype.toString()
方法時才會生效,對其他方法沒有影響。另外,如果沒有定義Symbol.toStringTag
屬性,則默認使用構(gòu)造函數(shù)的名稱作為字符串描述。
4. Symbol.asyncIterator: 用于指定對象的默認異步迭代器。
當使用for await...of
循環(huán)迭代一個對象時,會調(diào)用該對象的Symbol.asyncIterator
方法獲取異步迭代器。
Symbol.asyncIterator
方法需要返回一個異步迭代器對象,該對象實現(xiàn)了next()
方法,并返回一個Promise
對象。當?shù)鞯浇Y(jié)束時,next()
方法應(yīng)該返回一個Promise
對象,該Promise
對象的value
屬性為undefined
,done
屬性為true
。
例如,下面的代碼演示了如何使用Symbol.asyncIterator屬性定義一個異步迭代器:
const myObject = { async *[Symbol.asyncIterator]() { yield Promise.resolve(1); yield Promise.resolve(2); yield Promise.resolve(3); } }; (async function() { for await (const value of myObject) { console.log(value); } })(); // 輸出:1 2 3
在上面的代碼中,我們?yōu)?code>myObject對象設(shè)置了Symbol.asyncIterator
符號,并指定了一個異步生成器函數(shù)作為異步迭代器的實現(xiàn)。然后,我們使用for await...of
循環(huán)迭代myObject
對象,并輸出其中的值。
需要注意的是,使用Symbol.asyncIterator
屬性定義的異步迭代器只能使用for await...of
循環(huán)進行迭代,不能使用普通的for...of
循環(huán)。此外,Symbol.asyncIterator
屬性只有在支持異步迭代器的環(huán)境中才能使用,例如Node.js
的版本必須在10.0.0
以上才支持異步迭代器。
Symbol的實現(xiàn)原理
symbol
作為基本數(shù)據(jù)類型實現(xiàn)比較簡單,在最新的v8
代碼實現(xiàn)如下:
Symbol Factory::NewSymbolInternal(AllocationType allocation) { DCHECK(allocation != AllocationType::kYoung); // Statically ensure that it is safe to allocate symbols in paged spaces. STATIC_ASSERT(Symbol::kSize <= kMaxRegularHeapObjectSize); Symbol symbol = Symbol::cast(AllocateRawWithImmortalMap( Symbol::kSize, allocation, read_only_roots().symbol_map())); DisallowGarbageCollection no_gc; // Generate a random hash value. int hash = isolate()->GenerateIdentityHash(Name::kHashBitMask); symbol.set_raw_hash_field(Name::kIsNotIntegerIndexMask | (hash << Name::kHashShift)); symbol.set_description(read_only_roots().undefined_value(), SKIP_WRITE_BARRIER); symbol.set_flags(0); DCHECK(!symbol.is_private()); return symbol; }
該函數(shù)使用AllocateRawWithImmortalMap()
方法為新的Symbol
對象分配內(nèi)存,并將其強制轉(zhuǎn)換為Symbol
類型。接著,該函數(shù)使用DisallowGarbageCollection
類禁用垃圾回收器,以確保不會在生成哈希值的過程中觸發(fā)垃圾回收。接下來,該函數(shù)使用GenerateIdentityHash()
方法生成一個隨機的哈希值,并將其存儲在新的Symbol
對象中。然后,該函數(shù)將Symbol
對象的描述設(shè)置為undefined
,并將其標志設(shè)置為0
。最后,該函數(shù)返回新創(chuàng)建的Symbol對象。
所以使用hash
來唯一標識一個symbol
,在v8
內(nèi)部還實現(xiàn)了symbol-table
來實現(xiàn)Symbol.for
的查找,本質(zhì)上也是一個哈希表。
為了簡單起見,我們用js
來模擬一下Symbol
的實現(xiàn):
const registry = {}; function createSymbol(description) { const symbol = Object.create(null); symbol.toString = () => `Symbol(${description || ''})`; Object.defineProperty(symbol, 'description', { value: description, writable: false, configurable: false, enumerable: false, }); return symbol; } function Symbol(description) { if (typeof description !== 'undefined') { description = String(description); } if (registry[description]) { return registry[description]; } const symbol = createSymbol(description); registry[description] = symbol; return symbol; } Symbol.for = function (key) { if (registry[key]) { return registry[key]; } const symbol = createSymbol(key); registry[key] = symbol; return symbol; }; Symbol.keyFor = function (symbol) { for (const key in registry) { if (registry.hasOwnProperty(key) && registry[key] === symbol) { return key; } } }; export default Symbol;
我們使用一個全局對象registry
來存儲Symbol
對象及其描述符信息。createSymbol()
函數(shù)用于創(chuàng)建新的Symbol
對象,其中使用了Object.create()
方法來創(chuàng)建一個沒有原型的對象,并通過定義toString()
和description
屬性來實現(xiàn)Symbol
對象的基本功能。Symbol()
函數(shù)用于創(chuàng)建新的Symbol
對象,它根據(jù)傳入的描述符信息從registry
中查找Symbol
對象,如果找到了則返回已有的Symbol
對象,否則創(chuàng)建新的Symbol
對象并添加到registry
中。
Symbol的使用場景
Symbol
在VSCode
的應(yīng)用其實不多,最新的代碼只有:
/** * Can be passed into the Delayed to defer using a microtask * */ export const MicrotaskDelay = Symbol('MicrotaskDelay');
在實際中,Symbol
經(jīng)常被用于:
1. 唯一屬性鍵:Symbol可以作為對象屬性的鍵,避免屬性名沖突。
這在創(chuàng)建第三方庫或插件時非常有用,因為可以確保庫或插件的屬性不會與其他代碼意外沖突。
const uniqueKey = Symbol('uniqueKey'); const obj = { [uniqueKey]: 'This value is uniquely keyed' };
2. 定義私有屬性(當然這一點現(xiàn)在ES規(guī)范已經(jīng)有更好的方式了)
使用Symbol可以在對象上創(chuàng)建"私有"屬性,它們不會被常規(guī)的屬性枚舉(如for...in
,Object.keys()
或JSON.stringify()
)包含在內(nèi)。這有助于保護對象內(nèi)部實現(xiàn)細節(jié)。
3. 內(nèi)置Symbol
JavaScript內(nèi)置了一些具有特定功能的Symbol
。例如,Symbol.iterator
可以定義對象的迭代行為,Symbol.toStringTag
可以自定義Object.prototype.toString.call()
方法的輸出。
4. 注冊全局Symbol
Symbol.for()
方法允許在全局Symbol
注冊表中創(chuàng)建或獲取Symbol
。這對于跨多個地方或模塊使用相同的Symbol
時非常有用。
const globalSymbol = Symbol.for('globalSymbol'); const sameGlobalSymbol = Symbol.for('globalSymbol'); console.log(globalSymbol === sameGlobalSymbol); // true
Symbol的發(fā)展
在tc39
上已經(jīng)有兩個關(guān)于Symbol
的提案:
Symbols as WeakMap keys(Stage3)
Symbol
作為一種新的數(shù)據(jù)類型,其功能和用途都比較有限,因此tc39
在Symbol
的基礎(chǔ)上提出了一些新的提案,以擴展其功能和用途。其中一個比較重要的提案是Symbols as WeakMap keys
,該提案已經(jīng)進入到Stage3
階段。
WeakMap
是一種新的集合類型,可以用于存儲對象和關(guān)聯(lián)的元數(shù)據(jù)。WeakMap
的特點是鍵必須是對象,值可以是任意類型。WeakMap
的另一個特點是,當鍵對象不再被引用時,WeakMap
會自動刪除該鍵值對,以避免內(nèi)存泄漏。
Symbols as WeakMap keys
提案的目的是將Symbol
作為WeakMap
的鍵。這樣,就可以在不影響WeakMap
的自動垃圾回收機制的情況下,將Symbol
作為對象的元數(shù)據(jù)來使用。
const weak = new WeakMap(); // Pun not intended: being a symbol makes it become a more symbolic key const key = Symbol('my ref'); const someObject = { /* data data data */ }; weak.set(key, someObject);
Symbol Predicates Proposal(Stage2)
這是另一個關(guān)于Symbol的提案,添加了以下判斷方法:Symbol.isRegistered(symbol)
和Symbol.isWellKnown(symbol)
。
其實對于庫作者而言,了解更多關(guān)于Symbol
的信息是很重要的。根據(jù)使用情況,了解一個Symbol
是否真正唯一、可偽造(已注冊)或跨域共享(眾所周知)可能非常關(guān)鍵。例如,將Symbol
用作WeakMap
鍵需要確保Symbol
未被注冊。該提案處于第二階段,正在受到JavaScript
社區(qū)的廣泛關(guān)注。如果被采納,它將為Symbol
的應(yīng)用帶來更多的靈活性。
function isWeakMapKey(key) { switch (typeof key) { case "object": return key !== null; case "function": return true; case "symbol": return !Symbol.isRegistered(sym); } return false; } isWeakMapKey({}); // true isWeakMapKey(Symbol()); // true isWeakMapKey("foo"); // false isWeakMapKey(Symbol.for("foo")); // false isWeakMapKey(Symbol.asyncIterator); // true
您還可以檢查是否獲得了真正唯一的Symbol
:
const isUniqueSymbol = sym => typeof sym === "symbol" && !(Symbol.isRegistered(sym) || Symbol.isWellKnown(sym)); isUniqueSymbol(Symbol()); // true isUniqueSymbol(Symbol.for("foo")); // false isUniqueSymbol(Symbol.asyncIterator); // false isUniqueSymbol({}); // false
小結(jié)
本文介紹了JavaScript
中的Symbol
類型,包括Symbol
的創(chuàng)建、使用場景以及實現(xiàn)原理。Symbol
是一種新的基本數(shù)據(jù)類型,用于表示唯一標識符。與字符串和數(shù)字不同,Symbol
值是唯一的,不可修改和可枚舉的。Symbol
的主要用途包括:定義唯一屬性鍵、定義私有屬性、內(nèi)置Symbol
和注冊全局Symbol
。
此外,文章還介紹了兩個關(guān)于Symbol
的提案:Symbols as WeakMap keys
和Symbol Predicates Proposal
。這些提案旨在擴展Symbol
的功能和用途,并為JavaScript
開發(fā)人員提供更多的選項。
總之,Symbol
為JavaScript
添加了一個新的基本數(shù)據(jù)類型,為開發(fā)人員提供了一種新的表示唯一標識符的方式,可以用于創(chuàng)建唯一屬性鍵、定義私有屬性、內(nèi)置Symbol
和注冊全局Symbol
等用途。
在實際項目中,如果遇到定義一個唯一的key
的場景,就可以考慮使用 Symbol
來完成,可以避免沖突。
以上就是vscode工具函數(shù)Symbol使用深入解析的詳細內(nèi)容,更多關(guān)于vscode工具函數(shù)Symbol的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
超越Node.js的JavaScript運行環(huán)境Bun.js功能特性詳解
這篇文章主要為大家介紹了超越Node.js的JavaScript運行環(huán)境Bun.js功能特性詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-09-09TypeScript實用技巧?Nominal?Typing名義類型詳解
這篇文章主要為大家介紹了TypeScript實用技巧?Nominal?Typing名義類型詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-09-09