javascript代码解析过程

执行上下文和作用域是javascript中十分重要的局部,要弄清楚它们首先就要说到javascript的运行机制,javascript代码被解析通过了以下几个步骤

  • Parser模块将javascript源码解析成形象语法树(AST)
  • Ignition模块将形象语法树编译成字节码(byteCode),再编译成机器码
  • 当函数被执行屡次时,Ignition会记录优化信息,由Turbofan间接将形象语法树编译成机器码

全局上下文

理解完以上javascript运行机制之后,咱们来看看以下全局代码的执行形式

console.log(user)var user = 'alice' var num = 10console.log(num)

以上代码通过如下步骤才被执行

  1. javascript --> ast

    • 全局创立一个GO( GlobalObject)对象,GO中有很多内置模块,如Math、String、以及window属性,其中window的值为this,也就是本身GO对象
    • 全局定义的变量user和num会增加GO对象中,并赋值为undefined
  2. ast --> Ignition

    • V8引擎执行代码时,存在调用栈(ECStack),此时创立全局上下文栈(Global Excution Context)
    • GEC存在VO(variable Object),在全局上下文中指向GO对象
  3. Ignition --> 运行后果

    • 通过VO找到GO
    • 将user赋值为alice,将num赋值为10

图示如下

以上代码的执行的后果为

undefined10
  • parser模块将源代码编译为AST时,曾经将user和num定义到VO对象中,值为undefined
  • 打印user的时候,没有执行到user的赋值语句,所以user的值依然为undefined
  • 打印num的时候,曾经执行了给num赋值的语句,所以num的值为10

函数上下文

定义函数的时候,执行形式和全局又有些不同

var name = 'alice' foo(12) function foo(num){     console.log(m)    var m = 10     var n = 20     console.log("foo") }

以上代码通过如下步骤才被执行

  1. javascript --> ast

    • 全局创立一个GO( GlobalObject)对象,GO中有很多内置模块,如Math、String、以及window属性,其中window的值为this,也就是本身GO对象
    • 全局定义的变量name会增加GO对象中,并赋值为undefined
    • 函数foo会开拓一块内存空间,比方为0x100,用来存储父级作用域(parent scope)和本身代码块,函数foo的父级作用域就是全局对象GO
    • 将foo增加到GO对象中,赋值为内存地址,如0x100
  2. ast --> Ignition

    • V8引擎执行代码时,存在调用栈(ECStack),此时创立全局上下文栈(Global Excution Context)
    • GEC存在VO(variable Object),在全局上下文中指向GO对象
  3. Ignition --> 运行后果
    (1)执行全局代码

    • 通过VO找到GO
    • 将name赋值为alice
    • 执行函数foo前,创立函数执行上下文(Function Excution Context),存在VO指向AO对象
    • 创立Activation Object,将num、m都定义为undefined

    (2) 执行函数

    • 将num赋值为12,m赋值为10,n赋值为20
    • 函数foo执行实现,从调用栈(ECStack)栈顶弹出

图示如下

所以下面代码执行后果为

undefined

预编译

在Parser模块将javascript源码编译成AST时,还通过了一些细化的步骤

  • Stram将源码解决为对立的编码格局
  • Scanner进行词法剖析,将代码转成token
  • token会被转换成AST,通过preparser和parser模块

parser用来解析定义在全局的函数和变量,定义在函数中的函数只会通过预解析Preparser

函数中定义函数的执行程序

var user = "alice"foo(12) function foo(num){     console.log(m)    var m = 10        function bar(){         console.log(user)    }     bar() } 

以上代码通过如下步骤才被执行

  1. javascript --> ast

    • 全局创立一个 GO( GlobalObject)对象,GO中有很多内置模块,如Math、String、以及window属性,其中window的值为this,也就是本身GO对象
    • 全局定义的变量user会增加GO对象中,并赋值为undefined
    • 函数foo会开拓一块内存空间,比方为0x100,用来存储父级作用域(parent scope)和本身代码块,函数foo的父级作用域就是全局对象GO
    • 将foo增加到GO对象中,赋值为内存地址,如0x100
  2. ast --> Ignition

    • V8引擎执行代码时,存在调用栈(ECStack),此时创立全局上下文栈(Global Excution Context)
    • GEC存在VO(variable Object),在全局上下文中指向GO对象
  3. Ignition --> 运行后果
    (1)执行全局代码

    • 通过VO找到GO
    • 将user赋值为alice
    • 执行函数foo前,创立foo函数的执行上下文(Function Excution Context),存在VO(variable Object)指向AO(Activation Object)对象
    • 创立Activation Object,将num、m都定义为undefined
    • 为函数bar开拓内存空间 0x200,用来存储父级作用域和本身代码,bar的父级作用域为函数foo的作用域AO+全局作用域GO
    • 将bar增加到foo的AO对象中,赋值为内存地址,0x200

    (2) 执行函数foo

    • 将num赋值为12,m赋值为10

    (3) 执行函数bar

    • 创立bar的执行上下文,存在VO(variable Object)指向AO(Activation Object)对象
    • 创立Activation Object,此时AO为空对象
    • 函数bar执行实现,从调用栈(ECStack)栈顶弹出
    • 函数foo也执行实现了,从调用栈(ECStack)栈顶弹出

所以下面代码执行后果为

undefinedalice
  • m 在打印的时候还没有被赋值,所以为undefined
  • 打印user,首先在本人作用域中查找,没有找到,往上在父级作用域foo的AO对象中查找,还没有找到,就找到了全局GO对象中

作用域

作用域是在解析成AST(形象语法树)的时候确定的,与它在哪里被调用没有分割

var message = "Hello Global"function foo(){     console.log(message) } function bar(){     var message = "Hello Bar"    foo() } bar()

以上代码通过如下步骤才被执行

  1. javascript --> ast

    • 全局创立一个 GO( GlobalObject)对象
    • 全局定义的变量message会增加GO对象中,并赋值为undefined
    • 函数foo开拓一块内存空间,为0x100,用来存储父级作用域(parent scope)和本身代码块,函数foo的父级作用域就是全局对象GO
    • 将foo增加到GO对象中,赋值为内存地址,0x100
    • 函数bar开拓一块内存空间,为0x200,用来存储父级作用域(parent scope)和本身代码块,函数foo的父级作用域就是全局对象GO
    • 将bar增加到GO对象中,赋值为内存地址,0x200
  2. ast --> Ignition

    • V8引擎执行代码时,存在调用栈(ECStack),此时创立全局上下文栈(Global Excution Context)
    • GEC存在VO(variable Object),在全局上下文中指向GO对象
  3. Ignition --> 运行后果
    (1)执行全局代码

    • 通过VO找到GO
    • 将message赋值为Hello Global
    • 执行函数bar前,创立bar函数的执行上下文(Function Excution Context),存在VO(variable Object)指向AO(Activation Object)对象
    • 创立Activation Object,将message定义为undefined

    (2) 执行函数bar

    • 将message赋值为Hello Bar

    (3) 执行函数foo

    • 创立foo的执行上下文,存在VO(variable Object)指向AO(Activation Object)对象
    • 创立Activation Object,此时AO为空对象
    • 打印 message,此时本人作用域内没有message,向上查找父级作用域,foo的父级作用域为GO
    • 函数foo执行实现,从调用栈(ECStack)栈顶弹出
    • 函数bar也执行实现了,从调用栈(ECStack)栈顶弹出

所以最初输入的后果为

Hello Gloabl

图示如下

易混同点

一、 没有通过var标识符申明的变量会被增加到全局

var n = 100 function foo(){     n = 200} foo()console.log(n)

执行过程如下

  1. javascript --> ast

    • GO对象中将n定义为undefined
    • 开拓foo函数的内存空间0x100,父级作用域为GO
    • 将foo增加到GO对象中,值为0x100
  2. ast --> Ignition

    • 创立全局上下文,VO指向GO
    • 执行foo函数前,创立函数上下文,VO对象指向AO对象
    • 创立AO对象,AO为空对象
  3. 赋值

    • GO中的变量n被赋值为100
    • 执行foo函数中的赋值,因为没有var标识符申明,所以间接给全局GO中的n赋值200

所以此时执行后果为

200

二、函数作用域内有变量,就不会向父级作用域查找

function foo(){     console.log(n)     var n = 200     console.log(n) } var n = 100foo()

执行程序如下

  1. javascript --> ast

    • GO对象中增加变量n,值为undefined
    • 为函数foo开拓内存空间0x300,父级作用域为GO
    • 将foo增加到GO对象中,值为0x300
  2. ast ---> Ignition

    • 创立全局上下文,VO指向GO
    • 执行函数foo之前创立函数上下文,VO指向AO
    • 创立AO对象,增加变量n,值为undefined
  3. 赋值

    • 将GO中的n赋值为100
    • 执行foo,打印n,此时先在本人的作用域内查找是否存在变量n,AO中存在n值为undefined,所以不会再向父级作用域中查找
    • 将AO中n赋值为200
    • 打印n,此时本人作用域中存在n,值为200

所以执行后果为

undefined200

三、return语句不影响ast的生成
在代码解析阶段,是不会受return语句的影响,ast生成的过程中,只会去查找var 和 function标识符定义的内容

var a = 100 function foo(){     console.log(a)     return     var a = 100     console.log(a)} foo()

执行过程如下

  1. javascript --> ast

    • GO对象中将a定义为undefined
    • 开拓foo函数的内存空间0x400,父级作用域为GO
    • 将foo增加到GO对象中,值为0x400
  2. ast --> Ignition

    • 创立全局上下文,VO指向GO
    • 执行foo函数前,创立函数上下文,VO对象指向AO对象
    • 创立AO对象,将a增加到AO对象中,值为undefined
  3. 赋值

    • GO中的变量a被赋值为100
    • 执行foo函数,打印a,此时a没有被定义,所以输入undefined
    • 执行return,return前面的代码不会执行

所以执行后果为

undefined

四、连等赋值
var a = b = 10,相当于var a = 10; b = 10

function foo(){     var a = b = 10} foo() console.log(b)console.log(a) 

执行过程如下

  1. javascript --> ast

    • 创立GO对象,GO对象为空
    • 开拓foo函数的内存空间0x500,父级作用域为GO
    • 将foo增加到GO对象中,值为0x500
  2. ast --> Ignition

    • 创立全局上下文,VO指向GO
    • 执行foo函数前,创立函数上下文,VO对象指向AO对象
    • 创立AO对象,将a增加到AO对象中,值为undefined
  3. 赋值

    • 执行foo函数,var a = b = 10,相当于var a = 10; b = 10,a变量有标识符,所以a被增加到AO对象中,赋值为10,b没有示意符,所以b被增加到全局对象GO,赋值为10
    • 打印b,GO对象中能找到b,值为10
    • 打印a,GO对象中没有a,且没有父级作用域,无奈向上查找,此时报错

所以执行后果为

10Uncaught ReferenceError: a is not defined

以上就是如何从javascript代码解析过程了解执行上下文与作用域晋升的具体介绍,对于js高级,还有很多须要开发者把握的中央,能够看看我写的其余博文,继续更新中~