js运行机制及异步编程(一)

相信你们在面试的过程当中常常遇到查看执行顺序的问题,如setTimeout,promise,async await等等,各类组合,是否是感受头都要晕掉了,其实这些问题最终仍是考察你们对js的运行机制是否掌握牢固,对promise,async的原理是否掌握,万变不离其宗,此次就来完全搞懂它。

1 js引擎的运行原理

js引擎也是程序,是属于浏览器的一部分,由浏览器厂商自行开发。从头至尾负责整个JavaScript程序的编译及执行过程面试

浏览器在渲染的过程当中,首先按顺序加载由<script>标签分割的js代码块,加载js代码块完毕后,须要js引擎进行解析。不管是外部脚本文件(不异步加载)仍是内部脚本代码块,都是同样的原理,而且都在同一个全局做用域中。

JavaScript被归类为“动态”或“解释执行”语言,因此它无需提早编译,而是由解释器实时运行chrome

js引擎执行过程分为三个阶段:编程

  • JS的解释阶段
  • JS的预处理(编译)阶段及执行阶段

1.1 JS的解释阶段

js脚本代码块加载完毕后,会首先JS的解释阶段。该阶段主要过程以下:数组

  1. 词法分析——这个过程会将由字符组成的字符串分解成(对编程语言来讲)有意义的代码块,这些代码块被称为词法单元(token)
  2. 语法分析——这个过程是将词法单元流(数组)转化成抽象语法树(Abstract Syntax Tree)
  3. 使用翻译器(translator),将代码转为字节码(bytecode)
  4. 使用字节码解释器(bytecode interpreter),将字节码转为机器码

最终计算机执行的就是机器码。promise

为了提升运行速度,现代浏览器通常采用即时编译(JIT-Just In Time compiler)浏览器

即字节码只在运行时编译,用到哪一行就编译哪一行,而且把编译结果缓存(inline cache)缓存

这样整个程序的运行速度能获得显著提高。安全

并且,不一样浏览器策略可能还不一样,有的浏览器就省略了字节码的翻译步骤,直接转为机器码(如chrome的v8)闭包

1.2 JS的预处理(编译)阶段及执行阶段

这里我理解为js为解释型语言,由解释器实时运行,通俗的说就是预处理完以后立刻执行,一边编译一边执行

1.2.1 js的执行环境主要有三种:

  1. 全局环境
  2. 函数环境
  3. eval(不建议使用,会有安全,性能问题)

1.2.2 如下段例子说明js的预编译与执行过程

function bar() {
    var B_context = "Bar EC";

    function foo() {
        var f_context = "foo EC";
    }

    foo()
}

bar()

这段函数通过词法解析,语法解析阶段以后,就开始进入预编译并执行,以下:异步

clipboard.png

  1. 首先,进入全局环境,就会先进行预处理,然建立全局上下文执行环境(Global ExecutionContext),会对var声明的变量和函数声明进行预处理,window对象就是全局执行上下文的变量对象,全部的变量和函数都是window对象的属性方法。因此函数声明提早和变量声明提高是在建立变量对象中进行的,且函数声明优先级高于变量声明。而后推入stack栈中。预处完成以后,开始执行js
  2. 当执行bar()时,就会进入bar函数运行环境,就会先进行预处理,建立bar函数执行上下文(bar Execution Context),推入stack栈中,预处理完后,开始执行foo()
  3. 在bar函数内部调用foo函数,则再进入foo函数运行环境,建立foo函数执行上下文(foo Execution Context),推入stack栈中
  4. 此刻栈底是全局执行上下文(Global Execution Context),栈顶是foo函数执行上下文(foo Execution Context),如上图,因为foo函数内部没有再调用其余函数,那么则开始出栈
  5. foo函数执行完毕后,栈顶foo函数执行上下文(foo Execution Context)首先出栈
  6. bar函数执行完毕,bar函数执行上下文(bar Execution Context)出栈
  7. Global Execution Context则在浏览器或者该标签页关闭时出栈。

1.2.3 执行上下文

clipboard.png

分析一段简单的代码,帮助咱们理解建立执行上下文的过程,以下:

function fun(a, b) {
    var num = 1;

    function test() {

        console.log(num)

    }
}

fun(2, 3)

这里咱们在全局环境调用fun函数,建立fun执行上下文,这里为了方便你们理解,暂时不讲解做用域链以及this指向,以下:

funEC = {
    //变量对象
    VO: {
        //arguments对象
        arguments: {
            a: undefined,
            b: undefined,
            length: 2
        },

        //test函数
        test: <test reference>, 

        //num变量
        num: undefined
    },

    //做用域链
    scopeChain:[],

    //this指向
    this: window
}
  • funEC表示fun函数的执行上下文(fun Execution Context简写为funEC)
  • funE的变量对象中arguments属性,上面的写法仅为了方便你们理解,可是在浏览器中展现是以类数组的方式展现的
  • <test reference>表示test函数在堆内存地址的引用
注:建立变量对象发生在预编译阶段,但还没有进入执行阶段,该变量对象都是不能访问的,由于此时的变量对象中的变量属性还没有赋值,值仍为undefined,只有进入执行阶段,变量对象中的变量属性进行赋值后,变量对象(Variable
Object)转为活动对象(Active Object)后,才能进行访问,这个过程就是VO –> AO过程。

创建做用域链
做用域链由当前执行环境的变量对象(未进入执行阶段前)与上层环境的一系列活动对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。

理清做用域链能够帮助咱们理解js不少问题包括闭包问题等,下面咱们结合一个简单的例子来理解做用域链,以下:

var num = 30;

function test() {
    var a = 10;

    function innerTest() {
        var b = 20;

        return a + b
    }

    innerTest()
}

test()

在上面的例子中,当执行到调用innerTest函数,进入innerTest函数环境。全局执行上下文和test函数执行上下文已进入执行阶段,innerTest函数执行上下文在预编译阶段建立变量对象,因此他们的活动对象和变量对象分别是AO(global),AO(test)和VO(innerTest),而innerTest的做用域链由当前执行环境的变量对象(未进入执行阶段前)与上层环境的一系列活动对象组成,以下:

innerTestEC = {

    //变量对象
    VO: {b: undefined}, 

    //做用域链
    scopeChain: [VO(innerTest), AO(test), AO(global)],  
    
    //this指向
    this: window
}

在这里咱们顺便思考一下,什么是闭包?
咱们先看下面一个简单例子,以下:

function foo() {
    var num = 20;

    function bar() {
        var result = num + 20;

        return result
    }

    bar()
}

foo()

我这里直接以浏览器解析,以浏览器理解的闭包为准来分析闭包,以下图:

clipboard.png

如上图所示,chrome浏览器理解闭包是foo,那么按浏览器的标准是如何定义闭包的,我总结为三点:

  • 在函数内部定义新函数
  • 新函数访问外层函数的局部变量,即访问外层函数环境的活动对象属性
  • 新函数执行,建立新的函数执行上下文,外层函数即为闭包

肯定this指向在全局环境下,全局执行上下文中变量对象的this属性指向为window;函数环境下的this指向却较为灵活,需根据执行环境和执行方法肯定

相关文章
相关标签/搜索