Symbol詳解

2023-02-22 06:01:26

Symbol

Symboles6引入的一個新的原始資料型別,是一個獨一無二的值。
目前為止,js的資料型別有以下幾種:

資料型別 說明
undefined undefined
null null
boolean 布林值
string 字串
number 數位
Bigint 大整數
Object 物件
Symbol Symbol

Symbol通過Symbol()函數生成。物件的屬性名現在除了可以使用字串以外,還可以使用新增的Symbol型別。如果屬性名使用Symbol,那麼它就是獨一無二的,不與其它屬性名產生衝突。

let s = Symbol()
console.log(typeof s);  // symbol

注意:Symbol()函數前不能使用new,否則報錯。因為生成的Symbol是一個原始型別的值,而不是物件,所以不能使用new來呼叫。而且,Symbol值不是物件,不能給Symbol新增屬性。可以這麼理解,Symbol是一種類似於字串的資料型別。

Symbol接收字串作為引數,表示對Symbol的描述,新增描述可以用來區分多個Symbol

let s2 = Symbol('desc')
let s3 = Symbol('desc2')
console.log(s2);  // Symbol(desc)
console.log(s3);  // Symbol(desc2)

如果Symbol的引數傳入的是物件,需要把物件轉為字串再生成Symbol,否則會顯示[object Object]

let obj = {
       name : '東方不敗'
  }
let s4 = Symbol(JSON.stringify(obj)) 
console.log(s4); // Symbol({"name":"東方不敗"})

let s5 = Symbol(obj) 
console.log(s5);// Symbol([object Object])

Symbol傳入的引數只是一個描述,實際上SymbolSymbol並不相等。

let sy = Symbol()
let sy2 = Symbol()
console.log(sy === s2); // false

let sy3 = Symbol('a')
let sy4 = Symbol('a')
console.log(sy3 === sy4); // false

每呼叫一次Symbol()都會生成一個獨一無二的值,每個Symbol都不相等。

Symbol值不能參與其他型別值的運算,否則報錯。

let a = Symbol('hello')
console.log(a + 'world');  // 報錯 Cannot convert a Symbol value to a string

Symbol轉換

Symbol可以轉換為字串

let a2 = Symbol('hello')
console.log(String(a2)); // Symbol(hello)

如果需要返回Symbol的描述需要使用es2019提供的Symbol範例屬性description返回描述。

let a2 = Symbol('hello')
console.log(a2.description); // hello

Symbol可以轉換為布林值(boolean)

let a2 = Symbol('hello')
console.log(Boolean(a2));  // true
console.log(Boolean(!a2)); // false

Symbol屬性名

Symbol作為屬性名

let n = Symbol()
// 方式一
let obj2 = {
      [n] : '東方不敗'
   }
console.log(obj2);  // {Symbol(): '東方不敗'}
console.log(obj2[n]);  // 東方不敗

// 方式二
obj2[n] = '東方求敗'
console.log(obj2[n]);  // 東方求敗

// 方式三
let obj3 = {}
let back = Object.defineProperty(obj3,n,{value : '藝術概論'})
console.log(obj3[n]); // 藝術概論

Object.defineProperty使用說明
第一個引數:要在其上定義屬性的物件
第二個引數:要定義或修改的屬性的名稱
第三個引數:將被定義或修改的屬性描述符

Symbol值作為物件屬性名時,不能用點運運算元獲得Symbol屬性,使用點運運算元相當於是給物件新增了一個字串屬性名,而不是獲取Symbol

let n2 = Symbol()
let obj4 = {}
console.log(obj4.n2 = '中國工藝美術史');  // 中國工藝美術史
console.log(obj4[n2]);  // undefined
console.log(obj4);  // {n2: '中國工藝美術史'}

屬性名遍歷

Symbol是不可列舉的,Symbol作為物件鍵名時,是不可被遍歷的,for...inObject.keys等方法都得不到Symbol鍵名,並且JSON.stringify()也不會返回Symbol

let m = Symbol('a')
let f = {
    [m]:'東方不敗',
    name:'西方求敗',
    name2: '光合作用'
}

// 西方求敗 、 光合作用
for(k in f){
   console.log(f[k]);
}

console.log(Object.keys(f)); // ['name','name2']
console.log(JSON.stringify(f));  // {"name":"西方求敗","name2":"光合作用"}

Reflect.ownKeys()可以返回常規鍵名和Symbol鍵名

console.log(Reflect.ownKeys(f)); //  ['name', 'name2', Symbol(a)] 

Object.getOwnPropertySymbols()只返回Symbol屬性

console.log(Object.getOwnPropertySymbols(f)); // [Symbol(a)]

Symbol.for()、Symbol.keyFor()

Symbol.for()
Symbol有一個特性就是Symbol不等於Sombol,但有時候我們需要同一個Symbol

let r = Symbol.for('a')
let r2 = Symbol.for('a')
console.log(r === r2);  // true

Symbol.for()Symbol()都會生成新的Symbol,前者會被登記在全域性環境提供搜尋,後者不會。
Symbol.for()每次呼叫都會先檢查引數key是否存在,如果不存在才會新建一個值。
Symbol()每次呼叫都會新建一個值。

Symbol.keyFor()
Symbol.keyFor()返回已經登記的Symbol值的key

let r3 = Symbol.for('b')
let r4 = Symbol('c')
console.log(Symbol.keyFor(r3));  // b
console.log(Symbol.keyFor(r4));  // undefined

Symbol內建值

Symbol.hasInstance

Symbol.hasInstance用來判斷某個物件是否為某個構造器範例

class myClass {
     static [Symbol.hasInstance](val){
            return typeof val === 'number'
     }
     // static [Symbol.hasInstance](val){
     //     return typeof val === 'boolean'
     // }
 }
console.log(100 instanceof myClass); // true
console.log('100' instanceof myClass); // false

多個Symbol.hasInstance會覆蓋,只保留最下面的那一個。


Symbol.isConcatSpreadable

Symbol.isConcatSpreadable用於表示Array.prototype.concat()是否可以展開,true、undefined可以展開,false不可展開。

let arr1 = [1,2]
let arr2 = [3,4]
console.log(arr1[Symbol.isConcatSpreadable]);  // undefined
console.log(arr1.concat(arr2));  // [1,2,3,4]

console.log(arr1[Symbol.isConcatSpreadable] = false)
console.log(arr1.concat(arr2)); // [[1,2],3,4]


Symbol.species

物件的Symbol.species屬性指向一個建構函式,建立衍生物件時會使用該屬性

// 這裡繼承了Array的原型
class MyArray extends Array { }
let a = new MyArray(1,2,3)
let b = a.map(el => el + 1)
console.log(b);  // constructor : class MyArray


bc呼叫的是陣列方法,那麼應該是Array的範例,但實際上它們也是MyArray的範例

class MyArray extends Array {
      static get [Symbol.species]() { return Array }
}

let a = new MyArray(1,2,3)
let b = a.map(el => el + 1)
let c = a.filter(el => el == 2)

console.log(a,b,c);  // 1,2,3    2,3,4   2
console.log(b instanceof MyArray); // false
console.log(b);  // constructor : class MyArray

Symbol.species可以在建立衍生物件時使用這個屬性返回的函數作為建構函式。
這裡returnArray,所以建立的衍生物件使用的Array作為建構函式,而不是MyArray
如果這裡return一個String,那麼上面的map、filter會報錯,因為衍生物件使用的是String作為建構函式,String是沒有陣列方法的。


Symbol.match

Symbol.match指向一個函數,如果函數存在則會被呼叫,並返回該方法的返回值

class MyMatch {
      [Symbol.match](val){
         return 'hello world'.indexOf(val)
      }
}

// match字串方法,可以在字串內檢索指定的值並返回
console.log('e'.match(new MyMatch()));  // 1

案例原始碼:https://gitee.com/wang_fan_w/es6-science-institute

如果覺得這篇文章對你有幫助,歡迎點亮一下star喲