从执行上下文分析——JS中的代码是怎么运行的?

src=http___img4.orsoon.com_ico_201910_09101709_af0923f23b.png&refer=http___img4.orsoon.png

一、了解JS

我们知道JS属于脚本语言,是一种解释型语言,可以一边转换一边执行,不会生成可执行文件,支持跨平台执行,而它能这样做主要是归功于解释器这个中间层。

这里指的跨平台是源代码可以跨平台,不是解释器跨平台。
官方针对不同的平台开发不同的解释器,使得它们遵从相同的函数,相同的语法,有着相同功能。

在chrome浏览器中的V8引擎就充当着解释器这样一个身份,将JS源代码解释执行。

二、执行上下文

执行上下文简单的来说就是JS代码运行的环境,全局执行上下文就是全局环境,函数执行上下文就是函数内的环境了。

代码要运行首先要进入到全局执行上下文。

执行上下文分为

全局执行上下文:全局上下文只有一个,不在函数中的代码都位于全局执行上下文中。

函数执行上下文:每个函数都有自己的函数执行上下文,但只有函数调用时才会创建函数执行上下文,函数执行上下文数量是没有限制的。

eval执行上下文:基本很少用上。

执行上下文的阶段

执行上下文分为创建、执行、回收三个阶段,创建阶段分为三步:

  1. 变量对象(VO):用于变量提升,函数声明,这一步也可以称为预编译,在全局环境下VO 也可看作预编译创建的GO。

  2. 作用域链:作用域在创建变量对象之后创建,作用域用来解析变量,当变量在自己的作用域内无法找到,就会从它的父级作用域下寻找,依次下去,直到找到或到了最外层作用域。

  3. this:确定this的指向,在全局下this指向window,当然,this的值在执行时才会确定。

执行上下文的执行阶段:给变量赋值,函数调用,执行代码。

执行上下文回收阶段:将执行上下文弹出栈,等待回收。

三、执行堆栈

可以把他理解成一种栈结构,遵循先进后出原则,创建的执行上下文要执行就要将其放入执行栈中。

执行栈的运行过程

  1. 浏览器最开始执行代码创建全局执行上下文,将其压入执行栈的底部。

  2. 当遇到函数调用时,创建函数执行上下文,将其压入栈,此时位于全局执行上下文的上方,当在函数执行中又遇到函数调用,继续创建新的函数执行上下文,将其压入栈,此时它位于上一个函数上下文上方。当函数执行结束,将函数执行上下文弹出执行栈,等待回收。

  3. 浏览器只会调用位于栈顶的执行上下文。

  4. 全局执行上下文在浏览器关闭时弹出。

代码演示

我们通过一段简单的代码来演示这一过程

var a= 3
var b = 3
function output1() {
    console.log(a)
    output2()
}

function output2()  {
    console.log(b);
}
output1()
复制代码
  1. 浏览器创建全局执行上下文,将其压入最底层

  2. 函数声明不会创建函数执行上下文,直到output1函数调用,创建同名函数执行上下文,将它压入栈

  3. output1函数调用过程中,遇到了output2的调用,创建output2函数执行上下文,继续压入栈。

  4. output2执行完毕,将其弹出栈,将控制权交给它下方的上下文,即output1函数执行上下文。

  5. output1也执行完毕,同样也弹出执行栈,控制权交给全局执行上下文,直到全局也最后弹出。

未命名文件.png

四、变量提升/预编译

预编译发生在函数执行之前,会对代码进行一次扫描,找到函数声明以及变量声明。

变量提升发生在执行上下文的创建阶段,对变量,函数声明进行提升。

就我个人理解来说,其实预编译和变量提升的区别不是很大,应该说是预编译就是变量提升的另一种叫法。

预编译过程:

预编译发生在全局 三部曲

  1. 创建GO对象 (在全局执行上下文中VO = GO
  2. 找变量声明,将变量声明作为GO对象的属性名,值赋予undefined
  3. 找全局里的函数声明,将函数名作为GO对象的属性名,值赋予函数体

预编译发生在函数内 四部曲

  1. 创建一个AO对象(activation object)
  2. 找形参和变量声明,将形参和变量声明作为AO对象的属性名,值为undefined
  3. 将实参和形参统一
  4. 在函数体里找函数声明,将函数名作为AO对象的属性名,值赋予函数体

预编译规则:

  1. 变量声明提升:对于用var定义的变量,在预编译阶段会对变量进行提升,将变量提升到所在作用域的顶部,并初始化为undefined。

  2. 函数声明提升:对于函数声明来说,同样会受到提升,但是函数声明提升的是整个函数,会创建一个函数变量,并把函数中的所有东西都保存在里面,但不会执行。

  3. 函数声明优先级更高,当函数声明和变量声明同时提升时,函数声明会覆盖掉同名变量声明,但变量声明可以重新赋值。

  4. 例如 a = 2这样的赋值语句不属于声明,在执行阶段运行,创建的变量属于全局变量,作用域是全局。

代码演示

         var a = 1
         function fn(a) {
             var a = 2
             function a() {}
             var b = a
             console.log(a);
            
        }
         fn(3)     //2
复制代码
  1. 创建GO对象,对变量a进行变量提升,初始化为undefined,接着对fn函数声明提升,提升整个fn函数,但先不执行,接着进入代码执行,a被赋值为1,下一步调用函数fn

  2. 遇到fn函数调用,创建Ao对象,将形参及变量a提升,初始化为undefined,变量b提升,初始化为undefined。

  3. 将形参和实参值统一为3,即a为3,最后将函数a提升,由于函数优先原则,a此时被初始化为函数a,进入代码执行阶段,a被赋值为2,b被赋值为a,即2,最后输出a为2。

GO、AO对象的创建结果

    go:{
        a: undefined  1
        fn: function

    }

    ao: {
        a:undefined 3  function  2
        b: undefined  2 
    }
复制代码

五、分析代码运行步骤

我们通过下面这段代码分析代码是怎样一步步运行

var a = 1

function fo(a) {
    var b = 2
    console.log(a);  //输出2
    function fo1() {
        console.log(b);  // 输出2
    }

    return fo1  // 产生闭包
}

var c = fo(2)
c()
复制代码
  1. 创建全局执行上下文,将其压入执行栈最底部,上下文创建阶段–创建变量对象/预编译阶段(变量声明、函数声明提升)、创建作用域链、this绑定。
  • 创建go对象,将ac提升并初始化为undefinedfo函数声明提升整个函数体,将它们都保存在go对象中,this指向window。
  1. 全局执行上下文进入执行阶段。
  • go对象中将a赋值为1,将c赋值为fo(2)的返回值,此时遇到函数调用,创建函数执行上下文。
  1. 创建fo函数执行上下文,将它压入执行栈,此时全局执行上下文在它下方,进入函数执行上下文创建阶段,依然进行同样的三步。
  • 创建ao对象,将a,b初始化为undefined,之后将形参和实参统一,即a赋值为2,最后进行函数声明提升,将它们都保存在ao对象中。
  1. fo函数执行上下文进入执行阶段。
  • b赋值为2,输出此时的a的值,由于创建阶段进行了预编译,此时a的值为2,于是输出2,遇到return需要返回,将fo1函数里面的所有内容保存在fo1中返回,此时函数fo调用结束,通常情况会下,将fo执行上下文弹出执行栈,但是由于return的是一个函数,产生闭包机制,使得作用域未被删除,里面的变量仍可以使用。
  1. fo函数调用结束,返回值是一个函数fo1,将它赋值给c,于是在全局上下文中,它不在是fo1,它被叫做c,接下来,调用c

  2. 遇到函数调用,创建c函数执行上下文,压入执行栈,上下文创建阶段–创建变量对象、创建作用域链、this绑定。

  • 创建ao对象,只有一句输出语句,所以没有变量、函数提升。
  1. fo1执行阶段,由于在外部调用fo的内部函数,闭包机制导致fo作用域不释放,所以输出b时,向父级作用域查找,找到此时b为2,输出2。
  • 调用fo1结束,弹出函数执行栈,最后代码执行完毕,将执行栈清空。

创建的变量对象:

go: {
    a: undefined  1
    fo: function
    c: undefined fo1
}

ao: {
    b: undefined 2
    a: undefined 2
    fo1: function  
}

ao: {
    
}
复制代码

六、总结

本文介绍了js、执行上下文内容、执行栈、变量提升规则以及从执行上下文方面分析代码
,由于本人也是只是位初学者,将自己的看法进行总结记录,还是希望各位大佬能多多斧正,喜欢的可以点个小小的?,感谢?‍?‍。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享