常用的内置符号
ECMAScript
引入了一批 常用的内置符号(well-known symbol) 可以用于暴露语言的内部行为,开发者可以直接访问、重写、或模拟这些行为。
这些内置符号都以Symbol
工厂函数字符串属性的形式存。
内置符号最重要的用途之一就是重新定义它们,从而改变原生结构的行为。比如for-of
循环会在相关对象上使用 Symbol.iterator
属性,那么就可以通过在
定义对象上重新定义Symbol.iterator
的值 来改变for-of
在迭代该对象时的行为。
Symbol.asyncIterator
Symbol.asyncIterator
符号指定了一个对象的默认异步迭代器。如果一个对象设置了这个属性,它就是异步可迭代对象,可用于 for await...of
循环。
示例
for await...of
循环会利用这个函数执行异步迭代操作。循环的时候,它们会调用以Symbol.asyncIterator
为key
的函数,
并期望这个函数会返回一个实现迭代器的 API 的对象
class Foo {
async *[Symbol.asyncIterator]() {}
}
const f = new Foo();
console.log(f[Symbol.asyncIterator]()); // AsyncGenerator {<suspended>}
技术上这个由 Symbol.asyncIterator
函数生成的对象,应该通过其 next
方法陆续返回 Promise
实例,可以通过显示的调用 next
方法返回,
也可以通过异步生成器函数返回:
class Emitter {
constructor(max) {
this.max = max;
this.asyncIndex = 0;
}
async *[Symbol.asyncIterator]() {
while (this.max > this.asyncIndex) {
yield Promise.resolve(this.asyncIndex++);
}
}
}
const emitter = new Emitter(5);
async function asyncCount() {
for await (const x of emitter) {
console.log(x);
}
}
asyncCount();
// 0
// 1
// 2
// 3
// 4
Symbol.hasInstance
Symbol.hasInstance
用于判断某对象是否为某构造函数的实例。因此你可以用它自定义 instanceof
操作符在某个类上的行为。
instanceof
操作符可以 用来确定一个对象实例的原型链上是否有原型。
function Foo() {}
const foo = new Foo();
console.log(foo instanceof Foo); // true
function Bar() {}
const bar = new Bar();
console.log(bar instanceof Bar); // true
instanceof
instanceof
运算符用于检测构造函数的 prototype
属性是否出现在某个实例对象的原型链上。
下面是 instanceof
运算符的工作原理的简单步骤:
- 首先,它检查对象的
proto
属性,也就是对象的原型。 - 如果对象的原型与指定构造函数的原型相等,那么返回
true
。 - 如果对象的原型不等于指定构造函数的原型,则继续向上查找,将对象的原型设置为当前原型的原型,并重复步骤 2。
- 如果在原型链的顶端(即
Object.prototype
)还没有找到匹配的构造函数,那么返回false
。
function Foo() {}
const foo = new Foo();
console.log(foo instanceof Foo); // true, 因为Object.getPrototypeOf(foo) === Foo.prototype
function Bar() {}
const bar = new Bar();
console.log(bar instanceof Bar); // true, 因为Object.getPrototypeOf(bar) === Bar.prototype
在 ES6
中instanceof
操作符会使用 Symbol.hasInstance
函数来确定关系 以Symbol.hasInstance
为 key 的函数会执行同样的操作
function Foo() {}
const foo = new Foo();
console.log(Foo[Symbol.hasInstance](foo)); // true
function Bar() {}
const bar = new Bar();
console.log(Bar[Symbol.hasInstance](bar)); // true
因为这个属性定义在来 Function
的原型上 所以默认所有函数和类都可以调用
class Bar {}
class Baz extends Bar {
static [Symbol.hasInstance]() {
return false;
}
}
const 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); // true
Symbol.isConcatSpreadable
Symbol.isConcatSpreadable
符号用于配置对象作为 Array.prototype.concat()
方法的参数时是否展开其数组元素,
描述
Symbol.isConcatSpreadable
可以直接定义为对象属性或继承而来,它是布尔类型。它可以控制数组或类似数组(array-like)的对象的行为:
- 对于数组对象,默认情况下,用于
concat
时,会按数组元素展开然后进行连接(数组元素作为新数组的元素)。 如果为true
和默认情况基本一样, 如果是false
或者假值会导致整个对象被追加到数组末尾, - 对于类似数组的对象,用于
concat
时,该对象整体作为新数组的元素,如果为false
或者假值 和默认情况基本一样,如果为true
或真值会导致这个类数组对象展开然后进行连接。 - 其他不是类数组的对象在
Symbol.isConcatSpreadable
被设置为 true 的情况下将被忽略
示例
数组
let initial = ["foo"];
let array = ["bar"];
// 数组对象的 Symbol.isConcatSpreadable 没有默认值
console.log(array[Symbol.isConcatSpreadable]); // undefined
// 数组对象默认情况下会被打平到 已有的数组
console.log(initial.concat(array)); // ['foo', 'bar']
array[Symbol.isConcatSpreadable] = false;
// 如果是 false 或者假值会导致整个对象被追加到数组末尾,
console.log(initial.concat(array)); // ['foo', ['bar']]
ArrayLike(类数组)
let initial = ["foo"];
let arrayLikeObj = {
length: 1,
0: "bar",
};
// 类数组对象的 Symbol.isConcatSpreadable 没有默认值
console.log(arrayLikeObj[Symbol.isConcatSpreadable]); // undefined
// 该对象整体作为新数组的元素
console.log(initial.concat(arrayLikeObj)); // ['foo',{...}]
arrayLikeObj[Symbol.isConcatSpreadable] = true;
// 如果为`true` 或真值会导致这个类数组对象展开然后进行连接。
console.log(initial.concat(arrayLikeObj)); // ['foo', bar]
其他不是类数组的
let initial = ["foo"];
let otherObject = new Set().add("qux");
// Symbol.isConcatSpreadable 没有默认值
console.log(otherObject[Symbol.isConcatSpreadable]); // undefined
// 该对象整体作为新数组的元素
console.log(initial.concat(otherObject)); // ['foo',Set(1)]
otherObject[Symbol.isConcatSpreadable] = true;
// 其他不是类数组的对象在 `Symbol.isConcatSpreadable`被设置为 true 的情况下将被忽略
console.log(initial.concat(otherObject)); // ['foo']
Symbol.iterator
Symbol.iterator
一个方法 为每一个对象定义了默认的迭代器。该迭代器可以被 for...of
循环使用。
描述
当需要对一个对象进行迭代时(比如开始用于一个 for...of
循环中),它的 @@iterator
方法都会在不传参情况下被调用,返回的迭代器用于获取要迭代的值。
一些内置类型拥有默认的迭代器行为,其他类型(如 Object
)则没有。拥有默认的 @@iterator
方法的内置类型是:
Array.prototype[@@iterator]
String.prototype[@@iterator]
Map.prototype[@@iterator]
Set.prototype[@@iterator]
TypeArray.prototype[@@iterator]
示例
自定义迭代器
const iterable = {};
iterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
console.log([...iterable]); // [1,2,3]
在类或对象中使用计算属性
定义迭代:
class Foo {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
}
const someObj = {
*[Symbol.iterator]() {
yield "a";
yield "b";
},
};
console.log(...new Foo()); // 1, 2, 3
console.log(...someObj); // a, b
Symbol.match
Symbol.match
一个正则表达式的方法,这个方法用正则表达式去匹配字符串。 可以被 String.prototype.match
方法使用。
正则表达式的原型上默认有Symbol.match
为 key
函数的定义
console.log(RegExp.prototype[Symbol.match]); // ƒ [Symbol.match]() { [native code] }
console.log("foobar".match(/bar/)); // ['bar', index: 3, input: 'foobar', groups: undefined]
String.prototype.match
该方法检索字符串与正则表达式进行匹配的结果。
参数
-
regexp
一个正则表达式对象或者任何具有
Symbol.match
方法的对象。如果
regexp
不是RegExp
对象并且对象上无Symbol.match
方法,则会使用new RegExp(regexp)
将其隐式地转换为RegExp
。如果你没有给出任何参数并直接使用
match()
方法,你将会得到一个包含空字符串的数组:[""]
,因为这等价于match(/(?:)/)
描述
String.prototype.match
方法本身的实现非常简单,它只是使用字符串作为第一个参数调用了参数的 Symbol.match
方法。实际的实现来自于 RegExp.prototype[@@match]()
。
Symbol.match
函数接收一个参数 调用match()
方法的字符串。它的返回值成为 match()
的返回值。在这种情况下,match()
的行为完全由 @@match
方法定义
class FooMatcher {
[Symbol.match](a) {
return a.includes("foo");
}
}
console.log("foobar".match(new FooMatcher())); //true;
console.log("barbaz".match(new FooMatcher())); //false
Symbol.replace
Symbol.replace
一个正则表达式的方法,这个方法替换一个字符串中匹配的子串。 可以被 String.prototype.replace
方法使用。
正则表达式的原型上默认有Symbol.replace
为 key
函数的定义
console.log(RegExp.prototype[Symbol.replace]); // ƒ [Symbol.replace]() { [native code] }
console.log("foobarbaz".replace(/bar/, "qux")); // 'fooquxbaz'
String.prototype.replace
该方法返回一个新字符串,其中一个、多个或所有匹配的 pattern
被替换为 replacement
。
参数说明
-
pattern
可以是字符串或者一个带有
Symbol.replace
方法的对象,任何没有Symbol.replace
方法的值都会被强制转换为字符串。 -
replacement
可以是字符串或函数。
- 如果是字符串,它将替换由
pattern
匹配的子字符串 - 如果是函数,将为每个匹配调用该函数,并将其返回值用作替换文本。
- 如果是字符串,它将替换由
描述
该方法并不改变调用它的字符串本身,而是返回一个新的字符串。
如果 pattern
是一个带有 Symbol.replace
方法的对象(包括 RegExp
对象),则该方法将被调用。
Symbol.replace
函数接受两个参数 调用replace()
方法的字符串 和 replacement
。它的返回值成为 replace()
的返回值。在这种情况下,replace()
的行为完全由 @@replace
方法定义
class FooReplacer {
static [Symbol.replace](pattern, replacement) {
return pattern.split("foo").join(replacement);
}
}
console.log("barfoobaz".replace(FooReplacer, "qux")); // 'barquxbaz'
Symbol.search
Symbol.search
一个正则表达式的方法,这个方法返字符串中匹配正则表达式的索引。 可以被 String.prototype.search
方法使用。
正则表达式的原型上默认有Symbol.search
为 key
函数的定义
console.log(RegExp.prototype[Symbol.search]); // ƒ [Symbol.search]() { [native code] }
console.log("foobar".search(/bar/)); // 3
String.prototype.search
search()
方法用于在 String
对象中执行正则表达式的搜索,寻找匹配项。
参数说明
-
pattern
一个正则表达式对象,或者具有 **
Symbol.search
**方法的任意对象。如果
regexp
不是RegExp
对象,并且不具有Symbol.search
方法,则会使用new RegExp(regexp)
将其隐式转换为RegExp
。
返回值
如果匹配成功,则返回正则表达式在字符串中首次匹配的索引;否则,返回 -1。
描述
String.prototype.search()
方法的实现非常简单 ,它只是使用字符串作为第一个参数调用了参数的 Symbol.search
方法。
实际的实现来自于 RegExp.prototype[@@search]()
。
Symbol.search
函数接收一个参数 调用search()
方法的字符串。它的返回值成为 search()
的返回值。在这种情况下,search()
的行为完全由 @@search
方法定义
class FooSearcher {
static [Symbol.search](target) {
return target.indexOf("foo");
}
}
console.log("foobar".search(FooSearcher)); //0
console.log("barfoo".search(FooSearcher)); //3
console.log("barbaz".search(FooSearcher)); //-1
Symbol.species
Symbol.species
是个函数值属性,这个函数作为创建派生对象的构造函数。
什么叫派生对象
派生对象
是指通过继承一个基类(也称为父类或超类)来创建的对象。
实例
使用 Symbol.species
class Baz extends Array {
static get [Symbol.species]() {
return Array;
}
}
const baz = new Baz(1, 2, 3);
console.log(baz instanceof Baz); // true
const mapped = baz.map((x) => x * x);
console.log(mapped instanceof Baz); // false
不使用 Symbol.species
class Baz extends Array {}
const baz = new Baz(1, 2, 3);
console.log(baz instanceof Baz); // true
const mapped = baz.map((x) => x * x);
console.log(mapped instanceof Baz); // true
Symbol.split
Symbol.split
一个正则表达式的方法, 该方法 指向一个正则表达式的索引处分割字符串的方法 。 可以被 String.prototype.split
方法使用。
正则表达式的原型上默认有Symbol.split
为 key
函数的定义
console.log(RegExp.prototype[Symbol.split]); // ƒ [Symbol.split]() { [native code] }
console.log("foobarbaz".split(/bar/)); // ['foo','baz']
String.prototype.split
split()
,通过搜素模式将字符串分割成一个有序的子串列表,将这些子串放入一个数组,并且返回该数组。
参数
-
separator
描述每个分割应该发生在哪里的模式。可以是
undefined
,一个字符串,或者一个具有Symbol.split
方法的对象(正则表达式)。 省略separator
或传递undefined
会导致split()
返回一个只包含所调用字符串数组,所有不是undefined
的值或不具有Symbol.split
方法的对象都被强制转换为字符串。如果
separator
是一个非空字符串,目标字符串会被所有匹配的separator
分割,结果中不包括separator
-
limit
可选一个非负整数,指定数组中包含的子字符串的数量限制。
返回值
在给定字符串中出现 separator
的每一个点上进行分割而成的字符串数组
描述
Symbol.split
函数接收两个参数 调用split()
方法的字符串 。它的返回值成为 split()
的返回值。在这种情况下,split()
的行为完全由 @@split
方法定义
class FooSplitter {
static [Symbol.split](target, limit) {
return target.split("foo");
}
}
console.log("barfoobaz".split(FooSplitter)); //['bar','baz']
Symbol.toPrimitive
Symbol.toPrimitive
一个方法 该方法可以用于定义对象的默认转换行为。
指定了一种接受首选类型并返回对象原始值的表示的方法 , 它被所有的强类型转换制算法优先调用(valueOf()
,toString()
)。
在执行表达式 3 + foo
时,会先尝试调用对象的 valueOf()
方法来获取其原始值。如果 valueOf()
方法返回的是一个原始值(比如数字)。
那么加法运算就会继续进行,并以这个原始值作为结果。如果 valueOf()
方法返回的仍然是一个对象,那么会继续尝试调用对象的 toString()
方法来获取字符串表示形式,然后再进行加法运算。
class Foo {}
let foo = new Foo();
console.log(3 + foo); // 3[object Object];
console.log(3 - foo); // NaN
console.log(String(foo)); // [object Object];
class Bar {
[Symbol.toPrimitive] = function (hint) {
switch (hint) {
case "number":
return 3;
case "string":
return "string bar";
default:
return "default bar";
}
};
}
let bar = new Bar();
//
console.log(3 + bar); // '3default bar';
console.log(3 - bar); // 0
console.log(String(bar)); // string bar;
描述
在 Symbol.toPrimitive
属性(用作函数值)的帮助下,对象可以转换为一个原始值。
该函数被调用时,会被传递一个字符串参数 hint
,表示要转换到的原始值的预期类型。hint
参数的取值是 "number"
、"string"
和 "default"
中的任意一个。
"number"
hint 用于强制 数字类型转换方法,"string"
hint 用于强制字符串类型转换算法。default
hint 用于强制原始值转换算法。hint 仅是作为首选项的偏弱的信号提示。
实现时,可以自由忽略它(就像 Symbol.prototype[@@toPrimitive]()
一样)。该语言不会在 hint
和结果类型之间强制校正,尽管 [@@toPrimitive]()
必须返回一个原始值,否则将抛出 TypeError
。
Symbol.toStringTag
Symbol.toStringTag
该符号作为一个字符串值的属性 用于创建对象的默认字符串描述,由内置方法Object.prototype.toString()
使用
通过 Object.prototype.toString()
方法获取对象标识时,会检索 Symbol.toStringTag
指定的实例标识符,默认为 “Object"
,内置类型已经指定了这个值
但是自定义内型需要明确定义。
let o = new Object();
console.log(o.toString()); // [object Object]
console.log(o[Symbol.toStringTag]); // undefined
const set = new Set();
console.log(set.toString()); // [object Set]
console.log(set[Symbol.toStringTag]); // Set
class ValidatorClass {
[Symbol.toStringTag] = "Validator";
}
console.log(new ValidatorClass().toString()); // [object Validator];
console.log(new ValidatorClass()[Symbol.toStringTag]); // Validator
Symbol.unscopables
Symbol.unscopables
这个符号作为一个属性表示一个对象,该对象所有的以及继承的属性都会从关联对象的 with 环境绑定中解除
。
设置这个符号并让其映射对应属性的 key
为 true
,就可以阻止该属性出现在 with
环境绑定中.
let o = { foo: "bar" };
with (o) {
console.log(foo); //bar
}
o[Symbol.unscopables] = {
foo: true,
};
with (o) {
console.log(foo); //ReferenceError
}