想起之前面经中的问题,为什么js对象不能用for…of去遍历,背了八股文的都会回答因为对象是不可迭代的数据类型,今天也是回头看了js的Symbol类型,并在此找到了这道题的满意答案。
在此记录一些js中Symbol常用的属性或方法。
1. Symbol.asyncIterator
根据 ECMAScript 规范,这个符号作为一个属性表示“一个方法,该方法返回对象默认AsyncIterator。 由for-await-of 语句使用”。换句话说,这个符号表示实现异步迭代器 API 的函数。
for-await-of循环会利用这个函数执行异步迭代操作。循环时,它们会调用以 Symbol.asyncIterator为键的函数,并期望这个函数会返回一个实现迭代器 API 的对象。很多时候,返回的对象是实现该 API 的 AsyncGenerator:
技术上,这个由 Symbol.asyncIterator 函数生成的对象应该通过其 next()方法陆续返回 Promise 实例。可以通过显式地调用 next()方法返回,也可以隐式地通过异步生成器函数返回:
class Emitter {max: number;asyncIdx: number;constructor(max: number) {this.max = max;this.asyncIdx = 0;}async *[Symbol.asyncIterator]() {while (this.asyncIdx < this.max) {yield new Promise((resolve) => resolve(this.asyncIdx++));}}
}async function asyncCount() {const emitter = new Emitter(5);for await(const x of emitter) {console.log(x);}
}asyncCount();
2. Symbol.hasInstance
根据 ECMAScript 规范,这个符号作为一个属性表示“一个方法,该方法决定一个构造器对象是否认可一个对象是它的实例。由instanceof 操作符使用”。instanceof 操作符可以用来确定一个对象实例的原型链上是否有原型。
在 ES6 中,instanceof 操作符会使用 Symbol.hasInstance 函数来确定关系。以 Symbol. hasInstance 为键的函数会执行同样的操作,只是操作数对调了一下
function Foo() {}
let f = new Foo();
console.log(Foo[Symbol.hasInstance](f)); // true
class Bar {}
let b = new Bar();
console.log(Bar[Symbol.hasInstance](b)); // true
这个属性定义在 Function 的原型上,因此默认在所有函数和类上都可以调用。由于 instanceof 操作符会在原型链上寻找这个属性定义,就跟在原型链上寻找其他属性一样,因此可以在继承的类上通过静态方法重新定义这个函数:
class Bar {}
class Baz extends Bar {static [Symbol.hasInstance](_: any) {return false;}
}
let b = new Baz();
console.log(Bar[Symbol.hasInstance](b)); // true
console.log(b instanceof Bar); // true
console.log(Baz[Symbol.hasInstance](b)); // false
console.log(b instanceof Baz); // false
3. Symbol.iterator
根据 ECMAScript 规范,这个符号作为一个属性表示“一个方法,该方法返回对象默认的迭代器。 由 for-of语句使用”。换句话说,这个符号表示实现迭代器 API 的函数。
for-of循环这样的语言结构会利用这个函数执行迭代操作。循环时,它们会调用以 Symbol.iterator为键的函数,并默认这个函数会返回一个实现迭代器 API 的对象。很多时候,返回的对象是实现该 API 的 Generator:
例如我们知道对象是不可以被for of所迭代的,但是我们如果在其内部实现了Symbol.iterator方法,对象就可以被迭代。
const obj = {*[Symbol.iterator]() {for (let i = 0; i < 5; i++) {yield i;}}
};for (const iterator of obj) {console.log(iterator);
}
// 0 1 2 3 4
4. Symbol.toStringTag
根据 ECMAScript 规范,这个符号作为一个属性表示“一个字符串,该字符串用于创建对象的默认字符串描述。由内置方法Object.prototype.toString()使用”。
通过 toString()方法获取对象标识时,会检索由Symbol.toStringTag 指定的实例标识符,默认为"Object"。内置类型已经指定了这个值,但自定义类实例还需要明确定义:
class Foo {};
const foo = new Foo();console.log(foo); // Foo {}
console.log(foo.toString()); // [object Object]
console.log(foo[Symbol.toStringTag]); // undefinedclass Bar {constructor() {this[Symbol.toStringTag] = 'Bar';}
}
const bar = new Bar();
console.log(bar); // Bar { [Symbol(Symbol.toStringTag)]: 'Bar' }
console.log(bar.toString()); // [object Bar]
console.log(bar[Symbol.toStringTag]); // Barconst obj = {a: 1,[Symbol.toStringTag]: '1234'
}console.log(obj.toString()); // [object 1234]
5. 小小总结
以前在项目开发中很少用到Symbol就认为Symbol不重要,重新学习JS红宝书之后才发现js很多的方法和场景都是借助Symbol来实现的。学无止境,始终保持学习的热情和求知的精神,不断探索和发现新的知识和技能。