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


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

    1 js引擎的运行原理

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

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

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

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

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

    1.1 JS的解释阶段

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

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

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

    为了提高运行速度,现代浏览器一般采用即时编译(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()
    
    

    这段函数经过词法解析,语法解析阶段之后,就开始进入预编译并执行,如下:

    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 执行上下文

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

    
    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: &lt;test reference&gt;, 
    
            //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()
    

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

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

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

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

    来源:https://segmentfault.com/a/1190000017394625

  • 相关阅读:
    MySQL优化
    SLAM01
    图像处理04
    Be accepted for inclusion in the IEEE INFOCOM 2018 technical program
    CS229 Lecture 02
    图像处理03
    Codeforces 900D Unusual Sequences:记忆化搜索
    Codeforces 914C Travelling Salesman and Special Numbers:数位dp
    BZOJ 4199 [Noi2015]品酒大会:后缀数组 + 并查集
    BZOJ 4650 [Noi2016]优秀的拆分:后缀数组
  • 原文地址:https://www.cnblogs.com/qixidi/p/10130313.html
Copyright © 2020-2023  润新知