ES6 Symbol类型

注:这是一篇学习笔记,记录自己在ES6学习过程中按照自己的思路觉得应该记录的一些要点,方便以后查看和复习。参考:

阮一峰ES6的书籍中数组的扩展
我的ES6入门学习规划

Symbol类型是ES6新推出的基础类型,至此ES一共有了7个数据类型,分别是:null, undefined, string, boolean, number, symbol和object。 Symbol这个类型具有独一无二的特性,在可以使用symbol类型的情况下,就应该多去用它,至少看过别人写的一些代码,都已经是这么实践地了。

基本特性

Symbol值通过Symbol函数生成

1
2
3
4
let s = Symbol();

typeof s
// "symbol"

由于 Symbol 值不是对象,所以不能添加属性。Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。 如果 Symbol 的参数是一个对象,就会调用该对象的toString方法,将其转为字符串,然后才生成一个 Symbol 值。Symbol函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的。

对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型, Symbol类型用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let mySymbol = Symbol();

// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';

// 第二种写法
let a = {
[mySymbol]: 'Hello!'
};

// 第三种写法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });

// 以上写法都得到同样结果
a[mySymbol] // "Hello!"

Symbol 值不能与其他类型的值进行运算,会报错。Symbol 值可以显式转为字符串。

1
2
3
4
let sym = Symbol('My symbol');

String(sym) // 'Symbol(My symbol)'
sym.toString() // 'Symbol(My symbol)'

Symbol 值也可以转为布尔值,但是不能转为数值。

ES2019 提供了一个实例属性description,直接返回 Symbol 的描述。

1
2
const sym = Symbol('foo');
sym.description // "foo"

Symbol.for(key)

它接受一个字符串作为key,然后搜索全局环境中有没有以该key作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建并返回一个以该字符串为名称的 Symbol 值。

Symbol.for()与Symbol()这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。

Symbol.keyFor(symbol)方法返回一个已登记的 Symbol 类型值的key。

1
2
3
4
5
let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"

let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined

Symbol.for(key)是把symbol值注册到全局环境的,可以在不同的 iframe 或 service worker 中取到同一个值。

内置Symbol值

Symbol.hasInstance

这个symbol值,应该部署为一个在类上面的静态的函数值属性。 在A instance of B运算中,只要B对象通过[Symbol.hasInstance]部署了一个方法,这个方法就会被调用。 设计的作用是主要是为了自定义Class的instance of时的行为。所以从初衷出发,这个方法最合适部署的位置是Class。

1
2
3
4
5
6
class MyArray {  
static [Symbol.hasInstance](instance) {
return Array.isArray(instance);
}
}
console.log([] instanceof MyArray); // true

虽然在js语言里面,也可以直接把这个方法部署到对象上面,比如字面量对象,但是不推荐这么用,完全不合语义

Symbol.isConcatSpreadable

这个symbol值,应该部署为一个在对象上面的boolean值属性。它的作用是用于配置某对象作为Array.prototype.concat()方法的参数时是否展开其数组元素。主要作用的目标是数组和类数组对象。

1
2
3
4
5
6
7
let arr1 = ['c', 'd'];
['a', 'b'].concat(arr1, 'e') // ['a', 'b', 'c', 'd', 'e']
arr1[Symbol.isConcatSpreadable] // undefined

let arr2 = ['c', 'd'];
arr2[Symbol.isConcatSpreadable] = false;
['a', 'b'].concat(arr2, 'e') // ['a', 'b', ['c','d'], 'e']

  • 数组的默认行为是可以展开,Symbol.isConcatSpreadable默认等于undefined。该属性等于true时,也有展开的效果
  • 类似数组的对象正好相反,默认不展开。它的Symbol.isConcatSpreadable属性设为true,才可以展开

在类上面部署这个属性的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class A1 extends Array {
constructor(args) {
super(args);
this[Symbol.isConcatSpreadable] = true;
}
}
class A2 extends Array {
constructor(args) {
super(args);
}
get [Symbol.isConcatSpreadable] () {
return false;
}
}

Symbol.species

这个symbol值,应该部署为一个在类上面的静态的函数值属性。它需要返回创建衍生对象时的构造函数。默认的Symbol.species属性等同于下面的写法。

1
2
3
static get [Symbol.species]() {
return this;
}

不是所有的原生对象都默认部署了这个接口,目前只看到Array和Promise才部署有:

1
2
3
4
5
6
7
8
9
10
11
Array[Symbol.species]
// ƒ Array() { [native code] }

Object[Symbol.species]
// undefined

Date[Symbol.species]
// undefined

Promise[Symbol.species]
// ƒ Promise() { [native code] }

数组实例方法,如map,filter;Promise实例方法,如then,catch;它们所返回的实例对象就是所谓的衍生对象。 从 Symbol.species 这个属性的作用来看,肯定是前面这些实例方法调用时,会通过Symbol.species方法返回的函数来作为这些实例方法返回值的构造函数。 所以通过重新定义这个属性,可以重置衍生对象的构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class MyArray extends Array {
// 覆盖 species 到父级的 Array 构造函数上
// 如果不重置这个,后面的a.map()返回的衍生对象就是MyArray的实例
static get [Symbol.species]() { return Array; }
}
var a = new MyArray(1,2,3);
var mapped = a.map(x => x * x);

console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array); // true

class T1 extends Promise {
}

class T2 extends Promise {
static get [Symbol.species]() {
return Promise;
}
}

new T1(r => r()).then(v => v) instanceof T1 // true
new T2(r => r()).then(v => v) instanceof T2 // false

Symbol.match

这个symbol值,应该部署为一个在对象上面的函数值属性。当对象被用于String.prototype.match()的参数时,这个symbol指定的函数就会调用。

1
2
3
4
5
6
7
class MyMatcher {
[Symbol.match](string) {
return 'hello world'.indexOf(string);
}
}

'e'.match(new MyMatcher()) // 1

正则表达式类部署了这个属性;

1
2
RegExp.prototype[Symbol.match]
// ƒ [Symbol.match]() { [native code] }

这个属性我认为有2个价值,第一是可以自定义用于字符串match行为的特殊组件;第二是这个方式,让我们更加清楚地看到了js这门语言面向对象编程方式的特点,比如前面的例子中MyMatcher这个名词,当实际使用的时候,特别有面向对象的语义。 不仅仅是这个属性,后面要记录的另外跟字符串实例方法相关的三个其它内置symbol值,包括Symbol.replace, Symbol.search, Symbol.split都是类似的。

Symbol.replace

这个symbol值,应该部署为一个在对象上面的函数值属性。当对象被用于String.prototype.replace()的第一个参数时,这个symbol指定的函数就会调用。

1
2
3
String.prototype.replace(regexp, replaceValue)
// 等同于
regexp[Symbol.replace](this, replaceValue)

正则表达式类默认部署了这个属性。

这个函数属性,接受2个参数,第一个是字符串实例,第二个是替换后的值。 通过这个属性,可以自定义特殊的字符串replace的行为。

这个symbol值,应该部署为一个在对象上面的函数值属性。当对象被用于String.prototype.search()的参数时,这个symbol指定的函数就会调用。

1
2
3
String.prototype.search(regexp)
// 等同于
regexp[Symbol.search](this)

正则表达式类默认部署了这个属性。

这个函数属性,接受1个参数,是字符串实例。 通过这个属性,可以自定义特殊的字符串search的行为。

Symbol.split

这个symbol值,应该部署为一个在对象上面的函数值属性。当对象被用于String.prototype.split()的第一个参数时,这个symbol指定的函数就会调用。

1
2
3
String.prototype.split(spliter, limit)
// 等同于
spliter[Symbol.split](this, limit)

正则表达式类默认部署了这个属性。

这个函数属性,接受2个参数,第一个是字符串实例,第二个是split的次数限制limit。 通过这个属性,可以自定义特殊的字符串split的行为。

Symbol.iterator

这个symbol值,应该部署为一个在对象上面的函数值属性。在需要获取对象的遍历器对象的时候,这个属性对应的方法会被调用,返回一个遍历器对象。 比如for…of循环中:

1
2
3
4
5
6
7
8
const myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};

[...myIterable] // [1, 2, 3]