首发地址:mp.weixin.qq.com/s/A-qjZZSW7…
“ 用最精简的方式,抓住最核心的知识点,帮助你快读ES6。”
这几个知识点在【框架设计】以及【任务流控制】中起着重要作用,属于必备知识。本文部分内容需要你具备相关基本知识,帮你划出重点****,结合笔者的一些思考,有查缺补漏之功效。
关键词: Proxy、Promise、Iterator、Generator、Async
Proxy
-
原理:重载了
点运算符 -
存在This指向问题
Proxy 针对目标对象的访问,不是透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。主要原因就是在 Proxy 代理的情况下,目标对象内部的this关键字会指向 Proxy 代理。 -
Proxy实例的方法
1)get(target, property, proxy)
-
作为原型对象
Object.create(new Proxy({}, handler)), handler中的get可以被继承 -
实现属性链式操作(
内部DSL:**级联属性**):pipe(3).double.pow.get; -
dom.div | dom.h1 |...:用作生成各种DOM节点的通用函数,避免穷举(内部DSL:**动态代理**)备注:内部DSL相关介绍,可参考:内部DSL,你不可不知
-
2)set(target, property, value, proxy)
-
vue实现的数据双向绑定:数据初始化时拦截
getter做依赖收集,当编译模板时创建watcher对象,触发数据的读操作,将watcher对象存入deps,当数据变更,触发setter执行deps中的回调进行更新操作; -
内部会调用Proxy的
defineProperty()
Promise
异步编程(回调函数 -> 事件监听 -> 发布订阅 -> promise,Generator,async 对 **流程、时间 **控制的探索)
特点
-
链式调用的方式:适合
流程、时间敏感的场景; -
状态不受外界影响;
-
回调函数注册时机不必须在异步执行完前。
不足
-
进度未知
-
无法中途取消
原型方法
-
Promise.prototype.then()
-
Promise.prototype.**catch**()是.then(undefined, rejection)的语法糖 -
Promise.prototype.**finally**()是.then(() => {}, () => {})的语法糖
Promise私有方法
-
resolve():等价于
new Promise(resolve => resolve(参数)),其中参数的形式如下:-
promise:原样返回此实例
-
thenable
-
原始值
-
无参数
-
-
reject()
- 其参数会
原封不动的作为reject的理由,和resolve不同;
- 其参数会
-
all()
-
用于将多个 Promise 实例,包装成一个新的 Promise 实例;
-
如果数组中的值不是Promise实例,会
先调用Promise.resolve方法,将参数转为 Promise 实例 -
参数可以不是数组,但
必须具有Iterator接口 -
所有参数中的Promise实例为fulfilled,才会为fulfilled
-
-
race()
- 基本同all;差异在于参数中的Promise实例最先改变的状态决定了包装实例的状态
-
allSettled() :该方法由 ES2020 引入
- 等到所有状态变更后,无论是fulfilled还是rejected,才会执行外层,且返回的Promise实例状态始终是fulfilled。
-
any()
- 只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态
【注意】
-
如果没有对应状态的回调函数,会冒泡传递,直到被相应的回调函数处理
-
状态为reject且没有相应处理函数,会最终被catch捕获
-
返回的状态值,仅能被消费一次;之后的传递值由对应回调函数的return值决定
错误处理
1. Promise执行过程中抛出异常的处理
throw new Error('test');
//等价于
try {
throw new Error('test');
} catch(e) {
reject(e);
}
//或者
reject(new Error('test'));
2. 内部的promise的异常捕获
new Promise((r,j)=>{r()}).then(() => {
try{
new Promise(() => { throw 'error' })
return 'try';
}catch(e) {
console.log('inner', e);
return 'catch'
}
}).then(info => console.log(info)).catch(err=> {console.log('outer:', err)})
//执行结果:try
由上可知,promise会吃掉错误(即**不会传递到外层代码,不能被外层代码捕获,不会影响外层代码的执行**)
**Q:**想要捕获内部错误怎么办?
-
对抛出异常的promise加上catch
-
return掉promise,就可以统一使用最外层的catch捕获处理
promise的实现原理:
1. .then返回新的上下文
2. status完成,记录value,等待回调到位执行;回调就位,等待status完成执行
.then的返回值为什么不使用同一个上下文?
1.状态不固定status在变动:pedding->fulfilled | rejected->pedding...
2..then返回promise时resolve(消费)多次问题
3.中间状态不能被记录
Iterator
Iterator:遍历器,本质为指针对象
-
Iterator
-
集合:数组、对象、Map、Set。可以组合使用的前提是需要统一的接口机制,来处理所有不同数据结构的访问。
-
遍历器可以不依附于别的数据结构;
-
遍历器对象的特征是具有next方法,返回
{value:当前成员的值, done:是否结束标志}对象:
{
next:()=> {
//...
return {
value: ,//任意类型值
done: ,//Boolean
}
}
}
-
Iterator接口:
遍历器生成函数- 要部署到数据结构的
Symbol.iterator属性。
- 要部署到数据结构的
{
[Symbol.iterator]() {
return { next: () => {}}
}
}
-
因此具有Symbol.iterator属性的数据结构,可以认为是可遍历的。
-
提供了统一的访问机制:
for...of,会调用Symbol.iterator方法,返回遍历器对象。 -
原生具备Iterator接口的数据结构:
Array、Map、Set、String、Typedarray、Arguments对象、Nodelist对象
注意
-
**对象(Object)**之所以没有默认部署Iterator接口,是因为
属性遍历次序不确定;对象部署遍历器接口并不是很必要,因为其等效于Map结构; -
类数组的对象(
存在数值键名和length属性),获取Iterator接口的方式:-
可以引用数组的Iterator接口
-
Array.from()
-
Array.prototype.slice.call(arguments, 0)
-
...
-
应用场景(默认调用symbol.iterator)
-
解构赋值
-
扩展运算符(
只要某个数据结构部署了iterator接口,就可以将其转为数组) -
yield*后是一个可遍历的结构 -
for...of等 -
相关Tips
-
Symbol.iterator方法的
最简单实践是使用Generator函数。
let myIterable = {
//简洁写法:* [Symbol.iterator]() {
[Symbol.iterator]: function* () {
yield 1;
yield 2;
yield 3;
}
}
[...myIterable] // [1, 2, 3]
-
遍历器对象的return():遍历时的提前退出「异常」或「break语句」都会调用return()
-
forEach(无法中断);**for...in **循环读取键名(包括原型链上的key);**for...of **循环读取键值(只返回具有数字索引的属性;可以中断)
Generator
状态机 | 遍历器生成函数:返回的遍历器对象依次遍历内部的每个状态
使用
// 形式1
function* () {
yield ;
}
// 形式2:作为对象属性的写法
let obj = {
* myGeneratorMethod() {
···
}
};
特性
-
惰性求值的语法功能
-
分段执行,yield表达式是暂停执行的标记,而next方法可以恢复执行,每次next返回对象中的value为yield后的表达式值; -
yield表达式的返回值:其本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表示的返回值,因此第一次传参无效。
-
yield 表达式如果用在另一个表达式中,
需放在括号中(表示其为表达式,和({})作用相似)
function* demo() {
console.log('Hello' + yield 123); // SyntaxError
console.log('Hello' + (yield)); // OK
console.log('Hello' + (yield 123)); // OK
}
注意return的使用
for...of循环中,一旦done为true,就会中止,且不会包含该返回对象
**yield* **
yield*表达式:表明后面的表达式是一个带有[Symbol.iterator]的数据结构。如:yield* [1,2,3,4]。
- **案例1:**等同于在 Generator 函数内部,部署一个for...of循环:
let A = (function* () {
yield 'Hello!';
yield 'Bye!';
}());
let B = (function* () {
yield 'Greetings!';
//yield* A; 等价于
for (var value of A) {
yield value;
}
yield 'Ok, bye.';
}());
- **案例2:**yield*的返回值
function* () {
Let res = yield* gen(); // res为generator中return的值
}
- **应用:**取出嵌套数组的所有成员
function* iterTree(tree) {
if (Array.isArray(tree)) {
for(let i=0; i < tree.length; i++) {
yield* iterTree(tree[i]);
}
} else {
yield tree;
}
}
const tree = [ 'a', ['b', 'c'], ['d', 'e'] ];
next 、 throw、return
都是让 Generator 函数恢复执行,并且使用不同的语句替换yield表达式。
- **next():**是将yield表达式替换成
一个值,供下一段执行代码使用:
// 相当于将
let result = yield x + y
// 替换成
let result = 1;
- **throw():**是将yield表达式替换成
一个throw语句
// 相当于将
let result = yield x + y
// 替换成
let result = throw(new Error('出错了'));
- **return():**是将yield表达式替换成
一个return语句。
// 相当于将
let result = yield x + y
// 替换成
let result = return 2; // {value:2,done:true}
**注意点 **
-
gen.return:只能执行一次
-
gen.throw(
async内部实现reject中用到)- 可以外部捕获try...catch捕获
- 也可以内部
try...catch就可以内部捕获(内部错误未捕获,就不会再执行下去,认为generator已结束);
var g = function* () {
try {
yield;
} catch (e) {
console.log('内部捕获', e);
}
};
【疑问】For...of、扩展运算符(...)、解构赋值、Array.from()可以直接适用于Generator的原因?如下例:
function* numbers () {
yield 1
yield 2
return 3
yield 4
}
for (let n of numbers()) {
console.log(n)
} // 1, 2
已知for...of需要处理的是一个带有[Symbol.iterator]的对象,而生成器执行(numbers())返回的是遍历器对象;那为什么可以如下使用:for...of **? **
原因如下:
numbers() === numbers()[Symbol.iterator]
应用
-
状态机
-
控制流管理:函数嵌套 -> promise -> generator+自动执行器 (
异步操作的同步化表达) -
Iterator接口
异步编程的完整解决方案
-
Generator 函数可以
暂停执行和恢复执行,这是它能封装异步任务的根本原因; -
函数体内外的数据交换(双向消息传递机制)
1) 通过next()的参数给下一个代码块传参
2) 通过yield返回上一个代码块执行的结果
- 错误处理机制。
自动执行
需要有一种机制,自动控制 Generator 函数的流程,接收和交还程序的执行权。
- 回调函数【缓存数据延迟执行,如compose】
将异步操作包装成 Thunk 函数,在回调函数里面交回执行权。
函数传参的底层设计方式有如下2种方式:
1) 传名调用(thunk是"传名调用"的一种实现策略,用来替换某个表达式)
f(x + 5)
// 传名调用时,等同于
(x + 5) * 2
2) 传值调用
f(x + 5)
// 传值调用时,等同于
f(6)
js是传值调用,而js中,Thunk 函数替换的不是表达式,而是多参数函数,将其替换成单参数的版本,且只接受回调函数作为参数。
-
Promise 对象
将异步操作包装成 Promise 对象,用then方法交回执行权。
Async
Generator的语法糖
内置执行器
语义清晰(async和await 对比 * yield)
更广的适用性(await后可以是「promise」、「thenable」和原始类型的值,
会被用promise.resove()包装成promise)返回值是promise对象,Generator返回的是Iterator对象
错误处理
任何一个await语句后面的 Promise 对象变为reject状态,等同于「async函数返回的Promise对象」被reject。那么整个async函数都会中断执行:
- await后面的可以使用try...catch捕获:
await后面的表达式抛出异常,内部promise实例reject对应的回调函数为使用「gen.throw(e)」抛出异常,因此当执行下一个代码块时,遵循Generator的错误处理,错误抛出,可以被外部捕获(tip:promise内部的错误不会被被外界感知,注意不要混淆)。因此可以使用try...catch捕获异常,不影响函数内之后代码的执行async function test() { try { await myPromise; } } ``` - 可以用返回的promise的catch捕获:
无论是否使用try...catch捕获异常,内部的错误都不会影响外层,是因为自执行器对next的执行做了try...catch处理,异常时等同于try{gen.throw(e)}catch(e){return reject(e)},那么等同于async函数返回的 Promise 对象被reject。let promise = async function test() { await myPromise; } promise.catch(err => console.log(err)); ``` - await后的promise内部消化异常:
let myPromise = Promise().catch(err => console.log(err)); async function test() { await myPromise; } ```
实现原理:
function fn(args) {
return spawn(function* () {
//代码如下
});
}
function spawn(genF) {
return new Promise(function(resolve, reject) {
const gen = genF();
function step(nextF) {
let next;
try {
next = nextF();
} catch(e) {
return reject(e);
}
if(next.done) {
return resolve(next.value);
}
Promise.resolve(next.value).then(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
step(function() { return gen.throw(e); });
});
}
step(function() { return gen.next(undefined); });
});
}
至此,ES6**【代理】和【流程控制】**相关的内容就介绍这么多~
更多技术分享及时获取,欢迎关注~


基础薄弱的的童鞋,可以自行品读:阮一峰的ECMAScript 6 入门.