Promise里的代码为什么比setTimeout先执行?
宏观任务和微观任务
由于我们这里主要讲 JavaScript 语言,那么采纳 JSC 引擎的术语,我们把宿主(浏览器)发起的任务称为宏观任务,把 JavaScript 引擎发起的任务称为微观任务。
- 宏观任务的队列就相当于事件循环。
- 在宏观任务中,JavaScript 的 Promise 还会产生异步代码,JavaScript 必须保证这些异步代码在一个宏观任务中完成,因此,每个宏观任务中又包含了一个微观任务队列
- Promise 永远在队列尾部添加微观任务。setTimeout 等宿主 API,则会添加宏观任务。
Promise
Promise 是 JavaScript 语言提供的一种标准化的异步管理方式,它的总体思想是,需要进行 io、等待或者其它异步操作的函数,不返回真实结果,而返回一个“承诺”,函数的调用方可以在合适的时机,选择等待这个承诺兑现(通过 Promise 的 then 方法的回调)
setTimeout(()=>console.log("d"), 0)
var r = new Promise(function(resolve, reject){
resolve()
});
r.then(() => {
var begin = Date.now();
while(Date.now() - begin < 1000);
console.log("c1")
new Promise(function(resolve, reject){
resolve()
}).then(() => console.log("c2"))
});
// c1 c2 d
- 整个代码的执行过程是通过setTimeout()将所有代码分成了两个宏任务,第一个是setTimeout()之外的所有内容,第二个是setTimeout()之内的部分。
- 虽然耗时一秒的 c1 执行完毕,再将c2加入队列,但是他们都加入的是第一个宏任务的微任务队列,所以仍然先于 d 执行了,只有当第一个宏任务中的所有微任务都执行完成,才会执行第二个宏任务。
通过一系列的实验,我们可以总结一下如何分析异步执行的顺序:
- 首先我们分析有多少个宏任务;
- 在每个宏任务中,分析有多少个微任务;
- 根据调用次序,确定宏任务中的微任务执行次序;
- 根据宏任务的触发规则和调用次序,确定宏任务的执行次序;
- 确定整个顺序。
async/await
async 函数必定返回 Promise,我们把所有返回 Promise 的函数都可以认为是异步函数。
它的运行时基础是 Promise
async 函数是一种特殊语法,特征是在 function 关键字之前加上 async 关键字,这样,就定义了一个 async 函数,我们可以在其中使用 await 来等待一个 Promise。
小练习:
我们现在要实现一个红绿灯,把一个圆形 div 按照绿色 3 秒,黄色 1 秒,红色 2 秒循环改变背景色
function sleep(duration){
return new Promise(function(resolve){
setTimeout(resolve, duration);
})
}
async function changeColor(duration,color){
document.getElementById("traffic-light").style.background = color;
await sleep(duration);
}
async function main(){
while(true){
await changeColor(3000,"green");
await changeColor(1000, "yellow");
await changeColor(2000, "red");
}
}
main()
闭包和执行上下文到底是怎么回事?
- 闭包其实只是一个绑定了执行环境的函数
var 声明与赋值
- var 声明作用域函数执行的作用域。也就是说,var 会穿透 for 、if 等语句。
var b;
void function(){
var env = {b:1};
b = 2;
console.log("In function b:", b);
with(env) {
var b = 3;
console.log("In with b:", b);
}
}();
console.log("Global b:", b);
// In function b: 2
// In with b: 3
// Global b: undefined
with 内的 var b 作用到了 function 这个环境当中
let
以下语句会产生 let 使用的作用域:
- for;
- if;
- switch;
- try/catch/finally。
Realm
在实际的前端开发中,通过 iframe 等方式创建多 window 环境并非罕见的操作,所以,这才促成了新概念 Realm 的引入。
Realm 中包含一组完整的内置对象,而且是复制关系。
var iframe = document.createElement('iframe')
document.documentElement.appendChild(iframe)
iframe.src="javascript:var b = {};"
var b1 = iframe.contentWindow.b;
var b2 = {};
console.log(typeof b1, typeof b2); //object object
console.log(b1 instanceof Object, b2 instanceof Object); //false true
this
- 普通函数的 this 值由“调用它所使用的引用”决定
- 调用函数时使用的引用,决定了函数执行时刻的 this 值。
- 箭头函数中,不论用什么引用来调用它,都不影响它的 this 值
- 类中方法的this值情况也不相同
class C {
showThis() {
console.log(this);
}
}
var o = new C();
var showThis = o.showThis;
showThis(); // undefined
o.showThis(); // o
生成器函数、异步生成器函数和异步普通函数跟普通函数行为是一致的。
异步箭头函数与箭头函数行为是一致的。
try里面放return,finally还会执行吗?
function foo(){
try{
return 0;
} catch(err) {
} finally {
console.log("a")
}
}
console.log(foo());
// a
// 0
function foo(){
try{
return 0;
} catch(err) {
} finally {
return 1;
}
}
console.log(foo());
//1
通过上面的例子可以看出,是先执行finally里的代码,然后再执行try中的代码
Completion 类型
Completion Record 用于描述异常、跳出等语句执行过程
Completion Record 表示一个语句执行完之后的结果,它有三个字段:
- [[type]] 表示完成的类型,有 break continue return throw 和 normal 几种类型;
- [[value]] 表示语句的返回值,如果语句没有,则是 empty;
- [[target]] 表示语句的目标,通常是一个 JavaScript 标签。break/continue 语句如果后跟了关键字,会产生带 target 的完成记录。
每以个语句执行之后都会得到一个Completion Record
- 普通语句执行后,会得到 [[type]] 为 normal 的 Completion Record,JavaScript 引擎遇到这样的 Completion Record,会继续执行下一条语句
- 只有表达式语句会产生 [[value]]
- 如果你经常使用 Chrome 自带的调试工具,可以知道,输入一个表达式,在控制台可以得到结果,但是在前面加上 var,就变成了 undefined。
- Chrome 控制台显示的正是语句的 Completion Record 的[[value]]。
语句块
语句块就是拿大括号括起来的一组语句,它是一种语句的复合结构,可以嵌套。
语句块本身也有Completion Record
- 如果语句块内部全都是普通语句,那这个语句块本身也是普通类型的
- 语句块内部的语句的 Completion Record 的[[type]] 如果不为 normal,会打断语句块后续的语句执行。并使得语句块的属性也变为非normal。
{
var i = 1; // normal, empty, empty
i ++; // normal, 1, empty
console.log(i) //normal, undefined, empty
} // normal, undefined, empty
{
var i = 1; // normal, empty, empty
return i; // return, 1, empty
i ++;
console.log(i)
} // return, 1, empty
如果每一个语句都是 normal 类型,那么它会顺次执行。
如果在 block 中插入了一条 return 语句,产生了一个非 normal 记录,那么整个 block 会成为非 normal。这个结构就保证了非 normal 的完成类型可以穿透复杂的语句嵌套结构,产生控制效果。
控制型语句
控制型语句带有 if、switch 关键字,它们会对不同类型的 Completion Record 产生反应。
- finally 中的内容必须保证执行,所以 try/catch 执行完毕,即使得到的结果是非 normal 型的完成记录,也必须要执行 finally。
- 而当 finally 执行也得到了非 normal 记录,则会使 finally 中的记录作为整个 try 结构的结果。
穿透和消费
穿透指把当前的特殊语句穿透至上层环境中进行执行
消费是指当前的特殊语句与当前的关键字配合进行执行
function test(){
if(true){
console.log("111");
break;
}
if(true){
console.log("222");
}
}
test(); // SyntaxError: Illegal break statement
/*
1. if 和 break 相遇,break 穿透至 function
2. function 和 break 相遇,报错
*/
function test() {
var a = 0;
switch (a) {
case 0:
if (true) {
console.log("111");
break;
}
}
if (true) {
console.log("222");
}
}
test();
// 111
// 222
/*
1. if 和 break 相遇,break 穿透至 switch
2. swicth 和 break 相遇,消费掉 break
3. 接着执行之后的代码
*/