JavaScript Symbol 符号终极指南教程
在本教程中,您将了解 JavaScript Symbol 符号原始类型以及如何有效地使用符号。
创建符号
ES6 添加 Symbol 作为新的原始类型。与 number、boolean、null、undefined 和 string 等其他基本类型不同,符号类型没有字面量形式。
要创建新的符号,您可以使用全局 Symbol() 函数,如本例所示:
let s = Symbol('foo');每次调用 Symbol() 函数时都会创建一个新的唯一值:
console.log(Symbol() === Symbol()); // falseSymbol() 函数接受一个 description 作为可选参数。 description 参数将使您的符号更具描述性。
以下示例创建两个符号:firstName 和 lastName。
let firstName = Symbol('first name');
let lastName = Symbol('last name');您可以使用 toString() 方法访问符号的描述属性 description。 console.log()方法隐式调用符号的 toString() 方法,如下例所示:
console.log(firstName); // Symbol(first name)
console.log(lastName); // Symbol(last name)由于符号是原始值,您可以使用 typeof 运算符来检查变量是否是符号。当你传入一个符号变量时,ES6 扩展了 typeof 返回 symbol 字符串:
console.log(typeof firstName); // symbol由于符号是原始值,如果您尝试使用 new 运算符创建符号,则会抛出错误:
let s = new Symbol(); // error
共享符号
ES6 为您提供一个全局符号注册表,允许您在全球范围内共享符号。如果你想创建一个将被共享的符号,你可以使用 Symbol.for() 方法而不是调用 Symbol() 函数。
Symbol.for() 方法接受可用于描述符号的单个参数,如以下示例所示:
let ssn = Symbol.for('ssn');Symbol.for() 方法首先使用 ssn 键在全局符号注册表中的搜索符号。如果有一个,它返回现有的符号。否则,Symbol.for() 方法创建一个新的符号,使用指定的键将其注册到全局符号注册表,然后返回该符号。
稍后,如果您使用相同的键调用 Symbol.for() 方法,Symbol.for() 方法将返回现有符号。
let citizenID = Symbol.for('ssn');
console.log(ssn === citizenID); // true在这个例子中,我们使用 Symbol.for() 方法来查找带有 ssn 键的符号 。由于全局符号注册表已包含它,因此 Symbol.for() 方法返回现有符号。
要获取与符号关联的键,您可以使用 Symbol.keyFor() 方法,如下所示:
console.log(Symbol.keyFor(citizenID)); // 'ssn'如果全局符号注册表中不存在符号,则 System.keyFor() 方法返回 undefined。
let systemID = Symbol('sys');
console.log(Symbol.keyFor(systemID)); // undefined符号用法
符号作为唯一值
每当您在代码中使用字符串或数字作为标识时,都应该改用符号。例如,您必须在任务管理应用程序中管理状态。
在 ES6 之前,您会使用诸如 open, in progress, completed, canceled, 和 on hold 之类的字符串来表示任务的不同状态。在 ES6 ,你可以像下面这样使用符号:
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);符号作为对象的计算属性名称
您可以使用符号作为计算属性名称。请阅读以下示例:
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"]
要获取对象的所有符号属性,可以使用 ES6 添加的 Object.getOwnPropertySymbols() 方法。
console.log(Object.getOwnPropertySymbols(task)); //[Symbol(status)]
Object.getOwnPropertySymbols() 方法返回对象自身属性符号的数组。
内置通用(well-known)symbol
ES6 提供预定义的符号,称为well-known 符号。well-known 符号代表 JavaScript 中的常见行为。每个 well-known 符号都是 Symbol 对象的静态属性。
symbol.hasInstance
Symbol.hasInstance 是改变 instanceof 运算符行为的符号。通常,当您在使用instanceof 运算符时:
obj instanceof type;JavaScript 将按如下方式调用 Symbol.hasIntance 方法:
type[Symbol.hasInstance](obj);然后它依赖此方法来确定 obj 是否是 type 对象的实例。请参见以下示例。
class Stack {
}
console.log([] instanceof Stack); // false[] 数组不是 Stack 类的实例,因此,instanceof 运算符在此示例中返回 false 。
假设你想让 [] 数组是 Stack 类的一个实例,你可以添加 Symbol.hasInstance 方法如下:
class Stack {
static [Symbol.hasInstance](obj) {
return Array.isArray(obj);
}
}
console.log([] instanceof Stack); // trueSymbol.iterator
Symbol.iterator 函数是否将返回指定对象的迭代器。具有 Symbol.iterator 属性的对象称为可迭代对象。
在 ES6 ,所有集合对象 Array、Set和Map 和字符串都是可迭代对象。ES6 提供用于可迭代对象的 for...of 循环,如下例所示。
var numbers = [1, 2, 3];
for (let num of numbers) {
console.log(num);
}
// 1
// 2
// 3在内部,JavaScript 引擎首先调用 numbers 数组的 Symbol.iterator 方法来获取迭代器对象。
然后,它调用 iterator.next() 方法并将迭代器对象的值属性复制到 num 变量中。迭代三次后,done 结果对象的属性为 true,退出循环。
您可以通过 System.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
// CSymbol.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]数字 5 成为数组的第五个元素。
正如您在上面的示例中看到的,当我们将数组传递给方法时 concat(), concat()方法将数组展开为单个元素。
但是,它以不同的方式对待单个原始参数。在 ES6 之前,您无法更改此行为。这就是 Symbol.isConcatSpreadable 符号发挥作用的原因。
Symbol.isConcatSpreadable 属性是一个布尔值,用于确定是否将对象单独添加到 concat() 函数的结果中。
考虑以下示例:
let list = {
0: 'JavaScript',
1: 'Symbol',
length: 2
};
let message = ['Learning'].concat(list);
console.log(message); // ["Learning", Object]列表对象连接到数组['Learning']。但是,它的各个元素并没有传播。
要使 list 对象的元素在传递给 concat() 方法时单独添加到数组中,需要将Symbol.isConcatSpreadable 属性添加到 list 对象中,如下所示:
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 并将 list 对象传递给 concat() 方法,它将作为整个对象连接到数组。
Symbol.toPrimitive
Symbol.toPrimitive 方法确定将对象转换为原始值时应该做什么。JavaScript 引擎在每个标准类型的原型上定义 Symbol.toPrimitive 方法。
Symbol.toPrimitive 方法接受一个 hint 参数, hint 参数的值可以是:number 、 string 和 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 符号以及如何将符号用于唯一值和对象属性。此外,您还学习了如何使用well-known的符号来修改对象行为。