本文为《深入理解ES6》读书笔记,感谢作者尼古拉斯泽拉斯,支持正版。
由于红宝书、你不知道的javascript中相关内容已经很丰富,只阅读了我自己还有疑惑的部分。
1 Symbol内置符号
Symbol内置符号的意义
ECMAScript 5的一个中心主旨是将JavaScript中的一些“神奇”的部分暴露出来,并详尽定义了这些开发者们在当时模拟不了的功能。
ECMAScript 6延续了这个传统,新标准中主要通过在原型链上定义与Symbol相关的属性来暴露更多的语言内部逻辑。
Symbol.iterator
- 定义在每个可迭代对象的原型上。
-
Symbol.hasInstance
每一个函数中都有一个Symbol.hasInstance方法,用于确定对象是否为函数的实例。
- 该方法在Function.prototype中定义,所以所有函数都继承了instanceof属性的默认行为。
本质上,ES6只是将instanceof操作符重新定义为此方法的简写语法。
obj instanceof Array
// 相当于
Array[Symbol.hasInstance](obj)
为了确保Symbol.hasInstance不会被意外重写,该方法被定义为不可写、不可配置并且不可枚举。
- 想要改变instanceof的运行方式,不能在对象的原型对象上改写[Symbol.hasInstance]方法,只能在对象本身上添加[Symbol.hasInstance]方法
涉及类的instanceof重写中,要使用静态方法覆盖定义在原型对象上的[Symbol.hasInstance]方法,不使用静态方法则无法正确覆盖,因为该行为不被允许。
class C { static [Symbol.hasInstance]() {return false} }
var c = new C();
c instanceof C; // false
Symbol.species
species:种类、种族。
- 该符号用于定义返回函数的静态访问器属性。(这段描述过于官方,下面的例子比较清晰)
- 该属性的返回值为this,这也意味着该属性总会返回构造函数。
如果你有一个继承自Array的派生类MyArray,那么像slice()这样的方法也会返回一个MyArray的实例。
class MyArray extends Array { }
let items = new MyArray(1,2,3,4),
subItems = items.slice(1, 3);
items instance of MyArray; // true
subItems instance of MyArray; // true
内置该符号的类型:集合类型、Promise、正则。
实现原理(足够烧脑,总之理解是javascript黑魔法的具象化就行了):
class myClass {
static get [Symbo.species]() {
return this;
}
constructor(value) {
this.value = value;
}
clone() {
return new this.constructor[Symbol.species](this.value);
}
}
Symbol.isConcatSpreadable
- Array.proptotype.concat()的运行原理
- 是一个布尔值,为true,则表示对象有length属性和数字键,故它的数值型属性值应该被独立添加到concat()调用的结果中。 ```typescript let collection = { 0: “Hello”, 1: “world”, length: 2,
}; let message = [“Hi”].concat(collection); message.length; // 3 message; // [“Hi”, “Hello”, “world”]
<a name="OxZpj"></a>
## Symbol.match、Symbol.replace、Symbol.search、Symbol.split
- 这些内置符号被定义在RegExp.prototype中
- 这些内置符号定义了match()、replace()、search()和split()方法使用的**函数**。
- String实例和正则实例都使用这些Symbol内置符号。
- 具体来讲,这些内置符号定义了上述方法的第一个参数应该调用的正则表达式参数的方法。
<a name="zir4P"></a>
## Symbol.toPrimitive
- 隐式类型转换的原理
- 定义在每一个标准类型的原型上
- 规定了当对象被转换为原始值时应当执行的操作。
- 比如字符串与数字相加时,数字之所以会被转换为字符串,底层就是调用了定义在原型对象上的[Symbol.toPrimitive]方法
<a name="6RmyN"></a>
## Symbol.toStringTag
- JavaScript有时会同时存在多个全局执行环境
- 比如在Web浏览器中,如果一个页面包含iframe标签,就会分别为页面和iframe内嵌页面生成两个全局执行环境。
- 不同执行环境(不同领域)有自己的全局作用域,有自己的全局对象,在某个领域中创建一个数组,那它绝对就是一个数组。然而,如果将这个数组传递到另一个领域中,instanceof Array语句的检测结果会返回false
- 可以利用Object.prototype.toString.call()逃课
- 改符号定义了调用对象的Object.prototype.toString.call()方法时返回的值。
- 是Object.prototype.toString()返回值底层原理。
<a name="xHbr8"></a>
# 2 分清迭代器、生成器、可迭代对象
- 用循环语句迭代数据时,必须要初始化一个变量来记录每一次迭代在数据集合中的位置,虽然循环语句的语法简单,但是如果将多个循环嵌套则需要追踪多个变量,代码的复杂度会大大增加,比如:
```typescript
for(let i = 1; i < arr.length; i++) {
console.log(arr[i].name);
}
- 迭代器的使用可以极大地简化数据操作
-
什么是迭代器
迭代器是一种特殊对象。
- 所有的迭代器对象都有一个next()方法
- 每次调用都返回一个结果对象,结果对象有2个属性
- 一个是value,表示下一个将要返回的值;
- 另一个是done,它是一个布尔类型的值,当没有更多可返回数据时返回true。
- 迭代器还会保存一个内部指针,用来指向当前集合中值的位置
- 每调用一次next()方法,都会返回下一个可用的值。
迭代器对象通常都是在底层暗流涌动,不会暴露出来,在JavaScript引擎背后完成
- 展开运算符通过调用可迭代对象的Symbol.iterator方法来获取迭代器,然后将数组转换为其他形态的数据类型。
- for…of每次迭代都会调用迭代器对象
// 这段for-of循环的代码通过调用values数组的Symbol.iterator方法来获取迭代器
// 从其返回对象的value属性读取值并存储在变量num中,依次为1、2和3
// 当结果对象的done属性值为true时循环退出,所以num不会被赋值为undefined
const values = [1,2,3];
for(let num of values) { console.log(num) }
// 1 2 3
那么如何显式地创建迭代器?
可迭代对象具有Symbol.iterator属性,是一种与迭代器密切相关的对象。
- Symbol.iterator可以返回一个作用于附属对象的迭代器。
ES6中的常见可迭代对象
私有属性是实例中的属性,不会出现在原型上,且只能在类的构造函数或方法中创建
- 建议在构造函数中创建,从而只通过一处就可以控制类中的所有私有属性
- 由于这种类使用简洁语法来定义方法,因而不需要添加function关键字。