复习ES6笔记。
1、变量
1)let
- 不能重复声明;
- 支持块级作用域;
- 没有变量提升。
1 |
|
实现原理:
1 |
|
或
1 |
|
2)const
- 可以定义常量;
- 重复赋值会报错,不同作用域可以重新声明或赋值;
- 引用类型除外如:const obj = {name: ‘xx’}; obj.name = ‘yy’。
如何实现一个const
- ES5没有块级的概念,我们只能大概模拟一下const的定义。
- 我们将const 声明的变量绑定在全局对象上,借助 Object.defineProperty()劫持该对象,配置相关描述符实现const的特性。
- 关键字和数字不能作为对象属性
2、解构
1 |
|
编译成
1 |
|
默认解构,如果能取出来值就用,取不出来就用默认值。 解构的原理:(https://juejin.cn/post/6844903764772519943)
3、模板字符串
实现方法:
1 |
|
其特点:
1)可以折行
2)前面可以加函数,如:
1 |
|
带标签的模版字符串,就像函数调用,参数就是文本拆出来的数组。
字符串其他新方法: startsWith endsWith Includes->indexOf === -1 repeat
indexOf的实现代码:
includes的实现代码:
4、扩展运算符:…rest
- 只能作为最后一个参数;
- 负责收集所有剩余数组。
- 同arguments相似,但是区别在: 1)arguments对象是类数组,而rest是Array 的实例,可以直接调用其sort、map、forEach等方法; 2)rest只包含没有对应形参的实参,而arguments对象包含了传给函数的所有实参; 3)arguments对象还有一些附加属性,比如callee。
扩展运算符内部调用的是iterator接口,因此只要有iterator接口的对象,都可以使用扩展运算符。 任何iterator接口的对象,都可以用扩展运算符转为真正的数组。
1
2
3var nodeList = document.querySelectorAll(‘div’); var array = […nodealist]; // 原因在于nodelist对象实现了iterator接口,即遵循了iterator协议。
5、函数
1) 默认参数
指定了默认参数后,函数的length属性将失真,是以你为length属性的含义:该函数预期传入的参数个数。 某个参数指定默认值后,预期传入的参数个数就不包括这个参数了,同理,rest参数也不会计入length属性。
1 |
|
2) 展开操作符:
使用concat实现.
1 |
|
3)箭头函数
- 函数体内的this对象,是定义时所在的对象,而不是使用时所在的对象。或者理解成箭头函数没有this。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function foo() { setTimeout(() => { console.log('id:', this.id); }, 100); } var id = 21; foo.call({ id: 42 }); // id: 42 function foo() { setTimeout(function() { console.log('id:', this.id); }, 100); } var id = 21; foo.call({ id: 42 }); // id: 21
由于箭头函数没有自己的this,所以当然也就不能用call()、apply()、bind()这些方法去改变this的指向。
- 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
- 没有arguments。
- 没有yield命令,因此箭头函数不能作为generator函数。
4)尾调用优化
1 |
|
主要作用是:防止爆栈(死循环那个报错)。
1 |
|
上面的函数不会进行尾调用优化,因为内层函数inner用到了外层函数addOne的内部变量one。
5)reduce实现
1 |
|
6、遍历器iterator
1 |
|
Iterator接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即for…of循环。一种数据结构只要部署了Iterator接口,我们就称这种数据结构是”可遍历的“(iterable)。
1) 在ES6中,有三类数据结构原生具备Iterator接口:数组、某些类似数组的对象(nodeList、arguments、字符串)、Set和Map结构。
对象(Object)之所以没有默认部署Iterator接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。
类似数组的对象(存在数值键名和length属性),部署Iterator接口,有一个简便方法,就是Symbol.iterator方法直接引用数组的Iterator接口。
1 |
|
2) 调用iterator接口的场合:
- 解构赋值
- 扩展运算符
- yield*
- for of
- Array.form
- Map、Set、WeakMap、WeakSet
- Promise.all
- Promise.race
3) iterator的最简单实现:
1 |
|
4) 并不是所有类似数组的对象都具有iterator接口,一个简便的解决方法,就是使用Array.from方法将其转为数组。
1 |
|
7、generator
跟普通函数的区别: 有*、有yield关键字,执行函数后并不会返回值,而是要执行next,才会返回第一个yield语句后面跟随的表达式的值。
yield语句与return语句:
- 相似之处在于,都能返回紧跟在语句后面的那个表达式的值。
- 区别在于每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个)return语句,但是可以执行多次(或者说多个)yield语句。正常函数只能返回一个值,因为只能执行一次return;Generator函数可以返回一系列的值,因为可以有任意多个yield。
yield语句不能用在普通函数中,会报错。
由于Generator函数就是遍历器生成函数,因此可以把Generator赋值给对象的Symbol.iterator属性,从而使得该对象具有Iterator接口。
1 |
|
再来看一个例子。
1 |
|
从上述代码可以看出:每执行一次next,代码就会按顺序找相应的yeild,执行两个yeild之间的代码,并返回当前yeild语句后面的表达式的值。也就是说,next和yield是一一对应的。
yield句本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield语句的返回值。
1 |
|
这个功能有很重要的语法意义。Generator函数从暂停状态到恢复运行,它的上下文状态(context)是不变的。通过next方法的参数,就有办法在Generator函数开始运行之后,继续向函数体内部注入值。也就是说,可以在Generator函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。
for…of循环可以自动遍历Generator函数时生成的Iterator对象,且此时不再需要调用next方法。
下面是一个利用Generator函数和for…of循环,实现斐波那契数列的例子:
1 |
|
利用for…of循环,可以写出遍历任意对象(object)的方法。原生的JavaScript对象没有遍历接口,无法使用for…of循环,通过Generator函数为它加上这个接口,就可以用了。
1 |
|
上述代码通过generator和for of结合方式,为对象数组增加了遍历器接口。
generator嵌套,利用yield*实现。
1 |
|
从语法角度看,如果yield命令后面跟的是一个遍历器对象,需要在yield命令后面加上星号,表明它返回的是一个遍历器对象。这被称为yield*语句。
实际上,任何数据结构只要有Iterator接口,就可以被yield*遍历。
1 |
|
yield*命令可以很方便地取出嵌套数组的所有成员。
1 |
|
generator函数的this。
1 |
|
上面代码中,Generator函数g在this对象上面添加了一个属性a,但是obj对象拿不到这个属性。
1 |
|
上面代码表明,Generator函数g返回的遍历器obj,是g的实例,而且继承了g.prototype。但是,如果把g当作普通的构造函数,并不会生效,因为g返回的总是遍历器对象,而不是this对象。
generator函数不能跟new一起用,会报错。
generator状态机
1 |
|
Generator函数的暂停执行的效果,意味着可以把异步操作写在yield语句里面,等到调用next方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield语句下面,反正要等到调用next方法时再执行。所以,Generator函数的一个重要实际意义就是用来处理异步操作,改写回调函数。
Ajax是典型的异步操作,通过Generator函数部署Ajax操作,可以用同步的方式表达。
1 |
|
8、Promise
ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。 用来解决多个回调嵌套和多个错误处理。
- 1、实现了回调函数的延迟绑定。
- 2、将回调函数onResolve的返回值穿透到最外层。
1 |
|
再看一个经典的一步接口请求案例。
1 |
|
上述代码中,Promise在新建的时候,会立即执行,比如console.log数据,但是resolve或者reject是在handler被调用时才去执行的,并且在resolve或reject执行完后,后面的代码也会执行。在resolve或reject执行后实例有了确定的状态后再去执行then方法。
1 |
|
这里第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。
这里前一个回调函数,有可能返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。
1 |
|
在这还需要注意的是,p1的状态决定p2的状态。
catch方法 Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
reject()方法的作用,等同于抛出错误。 如果 Promise 状态已经变成resolved,再抛出错误是无效的。因为Promise的状态一经更改,就不会再改变了。
Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。(代码如下)
1 |
|
简单实现一个Promise构造函数。
主要根据一下几点来作为实现原理:
- 有一个初始状态pending;
- 构造函数接收一个function作为参数,该function包含resolve喝reject两个函数作为参数;
- resolve执行之后,状态改成resolved;
- reject执行之后,状态改成rejected。
- 实例继承了then和catch方法;
- 每个then执行之后,结果作为下一个then方法的回调参数,并且会隐式返回一个新的promise实例;
- catch捕获错误,并且只执行一次。
1 |
|
增加捕获错误和传递Promise实例为参数的后代码如下:
1 |
|
但以上代码也只是实现了promise的同步逻辑,没有实现异步。
异步的实现可参考:Promise实现原理(附源码)
Promise.all实现:
1 |
|
9、异步和async
在ES6诞生以前,我们用得最多的异步就是以下几种:
- 回调函数
- 事件监听
- 发布/订阅
- Promise对象
回调函数存在的弊端是:多个回调函数嵌套,使得代码横向发展,形成回调地狱,无法管理和维护。Promise就是为了解决这个问题而被踢出来的。它是一种新的写法,允许将多个回调函数嵌套改写成链式调用。
1 |
|
Promise的then方法提供回调,catch捕捉执行过程中的错误。但是Promise也只是一种改进写法,它最大的问题事代码冗余,不管什么操作,一眼看过去都是then,语义并不清晰。于是便有了generator。
generator函数最大的特点就是可以叫出函数的执行权(即由yield实现的暂停执行)。
1)async
async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。
async函数的返回值是 Promise 对象。你可以用then方法指定下一步的操作。
进一步说,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。
看一下两段代码:
1 |
|
等同于
1 |
|
也就是说,await后的代码都放在一整个promise。then里,并且出现多个await,就代表多个promise嵌套。
1 |
|
以上代码的输出是:
1 |
|
10、bind实现
1)先来看call的实现
1 |
|
具体实现:
1 |
|
完整版:
1 |
|
2)bind的例子
1 |
|
具体实现:
1 |
|