JavaScript执行

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. 接着执行之后的代码
*/
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值