JavaScript中执行上下文和执行栈( 二 )

  • 初始化作用域链
  • 创建变量对象:
  • 创建参数对象,检查上下文中的参数,初始化参数名称和值并创建引用副本
  • 浏览上下文中的函数声明:
  • 每找到一个函数,就在变量对象中添加一个新的属性,该属性命名为当前函数名,指向函数在内存中的引用
  • 如果函数名已经存在,所对应的属性值将被重写,指向新的函数引用
  • 浏览上下文中的变量声明:
  • 每找到一个变量声明,在变量对象中添加一个新的属性,该属性命名为当前变量名,并给该属性赋值为undefined
  • 如果变量名已经在变量对象中存在,将不进行任何操作,继续浏览当前上下文
  • 确定上下文中this的指向
  • 代码执行阶段:
  • 分配变量值并且逐行执行当前上下文中的代码
  • 下面看一个例子:
    function foo(i){ var a = 'hello', var b = function privateB(){}, function c(){}}foo(22);复制代码当调用函数foo的时候,创建阶段如下所示:
    fooExecutionContext = { 'scopeChain': {...}, 'variableObject':{ arguments:{ 0:22, length:1 }, i:22, c:pointer to function c(){}, a:undefined, b:undefined }, 'this':{...}}复制代码正如所示,创建阶段确定了属性的名称,除了实参和形参以外并没有给他们赋值 。一旦创建阶段完成,执行流进入函数内部并且激活/执行代码阶段,执行后的代码如下所示:
    fooExecutionContext = { 'scopeChain': {...}, 'variableObject':{ arguments:{ 0:22, length:1 }, i:22, c:pointer to function c(){}, a:'hello', b:pointer to function privateB(){} }, 'this':{...}}复制代码变量提升
    网上很多关于JavaScript中变量提升的定义,定义中指出变量和函数的声明会被提升至当前函数作用域的顶部 。但是,并没有解释为什么会存在变量提升以及解释器如何创建激活对象,其实原因很简单,以下面的代码为例:
    (function() { console.log(typeof foo); // function pointer console.log(typeof bar); // undefiendvar foo = 'hello', bar = function (){ return 'world'; };function foo(){ return 'hello'; };}())复制代码对于疑问和解答如下:
    • 为什么我们可以在声明foo前访问它?
    • 回顾创建阶段,变量在函数执行前已经被创建 。因此在函数执行前,foo已经在激活对象中创建 。
    • foo被声明了两次,为什么foo的类型是function而不是undefined或者string?
    • 尽管foo被声明两次,在创建阶段中,函数先于变量在激活对象中创建,并且如果激活对象中已经存在属性名,则不会影响已经存在的属性 。
    • 所以,对于函数foo的引用首先在激活对象中已经创建,并且当解释器到达var foo语句,解释器发现在变量对象中foo已经被创建,因此就会跳过然后继续后续操作 。
    • 为什么bar的值是undefined?
    • bar实际上是一个值为函数的变量,在创建阶段变量会被初始化为undefined。
    注:以上部分译自此文,如有侵权请告知;如有翻译不妥,还请各位读者指正 。以下是我对本文知识点的简要总结 。
    简要总结
    • 每个函数被调用的时候,都会创建一个新的执行上下文,并将当前执行上下文压入栈顶
    • 每个执行上下文可以看作是具有以下3个属性的对象:
    • 作用域链
    • 变量对象/激活对象(VO/AO)
    • this
    • 每个执行上下文的建立分为两个阶段:创建阶段和执行阶段
    • 执行上下文创建阶段,变量对象VO初始化的先后顺序:函数参数、函数声明、变量声明 。关于此部分两个常见问题的解答如下:
    • 1、"函数声明过程中,变量对象中如果已存在同名的属性,则替换它的值"这句话如何理解?以下述代码为例:
    function foo(i){ console.log(i); // function pointer var i = function (){}}foo(2);复制代码变量对象初始化第一步:函数参数复制代码executionContextObj = { 'scopeChain':{...}, 'variableObject':{ arguments:{ 0:2, length: 1, }, i:2 }}复制代码变量对象初始化第二步:函数声明 函数声明过程中,变量对象中已存在同名的属性i,将其值由"1"替换为新值"function"复制代码executionContextObj = { 'scopeChain':{...}, 'variableObject':{ arguments:{ 0:2, length: 1, }, i: function (){} }}复制代码
    • 2、"变量声明过程中,变量对象中如果已存在同名的属性,则不进行任何操作"这句话如何理解?以下述代码为例:
    function foo(i){ console.log(i); // function pointer var i = function (){}, var i = 9; } foo(2);复制代码 变量对象初始化第一步:函数参数复制代码executionContextObj = { 'scopeChain':{...}, 'variableObject':{ arguments:{ 0:2, length: 1, }, i:2 }}复制代码 变量对象初始化第二步:函数声明 函数声明过程中,变量对象中已存在同名的属性i,将其值由‘1’替换为新值‘function’复制代码executionContextObj = { 'scopeChain':{...}, 'variableObject':{ arguments:{ 0:2, length: 1, }, i: function (){} }}复制代码 变量对象初始化第三步:变量声明 变量声明过程中,变量对象中已存在同名的属性i,不进行任何操作 。复制代码executionContextObj = { 'scopeChain':{...}, 'variableObject':{ arguments:{ 0:2, length: 1, }, i: function (){} }, 'this':{...}}复制代码


    推荐阅读