今日学习内容 1、JS红皮书P48-54 第三章:语言基础
今日笔记 1、ECMAScript 6 也引入了一批常用内置符号(well-known symbol),用于暴露语言内部行为,开发者可以直接访问、重写或模拟这些行为。这些内置符号都以 Symbol 工厂函数字符串属性的形式存在。 for-of 循环会在相关对象上使用 Symbol.iterator 属性,那么就可以通过在自定义对象上重新定义Symbol.iterator 的值,来改变 for-of 在迭代该对象时的行为。 全局函数 Symbol 的普通字符串属性,指向一个符号的实例。所有内置符号属性都是不可写、不可枚举、不可配置的。 注意 在提到 ECMAScript 规范时,经常会引用符号在规范中的名称,前缀为@@。比如,@@iterator 指的就是 Symbol.iterator。 2、Symbol.asyncIterator: 根据 ECMAScript 规范,这个符号作为一个属性表示“一个方法,该方法返回对象默认的 AsyncIterator。由 for-await-of 语句使用”。换句话说,这个符号表示实现异步迭代器 API 的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Emitter { constructor (max ) { this .max = max; this .asyncIdx = 0 ; } async *[Symbol .asyncIterator ]() { while (this .asyncIdx < this .max ) { yield new Promise ((resolve ) => resolve (this .asyncIdx ++)); } } } async function asyncCount ( ) { let emitter = new Emitter (5 ); for await (const x of emitter ) { console .log (x); } } asyncCount ();
3、Symbol.hasInstance: 这个符号作为一个属性表示“一个方法,该方法确定一个构造器对象识别的对象是否为其实例”。由instanceof 操作符使用”。instanceof 操作符可以用来确定一个对象实例的原型链上是否有原型。instanceof 的典型使用场景如下:
1 2 3 4 5 6 function Foo ( ) {} let f = new Foo (); console .log (f instanceof Foo ); class Bar {} let b = new Bar (); console .log (b instanceof Bar );
ES6 中,instanceof 操作符会使用 Symbol.hasInstance 函数来确定关系。以 Symbol.hasInstance 为键的函数会执行同样的操作,只是操作数对调了一下:
1 2 3 4 5 6 function Foo ( ) {} let f = new Foo (); console .log (Foo [Symbol .hasInstance ](f)); class Bar {} let b = new Bar (); console .log (Bar [Symbol .hasInstance ](b));
这个属性定义在 Function 的原型上,因此默认在所有函数和类上都可以调用。由于 instanceof操作符会在原型链上寻找这个属性定义,就跟在原型链上寻找其他属性一样,因此可以在继承的类上通过静态方法重新定义这个函数:
1 2 3 4 5 6 7 8 9 10 11 class Bar {} class Baz extends Bar { static [Symbol .hasInstance ]() { return false ; } } let b = new Baz (); console .log (Bar [Symbol .hasInstance ](b)); console .log (b instanceof Bar ); console .log (Baz [Symbol .hasInstance ](b)); console .log (b instanceof Baz );
4、Symbol.isConcatSpreadable: 这个符号作为一个属性表示“一个布尔值,指示对象是否应该展开为数组元素”。这个属性会影响数组的 concat() 方法,如果一个对象的 Symbol.isConcatSpreadable 属性为 true,那么 concat() 方法会展开这个对象,否则会将这个对象作为一个整体添加到数组中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 let initial = ['foo' ]; let array = ['bar' ]; console .log (array[Symbol .isConcatSpreadable ]); console .log (initial.concat (array)); array[Symbol .isConcatSpreadable ] = false ; console .log (initial.concat (array)); let arrayLikeObject = { length : 1 , 0 : 'baz' }; console .log (arrayLikeObject[Symbol .isConcatSpreadable ]); console .log (initial.concat (arrayLikeObject)); arrayLikeObject[Symbol .isConcatSpreadable ] = true ; console .log (initial.concat (arrayLikeObject)); let otherObject = new Set ().add ('qux' ); console .log (otherObject[Symbol .isConcatSpreadable ]); console .log (initial.concat (otherObject)); otherObject[Symbol .isConcatSpreadable ] = true ; console .log (initial.concat (otherObject));
5、Symbol.iterator: 这个符号作为一个属性表示“一个方法,该方法返回对象默认的迭代器。由 for-of 语句使用”。换句话说,这个符号表示实现迭代器 API 的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 constructor (max ) { this .max = max; this .idx = 0 ; } *[Symbol .iterator ]() { while (this .idx < this .max ) { yield this .idx ++; } } } function count ( ) { let emitter = new Emitter (5 ); for (const x of emitter) { console .log (x); } } count ();
6、Symbol.match: 这个符号作为一个属性表示“一个正则表达式方法,该方法用于匹配字符串”。这个属性会影响 String.prototype.match() 方法,如果一个对象的 Symbol.match 属性为一个函数,那么 match() 方法会调用这个函数,而不是使用正则表达式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 console .log (RegExp .prototype [Symbol .match ]); console .log ('foobar' .match (/bar/ )); class FooMatcher { static [Symbol .match ](target) { return target.includes ('foo' ); } } console .log ('foobar' .match (FooMatcher )); console .log ('barbaz' .match (FooMatcher )); class StringMatcher { constructor (str ) { this .str = str; } [Symbol .match ](target) { return target.includes (this .str ); } } console .log ('foobar' .match (new StringMatcher ('foo' ))); console .log ('barbaz' .match (new StringMatcher ('qux' )));
7、Symbol.replace: 这个符号作为一个属性表示“一个正则表达式方法,该方法用于替换字符串”。这个属性会影响 String.prototype.replace() 方法,如果一个对象的 Symbol.replace 属性为一个函数,那么 replace() 方法会调用这个函数,而不是使用正则表达式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 console .log (RegExp .prototype [Symbol .replace ]);console .log ('foobarbaz' .replace (/bar/ , 'qux' )); class FooReplacer { static [Symbol .replace ](target, replacement) { return target.split ('foo' ).join (replacement); } } console .log ('barfoobaz' .replace (FooReplacer , 'qux' )); class StringReplacer { constructor (str ) { this .str = str; } [Symbol .replace ](target, replacement) { return target.split (this .str ).join (replacement); } } console .log ('barfoobaz' .replace (new StringReplacer ('foo' ), 'qux' ));
8、Symbol.search: 这个符号作为一个属性表示“一个正则表达式方法,该方法用于搜索字符串”。这个属性会影响 String.prototype.search() 方法,如果一个对象的 Symbol.search 属性为一个函数,那么 search() 方法会调用这个函数,而不是使用正则表达式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 console .log (RegExp .prototype [Symbol .search ]);console .log ('foobar' .search (/bar/ ));class FooSearcher { static [Symbol .search ](target) { return target.indexOf ('foo' ); } } console .log ('foobar' .search (FooSearcher )); console .log ('barfoo' .search (FooSearcher )); console .log ('barbaz' .search (FooSearcher )); class StringSearcher { constructor (str ) { this .str = str; } [Symbol .search ](target) { return target.indexOf (this .str ); } } console .log ('foobar' .search (new StringSearcher ('foo' ))); console .log ('barfoo' .search (new StringSearcher ('foo' ))); console .log ('barbaz' .search (new StringSearcher ('qux' )));
9、Symbol.species: 这个符号作为一个属性表示“一个函数值,该函数作为创建派生对象的构造函数”。这个属性在内置类型中最常用,用于对内置类型实例方法的返回值暴露实例化派生对象的方法。用 Symbol.species 定义静态的获取器(getter)方法,可以覆盖新创建实例的原型定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Bar extends Array {} class Baz extends Array { static get [Symbol .species ]() { return Array ; } } let bar = new Bar (); console .log (bar instanceof Array ); console .log (bar instanceof Bar ); bar = bar.concat ('bar' ); console .log (bar instanceof Array ); console .log (bar instanceof Bar ); let baz = new Baz (); console .log (baz instanceof Array ); console .log (baz instanceof Baz ); baz = baz.concat ('baz' ); console .log (baz instanceof Array ); console .log (baz instanceof Baz );
10、Symbol.split: 这个符号作为一个属性表示“一个正则表达式方法,该方法用于拆分字符串”。这个属性会影响 String.prototype.split() 方法,如果一个对象的 Symbol.split 属性为一个函数,那么 split() 方法会调用这个函数,而不是使用正则表达式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 console .log (RegExp .prototype [Symbol .split ]);console .log ('foobarbaz' .split (/bar/ ));class FooSplitter { static [Symbol .split ](target) { return target.split ('foo' ); } } console .log ('barfoobaz' .split (FooSplitter )); class StringSplitter { constructor (str ) { this .str = str; } [Symbol .split ](target) { return target.split (this .str ); } } console .log ('barfoobaz' .split (new StringSplitter ('foo' )));
11、Symbol.toPrimitive: 这个符号作为一个属性表示“一个方法,该方法将对象转换为相应的原始值”。这个属性会影响对象的类型转换,如果一个对象的 Symbol.toPrimitive 属性为一个函数,那么对象会调用这个函数,而不是使用默认的类型转换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Foo {} let foo = new Foo (); console .log (3 + foo); console .log (3 - foo); console .log (String (foo)); class Bar { constructor ( ) { this [Symbol .toPrimitive ] = function (hint ) { switch (hint) { case 'number' : return 3 ; case 'string' : return 'string bar' ; case 'default' : default : return 'default bar' ; } } } } let bar = new Bar (); console .log (3 + bar); console .log (3 - bar); console .log (String (bar));