你知道JavaScript中的Symbol類型怎么用嗎?

前言

ES5 的對象屬性名都是字符串晰韵,這容易造成屬性名沖突的問題发乔。比如,你使用別人的模塊/對象, 又想為之添加新的屬性,這就容易使得新屬性名與原有屬性名沖突雪猪。這就是 ES6 引入Symbol的原因栏尚,symbol能夠保證每個屬性的名字獨一無二。

一只恨、Symbol介紹

Symbol是 ES6 引入了一種新的原始數據類型译仗,它是一種特殊的桶蝎、不可變的數據類型坐儿,可以作為對象屬性的標識符使用,表示獨一無二的值。凡是屬性名屬于 Symbol 類型雹顺,就都是獨一無二的他炊,可以保證不會與其他屬性名產生沖突盯孙。

二羽利、語法

Symbol 值通過Symbol() 函數生成,Symbol()函數前不能使用new命令序苏,否則會報錯手幢。這是因為生成的 Symbol 是一個原始類型的值,不是對象忱详。也就是說围来,由于 Symbol 值不是對象,所以不能添加屬性踱阿」芮基本上,它是一種類似于字符串的數據類型软舌。

Symbol([description])

description :可選的字符串才漆。表示對symbol的描述,可用于調試但不訪問符號本身的符號的說明佛点。如果不加參數醇滥,在控制臺打印的都是Symbol,不利于區(qū)分超营。

let s = Symbol();
typeof s // "symbol"

// 上面代碼中鸳玩,變量s就是一個獨一無二的值。
// typeof運算符的結果演闭,表明變量s是Symbol數據類型不跟,而不是字符串之類的其他類型。
let s1 = Symbol()
let s2 = Symbol()
s1 // Symbol()
s2 // Symbol()
// s1和s2是兩個 Symbol 值米碰。如果不加參數窝革,它們在控制臺的輸出都是Symbol(),不利于區(qū)分吕座。

let s3 = Symbol('foo');
let s4 = Symbol('bar');
s3 // Symbol(foo)
s4 // Symbol(bar)
// 有了參數以后虐译,就等于為它們加上了描述,輸出的時候就能夠分清吴趴,到底是哪一個值漆诽。

s3.toString() // "Symbol(foo)"
s4.toString() // "Symbol(bar)"
// Symbol函數的參數只是表示對當前Symbol值的描述,因此相同參數的Symbol函數的返回值是不相等的。

// 沒有參數的情況
let s1 = Symbol();
let s2 = Symbol();
s1 === s2 // false

// 有參數的情況
let s1 = Symbol('foo');
let s2 = Symbol('foo');
s1 === s2 // false

三厢拭、Symbol值的類型轉換與運算

【3.1】Symbol不能與其他類型的值進行運算兰英,會報錯

let sym = Symbol('My symbol');
console.log("your symbol is " + sym) // TypeError: can't convert symbol to string
console.log(`your symbol is ${sym}`) // TypeError: can't convert symbol to string
console.log(2 + sym) // TypeError: can't convert symbol to number

【3.2】 Symbol 值可以顯式轉為字符串,也可以轉為布爾值蚪腐,但是不能轉為數值箭昵。

let sym = Symbol('My symbol');

// 轉字符串
String(sym)    // 'Symbol(My symbol)'
sym.toString() // 'Symbol(My symbol)'

// 轉布爾值
Boolean(sym) // true
!sym         // false

// 轉數字
Number(sym) // TypeError
sym + 2     // TypeError

【3.3】Symbol.prototype.description

上面代碼中税朴,我們可以將Symbol值顯示轉換字符串回季,但是這種用法不是很方便,ES2019提供了一個實例屬性description正林,直接返回 Symbol 的描述泡一。

const sym = Symbol('foo');
sym.description // "foo"
// sym的描述就是字符串foo

四、Symbol應用場景

【4.1】作為屬性名的使用

由于每一個 Symbol 值都是不相等的觅廓,這意味著 Symbol 值可以作為標識符鼻忠,用于對象的屬性名,就能保證不會出現同名的屬性杈绸。這對于一個對象由多個模塊構成的情況非常有用帖蔓,能防止某一個鍵被不小心改寫或覆蓋。注意瞳脓,Symbol 值作為對象屬性名時塑娇,不能用點運算符劫侧。

let mySymbol = Symbol();

// 第一種寫法
let a = {};
a[mySymbol] = 'Hello!';

// 第二種寫法
let a = {
    [mySymbol]: 'Hello!'
};

// 第三種寫法
let a = {};
Object.defineProperty(a, mySymbol, {
    value: 'Hello!'
});

// 以上寫法都得到同樣結果
a[mySymbol] // "Hello!"

// 不能用點運算符
const sym = Symbol();
const b = {};

b.sym = 'Hello!';
b[sym] // undefined  
b['sym'] // "Hello!" 字符串

【4.1】定義常量

Symbol 類型還可以用于定義一組常量,保證這組常量的值都是不相等的烧栋。

const RED = Symbol();
const BLACK = Symbol();

function getComplement(color) {
  switch (color) {
    case RED:
        return '紅色';
    case BLACK:
        return '黑色';
    default:
        throw new Error('不存在');
  }
}
getComplement(RED) // 紅色

【4.3】Symbol類型的屬性具有一定的隱藏性

let name = Symbol('name');
let obj = {
    age: 22,
    [name]: 'Joh'
};
console.log(Object.keys(obj)); // ["age"], 打印不出類型為Symbol的[name]屬性

// 使用for-in也打印不出 類型為Symbol的[name]屬性
for (let k in obj) {
    console.log(k); // age
}

// 使用 Object.getOwnPropertyNames 同樣打印不出 類型為Symbol的[name]屬性
console.log(Object.getOwnPropertyNames(obj)); ["age"]

// 使用 Object.getOwnPropertySymbols 可以
let key = Object.getOwnPropertySymbols(obj)[0];
console.log(obj[key]); // Joh

【4.4】Symbol.for 的應用

有時,我們希望重新使用同一個 Symbol 值珍特,Symbol.for方法可以做到這一點。它接受一個字符串作為參數魔吐,然后搜索有沒有以該參數作為名稱的 Symbol 值扎筒。如果有,就返回這個 Symbol 值画畅,否則就新建一個以該字符串為名稱的 Symbol 值砸琅,并將其注冊到全局。

let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');
s1 === s2 // true
// 上面代碼中轴踱,s1和s2都是Symbol值症脂,但是它們都是由同樣參數的Symbol.for方法生成的,所以實際上是同一個值。

let s3 = Symbol('foo');
let s4 = Symbol('foo');
s3 === s4 // false
// 由于Symbol()寫法沒有登記機制诱篷,所以每次調用都會返回一個不同的值壶唤。

Symbol.for() 與 Symbol() 這兩種寫法,都會生成新的 Symbol棕所。它們的區(qū)別是:

  1. Symbol.for() 會被登記在全局環(huán)境中供搜索闸盔,Symbol() 不會
  2. Symbol.for() 不會每次調用就返回一個新的 Symbol 類型的值,而是會先檢查給定的 key 是否已經存在琳省,如果不存在才會新建一個值迎吵。比如,如果你調用Symbol.for("cat") 30 次针贬,每次都會返回同一個 Symbol 值击费,但是調用Symbol("cat") 30 次,會返回 30 個不同的 Symbol 值桦他。

【4.5】Symbol.keyFor的應用

Symbol.keyFor()方法返回一個已登記的 Symbol 類型值的key蔫巩。注意,Symbol.keyFor()在函數內部運行快压,但是生成的 Symbol 值也是登記在全局環(huán)境的圆仔。

let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"

let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined
// 上面代碼中,變量s2屬于未登記的 Symbol 值蔫劣,所以返回undefined

function bar() {
  return Symbol.for('bar');
}
const x = bar();
const y = Symbol.for('bar');
console.log(x === y); // true
// 上面代碼中坪郭,Symbol.for('bar')是函數內部運行的,但是生成的 Symbol 值是登記在全局環(huán)境的
// 所以拦宣,第二次運行Symbol.for('bar')可以取到這個 Symbol 值截粗。

五、內置的Symbol值

除了定義自己使用的 Symbol 值以外鸵隧,ES6 還提供了 11 個內置的 Symbol 值绸罗,指向語言內部使用的方法。

【5.1】Symbol.hasInstance

對象的Symbol.hasInstance 屬性豆瘫,指向一個內部方法珊蟀。當其他對象使用instanceof運算符,判斷是否為該對象的實例時外驱,會調用這個方法育灸。比如昵宇,foo instanceof Foo 在語言內部瓦哎,實際調用的是FooSymbol.hasInstance ???????

class MyClass {
  [Symbol.hasInstance](foo) {
    return foo instanceof Array;
  }
}

[1, 2, 3] instanceof new MyClass() // true

// 上面代碼中柔逼,MyClass是一個類愉适,new MyClass()會返回一個實例维咸。
// 該實例的Symbol.hasInstance方法癌蓖,會在進行instanceof運算時自動調用疆股,判斷左側的運算子是否為Array的實例旬痹。

【5.2】Symbol.isConcatSpreadable???????

對象的Symbol.isConcatSpreadable???????屬性等于一個布爾值讨越,表示該對象用于Array.prototype.concat()時把跨,是否可以展開。

let arr1 = ['c', 'd'];
['a', 'b'].concat(arr1, 'e') // ['a', 'b', 'c', 'd', 'e']
arr1[Symbol.isConcatSpreadable] // undefined

let arr2 = ['c', 'd'];
arr2[Symbol.isConcatSpreadable] = false;
['a', 'b'].concat(arr2, 'e') // ['a', 'b', ['c','d'], 'e']

// 上面代碼說明崔赌,數組的默認行為是可以展開健芭,Symbol.isConcatSpreadable默認等于undefined慈迈。
// 該屬性等于true時省有,也有展開的效果蠢沿,和undefined一樣
// 該屬性等于false時,不展開 

類似數組的對象正好相反恤磷,默認不展開。它的Symbol.isConcatSpreadable???????屬性設為true精绎,才可以展開代乃。

let obj = {length: 2, 0: 'c', 1: 'd'};
['a', 'b'].concat(obj, 'e') // ['a', 'b', obj, 'e']

obj[Symbol.isConcatSpreadable] = true;
['a', 'b'].concat(obj, 'e') // ['a', 'b', 'c', 'd', 'e']

// 上面類似數組Symbol.isConcatSpreadable設為true才可以展開

【5.3】Symbol.species

對象的Symbol.species屬性仿粹,指向一個構造函數。創(chuàng)建衍生對象時堕仔,會使用該屬性摩骨。

class MyArray extends Array {
}

const a = new MyArray(1, 2, 3);
const b = a.map(x => x);
const c = a.filter(x => x > 1);

b instanceof MyArray // true
c instanceof MyArray // true

// 上面代碼中朗若,子類MyArray繼承了父類Array,a是MyArray的實例灾馒,b和c是a的衍生對象遣总。
// 你可能會認為旭斥,b和c都是調用數組方法生成的,所以應該是數組(Array的實例)董饰,但實際上它們也是MyArray的實例卒暂。

Symbol.species屬性就是為了解決這個問題而提供的÷μ現在近速,我們可以為myArray設置Symbol.species屬性堪旧。

class MyArray extends Array {
  static get [Symbol.species]() { return Array; }
}
// 上面代碼中淳梦,由于定義了Symbol.species屬性昔字,創(chuàng)建衍生對象時就會使用這個屬性返回的函數作郭,作為構造函數夹攒。
// 這個例子也說明,定義Symbol.species屬性要采用get取值器

const a = new MyArray();
const b = a.map(x => x);

b instanceof MyArray // false
b instanceof Array // true
// 上面代碼中压语,a.map(x => x)生成的衍生對象b无蜂,就不是MyArray的實例蒙谓,而直接就是Array的實例累驮。

【5.4】Symbol.match

對象的 Symbol.match 屬性谤专,指向一個函數午绳。當執(zhí)行 str.match(myObject) 時拦焚,如果該屬性存在,會調用它秕衙,返回該方法的返回值据忘。

String.prototype.match(regexp)
// 等同于
regexp[Symbol.match](this)
class MyMatcher {
  [Symbol.match](string) {
    return 'hello world'.indexOf(string);
  }
}

'e'.match(new MyMatcher()) // 1

【5.5】Symbol.replace

對象的Symbol.replace屬性,指向一個方法勇吊,當該對象被String.prototype.replace方法調用時汉规,會返回該方法的返回值。Symbol.replace方法會收到兩個參數膏燕,第一個參數是replace方法正在作用的對象悟民,第二個參數是替換后的值

String.prototype.replace(searchValue, replaceValue)
// 等同于
searchValue[Symbol.replace](this, replaceValue)

const x = {};
x[Symbol.replace] = (...s) => console.log(s);
'Hello'.replace(x, 'World') // ["Hello", "World"]
// 上面代碼中射亏, Symbol.replace方法收到兩個參數
// 第一個參數是replace方法正在作用的對象,上面例子是Hello
// 第二個參數是替換后的值及舍,上面例子是World锯玛。

【5.6】Symbol.search

對象的Symbol.search屬性兼蜈,指向一個方法为狸,當該對象被String.prototype.search方法調用時辐棒,會返回該方法的返回值。

String.prototype.search(regexp)
// 等同于
regexp[Symbol.search](this)

class MySearch {
  constructor(value) {
    this.value = value;
  }
  [Symbol.search](string) {
    return string.indexOf(this.value);
  }
}
'foobar'.search(new MySearch('foo')) // 0

【5.7】Symbol.split

對象的Symbol.split屬性泰涂,指向一個方法负敏,當該對象被String.prototype.split方法調用時其做,會返回該方法的返回值。

String.prototype.split(separator, limit)
// 等同于
separator[Symbol.split](this, limit)

// 下面方法使用Symbol.split方法驹沿,重新定義了字符串對象的split方法的行為
class MySplitter {
  constructor(value) {
    this.value = value;
  }
  [Symbol.split](string) {
    let index = string.indexOf(this.value);
    if (index === -1) {
      return string;
    }
    return [
      string.substr(0, index),
      string.substr(index + this.value.length)
    ];
  }
}

'foobar'.split(new MySplitter('foo'))
// ['', 'bar']

'foobar'.split(new MySplitter('bar'))
// ['foo', '']

'foobar'.split(new MySplitter('baz'))
// 'foobar'

【5.8】Symbol.iterator

對象的Symbol.iterator屬性渊季,指向該對象的默認遍歷器方法

const myIterable = {};
myIterable[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};

[...myIterable] // [1, 2, 3]

【5.9】Symbol.toPrimitive

對象的Symbol.toPrimitive 屬性,指向一個方法荷并。該對象被轉為原始類型的值時,會調用這個方法翩伪,返回該對象對應的原始類型值缘屹。Symbol.toPrimitive 被調用時轻姿,會接受一個字符串參數傅瞻,表示當前運算的模式,一共有三種模式嗅骄。

  • Number:該場合需要轉成數值
  • String:該場合需要轉成字符串
  • Default:該場合可以轉成數值饼疙,也可以轉成字符串
let obj = {
  [Symbol.toPrimitive](hint) {
    switch (hint) {
      case 'number':
        return 123;
      case 'string':
        return 'str';
      case 'default':
        return 'default';
      default:
        throw new Error();
     }
   }
};

2 * obj // 246
3 + obj // '3default'
obj == 'default' // true
String(obj) // 'str'

【5.10】Symbol.toStringTag

對象的Symbol.toStringTag屬性,指向一個方法窑眯。在該對象上面調用Object.prototype.toString方法時屏积,如果這個屬性存在,它的返回值會出現在toString方法返回的字符串之中磅甩,表示對象的類型炊林。也就是說,這個屬性可以用來定制[object Object][object Array]中object后面的那個字符串卷要。

// 例一
({[Symbol.toStringTag]: 'Foo'}.toString())
// "[object Foo]"

// 例二
class Collection {
  get [Symbol.toStringTag]() {
    return 'xxx';
  }
}
let x = new Collection();
Object.prototype.toString.call(x) // "[object xxx]"

【5.11】Symbol.unscopables

對象的Symbol.unscopables屬性渣聚,指向一個對象独榴。該對象指定了使用with關鍵字時,那些屬性會被with環(huán)境排除奕枝。

Array.prototype[Symbol.unscopables]
// {
//   copyWithin: true,
//   entries: true,
//   fill: true,
//   find: true,
//   findIndex: true,
//   includes: true,
//   keys: true
// }

Object.keys(Array.prototype[Symbol.unscopables])
// ['copyWithin', 'entries', 'fill', 'find', 'findIndex', 'includes', 'keys']
// 上面代碼說明棺榔,數組有 7 個屬性,會被with命令排除隘道。
// 沒有 unscopables 時
class MyClass {
  foo() { return 1; }
}

let foo = function () { return 2; };

with (MyClass.prototype) {
  foo(); // 1
}

// 有 unscopables 時
class MyClass {
  foo() { return 1; }
  get [Symbol.unscopables]() {
    return { foo: true };
  }
}

let foo = function () { return 2; };

with (MyClass.prototype) {
  foo(); // 2
}
// 上面代碼通過指定Symbol.unscopables屬性症歇,使得with語法塊不會在當前作用域尋找foo屬性设塔,即foo將指向外層作用域的變量。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末爆办,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子爆土,更是在濱河造成了極大的恐慌背犯,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件提陶,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進店門峻凫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事镐侯≈辉酰” “怎么了贴谎?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵仲翎,是天一觀的道長玫坛。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任停忿,我火速辦了婚禮席赂,結果婚禮上颅停,老公的妹妹穿的比我還像新娘喊熟。我一直安慰自己,他們只是感情好案铺,可當我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著磁携,像睡著了一般闷供。 火紅的嫁衣襯著肌膚如雪粮呢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天这难,我揣著相機與錄音赖草,去河邊找鬼扣囊。 笑死健提,一個胖子當著我的面吹牛网沾,可吹牛的內容都是我干的癞蚕。 我是一名探鬼主播,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼辉哥,長吁一口氣:“原來是場噩夢啊……” “哼桦山!你這毒婦竟也來了攒射?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤恒水,失蹤者是張志新(化名)和其女友劉穎会放,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體钉凌,經...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡咧最,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了御雕。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片矢沿。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖酸纲,靈堂內的尸體忽然破棺而出捣鲸,到底是詐尸還是另有隱情,我是刑警寧澤闽坡,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布栽惶,位于F島的核電站,受9級特大地震影響无午,放射性物質發(fā)生泄漏媒役。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一宪迟、第九天 我趴在偏房一處隱蔽的房頂上張望酣衷。 院中可真熱鬧,春花似錦次泽、人聲如沸穿仪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽啊片。三九已至,卻和暖如春玖像,著一層夾襖步出監(jiān)牢的瞬間紫谷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工捐寥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留笤昨,地道東北人。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓握恳,卻偏偏與公主長得像瞒窒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子乡洼,可洞房花燭夜當晚...
    茶點故事閱讀 44,947評論 2 355