Symbol
是 ECMAScript 6 新增的基本數(shù)據(jù)類型茸塞。Symbol
提供的實例是唯一抵代、不可變的。它的用途可以確保對象屬性使用唯一標識符蟀悦,不會發(fā)生屬性沖突的危險。
Symbol
提供 Symbol()
函數(shù)來返回 Symbol
類型的值氧敢。語法如下所示:
Symbol(desc)
-
desc
可選的字符串類型的參數(shù)日戈。是用來描述Symbol
的。
通過 Symbol()
函數(shù)初始化孙乖,可以通過 typeof
運算符識別 Symbol
類型浙炼,當然也可以傳入?yún)?shù)對 Symbol
進行描述,但與其 Symbol
定義或標識完全無關(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)肿员恚绻榈脚c key
對應的 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]()
方法;當對象需要被轉(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
]
*/
給這個方法傳入非正則表達式值會導致該值被轉(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
該屬性指定了當一個字符串替換所匹配字符串時所調(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
方法傳入非正則表達式值會導致該值被轉(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
時的情形,如果設置為 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)境綁定中排除。設置 Symbol.unscopables
并讓其映射對應屬性的鍵值為 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ù)值屬性存在的,當一個對象轉(zhuǎn)換為對應的原始值時汁讼,會調(diào)用此函數(shù)淆攻。
Symbol.toPrimitive
屬性可以將對象轉(zhuǎn)換為相應的原始值。很多內(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)注公眾號「海人為記」