深入了解JavaScript中的Symbol的使用方法_基礎知識
Symbol 是什麼?
Symbols 不是圖標,也不是指在程式碼中可以使用小圖片:
也不是指其他一些東西的語法。那麼,Symbol 到究竟是什麼呢?
七種資料型別
JavaScript 在 1997 年被標準化時,就有 6 種資料類型,直到 ES6 出現之前,程式中的變數一定是以下 6 種資料型態之一:
Undefined
Null
Boolean
Number
String
Object
每種資料型態都是一系列值的組合,前面 5 種資料型態值的數量都是有限的。 Boolean 型別只有兩個值:true 和 false,當 Boolean 類型的變數賦值時,並不會產生新的值(共用了true 和 false 這兩個值)。對於 Number 和 String 來說,它們的值則多得多了,標準的說法是有 18,437,736,874,454,810,627 個 Number 類型的值(包括 NAN)。 String 類型的數量就難以統計了,我原以為是 (2144,115,188,075,855,872 ? 1) ÷ 65,535…不過也許我算錯了。
物件值的數量是無限的,每個物件都是獨一無二的,每次開啟一個網頁,都建立了一系列的物件。
ES6 中的 Symbol 也是一種資料類型,但是不是字串,也不是對象,而是一種新的資料類型:第七種資料類型。
下面我們來看一個場景,也許 Symbol 能派上用場。
一個布林值引出的問題
有時,把一些屬於其他物件的資料暫存在另一個物件中是非常方便的。例如,假設你正在編寫一個JS 庫,使用CSS 中的transition 來讓一個DOM 元素在屏幕上飛奔,你已經知道不能同時將多個transition 應用在同一個div 上,否則將使得動畫非常不美觀,你也確實有辦法來解決這個問題,但是首先你需要知道該div 是否已經在移動中。
怎麼解決這個問題呢?
其中一個方法是使用瀏覽器提供的 API 來探測元素是否處於動畫狀態,但殺雞焉用牛刀,在將元素設為移動時,你的庫就知道了該元素正在移動。
你真正需要的是一種機制來追蹤哪些元素正在移動,你可以將正在移動的元素保存在一個陣列中,每次要為一個元素設定動畫時,首先檢查一下這個元素是否已經在這個列表中。
啊哈,但是如果你的陣列非常龐大,即便是這樣的線性搜尋也會產生效能問題。
那麼,你真正想做的就是直接在元素上設定一個標誌:
if (element.isMoving) { smoothAnimations(element); } element.isMoving = true; if (element.isMoving) { smoothAnimations(element); } element.isMoving = true;
這也有一些潛在的問題,不得不承認這樣一個事實:還有其他程式碼也可能操作該 ODM 元素。
- 在其他程式碼中,你所建立的屬性會被 for-in 或 Object.keys() 列舉;
- 在其他一些函式庫中也許已經使用了相同的方式(在元素上設定了相同的屬性),那麼這將和你的程式碼發生衝突,產生不可預期的結果;
- 其他一些函式庫可能在將來會使用相同的方式,這也會與你的程式碼發生衝突;
- 標準委員會可能會為每個元素添加一個 .isMoving() 原生方法,那麼你的程式碼就徹底不能工作了。
當然,對於最後三個問題,你可以選擇一個無意義的不會有人會使用到的字串:
if (element.__$jorendorff_animation_library$PLEASE_DO_NOT_USE_THIS_PROPERTY$isMoving__) { smoothAnimations(element); } element.__$jorendorff_animation_library$PLEASE_DO_NOT_USE_THIS_PROPERTY$isMoving__ = true; if (element.__$jorendorff_animation_library$PLEASE_DO_NOT_USE_THIS_PROPERTY$isMoving__) { smoothAnimations(element); } element.__$jorendorff_animation_library$PLEASE_DO_NOT_USE_THIS_PROPERTY$isMoving__ = true;
這似乎太不靠譜了,看了讓人眼睛痛。
你也可以用加密演算法來產生一個幾乎唯一的字串:
// get 1024 Unicode characters of gibberish var isMoving = SecureRandom.generateName(); ... if (element[isMoving]) { smoothAnimations(element); } element[isMoving] = true; // get 1024 Unicode characters of gibberish var isMoving = SecureRandom.generateName(); ... if (element[isMoving]) { smoothAnimations(element); } element[isMoving] = true;
object[name] 語法允許我們將任何字串作為屬性名,程式碼能正常運作,衝突幾乎是不可能了,程式碼看起來也美觀多了。
但是,這回導致糟糕的調試體驗,每次使用console.log() 打印出包含該屬性的元素時,你回看到一個龐大的垃圾字符串,並且如果還不止一個這樣的屬性呢?每次刷新後屬性名都發生了變化,怎麼樣讓這些屬性看起來更直覺呢?
為什麼這麼難?我們只是為了保存一個小小的標誌位。
用 Symbol 來解決問題
Symbol 值可以由程式創建,並可以作為屬性名,而且不用擔心屬性名衝突。
var mySymbol = Symbol(); var mySymbol = Symbol();
调用 Symbol() 方法将创建一个新的 Symbol 类型的值,并且该值不与其它任何值相等。
与数字和字符串一样,Symbol 类型的值也可以作为对象的属性名,正是由于它不与任何其它值相等,对应的属性也不会发生冲突:
obj[mySymbol] = "ok!"; // guaranteed not to collide console.log(obj[mySymbol]); // ok! obj[mySymbol] = "ok!"; // guaranteed not to collide console.log(obj[mySymbol]); // ok!
下面是使用 Symbol 来解决上面的问题:
// create a unique symbol var isMoving = Symbol("isMoving"); ... if (element[isMoving]) { smoothAnimations(element); } element[isMoving] = true; // create a unique symbol var isMoving = Symbol("isMoving"); ... if (element[isMoving]) { smoothAnimations(element); } element[isMoving] = true;
上面代码需要注意几点:
- 方法 Symbol("isMoving") 中的 "isMoving" 字符串被称为 Symbol 的描述信息,这对调试非常有帮助。可以通过 console.log(isMoving) 打印出来,或通过 isMoving.toString() 将 isMoving 转换为字符串时,或在一些错误信息中显示出来。
- element[isMoving] 访问的是 symbol-keyed 属性,除了属性名是 Symbol 类型的值之外,与其它属性都一样。
- 和数组一样,symbol-keyed 属性不能通过 . 操作符来访问,必须使用方括号的方式。
- 操作 symbol-keyed 属性也非常方便,通过上面代码我们已经知道如何获取和设置 element[isMoving] 的值,我们还可以这样使用:if (isMoving in element) 或 delete element[isMoving]。
- 另一方面,只有在 isMoving 的作用域范围内才可以使用上述代码,这可以实现弱封装机制:在一个模块内创建一些 Symbol,只有在该模块内部的对象才能使用,而不用担心与其它模块的代码发生冲突。
由于 Symbol 的设计初衷是为了避免冲突,当遍历 JavaScript 对象时,并不会枚举到以 Symbol 作为建的属性,比如,for-in 循环只会遍历到以字符串作为键的属性,Object.keys(obj)和 Object.getOwnPropertyNames(obj) 也一样,但这并不意味着 Symbol 为键的属性是不可枚举的:使用 Object.getOwnPropertySymbols(obj) 这个新方法可以枚举出来,还有 Reflect.ownKeys(obj) 这个新方法可以返回对象中所有字符串和 Symbol 键。(我将在后面的文章中详细介绍 Reflect 这个新特性。)
库和框架的设计者将会发现很多 Symbol 的用途,稍后我们将看到,JavaScript 语言本身也对其有广泛的应用。
Symbol 究竟是什么呢
> typeof Symbol() "symbol" > typeof Symbol() "symbol"
Symbol 是完全不一样的东西。一旦创建后就不可更改,不能对它们设置属性(如果在严格模式下尝试这样做,你将得到一个 TypeError)。它们可以作为属性名,这时它们和字符串的属性名没有什么区别。
另一方面,每个 Symbol 都是独一无二的,不与其它 Symbol 重复(即便是使用相同的 Symbol 描述创建),创建一个 Symbol 就跟创建一个对象一样方便。
ES6 中的 Symbol 与传统语言(如 Lisp 和 Ruby)中的 Symbol 中的类似,但并不是完全照搬到 JavaScript 中。在 Lisp 中,所有标识符都是 Symbol;在 JavaScript 中,标识符和大多数属性仍然是字符串,Symbol 只是提供了一个额外的选择。
值得注意的是:与其它类型不同的是,Symbol 不能自动被转换为字符串,当尝试将一个 Symbol 强制转换为字符串时,将返回一个 TypeError。
> var sym = Symbol("<3"); > "your symbol is " + sym // TypeError: can't convert symbol to string > `your symbol is ${sym}` // TypeError: can't convert symbol to string > var sym = Symbol("<3"); > "your symbol is " + sym // TypeError: can't convert symbol to string > `your symbol is ${sym}` // TypeError: can't convert symbol to string
應該避免這樣的強制轉換,應該使用 String(sym) 或 sym.toString() 來轉換。
取得 Symbol 的三種方法
- Symbol() 每次呼叫時都會傳回一個唯一的 Symbol。
- Symbol.for(string) 從 Symbol 註冊表中傳回對應的 Symbol,與上個方法不同的是,Symbol 註冊表中的 Symbol 是共用的。也就是說,如果你呼叫 Symbol.for("cat") 三次,都會傳回相同的 Symbol。當不同頁面或同一頁面不同模組需要共用 Symbol 時,註冊表就非常有用。
- Symbol.iterator 傳回語言預先定義的某些 Symbol,每個都有其特殊的用途。
如果你仍不確定 Symbol 是否有用,那麼接下來的內容將會非常有趣,因為我將為你示範 Symbol 的實際應用程式。
Symbol 在 ES6 規範中的應用
我們已經知道可以使用 Symbol 來避免程式碼衝突。之前在介紹iterator 時,我們也解析了for (var item of myArray) 內部是以呼叫myArray[Symbol.iterator]() 開始的,當時我提到這個方法可以使用myArray.iterator() 來代替,但使用Symbol 的後向相容性更好。
在 ES6 中還有一些地方使用到了 Symbol。 (這些特性還沒有在 FireFox 中實現。)
- 使 instanceof 可擴展。在 ES6 中,object instanceof constructor 表達式被標準化為建構子的一個方法:constructor[Symbol.hasInstance](object),這意味著它是可擴展的。
- 消除新功能與舊程式碼之間的衝突。
- 支援新類型的字串符合。在 ES5 中,當呼叫 str.match(myObject) 時,首先會嘗試將 myObject 轉換為 RegExp 物件。在 ES6 中,首先將檢查 myObject 中是否有 myObject[Symbol.match](str) 方法,在所有正規表示式工作的地方都可以提供一個自訂的字串解析方法。
這些用途還比較窄,但僅僅透過我文章中的程式碼很難看到這些新特性產生的重大影響。 JavaScript 的 Symbol 是 PHP 和 Python 中 __doubleUnderscores 的改進版本,標準組織將使用它來為語言添加新特性,而不會對已有程式碼產生影響。

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

JavaScript是現代Web開發的基石,它的主要功能包括事件驅動編程、動態內容生成和異步編程。 1)事件驅動編程允許網頁根據用戶操作動態變化。 2)動態內容生成使得頁面內容可以根據條件調整。 3)異步編程確保用戶界面不被阻塞。 JavaScript廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。

JavaScript的最新趨勢包括TypeScript的崛起、現代框架和庫的流行以及WebAssembly的應用。未來前景涵蓋更強大的類型系統、服務器端JavaScript的發展、人工智能和機器學習的擴展以及物聯網和邊緣計算的潛力。

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務
