
Symbol 是 ECMAScript 6 新增的基本數(shù)據(jù)類型。Symbol 提供的實例是唯一、不可變的。它的用途可以確保對象屬性使用唯一標(biāo)識符,不會發(fā)生屬性沖突的危險。
Symbol 提供 Symbol() 函數(shù)來返回 Symbol 類型的值。語法如下所示:
Symbol(desc)
-
desc可選的字符串類型的參數(shù)。是用來描述Symbol的。
通過 Symbol() 函數(shù)初始化,可以通過 typeof 運算符識別 Symbol 類型,當(dāng)然也可以傳入?yún)?shù)對 Symbol 進行描述,但與其 Symbol 定義或標(biāo)識完全無關(guān)。
let s1 = Symbol();
console.log(typeof s1); // symbol
let s2 = Symbol('symbol instance');
let s3 = Symbol('symbol instance');
console.log(s1 == s2); // false
console.log(s2 == s3); // false
注意,Symbol 不能通過 new 關(guān)鍵字像構(gòu)造函數(shù)一樣創(chuàng)建實例,并拋出 TypeError 錯誤。這樣做是為了避免創(chuàng)建 Symbol 包裝對象。
let s = new Symbol(); // TypeError: Symbol is not a constructor
但是,可以使用 Object() 函數(shù)創(chuàng)建一個 Symbol 包裝器對象。
let s = Symbol("symbol instance");
console.log(typeof s); // symbol
let o = Object(s);
console.log(typeof o); // object
全局共享的 Symbol
Symbol() 函數(shù)創(chuàng)建的 Symbol 實例不是全局可用的 Symbol 類型。如果需要全局可用的 Symbol 實例,可以使用 Symbol.for(key) 方法和 Symbol.keyFor(key) 方法。
Symbol.for() 方法創(chuàng)建的 Symbol 會被放入一個全局的 Symbol 注冊表中。Symbol.for() 并不是每次都會創(chuàng)建一個新的 Symbol 實例,它會首先檢查給定的 key 是否已經(jīng)在注冊表中了。如果是,則會直接返回上次存儲的Symbol 值。否則,它會在新鍵一個。
let gs1 = Symbol.for('gloal symbol'); // 創(chuàng)建新符號
let gs2 = Symbol.for('global symbol'); // 重用已有符號
console.log(gs1 === gs2); // true
可以使用 Symbol.keyFor() 來查詢?nèi)肿员?,如果查到與 key 對應(yīng)的 Symbol 實例,則返回該 Symbol 實例的 key 值,否則返回 undefined。
// 創(chuàng)建全局符號
let s = Symbol.for('foo');
console.log(Symbol.keyFor(s)); // foo
// 創(chuàng)建普通符號
let s2 = Symbol('bar');
console.log(Symbol.keyFor(s2)); // undefined
如果傳給 Symbol.keyFor() 的不是符號,則該方法拋出 TypeError:
Symbol.keyFor(123); // TypeError: 123 is not a symbol
Symbol 作為屬性
Symbol 可以作為屬性來使用。如下所示:
let s1 = Symbol("property one");
let o = {
[s1]: 'symbol value one'
};
console.log(o); // { [Symbol(property one)]: 'symbol value one' }
Object.getOwnPropertySymbols() 方法會讓你在查找給定對象的 Symbol 屬性時返回一個 Symbol 類型的數(shù)組。
let s1 = Symbol("property one"),
s2 = Symbol("property two");
let o = {
[s1]: 'symbol value one',
[s2]: 'symbol value two',
name: 'symbol property sample',
index: 12
};
console.log(Object.getOwnPropertySymbols(o)); // [ Symbol(property one), Symbol(property two) ]
使用 Object.getOwnPropertyNames() 正好相反。如下所示:
console.log(Object.getOwnPropertyNames(o)); // [ 'name', 'index' ]
Symbol 原型
所有 Symbol 都繼承自 Symbol.prototype,而它也提供了 constructor 和 description 兩個屬性。constructor 屬性會返回實例原型的函數(shù),默認為 Symbol 函數(shù);而 description 屬性是一個只讀字符串,返回 Symbol 對象的描述。
Symbol.prototype 也提供了 toString() 和 valueOf() 方法用于返回 Symbol 描述符的字符串方法和對象的原始值。
Symbol.prototype[@@toPrimitive] 會將 Symbol 對象轉(zhuǎn)換為原始值。語法如下:
Symbol()[Symbol.toPrimitive](hint)
Symbol 的 [@@toPrimitive]() 方法返回該 Symbol 對象原始值作為 Symbol 數(shù)據(jù)形式。hint 參數(shù)未被使用。
JavaScript 調(diào)用 [@@toPrimitive]() 方法將一個對象轉(zhuǎn)換為原始值表示。你不需要自己調(diào)用 [@@toPrimitive]() 方法;當(dāng)對象需要被轉(zhuǎn)換為原始值時,JavaScript 會自動地調(diào)用該方法。
內(nèi)置的 Symbol 屬性
除了自己創(chuàng)建的 Symbol,JavaScript 還內(nèi)建了一些在 ECMAScript 5 之前沒有暴露給開發(fā)者的 Symbol,它們代表了內(nèi)部語言行為。這些 Symbol 最重要的用途之一是重新定義它們,從而改變原生結(jié)構(gòu)的行為。如重新定義 Symbol.iterator 屬性的值,來改變 for-of 在迭代對象時的行為。
注意 ,在提到 ECMAScript 規(guī)范時,經(jīng)常會引用符號在規(guī)范中的名稱,前綴為 @@。比如,@@iterator 指的就是 Symbol.iterator。
Symbol.iterator
該屬性返回一個對象默認的迭代器,被 for-of 使用。下面自定義一個迭代器。
class List {
constructor() {
this.index = 0;
this.data = arguments;
}
*[Symbol.iterator]() {
while(this.index < this.data.length) {
yield this.data[this.index++];
}
}
}
function iter() {
let list = new List("小玲", "小霞", "小星", "小民");
for (const v of list) {
console.log(v);
}
}
iter();
// 小玲
// 小霞
// 小星
// 小民
如上所示,通過 for-of 循環(huán)對一個對象進行迭代時,@@iterator 方法在不傳參的情況下被調(diào)用,返回的迭代器用于獲取要迭代的值。
Symbol.asyncIterator
一個返回對象默認的異步迭代器的方法。被 for await of 使用。
該方法返回一個對象默認的異步迭代器,由 for-await-of 語句使用。
class List {
constructor() {
this.index = 0;
this.data = arguments;
}
async *[Symbol.asyncIterator]() {
while(this.index < this.data.length) {
yield new Promise((resolve) => resolve(this.data[this.index++]));
}
}
}
async function asyIter() {
let list = new List("小玲", "小霞", "小星", "小民");
for await(const v of list) {
console.log(v);
}
}
asyIter();
// 小玲
// 小霞
// 小星
// 小民
如上所示,for-await-of 循環(huán)會利用 List 類中的函數(shù)執(zhí)行異步迭代操作。
注意,Symbol.asyncIterator 是 ES2018 規(guī)范定義的,只有新版本的瀏覽器支持它。
Symbol.match
該方法指定了用正則表達式去匹配字符串。String.prototype.match() 方法會調(diào)用此函數(shù)。
console.log('It\'s a real horror story'.match(/horror/));
/*
[
'horror',
index: 12,
input: "It's a real horror story",
groups: undefined
]
*/
給這個方法傳入非正則表達式值會導(dǎo)致該值被轉(zhuǎn)換為RegExp對象。如果想改變這種行為,讓方法直接使用參數(shù),則重新定義 Symbol.match 函數(shù)以取代默認對正則表達式求值的行為,從而讓match()方法使用非正則表達式實例。Symbol.match 函數(shù)接收一個參數(shù),就是調(diào)用 match()方法的字符串實例。返回的值沒有限制:
class StringMatcher {
static [Symbol.match](target) {
return target.includes('horror');
}
constructor(str) {
this.str = str;
}
[Symbol.match](target) {
return target.includes(this.str);
}
}
console.log('It\'s a real horror story'.match(StringMatcher)); // true
console.log('It\'s a real relaxing story'.match(StringMatcher)); // false
console.log('It\'s a real horror story'.match(new StringMatcher('horror'))); // true
console.log('It\'s a real horror story'.match(new StringMatcher('relaxing'))); // false
Symbol.replace
該屬性指定了當(dāng)一個字符串替換所匹配字符串時所調(diào)用的方法。供 String.prototype.replace() 方法調(diào)用此方法,用于替換子字符串。
console.log('It\'s a real horror story'.replace(/horror/, 'relaxing'));
// It's a real relaxing story
而正則表達式的原型上默認由 Symbol.replace 函數(shù)定義。給 String.prototype.replace 方法傳入非正則表達式值會導(dǎo)致該值被轉(zhuǎn)換為 RegExp 對象。
可以重新定義 Symbol.replace 函數(shù),用來取代默認行為。兩種定義 Symbol.replace 函數(shù)的方式如下所示:
class StringReplacer {
static [Symbol.replace](target, replacement) {
return target.split('horror').join(replacement);
}
constructor(str) {
this.str = str;
}
[Symbol.replace](target, replacement) {
return target.split(this.str).join(replacement);
}
}
console.log('It\'s a real horror story'.replace(StringReplacer, 'qux')); // It's a real qux story
console.log('It\'s a real horror story'.replace(new StringReplacer('horror'), 'relaxing')); // It's a real relaxing story
Symbol.replace 函數(shù)接收兩個參數(shù),即調(diào)用 replace() 方法的字符串實例和替換字符串。
Symbol.search
該方法會返回正則表達式在字符串中匹配的索引。String.prototype.search() 方法會調(diào)用此方法,用于查找索引。
console.log('It\'s a real horror story'.search(/horror/)); // 12
可以重新定義 Symbol.search 函數(shù),用來取代默認行為,從而讓 search() 方法使用非正則表達式的實例。如下所示:
class StringSearcher {
static [Symbol.search](target) {
return target.indexOf('qux');
}
constructor(str) {
this.str = str;
}
[Symbol.search](target) {
return target.indexOf(this.str);
}
}
console.log('It\'s a real horror story'.search(StringSearcher)); // -1
console.log('It\'s a real qux story'.search(StringSearcher)); // 12
console.log('qux, It\'s a real horror story'.search(StringSearcher)); // 0
console.log('It\'s a real horror story'.search(new StringSearcher('qux'))); // -1
console.log('It\'s a real horror story'.search(new StringSearcher('horror'))); // 12
console.log('It\'s a real horror story'.search(new StringSearcher('s'))); // 3
Symbol.search 函數(shù)接收一個參數(shù),就是調(diào)用 search() 方法的字符串實例。
Symbol.split
該方法會通過一個正則表達式的索引,來分隔字符串。String.prototype.split() 方法會調(diào)用此方法。
console.log('It\'s a real horror story'.split(/ /)); // 正則匹配的是空格
// [ "It's", 'a', 'real', 'horror', 'story' ]
可以重新定義 Symbol.split 函數(shù),用來取代默認行為,從而讓 split() 方法使用非正則表達式實例。
class StringSplitter {
static [Symbol.split](target) {
return target.split('qux');
}
constructor(str) {
this.str = str;
}
[Symbol.split](target) {
return target.split(this.str);
}
}
console.log('It\'s a real qux story'.split(StringSplitter)); // [ "It's a real ", ' story' ]
console.log('It\'s a real horror story'.split(new StringSplitter(' '))); // [ "It's", 'a', 'real', 'horror', 'story' ]
Symbol.split 函數(shù)接收一個參數(shù),就是調(diào)用 split() 方法的字符串實例。
Symbol.hasInstance
該屬性用于判斷某對象是否為某構(gòu)造器對象的實例??梢杂?Symbol.hasInstance 函數(shù)自定義 instanceof 在某個類上的行為。
instanceof 的場景如下:
class Instance {}
let ist = new Instance();
console.log(ist instanceof Instance); // true
而 ES6 中,instanceof 會使用 Symbol.hasInstance 屬性來確定關(guān)系。
console.log(Instance[Symbol.hasInstance](ist)); // true
Symbol.hasInstance 屬性定義在 Function 的原型上,默認所有函數(shù)和類都可以調(diào)用。由于 instanceof 會在原型鏈上尋找這個屬性定義,就跟在原型鏈上尋找其他屬性一樣,因此可以在繼承的類上通過靜態(tài)方法重新定義這個屬性:
class Instance {}
class SubInstance extends Instance {
static [Symbol.hasInstance]() {
return false;
}
}
let sist = new SubInstance();
console.log(Instance[Symbol.hasInstance](sist)); // true
console.log(sist instanceof Instance); // true
console.log(SubInstance[Symbol.hasInstance](sist)); // false
console.log(sist instanceof SubInstance); // false
Symbol.isConcatSpreadable
Symbol.isConcatSpreadable 用于配置某對象作為 Array.prototype.concat() 方法的參數(shù)時是否展開其數(shù)組元素。
const arr1 = ['a', 'b', 'c'];
const arr2 = [1, 2, 3];
let arr3 = arr1.concat(arr2);
console.log(arr3); // [ 'a', 'b', 'c', 1, 2, 3 ]
這是在 Symbol.isConcatSpreadable = true 時的情形,如果設(shè)置為 false,就不會展開數(shù)組。
arr2[Symbol.isConcatSpreadable] = false;
let arr4 = arr1.concat(arr2);
console.log(arr4);
/*
[
'a',
'b',
'c',
[ 1, 2, 3, [Symbol(Symbol.isConcatSpreadable)]: false ]
]
*/
由上可知,對于數(shù)組對象,使用 concat 在默認情況下會將數(shù)組中元素展開進行連接。重置 Symbol.isConcatSpreadable 可以改變默認行為。
Symbol.unscopables
Symbol.unscopables 指定對象值,其對象所有的以及繼承屬性,都會從關(guān)聯(lián)對象的 with 環(huán)境綁定中排除。設(shè)置 Symbol.unscopables 并讓其映射對應(yīng)屬性的鍵值為 true,就可以阻止該屬性出現(xiàn)在 with 環(huán)境綁定中。如下所示:
let o = {
name: '小玲'
};
with (o) {
console.log(name); // 小玲
}
o[Symbol.unscopables] = {
name: true
};
with (o) {
console.log(name); // ReferenceError: name is not defined
}
注意,不推薦使用 with,因此也不推薦使用 Symbol.unscopables。
Symbol.species
Symbol.species 作為創(chuàng)建派生對象的構(gòu)造函數(shù)。用 Symbol.species 定義靜態(tài)的 getter 方法,可以覆蓋新創(chuàng)建實例的原型定義:
class Array1 extends Array {}
class Array2 extends Array {
static get [Symbol.species]() {
return Array;
}
}
let a1 = new Array1();
console.log(a1 instanceof Array); // true
console.log(a1 instanceof Array1); // true
a1 = a1.concat('species');
console.log(a1 instanceof Array); // true
console.log(a1 instanceof Array1); // true
let a2 = new Array2();
console.log(a2 instanceof Array); // true
console.log(a2 instanceof Array2); // true
a2 = a2.concat('species');
console.log(a2 instanceof Array); // true
console.log(a2 instanceof Array2); // false
Symbol.toPrimitive
Symbol.toPrimitive 是一個內(nèi)置的 Symbol 值,它是作為對象的函數(shù)值屬性存在的,當(dāng)一個對象轉(zhuǎn)換為對應(yīng)的原始值時,會調(diào)用此函數(shù)。
Symbol.toPrimitive 屬性可以將對象轉(zhuǎn)換為相應(yīng)的原始值。很多內(nèi)置操作都會嘗試將值將對象轉(zhuǎn)換為原始值,包括字符串、數(shù)值和未指定的原始類型。如下所示:
class Sample1 {}
let s1 = new Sample1();
console.log(+s1); // NaN
console.log(`${s1}`); // [object Object]
console.log(s1 + ""); // [object Object]
class Sample2 {
constructor() {
this[Symbol.toPrimitive] = function(hint) {
switch (hint) {
case 'number': return 77;
case 'string': return 'hello world!';
default: return true;
}
}
}
}
let s2 = new Sample2();
console.log(+s2); // 77
console.log(`${s2}`); // hello world!
console.log(s2 + ""); // true
Symbol.toStringTag
Symbol.toStringTag 用于創(chuàng)建對象的默認字符串描述。由 Object.prototype.toString() 調(diào)用。許多內(nèi)置類型已經(jīng)指定了這個值,但自定義類實例可以明確定義:
// 沒有定義 `Symbol.toStringTag` 時
class StringTag {
constructor() {
}
}
let a = new StringTag();
console.log(a); // StringTag {}
console.log(a.toString()); // [object Object]
// 定義 `Symbol.toStringTag` 時
class StringTag {
constructor() {
this[Symbol.toStringTag] = 'StringTag';
}
}
let a = new StringTag();
console.log(a); // StringTag { [Symbol(Symbol.toStringTag)]: 'StringTag' }
console.log(a.toString()); // [object StringTag]
更多內(nèi)容請關(guān)注公眾號「海人為記」