执行环境
执行环境(execution context)定义了变量或者函数有权访问的其他数据,每个执行环境都有一个与之关联的变量对象(variable object),执行环境中定义的变量和函数就保存在这个变量对象中;
全局执行环境是最外围的一个执行环境,通常被认为是window对象
执行环境和变量对象在运行函数时生成
执行环境中的所有代码执行完以后,执行环境被销毁,保存在其中的变量和函数也随之销毁;(全局执行环境到应用退出时销毁)
声明提前
var scope ="global";
function f() {
console.log(scope); //输出"undefined"
var scope = "local"; //变量在此赋初值,但变量本身在函数体内任何地方都是有定义的
console.log(scope); //输出"local"
}
即将函数内的变量声明“提前”至函数体顶部,同时变量初始化留在原位置。
作用域链
当代码在一个执行环境中执行时,会创建变量对象的一个作用域链(scope chain),作用域链用来指定执行环境有权访问的所有变量和函数的访问顺序;
作用域链的最前端,始终是当前代码执行环境的变量对象,如果这个环境是函数,则其活动对象就是变量对象
作用域链的下一个变量对象,来自外部包含环境,再下一个变量对象,来自下一个外部包含环境,以此类推直到全局执行环境
在函数执行过程,根据当前执行环境的作用域链来逐层向外查找变量,并且进行标识符解析
闭包
闭包是指有权访问另一个函数作用域变量的函数,创建闭包的通常方式,是在一个函数内部创建另一个函数,闭包的本质还是函数
<script>
function A(){
var funs=[];
for(var i=0;i<10;i++){
funs[i]=function(){
return i;
}
}
return funs;
}
var funs = A();//定义funs[0]-funs[9],10个函数
funs[0]();//10
funs[1]();//10
funs[6]();//10
</script>
以上代码创建了10个闭包,并存储于同一个数组中。这些闭包都是在同一个函数调用中定义的,因此可以共享变量i。
<script>
function A(){
var funs=[];
for(var i=0;i<10;i++){
funs[i] = (function anonymous1(num){
return function anonymous2(){
return num;
}
}(i));
}
return funs;
}
var funs = A();//定义funs[0]-funs[9],10个函数
funs[0]();//0
funs[1]();//1
funs[6]();//6
</script>
function anonymous1(num){}(i),这是一个立即执行函数。即( function(){…} )()和( function (){…} () ),
立即执行函数把i的值立即传递给num这个局部变量,然后再返回anonymous2,存贮的num值就是每次传入的i值,也就是0-9
Promise
特点:
(1)对象的状态不受外界影响。有三种状态:
pending(初始状态)、fulfilled(操作成功)、
rejected(操作失败)
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。状态改变只有两种可能:从 pending 变为 fulfilled 和从 pending 变为 rejected。
优点:可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise
对象提供统一的接口,使得控制异步操作更加容易。
缺点:首先,无法取消Promise
,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise
内部抛出的错误,不会反应到外部。第三,当处于pending
状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
基本用法
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hello resolve');
}, 500);
})
.then(val => {
console.log(val);// hello resolve
}, err => {
console.error(err);// 该条语句不被执行
});
- resolve 函数的作用:将 Promise 对象的状态从“未完成(pending)”变为“成功(resolved)”,在异步操作成功时调用,并将异步操作的结果作为参数传递出去。
- reject 函数的作用:将 Promise 对象的状态从“未完成(pending)”变为“失败(rejected)”在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
- then 方法作用:接受两个回调函数作为参数。第一个回调函数是 Promise 对象的状态变为 resolved 时调用,第二个回调函数是 Promise 对象的状态变为 rejected 时调用。第二个函数可选,不一定要提供,也可以将第二个函数作为 catch 方法的参数。
- catch 方法作用:用于指定发生错误时的回调函数。Promise 对象异步操作抛出错误,状态就会变为 rejected,就会调用 catch 方法指定的回调函数处理这个错误。另外,then 方法指定的回调函数,如果运行中抛出错误,也会被 catch 方法捕获
Promise执行顺序
const promise = new Promise((resolve, reject) => {
console.log("我是第一个执行的"); //Promise 新建后立即执行
resolve();
});
promise.then(res => {
console.log("我是第三个执行的"); //then 方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,同catch
});
console.log("我是第二个执行的")
Promise链式调用
then 方法可以返回一个新的 Promise 实例(注意,不是原来那个 Promise 实例)。因此可以采用链式写法,即 then 方法后面再调用另一个 then 方法。只要一个 Promise 中抛出错误,将执行 catch 方法,then 链终止
Promise.prototype.finally()
finally 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。本质上是 then 方法的特例,不接受任何参数,不依赖于 Promise 的执行结果
Promise.all()
用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const promise = Promise.all([promise1, promise2, promise3])
Promise.all 方法接受一个数组作为参数,promise、promise2、promise3 都是 Promise 实例,如果不是,就会先调用下面讲到的 Promise.resolve 方法,将参数转为 Promise 实例,再进一步处理。
promise 的状态由 promise1、promise2、promise3 决定,分成两种情况:
- 三者状态都变成 fulfilled,p 的状态才会变成 fulfilled,此时 promise1、promise2、promise3 的返回值组成一个数组,传递给 p 的回调函数。
- 三者中有一个被 rejected,promise 的状态就变成 rejected,此时第一个被 reject 的实例的返回值,会传递给 promise 的回调函数。
注意,如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()的catch方法。
const p1 = new Promise((resolve, reject) => {
resolve("hello");
})
.then(result => result)
.catch(e => e);
const p2 = new Promise((resolve, reject) => {
throw new Error("报错了");
})
.then(result => result)
.catch(e => e);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了]
上面代码中,p1 会 resolved,p2 首先会 rejected,但是 p2 有自己的 catch 方法,该方法返回的是一个新的 Promise 实例,p2 指向的实际上是这个实例。该实例执行完 catch 方法后,也会变成 resolved,导致 Promise.all()方法参数里面的两个实例都会 resolved,因此会调用 then 方法指定的回调函数,而不会调用 catch 方法指定的回调函数。
如果 p2 没有自己的 catch 方法,就会调用 Promise.all()的 catch 方法。
Promise.race()
同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3])
只要 p1、p2、p3 之中有一个实例率先改变状态,p 的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给 p 的回调函数。
Promise.resolve()
将现有对象转化为 Promise 对象。
const promise = Promise.resolve('Hello world')
- 参数是 Promise 实例,该方法不做任何改变。
- 参数是一个 thenable 对象,先将对象转为 Promise 对象,然后立即执行 thenable 方法。相当于将 thenable 对象中的 then 方法处理的值作为参数传给 promise then 方法。
- 参数不是具有 then 方法的对象,或根本就不是对象,则 Promise.resolve 方法返回一个新的 Promise 对象,状态为 resolved。
Promise.reject()
Promise.reject()
Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为 rejected。
JS异步
JS只能是单线程,但任务可以被设计为同步任务和异步任务。
EVENT LOOP(事件循环)
更详细的流程图:
- 同步任务直接放入到主线程执行,异步任务(点击事件,定时器,ajax等)挂在后台执行,等待I/O事件完成或行为事件被触发。
- 系统后台执行异步任务,如果某个异步任务事件(或者行为事件被触发),则将该任务添加到任务队列的末端,每个任务会对应一个回调函数进行处理。
- 执行任务队列中的任务具体是在执行栈中完成的,全部执行完毕后,去读取任务队列中的下一个任务,继续执行,是一个循环的过程,处理一个队列中的任务称之为tick。
定时器
- setInterval() :按照指定的周期(以毫秒计)来调用函数或计算表达式。方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭。
setInterval(code,millisec,lang) //lang为可选参数
- setTimeout() :在指定的毫秒数后调用函数或计算表达式
setTimeout(code,millisec,lang) //lang为可选参数
四种异步实现方案
1.回调函数
function f1(callback){
setTimeout(function(){
//f1的代码
callback();
},1000)
}
f1(f2);
思想:把同步操作变成异步,相当于先执行程序的主要逻辑并将耗时的延迟执行
优点:简单明了;
缺点:①、耦合严重;②、容易陷入回调地狱
2.事件监听
常见的监听函数有:on,bind,listen、addEventListener,observe
f1.on("done".f2); //f1执行完成后立即触发done事件从而执行f2
function f1(){
setTimeout(function(){
//f1的代码量
f1.trigger("done"); // 执行完函数体部分 触发done事件
},1000)
}
优点:避免了直接使用回调的高耦合问题,可以绑定多个回调函数
缺点:变成了事件驱动模型,不易看出执行的主流程
3.发布/订阅模式
jQuery.subscribe("done",f2); //f2向"信号中心"jQuery订阅"done"信号。
function f1(){
setTimeout(function(){
//f1的代码
jQuery.publish("done"); //f1执行完成后,向"信号中心"jQuery发布"done"信号,从而引发f2的执行。
},1000)
}
jQuery.unsubscribe("done", f2); //f2完成执行后,可以取消订阅(unsubscribe)
- 事件监听模式中,被监听函数f1与监听函数f2直接交流
- 发布/订阅模式中,发布者f1和消息中心交流,订阅者f2也和消息中心交流
4.Promises对象
每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数
f1().then(f2).then(f3);
5. Async/Await
Async - 定义异步函数(async function someName(){...}
)
- 自动把函数转换为 Promise
- 当调用异步函数时,函数返回值会被 resolve 处理
- 异步函数内部可以使用
await
Await - 暂停异步函数的执行 (var result = await someAsyncCall();
)
- 当使用在 Promise 前面时,
await
等待 Promise 完成,并返回 Promise 的结果 await
只能和 Promise 一起使用,不能和 callback 一起使用-
await
只能用在async
函数中
与Promise关系
- Async/Await 底层依然使用了 Promise。
- 多个异步函数同时执行时,需要借助 Promise.all
每次遇到 await 关键字时,Promise 都会停下在,一直到运行结束,await 把异步变成了同步。
async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time1, time2);
const result = await step3(time1, time2, time3);
console.log(`result is ${result}`);
console.timeEnd("doIt");
}
doIt();
// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 800 = 300 + 500
// step3 with 1800 = 300 + 500 + 1000
// result is 2000
// doIt: 2907.387ms
错误处理
使用 try/catch 进行错误处理。在 Promise 中的 .catch() 分支会进入 catch 语句。