JavaScript ES6 Symbol

前言

之前在JavaScript 原型链机制实现in的时候提到过Symbol.iterator,虽然明白具体作用,但是感觉到是用来当属性的 key 的,但是为啥这样干…内置的只有Array有么?

介绍

1
2
3
4
5
6
7
8
9
10
11
const foo = Symbol("foo");
const eight = Symbol(8);

console.log(typeof foo); // symbol

console.log(foo.toString()); // Symbol(foo)

console.log(foo); // Symbol(foo)
console.log(eight); // Symbol(8)

console.log(foo === Symbol("foo")); // falsev

Symbol的诞生是 ES6 新添加的数据类型,起初就是设计称为一个属性 key,现在很简单的方式可以得到一个唯一的对象, 比如代码里面的foo === Symbol("foo")就意味着即便输入description相同,也不是同一个对象,这就很轻易的定义私有变量或者防止 key 冲突的问题出现。

用法

1
2
3
4
5
6
7
8
9
10
11
12
const foo = Symbol();

const obj0 = {
[foo]: "foo"
};

const obj1 = {};
obj1[foo] = "foo";

const obj2 = Object.defineProperty({}, foo, {
value: "foo"
});

Symbol属性不能通过.的方式来定义。

通过这样的方式定义属性,同时不暴露Symbolkey 值,外部一般手段都没办法使用了,这算是私有变量了。

非常规手段和判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const foo = Symbol.for("foo");
const obj = {
[foo]: "12345"
};

let symbols = Object.getOwnPropertySymbols(obj);

console.log(symbols.length); // 1
console.log(symbols[0]); // "Symbol(uid)"
console.log(obj[symbols[0]]); // "12345"

symbols = Reflect.ownKeys(obj);

console.log(symbols.length); // 1
console.log(symbols[0]); // "Symbol(uid)"
console.log(obj[symbols[0]]); // "12345"

我以前写 Java,也有反射这样的方法来获取和修改,那么 JavaScript 也一定有的,就是通过Object.getOwnPropertySymbolsReflect.ownKeys()来获取所有 key。
如果判断的话就麻烦许多,包括之前谈过的枚举属性,和继承属性,这边的方法其实全记住不太可能…我的做法是记住in是万能的判断,Object.hasOwnProperty()是自身所有枚举不枚举和Symbol keyObject.keys()只有自身,其他稍微了解一下比较好,具体可以在属性的可枚举性和所有权查阅具体差异。

进阶用法

1
2
3
4
5
6
7
const foo = Symbol.for("foo");

console.log(foo === Symbol.for("foo")); // true
console.log(Symbol.keyFor(foo)); // foo

const bar = Symbol("bar");
console.log(Symbol.keyFor(bar)); // undefined

有时候可能需要一些 key 暴露出去,如果用各种传递的方式,其实比较麻烦又容易出错,这个时候就可以用一个 runtime-wide symbol registry(运行时 Symbol 注册)了。

内置 Symbol

一开始提到过的Symbol.iterator就是其中之一,Symbol的出现暴露了很多原来 JavaScript 原来无法接触的地方,ES6 就定义了一些内置Symbol,其实类似 Java 的实现 iterator接口差不多。

Symbol.iterator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const iterable = {};
iterable[Symbol.iterator] = function() {
let i = 0;
return {
next() {
const done = i >= 3;
const value = !done ? i++ : undefined;
return {
done,
value
};
}
};
};

console.log([...iterable]); // [ 0, 1, 2 ]

一开始接触就是这个Symbol,里面就只是一个iterable,为了方便的使用,还有generator的概念,我决定这个今天先不写~

Symbol.hasInstance

在我们用到instanceof的时候其实就用到了这个Symbol,具体代码表现是

1
2
console.log([] instanceof Array); // true
console.log(Array[Symbol.hasInstance]([])); // true

每个函数内部其实都有一个Symbol.hasInstance的方法去判断是否继承自这个函数,所以如果我们改了这个函数…就能输出跟预期不一致的效果。

1
2
3
4
5
6
7
8
9
function Foo() {}

Object.defineProperty(Foo, Symbol.hasInstance, {
value() {
return false;
}
});

console.log(new Foo() instanceof Foo);

Symbol.toPrimitive

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Foo(bar) {
this.bar = bar;
}

Foo.prototype[Symbol.toPrimitive] = function(hint) {
switch (hint) {
case "string":
return "(´・_・`) " + this.bar;

case "number":
return this.bar;

// case 'default':
default:
return this.bar + " baz";
}
};

const foo = new Foo(8);

console.log(foo + "!"); // 8 baz!
console.log(foo / 2); // 4
console.log(String(foo)); // (´・_・`) 8

该属性定义在 JavaScript 数据类型的原型上,主要作用就是等数据类型转变会调用此方法,就是1 + '1'的时候,这个方法参数有三种类型。

Symbol.toStringTag

1
2
3
4
5
function Foo() {}

console.log(new Foo().toString()); // [object object]
Foo.prototype[Symbol.toStringTag] = "Foo";
console.log(new Foo().toString()); // [object Foo]

在我刚转过来写 JavaScript 的时候不太习惯,我在输出obj.toString()基本只能得到[object Object]等信息,实际上没有什么帮助,但是Symbol.toStringTag改变了这个问题。

其他

  • Symbol.isConcatSpreadable :一个布尔类型值,在集合对象作为参数传递给
  • Symbol.match :供 String.prototype.match() 函数使用的一个方法,用于比较字符串。
  • Symbol.replace :供 String.prototype.replace() 函数使用的一个方法,用于替换子字符串。
  • Symbol.search :供 String.prototype.search() 函数使用的一个方法,用于定位子字符串。
  • Symbol.species :用于产生派生对象的构造器。
  • Symbol.split :供 String.prototype.split() 函数使用的一个方法,用于分割字符串。
  • Symbol.unscopables :一个对象,其属性指示了哪些属性名不允许被包含在 with 语句中。