ES6 Symbol 及其属性

symbol 是一种新的原始数据类型, 用于建立须要在必定程度上被保护的属性. 在以前, 不管是什么属性都须要使用字符串类型的名称来访问; 在 Symbol 出现以后, 能够将属性名定义成 Symbol 类型了, 尽管这样定义的属性不是彻底私有的, 可是比较难以被意外的改变.函数

建立一个 Symbol 的语法

其余原始数据类型都有各自的字面量形式, 例如数字类型的 123 , 因此能够经过字面量的形式来建立这个类型的实例, 好比经过字面量形式建立一个数值类型和一个字符串类型的变量:ui

let age = 99;
let name = 'Ross';
复制代码

然而 Symbol 没有本身的字面量形式, 因此没法像这样建立一个 Symbol 实例. 要使用全局的函数 Symbol() 来作这件事, 例如建立一个名为 name 的Symbol:this

let name  = Symbol();  // 建立名为 name 的Symbol

let person = {};
person[name] = 'Ross';  // 使用这个 Symbol

// 输出以 Symbol 为属性名的属性值
console.log(person[name]);  // Ross
复制代码

注意: 不能使用 new 关键字加上 Symbol() 函数来建立, 这样会报错. 虽然其余原始类型均可以使用 new 加上构造函数来建立一个对应类型的变量, 可是 Symbol() 不能当作构造函数来使用.spa

建立一个带描述性文字的 Symbol

在建立一个 Symbol 时能够加上一段描述性的字符串, 用来标记这个 Symbol 是干吗的. 当使用 console.log 打印出这个 Symbol 时, 会将标记同时打印出来. 例如:prototype

let firstName = Symbol('first name'); // 标记是 'first name'

let person = {};
person[firstName] = 'Ross';  // 使用这个 Symbol
console.log(person[firstName]);  // Ross

// 输出这个 Symbol
console.log(firstName);  // "Symbol(first name)"
复制代码

使用 typeof 来准确判断 Symbol 类型

对 Symbol 使用 typeof 会返回 "symbol", 因此能够经过 typeof 操做符准确判断 Symbol 的类型:code

console.log(typeof firstName);  // symbol
复制代码

这是首选的判断 Symbol 类型的方式.对象

Symbol 的使用场合--全部使用可计算属性名的地方均可以使用 Symbol

不只能够经过中括号调用属性名的方法来使用 Symbol, 它还能够用在全部可计算属性名的地方, 例如可计算对象字面量属性名、Object.defineProperty()方法和Object.defineProperties()方法的调用过程当中.ip

let firstName = Symbol('first name');
let lastName = Symbol('last name');

// 可计算对象字面量属性
let person = {
    [firstName]: 'Ross',
    [lastName]: 'Geller'
};

// 在 Object.defineProperty() 方法设置以 Symbol:firstName 为属性名的属性值的属性, 将其属性值设置成只读
Object.defineProperty(person, firstName, { writable: false });

// 在 Object.defineProperties() 方法设置以 Symbol:lastName 为属性名的属性值的属性, 将其属性值设置成只读
Object.defineProperties(person, {
    [lastName]: {
        writable: false
    }
});
复制代码

共享的 Symbol

有时咱们但愿在不一样的代码中共享同一个 Symbol, 例如在不一样的文件中使用同一个 Symbol. 若是代码规模较大, 像这样就比较困难了, 所幸 Symbol 带有一个共享体系, 能够供咱们在全局中建立并使用全局共享的 Symbol.原型链

建立共享 Symbol 的语法

在全局中存在一个注册表, 用来保存全局的共享 Symbol 的名单, 经过向这个注册表中增长元素来设置一个 Symbol 为全局共享的.开发

使用 Symbol.for(description) 来向注册表中添加一个 Symbol, 其中 description 是个字符串, 起到描述的做用, 只要两个全局 Symbol 的描述相同, 那么他们就是同一个 Symbol.

Symbol.for(description) 方法先在注册表中寻找这个 Symbol 有没有被注册, 也就是找注册表中有没有这个 description, 若是有就直接返回, 不然就用这个description新建一个, 保存以后将这个 Symbol 返回. 这样这个Symbol 就是全局共享的了. 例如:

// 新建两个描述符都是 dog 的共享 Symbol
let wangcai = Symbol.for('dog');
let dahuang = Symbol.for('dog');

console.log(wangcai === dahuang);  // true
复制代码

这里可能会有个疑问: 若是两个普通 Symbol的描述字符串同样, 那么他们是同一个 Symbol 吗? 答案为不是, 经过实验能够得出:

// 建立两个描述同样的非共享的 Symbol 
let wangcai = Symbol('dog');
let dahuang = Symbol('dog');

console.log(wangcai === dahuang);  // false
复制代码

经过 Symbol.keyFor() 获得共享的 Symbol 的描述文字

若是有了一个共享的 Symbol, 可是不知道他的描述, 能够经过 Symbol.keyFor() 将他的描述性文字取出来, 例如把上面例子中的 wangcai和dahuang的描述文字取出来:

console.log(Symbol.keyFor(wangcai));  // dog
console.log(Symbol.keyFor(dahuang));  // dog
复制代码

Symbol 的强制类型转换

JavaScript 的一个比较让人头疼的语言特性是强制类型转换, 可是对于 Symbol 来讲就显得比较简单了.

虽然在使用 console.log 输出一个 Symbol 时会调用这个 Symbol 的 toString() 方法, 也可使用 String(symbol)来得到它的有关信息, 可是在其余状况下却并不会这样, 例如想把一个Symbol用加号操做符 "+" 转换为字符串就会报错, 若是想将它经过除法操做符转换成一个数值型变量则也会报错.

因此不要把 Symbol 强制转换成数值和字符串类型, 缘由不只如此, 也是由于 Symbol 的出现就是在某些场合下来替代字符串做为属性名的, 在不恰当的时候把他转化为字符串就违背了添加 Symbol 的本意了.

遍历全部的 Symbol 属性

对象的通常属性可使用 Object.getOwnPropertyNames() 方法和 Object.keys() 方法来遍历, 两者的区别是前者返回全部属性, 然后者只返回对象中可枚举的属性名. 可是这两个方法都不支持 Symbol 属性, 因此ES6 中新增了一个专门用来检索 Symbol 属性的方法 Object.getOwnPropertySymbols(), 这个方法返回一个对象中全部的 Symbol 属性名. 使用方法以下:

let firstName = Symbol.for('first name');
let lastName = Symbol('last name');

let person = {
    [firstName]: 'Ross',
    [lastName]: 'Geller'
};

let symbolPropertyNames = Object.getOwnPropertySymbols(person);

console.log(symbolPropertyNames.length, symbolPropertyNames[0], person[symbolPropertyNames[0]]);
// 2 Symbol(first name) Ross
复制代码

几个经过 well-known Symbol 暴露出来的内部操做

背景: 从 ES5 开始, JavaScript 语言就尝试将其提供的一些自建函数的内部逻辑展现出来并容许开发者本身修改. 在ES6 中因为 Symbol 的出现, 增长了在原型链上定义的与 Symbol 相关的属性来暴露更多的内部逻辑.

ES6经过Symbol对象的一些属性暴露了语言中一些方法的内部实现, 例如使用 Symbol.hasInstance 来暴露使用 instanceof 操做符时具体的工做流程; 使用 Symbol.toPrimitive 来暴露将一个对象转换为原始类型时会调用的方法.

Symbol.hasInstance

instanceof 操做符用来判断一个对象是否是某个类的实例, 例如:

function Person() {
    // ...
}

// 建立一个类的实例
let p1 = new Person();

console.log(p1 instanceof Person); // true , p1 是 Person 类的实例
复制代码

在 ES6 中, 能够经过修改一个类的 Symbol.hasInstance 属性来改变 instanceof 操做符的行为, 参照下面的实验:

// 和上面同样, 定义 Person 类
function Person() {
    // ...
}

// 经过修改 Person 对象的 Symbol.hasInstance 属性来修改对 Person 类使用 instanceof 的结果
Object.defineProperty(Person, Symbol.hasInstance, {
    value(v){
        return false;  // 修改成不管是否是 Person 的实例, 都返回 false
    }
});

// 建立 Person 类的实例
let p1 = new Person();

// 输出对 Person 类使用 instanceof 的结果
console.log(p1 instanceof Person); // false , 看到了效果
复制代码

从上面的例子能够看出来, 其实在对一个类使用 instanceof 时, 后台会调用这个类的名为 Symbol.hasInstance 的函数来进行判断并返回结果, 因此才能够经过修改它来修改 instanceof 操做符的行为.

Symbol.toPrimitive

名为 Symbol.toPrimitive 的函数定义了一个非原始类型的对象在转换为原始类型值时的行为, 以前写过一篇讨论强制类型转换的文章, 正片文章就能够归结为这个函数的行为. 简单来讲这个函数能够根据传入的偏好来决定将一个怎么转换成一个原始类型的值, 对于大多数对象, 这个函数定义的转换机制是这样的:

  1. 先调用这个对象的 valueOf() 方法, 若是结果是原始类型, 就直接返回, 不然
  2. 调用这个对象的 toString() 方法, 若果结果是原始类型, 则返回, 不然抛出一个错误.

可是对于 Date 这一个特殊的对象, 上面两个步骤是反过来的. 至于不一样种类对象的 valueOf()toString() 方法以前的文章中有讨论.

咱们能够经过本身定义这个函数来修改一个对象转换成原始值的方式, 例如:

// 建立一个类
function Temprature(degrees) {
    this.degrees = degrees;
}

// 经过修改原型链上的 Symbol.toPrimitive 来修改这个类被转换为基本数据类型的行为
Temprature.prototype[Symbol.toPrimitive] = function(hint){
    switch(hint){
        case 'string':
            return this.degrees + '\u00b0';
            break;

        case 'number':
            return this.degrees;
            break;

        default:
            return this.degrees + ' degrees';
            break;
    }
};

// 新建这个类的对象
let temprature = new Temprature(99); 

// 触发这个类转换为基本数据类型的行为
console.log(temprature + ''); // 触发默认行为, 结果是 "99 degrees"
console.log(temprature / 1);  // 触发转换为数值类型的行为, 结果是 99 
console.log(String(temprature)); // 触发转换为字符串类型的行为, 结果是 "99°"
复制代码

由上面的例子能够看到经过修改一个类的原型上的 Symbol.toPrimitive 方法能够修改这个类的对象转换为原始值的行为.

其余 well-known Symbol

除了以上提到的两个 well-known Symbol 方法以外, 还有许多相似的方法, 例如 Symbol.isConcatSpreadable, Symbol.iterator, Symbol.match, Symbol.split 等等, 详细能够参见 MDN

总结

Symbol 主要用来做为对象的属性名来使用, 它具有必定的隐私性, 能够用在全部可计算属性的地方. 经过一些 well-known Symbol 来暴露出一些语言内部机制的具体实现, 咱们能够经过这些实现来加深对于这门语言的了解, 这是有必要的.

相关文章
相关标签/搜索