zoukankan      html  css  js  c++  java
  • 【读书笔记】【深入理解ES6】#6-Symbol和Symbol属性

    在ES5及早期版本中,JS语言包含5中原始类型:

    • 字符串型
    • 数字型
    • 布尔型
    • null
    • undefined

    ES6引入了第六种原始类型:

    • Symbol

    创建Symbol

    let firstName = Symbol();
    let person = {};
    person[firstName] = "JiaJia";
    console.log(person[firstName]); // "JiaJia"
    

    Symbol的辨识方法

    使用 typeof 来检测辨识是否为Symbol。

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

    Symbol的使用方法

    所有使用可计算属性名的地方,都可以使用Symbol。

    let firstName = Symbol("first name");
    
    let person = {
        [firstName]: "JiaJia"
    };
    
    // 将属性设置为只读
    Object.defineProperty(person, firstName, { writable: false });
    
    let lastName = Symbol("last name");
    
    Object.defineProperties(person, {
        [lastName]: {
            value: "Liu",
            writable: false
        }
    });
    
    console.log(person[firstName]); // "JiaJia"
    console.log(person[lastName]); // "Liu"
    

    Symbol共享体系

    ES6提供了一个可以随时访问的全局Symbol注册表。
    使用 Symbol.for() 方法创建可共享的Symbol,它只接受一个参数,也就是即将创建的Symbol的字符串标识符,这个参数同样也被用作Symbol的描述。

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

    Symbol.for() 方法首先在全局Symbol注册表中搜索键为“uid”的Symbol是否存在,如果存在,直接返回已有的Symbol;否则,创建一个新的Symbol,并使用这个键在Symbol全局注册表中注册,随即返回新创建的Symbol。

    随后如果再传入同样的键调用 Symbol.for() 方法会返回相同的Symbol。

    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() 方法在 Symbol 全局注册表中检索与Symbol有关的键。

    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
    

    Symbol与类型强制转换

    其它类型没有与Symbol逻辑等价的值。
    可以使用Symbol的toString()方法返回Symbol描述里的内容,但是直接与字符串拼接或者参与数值计算,则会抛出错误。

    let uid = Symbol.for("uid"),
        desc = String(uid);
    console.log(desc); // "Symbol(uid)"
    
    desc = uid + ""; // 报错
    // Cannot convert a Symbol value to a string
    
    let sum = uid / 1; // 报错
    // Cannot convert a Symbol value to a number
    

    Symbol属性检索

    Object.keys() 方法和 Object.getOwnPropertyNames() 方法可以检索对象中所有的属性名。
    Object.keys() 方法返回所有可枚举的属性名;
    Object.getOwnPropertyNames() 方法不考虑属性的可枚举性一律返回。
    为了保持ES5函数的原有功能,这两个方法都不支持Symbol属性。
    ES6中添加了一个 Object.getOwnPropertySymbols() 方法来检索Symbol属性。
    该方法返回一个包含所有Symbol自有属性的数组。

    let uid = Symbol("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"
    

    通过 well-known Symbol 暴露内部操作

    通过在原型链上定义与Symbol相关的属性来暴露更多的语言内部逻辑。

    Symbol.hasInstance方法

    每个函数都要一个 Symbol.hasInstance 方法,用于确定对象是否为函数的实例。
    该方法在 Function.prototype 中定义,所以所有函数都继承了 instanceof 属性的默认行为。
    为了确保 Symbol.hasInstance 不会被意外重写,该方法被定义为不可写、不可配置并且不可枚举。

    Symbol.hasInstance 方法只接受一个参数,即要检查的值。如果传入的值时函数的实例,则返回 true。

    obj instanceof Array;
    

    上述代码等价于

    Array[Symbol.hasInstance](obj);
    

    本质上,ES6只是将 instanceof 操作符重新定义为此方法的简写语法。

    现在引入方法调用后,就可以随意改变 instanceof 的运行方式了。

    function MyObject() {
    
    }
    
    let obj = new MyObject();
    
    console.log(obj instanceof MyObject); // true
    
    Object.defineProperty(MyObject, Symbol.hasInstance, {
        value: function(v) {
            return false;
        }
    });
    
    console.log(obj instanceof MyObject); // false
    

    只有通过 Object.defineProperty() 才能改写一个不可写属性。

    Symbol.isConcatSpreadable 属性

    JS数组的 concat() 方法被设计用于拼接两个数组,但也可以接受非数组参数。

    let colors1 = [ "red", "green" ],
        colors2 = colors1.concat([ "blue", "black" ], "brown");
    console.log(colors2.length); // 5
    console.log(colors2); // ["red", "green", "blue", "black", "brown"]
    

    JS规范声明,凡是传入数组参数,就会自动将它们分解为独立元素。ES6之前无法调整这个特性。

    Symbol.isConcatSpreadable 属性是一个布尔值,如果该属性值为true,则表示对象有 length 属性和数字键,故它的数值型属性值应该被独立添加到 concat() 调用的结果中。

    这个 Symbol.isConcatSpreadable 属性默认情况下不会出现在标准对象中,它只是一个可选属性,用于增强作用于特定对象类型的 concat() 方法的功能,有效简化其默认特性。

    下面方法自定义了一个在concat()调用中与数组类型的新类型:

    let collection = {
        0: "Hello",
        1: "World",
        length: 2,
        [Symbol.isConcatSpreadable]: true
    };
    
    let message = [ "Hi" ].concat(collection);
    
    console.log(message.length); // 3
    console.log(message); // ["Hi", "Hello", "World"]
    

    也可以将 Symbol.isConcatSpreadable 设置false,来防止元素在调用 concat() 方法时被分解。

    let collection = {
        0: "Hello",
        1: "World",
        length: 2,
        [Symbol.isConcatSpreadable]: false
    };
    
    let message = [ "Hi" ].concat(collection);
    
    console.log(message.length); // 2
    console.log(message); // ["Hi", {0: "Hello", 1: "World", length: 2, Symbol(Symbol.isConcatSpreadable): false}]
    

    Symbol.match、Symbol.replace、Symbol.search和Symbol.split属性

    字符串类型的几个方法可以接受正则表达式作为参数:

    • match(regex)
      确定给定字符串是否匹配正则表达式regex
    • replace(regex, replacement)
      将字符串中匹配正则表达式regex的部分替换为replacement
    • search(regex)
      在字符串中定位匹配正则表达式regex的位置索引
    • split(regex)
      按照匹配正则表达式regex的元素将字符串分切,并将结果存入数组中

    在ES6中,可以使用对应的4个Symbol,自定义对象来替换正则表达式来进行匹配。

    • Symbol.match
      接受一个字符串类型的参数,如果匹配成功则返回匹配元素的数组,否则返回null
    • Symbol.replace
      接受一个字符串类型的参数和一个替换用的字符串,最终依然返回一个字符串
    • Symbol.search
      接受一个字符串参数,如果匹配到内容,则返回数字类型的索引位置,否则返回-1
    • Symbol.split
      接受一个字符串参数,根据匹配内容将字符串分解,并返回一个包含分解后片段的数组
    // 实际上等价于 /^.{10}$/
    let hasLengthOf10 = {
        [Symbol.match]: function(value) {
            return value.length === 10 ? [value.substring(0, 10)] : null;
        },
        [Symbol.replace]: function(value, replacement) {
            return value.length === 10 ? replacement : value;
        },
        [Symbol.search]: function(value) {
            return value.length === 10 ? 0 : -1;
        },
        [Symbol.split]: function(value) {
            return value.length === 10 ? ["", ""] : [value];
        }
    };
    
    let message1 = "Hello world", // 11个字符
        message2 = "Hello Dlph"; // 10个字符
    
    let match1 = message1.match(hasLengthOf10),
        match2 = message2.match(hasLengthOf10);
    
    console.log(match1); // null
    console.log(match2); // ["Hello Dlph"]
    
    let replace1 = message1.replace(hasLengthOf10),
        replace2 = message2.replace(hasLengthOf10);
    
    console.log(replace1); // "Hello world"
    console.log(replace2); // "Hello Dlph"
    
    let search1 = message1.search(hasLengthOf10),
        search2 = message2.search(hasLengthOf10);
    
    console.log(search1); // -1
    console.log(search2); // 0
    
    let split1 = message1.split(hasLengthOf10),
        split2 = message2.split(hasLengthOf10);
    
    console.log(split1); // ["Hello world"]
    console.log(split2); // ["", ""]
    

    Symbol.toPrimitive 方法

    在JS引擎中,当执行特定操作时,经常会尝试将对象转换到相应的原始值。
    在ES6中,可以通过 Symbol.toPrimitive 方法更改这个原始值。

    Symbol.toPrimitive 方法被定义在每一个标准类型的原型上,并且规定了当对象被转换为原始值时应当执行的操作。
    该方法接受一个参数 类型提示hint),该值只有三种选择:"number"、"string"和"default"。根据参数返回值分别为 数字、字符和无类型偏好的值。

    数字模式

    1. 调用 valueOf() 方法,如果结果为原始值,则返回;
    2. 否则,调用 toString() 方法,如果结果为原始值,则返回;
    3. 如果再无可选值,则抛出错误。

    字符串模式

    1. 调用 toString() 方法,如果结果为原始值,则返回;
    2. 否则,调用 valueOf() 方法,如果结果为原始值,则返回;
    3. 如果再无可选值,则抛出错误。

    默认模式

    1. 在大多数情况下,标准对象会将默认模式按数字模式处理(除了 Date 对象,在这种情况下,会将默认模式按照字符串模式处理)。

    如果自定义了 Symbol.toPrimitive 方法,则可以覆盖这些默认的强制转换类型。

    Note

    默认模式只用于 == 运算、+ 运算及给Date构造函数传递一个参数时。
    在大多数的操作中,使用的都是字符串模式或数字模式。

    function Temperature(degrees) {
        this.degrees = degrees;
    }
    
    Temperature.prototype[Symbol.toPrimitive] = function(hint) {
        switch (hint) {
            case "string":
                return this.degrees + "u00b0"; // degrees symbol
            case "number":
                return this.degrees;
            case "default":
                return this.degrees + " degrees";
        }
    };
    
    var freezing = new Temperature(32);
    
    console.log(freezing + "!"); // "32 degrees!"
    console.log(freezing / 2); // 16
    console.log(String(freezing)); // "32°"
    
    • + 操作符触发的是默认模式;
    • / 操作符触发的是数字模式;
    • String() 函数触发字符串模式。

    Note

    针对三种模式返回不同的值是可行的,但更常见的做法是,将默认模式设置设置成与字符串模式或数字模式相同的处理逻辑。

    Symbol.toStringTag属性

    Symbol.toStringTag 所代表的属性在每一个对象中都存在,其定义了调用对象的 Object.prototype.toString.call() 方法时返回的值。
    对于数组,调用该函数返回的值通常是“Array”,它正是存储在对象的 Symbol.toStringTag 属性中的。
    同样的,也可以为自己的对象定义 Symbol.toStringTag 的值。

    function Person(name) {
        this.name = name;
    }
    
    var me = new Person("JiaJia");
    
    console.log(me.toString()); // "[object Object]"
    console.log(Object.prototype.toString.call(me)); // "[object Object]"
    
    // 为对象定义自己的 Symbol.toStringTag 值
    Person.prototype[Symbol.toStringTag] = "Person";
    
    // toString() 方法默认返回 Symbol.toStringTag 的值
    console.log(me.toString()); // "[object Person]"
    console.log(Object.prototype.toString.call(me)); // "[object Person]"
    
    // 自定义 toString 方法
    Person.prototype.toString = function() {
        return this.name;
    }
    
    console.log(me.toString()); // "JiaJia"
    // 自定义 toString() 方法后,不会影响 Object.prototype.toString.call() 方法的值
    console.log(Object.prototype.toString.call(me)); // "[object Person]"
    
    • toString() 方法默认返回 Symbol.toStringTag 的值。
    • 自定义 toString() 方法后,不会影响 Object.prototype.toString.call() 方法的值

    Symbol.unscopables属性

    with 语句的初衷是可以免于编写重复的代码。但加入 with 语句后,代码变的难以理解,执行性能很差且容易导致程序出错。最终,标准固定,在严格模式下不可以使用 with 语句。
    尽管未来不会使用 with 语句,但是 ES6 仍在非严格模式下提供了向后兼容性。

    var values = [1, 2, 3],
        colors = ["red", "green", "blue"],
        color = "black";
    
    with(colors) {
        // 相当于调用了 colors.push 方法
        push(color);
        push(...values);
    }
    
    console.log(colors); // ["red", "green", "blue", "black", 1, 2, 3]
    

    Symbol.unscopables 通常用于 Array.prototype,以在 with 语句中标识出不创建绑定的属性名。
    Symbol.unscopables 是以对象的形式出现的,它的键是在with语句中要忽略的标识符,其对应的值必须是true。

    这里是一个为数组添加默认的 Symbol.unscopables 属性的示例:

    // 已默认内置到ES6中
    Array.prototype[Symbol.unscopables] = Object.assign(Object.create(null), {
        copyWithin: true
        entries: true
        fill: true
        find: true
        findIndex: true
        includes: true
        keys: true
    });
    
  • 相关阅读:
    x64 平台开发 Mapxtreme 编译错误
    hdu 4305 Lightning
    Ural 1627 Join(生成树计数)
    poj 2104 Kth Number(可持久化线段树)
    ural 1651 Shortest Subchain
    hdu 4351 Digital root
    hdu 3221 Bruteforce Algorithm
    poj 2892 Tunnel Warfare (Splay Tree instead of Segment Tree)
    hdu 4031 Attack(BIT)
    LightOJ 1277 Looking for a Subsequence
  • 原文地址:https://www.cnblogs.com/Ryukaka/p/7885772.html
Copyright © 2011-2022 走看看