javascript 实行环境,变量对象,作用域链

媒介

这几天在看《javascript高等递次设计》,看到实行环境和作用域链的时刻,就有些隐约了。书中照样讲的不够细致。
经由历程上网查资料,特来总结,以备回忆和修正。

要讲的顺次为:

  • EC(实行环境或许实行高低文,Execution Context)

  • ECS(实行环境栈Execution Context Stack)

  • VO(变量对象,Variable Object)|AO(运动对象,Active Object)

  • scope chain(作用域链)和[[scope]]属性

<!–more–>

EC

每当控制器抵达ECMAScript可实行代码的时刻,控制器就进入了一个实行高低文(好嵬峨上的观点啊)。

javascript中,EC分为三种:

  • 全局级别的代码 – 这个是默许的代码运转环境,一旦代码被载入,引擎最早进入的就是这个环境。

  • 函数级别的代码 – 当实行一个函数时,运转函数体中的代码。

  • Eval的代码 – 在Eval函数内运转的代码。

EC竖立分为两个阶段:进入实行高低文和实行阶段。

1.进入高低文阶段:发作在函数挪用时,然则在实行细致代码之前(比方,对函数参数举行细致化之前)
2.实行代码阶段:变量赋值,函数援用,实行其他代码。

我们能够将EC看作是一个对象。

EC={
    VO:{/* 函数中的arguments对象, 参数, 内部的变量以及函数声明 */},
    this:{},
    Scope:{ /* VO以及一切父实行高低文中的VO */}
}

ECS

一系列运动的实行高低文从逻辑上构成一个栈。栈底老是全局高低文,栈顶是当前(运动的)实行高低文。当在差别的实行高低文间切换(退出的而进入新的实行高低文)的时刻,栈会被修正(经由历程压栈或许退栈的情势)。

压栈:全局EC–>部分EC1–>部分EC2–>当前EC
出栈:全局EC<–部分EC1<–部分EC2<–当前EC

我们能够用数组的情势来示意环境栈:

ECS=[部分EC,全局EC];

每次控制器进入一个函数(哪怕该函数被递归挪用或许作为组织器),都邑发作压栈的操纵。历程相似javascript数组的push和pop操纵。

当javascript代码文件被浏览器载入后,默许最早进入的是一个全局的实行高低文。当在全局高低文中挪用实行一个函数时,递次流就进入该被挪用函数内,此时引擎就会为该函数建立一个新的实行高低文,而且将其压入到实行高低文客栈的顶部。浏览器老是实行当前在客栈顶部的高低文,一旦实行终了,该高低文就会从客栈顶部被弹出,然后,进入其下的高低文实行代码。如许,客栈中的高低文就会被顺次实行而且弹出客栈,直到回到全局的高低文。

VO|AO

VO

每一个EC都对应一个变量对象VO,在该EC中定义的一切变量和函数都寄存在其对应的VO中。

VO分为全局高低文VO(全局对象,Global object,我们一般说的global对象)和函数高低文的AO。

VO: {
  // 高低文中的数据 (变量声明(var), 函数声明(FD), 函数形参(function arguments))
}

1.进入实行高低文时,VO的初始化历程细致以下:

  • 函数的形参(当进入函数实行高低文时)
    —— 变量对象的一个属性,其属性名就是形参的名字,其值就是实参的值;关于没有通报的参数,其值为undefined

  • 函数声明(FunctionDeclaration, FD) —— 变量对象的一个属性,其属性名和值都是函数对象建立出来的;假如变量对象已包含了雷同名字的属性,则替代它的值

  • 变量声明(var,VariableDeclaration) —— 变量对象的一个属性,其属性名即为变量名,其值为undefined;假如变量名和已声明的函数名或许函数的参数名雷同,则不会影响已存在的属性。

注重:该历程是有先后递次的。

2.实行代码阶段时,VO中的一些属性undefined值将会肯定。

AO

在函数的实行高低文中,VO是不能直接接见的。它重要饰演被称作活泼对象(activation object)(简称:AO)的角色。

这句话怎样明白呢,就是当EC环境为函数时,我们接见的是AO,而不是VO。

VO(functionContext) === AO;

AO是在进入函数的实行高低文时建立的,并为该对象初始化一个arguments属性,该属性的值为Arguments对象。

AO = {
  arguments: {
    callee:,
    length:,
    properties-indexes: //函数传参参数值
  }
};

FD的情势只能是以下如许:

function f(){
  
}

示例

VO示例

alert(x); // function
 
var x = 10;
alert(x); // 10
 
x = 20;
 
function x() {};
 
alert(x); // 20

进入实行高低文时,

ECObject={
  VO:{
    x:<reference to FunctionDeclaration "x">
  }
};

实行代码时:

ECObject={
  VO:{
    x:20 //与函数x同名,替代掉,先是10,后变成20
  }
};

关于以上的历程,我们细致诠释下。

在进入高低文的时刻,VO会被添补函数声明; 统一阶段,另有变量声明“x”,然则,正如此前提到的,变量声明是在函数声明和函数形参今后,而且,变量声明不会对已存在的一样名字的函数声明和函数形参发作冲突。因而,在进入高低文的阶段,VO添补为以下情势:

VO = {};
 
VO['x'] = <援用了函数声明'x'>
 
// 发明var x = 10;
// 假如函数“x”还未定义
// 则 "x" 为undefined, 然则,在我们的例子中
// 变量声明并不会影响同名的函数值
 
VO['x'] = <值不受影响,还是函数>

实行代码阶段,VO被修正以下:

VO['x'] = 10;
VO['x'] = 20;

以下例子再次看到在进入高低文阶段,变量存储在VO中(因而,只管else的代码块永久都不会实行到,而“b”却仍然在VO中)


if (true) {
  var a = 1;
} else {
  var b = 2;
}
 
alert(a); // 1
alert(b); // undefined, but not "b is not defined"

AO示例

function test(a, b) {
  var c = 10;
  function d() {}
  var e = function _e() {};
  (function x() {});
}
 
test(10); // call

当进入test(10)的实行高低文时,它的AO为:

testEC={
    AO:{
        arguments:{
            callee:test
            length:1,
            0:10
        },
        a:10,
        c:undefined,
        d:<reference to FunctionDeclaration "d">,
        e:undefined
    }
};

因而可知,在竖立阶段,VO除了arguments,函数的声明,以及参数被给予了细致的属性值,别的的变量属性默许的都是undefined。函数表达式不会对VO形成影响,因而,(function x() {})并不会存在于VO中。

当实行test(10)时,它的AO为:

testEC={
    AO:{
        arguments:{
            callee:test,
            length:1,
            0:10
        },
        a:10,
        c:10,
        d:<reference to FunctionDeclaration "d">,
        e:<reference to FunctionDeclaration "e">
    }
};

可见,只要在这个阶段,变量属性才会被赋细致的值。

作用域链

在实行高低文的作用域中查找变量的历程被称为标识符剖析(indentifier resolution),这个历程的完成依赖于函数内部另一个同实行高低文相关联的对象——作用域链。作用域链是一个有序链表,其包含着用以通知JavaScript剖析器一个标识符究竟关联着哪一个变量的对象。而每一个实行高低文都有其本身的作用域链Scope。

一句话:作用域链Scope实在就是对实行高低文EC中的变量对象VO|AO有序接见的链表。能按递次接见到VO|AO,就可以接见到个中寄存的变量和函数的定义。

Scope定义以下:

Scope = AO|VO + [[Scope]]

个中,AO一直在Scope的最前端,不然为啥叫活泼对象呢。即:

Scope = [AO].concat([[Scope]]);

这说明了,作用域链是在函数建立时就已有了。

那末[[Scope]]是什么呢?

[[Scope]]是一个包含了一切上层变量对象的分层链,它属于当前函数高低文,并在函数建立的时刻,保存在函数中。

[[Scope]]是在函数建立的时刻保存起来的——静态的(稳定的),只要一次而且一直都存在——直到函数烧毁。 比方说,哪怕函数永久都不能被挪用到,[[Scope]]属性也已保存在函数对象上了。

var x=10;
function f1(){
  var y=20;
  function f2(){
    return x+y;
  }
}

以上示例中,f2的[[scope]]属性能够示意以下:

f2.[[scope]]=[
  f2OuterContext.VO
]

f2的外部EC的一切上层变量对象包含了f1的活泼对象f1Context.AO,再往外层的EC,就是global对象了。
所以,细致我们能够示意以下:

f2.[[scope]]=[
  f1Context.AO,
  globalContext.VO
]

关于EC实行环境是函数来讲,那末它的Scope示意为:

functionContext.Scope=functionContext.AO+function.[[scope]]

注重,以上代码的示意,也表现了[[scope]]和Scope的差别,Scope是EC的属性,而[[scope]]则是函数的静态属性。

(因为AO|VO在进入实行高低文和实行代码阶段差别,所以,这里及今后Scope的示意,我们都默许为是实行代码阶段的Scope,而关于静态属性[[scope]]而言,则是在函数声明时就建立了)

关于以上的代码EC,我们能够给出其Scope的示意:

exampelEC={
  Scope:[
    f2Context.AO+f2.[[scope]],
    f1.context.AO+f1.[[scope]],
    globalContext.VO
  ]
}

接下来,我们给出以上别的值的示意:

  • globalContext.VO

globalContext.VO={
  x:10,
  f1:<reference to FunctionDeclaration "f1">
}
  • f2Context.AO

f2Context.AO={
  f1Context.AO={
    arguments:{
      callee:f1,
      length:0
    },
    y:20,
    f2:<reference to FunctionDeclaration "f2">
  }
}
  • f2.[[scope]]

f2Context.AO={
  f1Context.AO:{
    arguments:{
      callee:f1,
      length:0
    },
    y:20,
    f2:<reference to FunctionDeclaration "f2">
  },
  globalContext.VO:{
    x:10,
    f1:<reference to FunctionDeclaration "f1">
  }
}
  • f1Context.AO

f1Context.AO={
  arguments:{
    callee:f1,
    length:0
  },
  y:20,
  f2:<reference to FunctionDeclaration "f2">
}
  • f1.[[scope]](f1的一切上层EC的VO)

f1.[[scope]]={
  globalContext.VO:{
    x:undefined,
    f1:undefined
  }
}

好,我们晓得,作用域链Scope呢,是用来有序接见VO|AO中的变量和函数,关于上面的示例,我们给出接见的历程:

  • x,f1

- "x"
-- f2Context.AO // not found
-- f1Context.AO // not found
-- globalContext.VO // found - 10

f1的接见历程相似。

  • y

- "y"
-- f2Context.AO // not found
-- f1Context.AO // found -20

我们发明,在变量和函数的接见历程,并没有涉及到[[scope]],那末[[scope]]存在的意义是什么呢?

这个照样看下一篇文章吧。

总结

  1. EC分为两个阶段,进入实行高低文和实行代码。

  2. ECStack治理EC的压栈和出栈。

  3. 每一个EC对应一个作用域链Scope,VO|AO(AO,VO只能有一个),this。

  4. 函数EC中的Scope在进入函数EC时建立,用来有序接见该EC对象AO中的变量和函数。

  5. 函数EC中的AO在进入函数EC时,肯定了Arguments对象的属性;在实行函数EC时,别的变量属性细致化。

  6. 函数的[[scope]]属性在函数建立时就已肯定,并坚持稳定。

参考

    原文作者:52lidan
    原文地址: https://segmentfault.com/a/1190000000533094
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞