《Understanding ES6》chapter 6 Symbols and Symbol Properties

268 阅读6分钟

2019-7-17

第六章 符号与符号属性

(一)符号值

  1. 创建: 符号没有字面量形式,使用全局Symbol()函数创建符号值。

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

    Symbol()函数可接受一个参数作为描述符号值,该参数不能用来访问对应属性,但能用于调试。符号的描述信息被存储在内部属性[[Description]]中,只有当符号的toSting()方法被显式或隐式调用时,该属性才会被读取。

    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)"
    
  2. 使用:

    可以在任意能使用“可计算属性名”的场合使用符号。

    let firstName = Symbol("first name");
    
    // use a computed object literal property
    let person = {
        [firstName]: "Nicholas"
    };
    
    // make the property read only
    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"
    
  3. 共享:

    使用Symbol.for()方法,创建共享符号值,仅接受字符串类型的单个参数,此参数即作为标识符(键),也作为描述信息。

    全局符号注册表:保存共享符号值和它的键。

    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()方法在全局符号注册表中根据符号值,查找出它的键。

    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
    
  4. 转换:

    符号值无法被转换为字符串值或数值

    // 调用String()方法可以获取符号的字符串描述信息
    let uid = Symbol.for("uid"),
        desc = String(uid); 
    
    console.log(desc);              // "Symbol(uid)"
    
    // 直接将符号值与字符串拼接会产生错误
    let uid = Symbol.for("uid"),
        desc = uid + "";            // error!
    
    // 对符号使用所有数学运算符会产生错误
    let uid = Symbol.for("uid"),
        sum = uid / 1;            // error!
    

    符号值在逻辑运算中会被认为等价于true

    // 使用逻辑运算,符号值会被认为等价于true
    let uid = Symbol.for("uid"),
        sum = uid || false;     //  "Symbol(uid)"
    
  5. 检索符号属性

    Object.keys(): 检索所有可枚举属性;不包括Symbol类型的属性

    Object.getOwnPropertyNames(): 检索所有属性,不论是否可枚举;不包括Symbol类型的属性

    Object.getOwnPropertySymbols(): 检索Symbol类型的属性

    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"
    

可以通过typeof识别符号值:

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

(二) 知名符号

知名符号:所有对象起初都不包含任何自有符号类型属性,但对象可以从它们的原型上继承符号类型属性。ES6预定义了一些此类属性,它们被称为“知名符号”。

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

ES6定义了“知名符号”来代表JS中一些公共行为,而这些行为此前被认为只能是内部操作。

每个知名符号都对应全局Symbol对象的一个属性,例如Symbol.create

知名符号 描述
Symbol.hasInstance A method used by instanceof to determine an object’s inheritance.
Symbol.isConcatSpreadable A Boolean value indicating that Array.prototype.concat() should flatten the collection’s elements if the collection is passed as a parameter to Array.prototype.concat().
Symbol.iterator A method that returns an iterator.
Symbol.match A method used by String.prototype.match() to compare strings.
Symbol.replace A method used by String.prototype.replace() to replace substrings.
Symbol.search A method used by String.prototype.search() to locate substrings.
Symbol.species The constructor for making derived objects.
Symbol.split A method used by String.prototype.split() to split up strings.
Symbol.toPrimitive A method that returns a primitive value representation of an object.
Symbol.toStringTag A string used by Object.prototype.toString() to create an object description.
Symbol.unscopables An object whose properties are the names of object properties that should not be included in a with statement.

1.Symbol.hasInstance

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

obj instanceof Array;
// 这句代码等价于
Array[Symbol.hasInstance](obj);

Symbol.hasInstance属性本身是不可写入、不可配置、不可枚举的,要重写一个不可写入的属性,必须使用

Object.defineProperty()

function SpecialNumber() {
    // empty
}

// 将介于1到100之间的数值认定为一个特殊的数值类型
Object.defineProperty(SpecialNumber, Symbol.hasInstance, {
    value: function(v) {
        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

2. Symbol.isConcatSpreadable

该知名符号与其他知名符号不同,默认情况下并不会作为任意常规对象的属性,它只出现在类数组对象上。

类数组对象:

1)拥有长度属性length;

2)属性名要为数值类型

Symbol.isConcatSpreadable 用于标识类数组对象在作为concat()方法的参数时,是否要分离出各个项的值。

let collection = {
    0: "Hello",
    1: "world",
    length: 2,
    [Symbol.isConcatSpreadable]: true  // 需要分离
};

let messages = [ "Hi" ].concat(collection);

console.log(messages.length);    // 3
console.log(messages);           // ["Hi","Hello","world"]

// 如果[Symbol.isConcatSpreadable]为false, 则message为['Hi', ['Hello', 'world']]

3. Symbol.matchSymbol.replaceSymbol.searchSymbol.split

这些符号属性定义在RegExp.prototype上,以供对应的字符串方法使用。

知名符号 描述 对应字符串的方法 描述
Symbol.match 接受一个字符串参数,返回一个包含匹配结果的数组,若匹配失败,返回null match(regex) 匹配
Symbol.replace 接受一个字符串参数与一个替换用的字符串, 返回替换后的结果字符串 replace(regex, replacement) 替换
Symbol.search 接受一个字符串参数,返回匹配结果的数值所有,匹配失败,返回-1 search(regex) 查找
Symbol.split 接受一个字符串参数,返回一个用匹配值分割而成的字符串数组 split(regex) 分割
// effectively equivalent to /^.{10}$/
let hasLengthOf10 = {
    [Symbol.match]: function(value) {
        return value.length === 10 ? [value] : 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 characters
    message2 = "Hello John";    // 10 characters


let match1 = message1.match(hasLengthOf10),
    match2 = message2.match(hasLengthOf10);

console.log(match1);            // null
console.log(match2);            // ["Hello John"]

let replace1 = message1.replace(hasLengthOf10, "Howdy!"),
    replace2 = message2.replace(hasLengthOf10, "Howdy!");

console.log(replace1);          // "Hello world"
console.log(replace2);          // "Howdy!"

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);            // ["", ""]

这虽然是个简单的例子,却表明能够进行比现有正则表达式更复杂的匹配,让自定义模式匹配更加可行。

4. Symbol.toPrimitive

定义在常规类型的原型上,规定了对象在被转换为基本类型值得时候会发生什么。

Symbol.toPrimitive接受一个提示性的字符串参数,该提示性参数会在调用时由js引擎自动填写。参数为“number”, 返回一个数值; 参数为“string”, 返回一个字符串; 参数为“default”返回值类型没有特别要求。

“数值模式”: valueOf() —> toString() —> 抛出error

“字符串模式”: toString() —> valueOf() —> 抛出error

"默认模式": 多数情况都等价于数值模式,只有Data类型例外,默认使用字符串模式。默认模式只在使用 ==运算符、+运算符或传递单一参数给Data()构造器的时候被使用。

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";
    }
};

let freezing = new Temperature(32);

console.log(freezing + "!");            // "32 degrees!"  默认模式
console.log(freezing / 2);              // 16  数值模式
console.log(String(freezing));          // "32°"  字符串模式

5. Symbol.toStringTag

定义在Object.prototype上

Symbol.toStringTag 属性中存储了 调用Object.prototype.toString.call()返回的值。

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]"