快速了解ES6的Symbol

JavaScript Symbol.png

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络它,而它也提供了 constructordescription 兩個屬性族檬。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.asyncIteratorES2018 規(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)注公眾號「海人為記

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市唱较,隨后出現(xiàn)的幾起案子扎唾,更是在濱河造成了極大的恐慌,老刑警劉巖南缓,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件胸遇,死亡現(xiàn)場離奇詭異,居然都是意外死亡汉形,警方通過查閱死者的電腦和手機纸镊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來概疆,“玉大人逗威,你說我怎么就攤上這事〔砑剑” “怎么了凯旭?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長楣颠。 經(jīng)常有香客問我尽纽,道長咐蚯,這世上最難降的妖魔是什么童漩? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮春锋,結(jié)果婚禮上矫膨,老公的妹妹穿的比我還像新娘。我一直安慰自己期奔,他們只是感情好侧馅,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著呐萌,像睡著了一般馁痴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上肺孤,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天罗晕,我揣著相機與錄音,去河邊找鬼赠堵。 笑死小渊,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的茫叭。 我是一名探鬼主播酬屉,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了呐萨?” 一聲冷哼從身側(cè)響起杀饵,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎谬擦,沒想到半個月后凹髓,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡怯屉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年蔚舀,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锨络。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡赌躺,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出羡儿,到底是詐尸還是另有隱情礼患,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布掠归,位于F島的核電站缅叠,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏虏冻。R本人自食惡果不足惜肤粱,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望厨相。 院中可真熱鬧领曼,春花似錦、人聲如沸蛮穿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽践磅。三九已至单刁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間府适,已是汗流浹背羔飞。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留细溅,地道東北人褥傍。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像喇聊,于是被迫代替她去往敵國和親恍风。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 在 ECMAScript 5 及早期版本中,語言包含 5 種原始類型:字符串型朋贬、數(shù)字型凯楔、布爾型、null 和 un...
    獨木舟的木閱讀 1,572評論 0 1
  • 1.概述 在ES5之前 數(shù)據(jù)類型只有六種 是: undefined ,Object, null,Number,St...
    黑云閱讀 149評論 0 2
  • 前言 該部分為書籍 深入理解ES6 第六章(符號與符號屬性)筆記 創(chuàng)建符號值 符號沒有字面量形式, 這在 JS 的...
    歲月靜好_不負此生閱讀 527評論 0 0
  • 概述 ES5 的對象屬性名都是字符串锦募,這容易造成屬性名的沖突摆屯。比如,你使用了一個他人提供的對象糠亩,但又想為這個對象添...
    硅谷干貨閱讀 172評論 0 0
  • 概述 ES5的對象屬性名都是字符串虐骑,這容易造成屬性名的沖突。比如赎线,你使用了一個他人提供的對象廷没,但又想為這個對象添加...
    oWSQo閱讀 529評論 1 3