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
    });
    
  • 相关阅读:
    asp.net 验证输入有效性
    优化SQL SERVER访问性能
    with nocheck ; nocheck
    VB SStab设置当前页
    相互关联的子查询
    mybatis 批量查询参数语句
    转载 mysql函数大全
    Tchar.h 中的一般文本映射
    字节序的概念、判断、及转换
    Windows风格与C/C++风格:UNICODE VS _UNICODE 与 TEXT() VS _T()
  • 原文地址:https://www.cnblogs.com/Ryukaka/p/7885772.html
Copyright © 2011-2022 走看看