【译】学会ES6的Symbols,让你前端能力飞起🚀!

avatar
Eat,Sleep,Code @汽车之家 | autohome

原文:《Well-known Symbols》 Hemanth.HM 发布于2023 年 2 月 14 日

最近我在公共论坛上询问人们是否能够在不查阅资料的情况下列举出至少三个JavaScript中知名的Symbols,但很少有人能够做到。这也是这篇文章的诞生原因。

每个symbol都是一种非注册符号,它在不同环境中都是相同的。如果我们列出这些知名的Symbols,它们将包括:

Symbol.iterator
Symbol.toStringTag
Symbol.toPrimitive
Symbol.asyncIterator
Symbol.hasInstance
Symbol.isConcatSpreadable
Symbol.species
Symbol.match
Symbol.matchall
Symbol.replace
Symbol.search
Symbol.split
Symbol.unscopables
Symbol.dispose

让我们看一些例子,以了解它们的实用性。

Symbol.iterator :用于为对象定义默认迭代器。它用于在 for-of 循环或使用扩展运算符中启用对对象的迭代。

const obj = { a: 1, b: 2, c: 3 };

obj[Symbol.iterator] = function*() {
for (const key of Object.keys(this)) {
yield [key, this[key]];
}
};

for (const [key, value] of obj) {
console.log(`${key}: ${value}`);
}

Symbol.toStringTag :用于指定在调用对象的 Object.prototype.toString 方法时返回的字符串值。它可用于为对象提供自定义字符串表示形式。

class MyClass {
static [Symbol.toStringTag] = 'MyClass';
}

const myInstance = new MyClass();

console.log(myInstance.toString()); // outputs '[object MyClass]'

Symbol.toPrimitive :用于指定当隐式调用valueOf和ToString方法时的对象的行为。它可用于为对象提供自定义字符串和数字表示形式。

class Life {
  valueOf() {
    return 42;
  }

  [Symbol.toPrimitive](hint) {
    switch (hint) {
      case "number":
        return this.valueOf();
      case "string":
        return "Forty Two";
      case "default":
        return true;
    }
  }
}

const myLife = new Life();
console.log(+myLife); // 42
console.log(`${myLife}`); // "Forty Two"
console.log(myLife + 0); // 42

Symbol.asyncIterator :用于为对象定义异步迭代器,它用于启用对象上的异步迭代。


class MyAsyncIterable {
async *[Symbol.asyncIterator]() {
for (let i = 0; i < 5; i++) {
await new Promise(resolve => setTimeout(resolve, 1000));
yield i;
}
}
}

(async () => {
for await (const value of new MyAsyncIterable()) {
console.log(value);
}
})();

// Output after one second:
// 0
// Output after two seconds:
// 1
// Output after three seconds:
// 2
// Output after four seconds:
// 3
// Output after five seconds:
// 4

Symbol.hasInstance :用于确定对象是否是构造函数的实例。它可用于更改 instanceof 运算符的行为。


class MyArray {
static [Symbol.hasInstance](instance) {
return Array.isArray(instance);
}
}

const arr = [1, 2, 3];
console.log(arr instanceof MyArray); // true

Symbol.isConcatSpreadable :用于确定在与其他对象连接时是否应展平对象。它可用于更改 Array.prototype.concat 方法的行为。


const arr1 = [1, 2, 3];
const spreadable = { [Symbol.isConcatSpreadable]: true, 0: 4, 1: 5, 2: 6, length: 3 };

console.log([].concat(arr1, spreadable)); // [1, 2, 3, 4, 5, 6]

Symbol.species :用于指定在创建派生对象时要使用的构造函数。它可用于自定义创建新对象的内置方法的行为。

class MyArray extends Array {
static get [Symbol.species]() {
return Array;
}
}

const myArray = new MyArray(1, 2, 3);
const mappedArray = myArray.map(x => x * 2);

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

P.S:此功能将来可能会被删除。

Symbol.match :用于指定在使用 String.prototype.match 方法时要搜索的值。它可用于更改类似于 RegExp 对象的 match 方法的行为。


const myRegex = /test/;
'/test/'.startsWith(myRegex); // Throws TypeError

const re = /foo/;
re[Symbol.match] = false;
"/foo/".startsWith(re); // true
"/bar/".endsWith(re); // false

P.S:它的存在标志着一个对象被标记为 "是一个正则表达式"。


const myRegex = /foo/g;
const str = 'How many foos in the the foo foo bar?';

for (result of myRegex[Symbol.matchAll](str)) {
console.log(result); // we will get the matches
}

Symbol.replace :用于指定使用 String.prototype.replace 方法时的替换值。它可用于更改类似于 RegExp 对象的 replace 方法的行为。


const customReplace = str => str.replace(/\d+/g, match => `-${match}-`);

const customString = {
[Symbol.replace]: customReplace
};

const originalString = "foo123bar456baz";

const result = originalString.replace(customString, '*');

console.log(result); // outputs "foo-123-bar-456-baz"

Symbol.search :用于指定使用 String.prototype.search 方法时要搜索的值。它可用于更改类似于 RegExp 对象的 search 方法的行为。


const customSearch = str => str.search(/\d+/);

const customRegExp = {
[Symbol.search]: customSearch
};

const string = "foo123bar456baz";

string.search(customRegExp); // outputs 3

Symbol.split :用于在使用String.prototype.split方法时确定分割值的符号。它可以用于更改类似于 RegExp 对象的split方法的行为。


const { Symbol } = require('es6-symbol');

const customSplit = str => str.split(/\d+/);

const customRegExp = {
[Symbol.split]: customSplit
};

const string = "foo123bar456baz";

string.split(customRegExp); // outputs [ 'foo', 'bar', 'baz' ]

Symbol.unscopables :用于确定应排除在 with 语句范围之外的对象的属性。它可用于更改 with 语句的行为。


const person = {
age: 42
};

person[Symbol.unscopables] = {
age: true
};

with (person) {
console.log(age);
// Expected output: Error: age is not defined
}

Symbol.dispose :显式资源管理是指用户通过使用命令式方法(如Symbol.dispose)或声明式方法(如using块作用域声明)来显式地管理"资源"的生命周期的系统。


{
console.log(1);
using {
[Symbol.dispose]() {
console.log(2);
}
};
console.log(3);
}
// will log 1, 3, 2

本文旨在加深对JavaScript语言固有的知名symbols的理解。这些symbol,例如 Symbol.iterator、Symbol.toStringTag 和 Symbol.for,代表了可用于增强和规范代码行为的复杂且通用的工具。全面了解 JavaScript 环境中的可用symbols对于开发高性能、可维护和可扩展的应用至关重要。因此,建议在项目的概念化和实施阶段对将这些symbols属性纳入其中的可行性进行评估,以简化代码并以更复杂和优雅的方式实现预期结果。