重学前端学习笔记之JavaScript执行

 

(一)JavaScript中的事件循环

1、JavaScript引擎存在于浏览器或者node环境中,它们拿到一段JS代码,就会传递给JavaScript引擎,让他们执行。

 

2、因为宿主环境以及各种API的变化,所以JS代码不是一次执行就可以完成的,而是不断会有js代码需要执行,因此js引擎会长蛀于内存,等待js代码,然后执行它

 

3、宿主环境发起的任务称为宏任务,js引擎发起的称为微观任务

 

4、事件循环

事件循环就是js引擎等待宿主环境分配宏观任务的等待行为。

它的实现就是在底层的代码中,大概像下面这样的伪代码

while (true){

    r = wait();

    execute(r);

}

此外需要注意的是,事件循环是一个独立线程,它主要的任务就是反复的等待-执行,而这个执行的过程,就是一个宏任务。

 

所以,可以这么理解,宏任务的队列就相当于事件循环

 

此外,在宏观任务里面,js还会产生异步代码,js必须要保证这些异步代码在一个宏任务中完成,因此,每个宏任务又包括一个微任务队列。(setTimeOut是一个宏任务,promise是一个微任务)

 

重学前端学习笔记之JavaScript执行

 

 

6、promise

promise是js提供的一种标准的异步管理方式管理方式,在进行IO、等待或者其他异步操作的函数,不返回结果,而是返回一个promise,函数的调用可以在合适的时机中选择等待完成这个promise(then)。

function sleep(duration) {

    return new Promise((resolve, reject)=>{

        setTimeout(resolve,duration);

    })

}

sleep(1000).then(()=>{console.log("finished")});

 

 

分析异步执行的顺序

  • 首先分析有多少个宏任务(由setTimeOut分隔)

  • 在每个宏任务中,分析有多少个微任务

  • 根据调用顺序,确定宏任务中微任务的执行顺序

  • 根据宏任务的触发规则和调用顺序,确定宏任务的执行顺序

  • 确定整个顺序。

 

7、两个新特性async,await

这两个特性都是基于promise的,async返回的是一个promise

(1)如何定义一个async函数?

在函数function前面加一个async关键字

function sleep(duration) {

    return new Promise((resolve, reject)=>{

        setTimeout(resolve,duration);

    })

}

async function foo() {

    console.log("a");

    await sleep(2000);

    console.log("b");

}

 

用上面只是写一个红绿灯demo

function sleep(duration) {

    return new Promise((resolve, reject)=>{

        setTimeout(resolve,duration);

    })

}

async function changeColor(duration,color) {

    document.getElementById("traffic-lights").style.backgroundColor = color;

    await sleep(duration);

}

async function trafficLights() {

   while (true){

       await changeColor(3000,"green");

       await changeColor(2000,"red");

       await changeColor(1000,"yellow");

   }

}

trafficLights();

 

(二)闭包和执行上下文

1、闭包就是一个绑定了执行环境的函数,携带了执行环境。其中环境部分是函数词法环境部分的组成,标识符列表是函数中用到的未声明变量,表达式部分是函数体

 

2、执行上下文

js标准把一段代码(包括函数)执行所需的信息定义为执行上下文。

(1)在ES5中,执行上下文包以下部分

  • 词法环境,获取变量时使用

  • 变量环境,声明变量时使用

  • this值

 

(2)在ES2018中,执行上下文包括以下部分

  • 词法环境,this值也绑定在词法环境中,获取变量或者获取this值时使用

  • 变量环境,声明变量时使用

  • code evaluation state,代码执行状态,用于恢复代码执行位置

  • Function执行的任务是函数时使用,表示正在执行的函数

  • ScriptOrModule 执行任务是脚本或者模块时使用,表示正在执行的代码

  • Realm 使用的基础库和内置对象实例

  • Generator:生成器上下文拥有,表示当前的生成器

 

(3)当执行一段代码的时候,发生了什么事情?

var b = {};

let c = 1;

this.a = 2;

var声明变量到哪里?

声明的变量b表示哪个变量,它的原型是谁?
let将c声明到哪里?this指向哪个对象

 

这些问题都是属于执行上下文范畴,每次执行代码都会关联到不同的执行上下文

 

首先回答第一个问题,var声明作用域函数执行的作用域。也就是当前函数所在的环境。

第二个问题,声明的变量b表示的是该作用域的b,它的原型是对应的值的引用类型,比如上面的b是一个对象,那么它的原型就是function对象,如果上面b是一个数字,那么它的原型就是Number,以此类推。

 

再说一下立即执行函数,通过创建一个函数,并且立即执行,来构造一个新的作用域,从而控制var的范围。常见的立即执行函数的写法:

(function () {

  console.log("a");

}());

 

(function () {

    console.log("b")

})();

 

void function () {

    console.log("c")

}();

 

再来回答第三个问题,let声明的作用域是该块级作用域范围,this的值为当前执行上下文的词法环境中的thisModule。

 

Relam

Relam中包含一组完整的内置对象,而且是复制对象。

 

(三)函数,在函数的执行机制上面理解

1、函数调用的时候,执行上下文会进行切换

 

2、函数的分类

(1)普通函数,用function关键字定义的函数

(2)箭头函数,用=>运算符定义的函数

(3)class中定义的方法

class PEOPLE{

    say(){

        

    }

}

(4)生成器函数,用function*定义的函数

 

 

function * foo() {

    yield console.log("liang");

}

let liang = foo();

liang.next();

(5)类,用class定义的类实际上也是函数

class Foo{

    constructor(){

        

    }

}

 

(6)异步函数,在普通函数和生成器函数、箭头函数前面加async

async function foo() {

    console.log("a");

}

async function * foo() {

    console.log("a");

}

 

3、this关键字的行为

(1)普通函数、带async的普通函数、生成器函数、带async的生成器函数

这类的函数是由调用它所使用的引用来决定this的值,调用的时候this的值会发生变化。

 

当我们获取函数的表达式(使用函数)时,返回的是Reference类型,这个类型由两个部分组成,一个对象和一个属性值,在使用这个函数的时候,此时this的值是这个对象,传入了执行函数的上下文。

function foo() {

    console.log(this);

}

foo();//window

当我们像下面这样将这个函数传给一个对象,然后调用该对象的函数,此时this的值会发生变化

function foo() {

    console.log(this);

}

let o = {

    foo:foo

};

o.foo();//{foo: ƒ}指向的是o

 

(2)箭头函数、带async的箭头函数

包裹箭头函数的作用域,指向箭头函数执行上下文中的词法环境,无论怎么调用,this的值始终如一。

 

(3)class定义的函数

等于实例化后的对象,如果将class里面的方法赋给外面的变量,此时外面变量调用该方法的this值为undefined。

 

4、this关键字的机制

(1)函数拥有一个保存定义该函数时上下文的私有属性[[Enviornment]]

 

(2)当一个函数执行的时候,会创建一条新的执行环境记录,记录的外层词法环境会被设置为该函数的[[Enviornment]],也就是此时this的值会等于该[[Enviornment]]

 

(3)函数可以访问定义时词法环境,但不能访问执行时词法环境。

 

(4)js用一个栈来管理上下文,这个栈每一项又包含一个链表,当函数调用时,会入栈一个新的执行上下文,函数调用结束的时候,该执行上下文出栈。

重学前端学习笔记之JavaScript执行

(5)[[thisModel]]私有属性有三个取值

  • lexical,表示从上下文中找this,对应箭头函数

  • global,当this的值为undefined的时候,取值为全局对象,对应普通函数

  • strict,严格按调用时传入的值,可能null或者undefined。(非常有意思的是,方法的行为跟普通函数有差异,class 设计成了默认按 strict 模式)

 

(6)调用函数创建新的执行上下文中词法环境,会根据[[thisModel]]来标记新记录的[[ThisBindingStatus]],代码执行遇到this的时候,会逐层检查当前词法环境记录中的[[ThisBindingStatus]],直至找到值。

 

(7)嵌套的箭头函数的this都指向最外层的this。

如下面代码

var o = {};

o.foo = function foo() {

    console.log(this);

    return ()=>{

        console.log(this);

        return ()=>console.log(this);

    }

}

o.foo()()();

重学前端学习笔记之JavaScript执行

 

5、操作this的内置函数(可以改变this值的函数)

apply,call,bind

 

(四)js的语句执行机制--Completion

1、js语句执行的完成状态--Completion Record,表示一个语句执行之后的结果,有三个字段。

  • [[type]],表示完成的状态,取值有break continue return throw 和normal

  • [[vaue]]语句的返回值,如果语句没有,则值为empty,一般只有表达式语句拥有。

  • [[target]]表示语句的目标。通常是一个js语句标签。

 

2、那么在completion record类型下面,js控制语句执行的过程是怎样的?

js语句的分类如下:

重学前端学习笔记之JavaScript执行

 

 

 

(1)普通语句

普通语句执行时(有var会有预处理),从前到后顺序执行,执行后会得到[[type]]的值为normal的Completion Record,js引擎遇到这样的语句,会继续执行下一条语句,只有表达式语句会产生[[value]]。

 

(2)语句块

  • 语句块就是用大括号{}括起来的一组语句,它是一种语句的符合结构,可以嵌套

 

  • 如果一个语句块只含有[[type]]为normal的语句,那么这个语句块里面的语句顺序执行,整个语句块的[[type]]也为normal。

  • 如果一个语句块含有[[type]]为非normal的语句,那么整个语句块的[[type]]也非normal。

 

(3)控制语句

  • 控制性语句带有if,switch等关键字,它们会对不同类型的Completion Record产生反应。

  • 对语句内部产生影响的有if,switch,while,for,try,对语句外部产生影响的有break,continue,return,throw,两者配合,控制代码执行顺序和逻辑

  • 即使try或者catch里面的语句执行完毕,如果有finally,也会执行finally里面的语句。

 

(4)带标签的语句

JavaScript语句是可以加标签的,如

    firstStatement: var i = 1;

 

作用:与Completion Record中的target相对于,用于跳出多层循环。

    outer: while(true) {

      inner: while(true) {

          break outer;

      }

    }

    console.log("finished")

 

 

 

(5)因为js语句存在嵌套关系,所以执行过程实际上主要在一个树形结构上完成,树形结构上的每一个节点执行后产生Completion Record,根据语句的结构和Completion Record,js实现了各种分值和跳出逻辑。