ES6 之 Symbol 对象
wangjing013 opened this issue · comments
Symbol
Createing symbols
ES6 新增 Symbol
作为新的原始类型. 它不像其它原始类型如 number
、boolean
、null
、undefined
和 字符串
, Symbol
类型不包含字面量形式.
去创建一个 symbol
, 你可以使用全局 Symbol()
方法, 如下:
let s = Symbol('foo');
每次调用 Symbol()
函数时都将创建一个新的唯一值:
console.log(Symbol() === Symbol()); // false
Symbol()
函数接收一个可选参数 description
, description
参数将让 symbol
更具有描述性.
下面实例创建两个 symbol
: firstName
and lastName
.
let firstName = Symbol('first name'),
lastName = Symbol('last name');
你可以使用 toString()
方法去访问 description
的值, console.log()
方法隐式的调用 symbol
的 toString()
方法, 如下示例:
console.log(firstName); // Symbol(first name)
console.log(lastName); // Symbol(last name)
在 ES2019
中, 可以通过 symbol.description
方式访问描述属性值:
console.log(firstName.description); // "first name"
由于 Symbol
是原始类型, 可以使用 typeof
操作符去检查一个变量是否为 symbol
类型. 当传入的是 symbol
变量时, typeof
将返回 symbol
字符串 .
console.log(typeof firstName); // symbol
由于 Symbol
是原始类型, 如果尝试使用 new 操作符去创建一个 symbol
, 将会报错:
let s = new Symbol(); // error
Sharing symbols
ES6 为您提供了全局 Symbol
注册表,允许您全局共享 Symbol
. 如果想创建被共享 symbol
, 你可以使用 Symbol.for()
方法, 而不是 Symbol()
方法.
Symbol.for()
方法接收一个参数, 用于表示 symbol
的描述信息, 如下
let ssn = Symbol.for('ssn');
Symbol.for()
方法首先在全局注册表中查找对应 key 为 ssn
的 symbol
. 如果存在则返回存在 symbol
. 否则 Symbol.for()
方法将创建一个新的 symbol
, 使用指定的 key(ssn)
将其注册到全局的 Symbol
列表中,并返回 symbol
.
最后,如果调用 Symbol.for()
时使用相同的 key, Symbol.for
方法将返回存在 symbol.
let citizenID = Symbol.for('ssn');
console.log(ssn === citizenID); // true
上面例子中, 我们使用 Symbol.for()
方法查找key
为 ssn
的 symbol. 因为全局注册表中已经包含它. 所以 Symbol.for()
返回已经存在的 symbol
.
如果想获取与 symbol
相关的 key
时, 可以通过 Symbol.keyFor()
方法, 如下示例:
console.log(Symbol.keyFor(citizenID)); // 'ssn'
如果symbol
在全局注册表中不存在, 则 Symbol,keyFor()
方法将返回 undefined
.
let systemID = Symbol('sys');
console.log(Symbol.keyFor(systemID)); // undefined
Symbol useges
- 使用 symbol 作为唯一值
每当你在代码中使用字符串或数字时, 你应该使用 symbols 替代. 例如: 你可能在应用需要管理任务的状态,在 ES6 之前, 你可以使用
open
、in progress
、completed
、canceled
和 on hold
等字符串表示不同任务状态. 在 ES6 中, 你可以使用 symbol
, 如下:
let statuses = {
OPEN: Symbol('Open'),
IN_PROGRESS: Symbol('In progress'),
COMPLETED: Symbol('Completed'),
HOLD: Symbol('On hold'),
CANCELED: Symbol('Canceled')
};
// complete a task
task.setStatus(statuses.COMPLETED);
- 使用
symbol
作为对象中计算属性的名字
你可以使用 symbol
作为计算属性的名称, 如下示例:
let status = Symbol('status');
let task = {
[status]: statuses.OPEN,
description: 'Learn ES6 Symbol'
};
console.log(task);
要获取对象所有可枚举属性, 可以使用 Object.keys()
方法.
console.log(Object.keys(task)); // ["description"]
要获取有所可枚举和不可枚举的属性, 可以使用 Object.getOwnPropertyNames()
方法.
console.log(Object.getOwnPropertyNames(task)); // ["description"]
要获取对象的所有 symbols
属性, 可以用 Object.getOwnPropertySymbols()
方法.
console.log(Object.getOwnPropertySymbols(task)); //[Symbol(status)]
Object.getOwnPropertySymbols()
方法返回数组, 该数组只包含对象自身的 symbol
属性.
Well-known symbols
ES6 提供一些预先定义好的 well-known symbols
. 这些 well-known symbols
表示 JavaScript 中的常见行为.
每个 well-known symbol
都是作为 Symbol
对象的静态属性.
Symbol.hasInstance
Symbol.hasInstance
是一个更改 instanceof
操作符行为的 symbol
. 通常, 当你使用 instanceof
操作符时:
obj instanceof type;
JavaScript 将调用 Symbol.hasIntance
方法如下:
type[Symbol.hasInstance](obj);
然后, 它依赖该方法来确定 obj
是否是 type
对象的实例. 请参考下面的示例:
class Stack {
}
console.log([] instanceof Stack); // false
[]
数组不是 Stack
类的实例, 因此, 在本例中 instanceof
操作符返回为 false
.
假设你想要 []
数组被当作是 Stack
类型的实例, 你可以在 Stack
类中添加 Symbol.hasInstance
方法, 如下:
class Stack {
static [Symbol.hasInstance](obj) {
return Array.isArray(obj);
}
}
console.log([] instanceof Stack); // true
Symbol.iterator
Symbol.iterator
指定该函数是否返回对象的迭代器.
通常对象包含 Symbol.iterator
属性则称它为可迭代对象.
在 ES6
, 所有集合对象(Array
、Set
和 Map
) 和 strings
属于迭代对象.
ES6 提供 for...of
循环, 用来遍历可迭代对象. 查看如下示例:
var numbers = [1, 2, 3];
for (let num of numbers) {
console.log(num);
}
// 1
// 2
// 3
在内部, JavaScript 引擎首先调用Symbol.iterator
方法,获取 numbers
数组的迭代对象. 然后,它调用 iterator.next()
方法并且把迭代器对象的 value
属性值赋值给 num
变量. 依次执行三次迭代, 当迭代返回的结果对象中 done
属性为 true
. 循环♻️退出.
你可以通过 Symbol.iterator
去访问默认的迭代器对象, 如下:
var iterator = numbers[Symbol.iterator]();
console.log(iterator.next()); // Object {value: 1, done: false}
console.log(iterator.next()); // Object {value: 2, done: false}
console.log(iterator.next()); // Object {value: 3, done: false}
console.log(iterator.next()); // Object {value: undefined, done: true}
默认情况下, 集合是不可迭代的. 然而, 你可以使用 Symbol.iterator
把它标记为迭代对象. 如下例所示:
class List {
constructor() {
this.elements = [];
}
add(element) {
this.elements.push(element);
return this;
}
*[Symbol.iterator]() {
for (let element of this.elements) {
yield element;
}
}
}
let chars = new List();
chars.add('A')
.add('B')
.add('C');
// because of the Symbol.iterator
for (let c of chars) {
console.log(c);
}
// A
// B
// C
Symbol.isConcatSpreadable
通常拼接两个数组, 使用 concat()
方法,如下例所示:
let odd = [1, 3],
even = [2, 4];
let all = odd.concat(even);
console.log(all); // [1, 3, 2, 4]
在例子中, 合并后的结果包含两个数组中的元素. 此外, concat()
方法也接受非数组参数.
let extras = all.concat(5);
console.log(extras); // [1, 3, 2, 4, 5]
从上面例子可以看到, 当传递一个数组给 concat()
方法时, concat()
方法会把数组展开为单个元素再添加到数组中. 然后, 它对单个原始参数处理方式有点不同. 在 ES6 之前. 你不可能更改这个行为.
这就是引入 Symbol.isConcatSpreadable
的原因.
Symbol.isConcatSpreadable
属性是一个 Boolean
值, 它决定是否将对象单独添加到 concat()
方法的结果中.
参考下面的示例:
let list = {
0: 'JavaScript',
1: 'Symbol',
length: 2
};
let message = ['Learning'].concat(list);
console.log(message); // ["Learning", Object]
类数组使用 concat()
时, 其并没有把类数组中每个属性添加到最终返回结果中.
如果实现期望的效果, 需要在类对象中添加 Symbol.isConcatSpreadable
.
let list = {
0: 'JavaScript',
1: 'Symbol',
length: 2,
[Symbol.isConcatSpreadable]: true
};
let message = ['Learning'].concat(list);
console.log(message); // ["Learning", "JavaScript", "Symbol"]
注意: 如果设置Symbol.isConcatSpreadable
值为 false
且将类数组传递给 concat()
方法时, 它将把整个对象添加到数组中.
Symbol.toPrimitive
Symbol.toPrimitive
方法决定当一个对象转换为一个原始值时会发生什么.
JavaScript 引擎在每个标准类型的原型上定义了 Symbol.toPrimitive
方法.
Symbol.toPrimitive
方法接收一个 hint
参数, 其可能值为这三个:string
、number
、default
. 该 hint
参数指定该类型应该返回的值. hint
参数的值是 JavaScript 引擎根据上下文对象去填充的.
下面使用 Symbol.toPrimitive
方法的案例:
function Money(amount, currency) {
this.amount = amount;
this.currency = currency;
}
Money.prototype[Symbol.toPrimitive] = function(hint) {
var result;
switch (hint) {
case 'string':
result = this.amount + this.currency;
break;
case 'number':
result = this.amount;
break;
case 'default':
result = this.amount + this.currency;
break;
}
return result;
}
var price = new Money(799, 'USD');
console.log('Price is ' + price); // Price is 799USD
console.log(+price + 1); // 800
console.log(String(price)); // 799USD
在这篇文章中, 您应该学习了 JavaScript symbols
以及如何将 symbols 用于唯一值和对象的属性. 此外, 您还学习了如何使用 Well-known symbols
来修改对象行为.