首頁  >  文章  >  web前端  >  深入了解JavaScript中的Symbol的使用方法_基礎知識

深入了解JavaScript中的Symbol的使用方法_基礎知識

WBOY
WBOY原創
2016-05-16 15:48:301449瀏覽

Symbol 是什麼?

Symbols 不是圖標,也不是指在程式碼中可以使用小圖片:

2015728114935470.jpg (291×49)

也不是指其他一些東西的語法。那麼,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 的三種方法

  1.     Symbol() 每次呼叫時都會傳回一個唯一的 Symbol。
  2.     Symbol.for(string) 從 Symbol 註冊表中傳回對應的 Symbol,與上個方法不同的是,Symbol 註冊表中的 Symbol 是共用的。也就是說,如果你呼叫 Symbol.for("cat") 三次,都會傳回相同的 Symbol。當不同頁面或同一頁面不同模組需要共用 Symbol 時,註冊表就非常有用。
  3.     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 的改進版本,標準組織將使用它來為語言添加新特性,而不會對已有程式碼產生影響。

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn