Symbol_ES6笔记7

一.Symbol是什么

typeof Symbol() === 'symbol',symbol是js中第7种基本类型(本来就有的6种是null, undefined, Number, Boolean, Object, String),不是字符串也不是对象

作用:symbol用来避免命名冲突,解决了篡改(添加属性)原生对象的后遗症,不用担心属性名以后和原生属性名或者其它类库操作冲突

二.语法

获取Symbol有3种方式,如下:

1.Symbol(desc)

返回symbol,desc可选,symbol.toString()返回`Symbol(${desc})`,例如:

var obj = {
    a: 1
};

// 不用new,Symbol不是构造器
var safeKey = Symbol();
obj[safeKey] = 'value';
console.log(obj[safeKey]);  // value

var anotherSafeKey = Symbol('isAnimActive');
console.log(anotherSafeKey);    // Symbol(isAnimActive)

上面的var safeKey = Symbol();看起来比较奇怪,new操作符呢?Symbol不是构造器,不能通过new操作符调用,非要new的话,会得到这样一个错误:

Uncaught TypeError: Symbol is not a constructor

这确实比较奇怪,编码规范中一般首字母大写表示类型名,理应通过new操作符调用,但API给出的就是一个不合编码规范的Symbol函数

特点:

  • Symbol('key') !== Symbol('key'),每次调用Symbol()返回的都是不一样的值

  • Symbol可以用作属性名,而且和任何东西(字符串、数字、其它Symbol)都不相等,所以Symbol类型的属性名不会和任何已有属性名冲突,也不会和将来的任何新属性名冲突

  • for...inObject.keys(obj)Object.getOwnPropertyNames(obj)会跳过symbol属性

  • Object.getOwnPropertySymbols(obj)返回对象所有的symbol属性名

  • Reflect.ownKeys(obj)返回对象的所有属性名(包括symbol属性名和字符串属性名)

  • symbol只读,类似于字符串,严格模式下给symbol添加属性会报错TypeError

  • symbol不会自动转换为字符串,尝试拼接symbol会报错TypeError,可以手动调用toString()再拼接

示例(接着上一个示例)如下:

obj[Symbol('ready')] = true;
//!!! undefined
// 因为特点1
console.log(obj[Symbol('ready')]);  // undefined
console.log(obj);   // Object {Symbol(): "value", Symbol(ready): true}
// 跳过symbol属性
for (var key in obj) {
    console.log(`obj[${key}] = ${obj[key]}`);
}   // obj[a] = 1

// 获取所有obj上所有Symbol类型的属性名
console.log(Object.getOwnPropertySymbols(obj)); // Array [ Symbol(), Symbol(ready) ]
// 获取objs上的所有属性名
console.log(Reflect.ownKeys(obj));  // Array [ "a", Symbol(), Symbol(ready) ]

// 只读
var s = Symbol();
s.a = 1;
console.log(s.a);   // undefined

// 不会自动转字符串
// console.log(Symbol('123') + '4');   // TypeError: can't convert symbol to string
console.log(Symbol('123').toString() + '4');    // Symbol(123)4

2.Symbol.for(str)

表示symbol注册表,用来创建共享symbol,特点如下:

  • Symbol.for('ready') === Symbol.for('ready')

  • Symbol('str') !== Symbol.for('str')

示例(接着前面的所有示例):

// Symbol.for()
obj[Symbol.for('ready')] = 'ready';
console.log(obj[Symbol.for('ready')]);  // ready
console.log(Object.getOwnPropertySymbols(obj));
// log print: Array [ Symbol(), Symbol(ready), Symbol(ready) ]

P.S.最后输出的数组中有两个Symbol(ready),但它们对应的symbol不相等,只是toString返回的结果相同(特点2)

注意:共享Symbol的话,只能保证属性名不与将来的DOM API冲突,不能保证不与其它代码冲突。类库作者不应该使用它,建议只在业务代码中需要跨模块或者跨页面Symbol共享时使用

3.Symbol.xxx

用来获取原生Symbol,例如Symbol.iterator,特点如下:

  • 新API向后兼容,比如实现iterable接口:obj[Symbol.iterator] = gen,不会影响旧代码

  • 准备好了hook,借助symbol以后新特性API都不会影响旧代码

尚未实现的新特性:

  • Symbol.hasInstance扩展instanceof

  • Symbol.unscopables阻止方法加入动态作用域

  • Symbol.match扩展str.match

这些Symbol已经作为原生Symbol预留出来了,很快就会实现,以后的(增强现有API的)新特性都可以通过Symbol简单安全地添加

三.应用场景

1.给DOM元素添个标记属性

可能多数时候不需要给DOM元素添加自定义属性,因为这存在副作用(自定义属性名可能与其它代码冲突,或者与将来的DOM API冲突),于是一般都选择维护一个表结构来保存元素对应的状态等附加信息,每次查表获得目标元素的附加信息,如果表很大,查表会比较耗时,如果表结构比较复杂,查表操作本身也会变得很麻烦。。。等等,我们为什么要查表获取元素的附加信息?因为副作用,那如果这个副作用没了呢?

Symbol就是用来消除这个副作用的,元素相关的信息,直接以Symbol对象为key,添在DOM元素上就可以了,简单粗暴安全有效,例如:

var IS_MOVING = Symbol('isMoving');
var INFO = Symbol('info');

if (!elem[IS_MOVING]) {
    anim(elem);
    elem[IS_MOVING] = true;
    elem[INFO] = {
        step: 1;
    };
}

需要持有自定义的Symbol类型的key,把该key放在自己的作用域里,就不会有任何副作用了

2.生成唯一的key

以前生成唯一的key可能需要一个稍复杂的小算法,而现在Symbol是最简单的唯一key生成方法,例如:

// 不关注属性名,只是想简单地存取obj
var data = {
    dir: {},
    save(val) {
        var key = Symbol();
        this.dir[key] = val;
        return key;
    },
    get(key) {
        return this.dir[key];
    }
}
var key = data.save('data');
console.log(data.get(key)); // data

3.通过原生Symbol实现支持的API扩展

标准将提供越来越多的hook,让我们来实现API扩展方法,例如让自定义对象可迭代:

var obj = {
'a': 1,
'b': 2
}
// generator实现迭代器
obj[Symbol.iterator] = function*() {
    for (var key in this) {
        yield this[key];
    }
}
for (var val of obj) {
    console.log(val);
}

四.总结

Symbol算是一个精致的特性,解决了扩展现有API的大问题(给新特性提供了一套hook机制,以后可以安全地扩展)

参考资料

  • 《ES6 in Depth》:InfoQ中文站提供的免费电子书

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*

code