六: ES6 符号 Symbol

前言

该部分为书籍 深入理解ES6 第六章(符号与符号属性)笔记

创建符号值

  • 符号没有字面量形式, 这在 JS 的基本类型中是独一无二的. 使用全局 Symbol 函数来创建一个符号值
  • 由于符号值是基本类型的值, 因此调用 new Symbol() 将会抛出错误. 你可以通过 new
    Object(yourSymbol) 来创建一个符号实例,但尚不清楚这能有什么作用。
  • Symbol 函数还可以接受一个额外的参数用于描述符号值, 该描述不能用来访问对应属性, 但它能用于调试. 符号的描述信息被存储在内部属性 [[Description]] 中, 当符号的 toString() 方法被显式或隐式调用时, 该属性都会被读取. 此外没有任何办法可以从代码中直接访问 [[Description]] 属性
let firstName = Symbol("first name");
let person = {};

person[firstName] = "Nicholas";

console.log("first name" in person); // false
console.log(person[firstName]); // "Nicholas"
console.log(firstName); // "Symbol(first name)"
  • 识别符号值: 可以使用 typeof 运算符来判断一个变量是否为符号

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

    尽管有其他方法可以判断一个变量是否为符号, typeof 运算符依然是最准确、最优先
    的判别手段。

使用符号值

可以在任意能使用 "需计算属性名" 的场合使用符号. 还可以在 Object.defineProperty()Object.defineProperties() 调用中使用它

let firstName = Symbol("first name");

// 使用一个需计算字面量属性
let person = {
    [firstName]: "Nicholas"
};

// 让该属性变为只读
Object.defineProperty(person, firstName, { writable: false });

let lastName = Symbol("last name");

Object.defineProperties(person, {
    [lastName]: {
        value: "Zakas",
        writable: false
    }
});

console.log(person[firstName]); // "Nicholas"
console.log(person[lastName]); // "Zakas"

共享符号值

  • 创建共享符号值, 应使用 Symbol.for() 方法而不是 Symbol() 方法. Symbol.for()方法仅接受单个字符串类型的参数,作为目标符号值的标识符,同时此参数也会成为该符号的描述信息。

    let uid = Symbol.for("uid");
    let object = {};
    
    object[uid] = "12345";
    
    console.log(object[uid]); // "12345"
    console.log(uid); // "Symbol(uid)"
    
  • Symbol.for() 方法首先会搜索全局符号注册表,看是否存在一个键值为 "uid" 的符号值。若是,该方法会返回这个已存在的符号值;否则,会创建一个新的符号值,并使用该键值将其记录到全局符号注册表中,然后返回这个新的符号值

  • let uid = Symbol.for("uid");
    let object = {
      [uid]: "12345"
    };
    
    console.log(object[uid]); // "12345"
    console.log(uid); // "Symbol(uid)"
    
    let uid2 = Symbol.for("uid");
    
    console.log(uid === uid2); // true
    console.log(object[uid2]); // "12345"
    console.log(uid2); // "Symbol(uid)"
    
  • 可以使用 Symbol.keyFor() 方法在全局符号注册表中根据符号值检索出对应的键值,

    注意: 使用 Symobol() 方法创建的是不会注册到全局符号注册表的

    let uid = Symbol.for("uid");
    console.log(Symbol.keyFor(uid)); // "uid"
    
    let uid2 = Symbol.for("uid");
    console.log(Symbol.keyFor(uid2)); // "uid"
    
    let uid3 = Symbol("uid");
    console.log(Symbol.keyFor(uid3)); // undefined
    
  • 全局符号注册表类似于全局作用域,是一个共享环境,这意味着你不应当假设某些值是否已存在于其中。在使用第三方组件时,为符号的键值使用命名空间能够减少命名冲突的可能性,举个例子: jQuery 代码应当为它的所有键值使用 "jquery." 的前缀,如"jquery.element" 或类似的形式。

符号值的转换

  • 符号类型在进行转换时非常不灵活, 因为其他类型缺乏与符号值的合理等价, 尤其是符号值无法被转换为字符串值或数值(会抛出错误)
  • 无论对符号使用哪种数学运算符都会导致错误,但使用逻辑运算符则不会,因为符号值在逻辑运算中会被认为等价于 true (就像 JS中其他的非空值那样)。

检索符号属性

ES6 新增了Object.getOwnPropertySymbols() 方法,以便让你可以检索对象的符号类型属性。

let uid = Symbol.for("uid");
let object = {
    [uid]: "12345"
};

let symbols = Object.getOwnPropertySymbols(object);

console.log(symbols.length); // 1
console.log(symbols[0]); // "Symbol(uid)"
console.log(object[symbols[0]]); // "12345"

使用知名符号暴露内部方法

ES5 的中心主题之一是披露并定义了一些魔术般的成分,而这些部分是当时开发者所无法自行模拟的。 ES6 延续了这些工作,对原先属于语言内部逻辑的部分进行了进一步的暴露,允许使用符号类型的原型属性来定义某些对象的基础行为。

**这些符号是: **

  1. Symbol.hasInstance :供 instanceof 运算符使用的一个方法,用于判断对象继承关系。
  2. Symbol.isConcatSpreadable :一个布尔类型值,在集合对象作为参数传递给Array.prototype.concat() 方法时,指示是否要将该集合的元素扁平化。
  3. Symbol.iterator :返回迭代器(参阅第七章)的一个方法。
  4. Symbol.match :供 String.prototype.match() 函数使用的一个方法,用于比较字符串。
  5. Symbol.replace :供 String.prototype.replace() 函数使用的一个方法,用于替换子字符串。
  6. Symbol.search :供 String.prototype.search() 函数使用的一个方法,用于定位子字符串
  7. Symbol.species :用于产生派生对象(参阅第八章)的构造器。
  8. Symbol.split :供 String.prototype.split() 函数使用的一个方法,用于分割字符串。
  9. Symbol.toPrimitive :返回对象所对应的基本类型值的一个方法。
  10. Symbol.toStringTag :供 String.prototype.toString() 函数使用的一个方法,用于创建对象的描述信息。
  11. Symbol.unscopables :一个对象,该对象的属性指示了哪些属性名不允许被包含在with 语句中。

重写知名符号所定义的方法, 会把一个普通对象改变成奇异对象, 因为它改变了一些默认的内部行为

1. Symbol.hasInstance 属性

  • 作用: 用于判断指定对象是否为本函数的一个实例。 方法定义在 Function.prototype 上, 因此所有函数都继承了面对 instanceof 运算符时的默认行为

    obj instanceof Array;
    
    // 等价于 -- ES6 从本质上将 instanceof 运算符重定义为上述方法调用的简写语法,这样使用 instanceof 便会触发一次方法调用,实际上允许你改变该运算符的工作。
    Array[Symbol.hasInstance](obj);
    
  • Symbol.hasInstance 属性自身是不可写入、不可配置、不可枚举的,从而保证它不会被错误地重写

  • 要重写一个不可写入的属性, 必须使用 Object.defineProperty()

    function SpecialNumber() {
      // empty
    }
    
    // 通过重写构造函数本身的属性来重写 instanceof 运算符的结果
    Object.defineProperty(SpecialNumber, Symbol.hasInstance, {
      value: function(v) {
            // 将介于 1 到 100 之间的数值认定为一个特殊的数值类型,
          return (v instanceof Number) && (v >=1 && v <= 100);
      }
    });
    
    let two = new Number(2),
      zero = new Number(0);
    
    console.log(two instanceof SpecialNumber); // true
    console.log(zero instanceof SpecialNumber); // false
    
    
  • 可以重写所有内置函数(例如 Date 或 Error )的 Symbol.hasInstance 属性,但并不建议这么做,因为这会让你的代码变得难以预测而又混乱。最好仅在必要时对自己的函数重写Symbol.hasInstance 。

2. Symbol.isConcatSpreadable

  • 作用: Symbol.isConcatSpreadable 属性是一个布尔类型的属性,它表示目标对象拥有长度属性与数值类型的键、并且数值类型键所对应的属性值在参与 concat() 调用时需要被分离为个体。

3. Symbol.match、 Symbol.replace、Symbol.search、Symobol.split

  • 作用: 这 4 个符号表示可以将正则表达式作为字符串对应方法的第一个参数传入, Symbol.match对应 match() 方法, Symbol.replace 对应 replace() , Symbol.search 对应 search(), Symbol.split 则对应 split() 。这些符号属性被定义在 RegExp.prototype 上作为默认实现,以供对应的字符串方法使用。

4. Symbol.toPrimittive

  • 作用: Symbol.toPrimitive 方法被定义在所有常规类型的原型上,规定了在对象被转换为基本类型值的时候会发生什么。当需要转换时, Symbol.toPrimitive 会被调用,并按照规范传入一个提示性字符串参数。该参数有 3 种可能:当参数值为 "number" 的时候,Symbol.toPrimitive 应当返回一个数值;当参数值为 "string" 的时候,应当返回一个字符串;而当参数为 "default" 的时候,对返回值类型没有特别要求。

    通过定义 Symbol.toPrimitive 方法,你可以重写这些默认的转换行为。

    对于大部分常规对象,“数值模式”依次会有下述行为:

    1. 调用 valueOf() 方法,如果方法返回值是一个基本类型值,那么返回它;
    2. 否则,调用 toString() 方法,如果方法返回值是一个基本类型值,那么返回它;
    3. 否则,抛出一个错误。

    类似的,对于大部分常规对象,“字符串模式”依次会有下述行为:

    1. 调用 toString() 方法,如果方法返回值是一个基本类型值,那么返回它;

    2. 否则,调用 valueOf() 方法,如果方法返回值是一个基本类型值,那么返回它;

    3. 否则,抛出一个错误

    在多数情况下,常规对象的默认模式都等价于数值模式(只有 Date 类型例外,它默认使用
    字符串模式)
    ==> “默认模式”只在使用 == 运算符、 + 运算符、或者传递单一参数给 Date 构造器的时候被使用,而大部分运算符都使用字符串模式或是数值模式

function Temperature(degrees) {
    this.degrees = degrees;
}

Temperature.prototype[Symbol.toPrimitive] = function(hint) {
    switch (hint) {
        case "string":
            return this.degrees + "\u00b0"; // 温度符号
        case "number":
            return this.degrees;
        case "default":
            return this.degrees + " degrees";
    }
};

let freezing = new Temperature(32);

console.log(freezing + "!"); // "32 degrees!"
console.log(freezing / 2); // 16
console.log(String(freezing)); // "32°"

5. Symbol.toStringTag

  • 作用: ES6 通过 Symbol.toStringTag 重定义了相关行为,该符号代表了所有对象的一个属性,定义了 Object.prototype.toString.call() 被调用时应当返回什么值。也就是修改对象内部属性[[class]]
function Person(name) {
    this.name = name;
}

Person.prototype[Symbol.toStringTag] = "Person";

let me = new Person("Nicholas");

console.log(me.toString()); // "[object Person]"
console.log(Object.prototype.toString.call(me)); // "[object Person]"

6. Symbol.unscopables

  • 作用: 在 Array.prototype 上使用,以指定哪些属性不允许在 with 语句内被绑定。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容