对象

原型链

构造函数

执行上下文堆栈

一、这里有三种类型的ECMAScript代码:全局代码、函数代码和eval代码。

  • 每个代码是在其执行上下文(execution context)中被求值的。
  • 这里只有一个全局上下文,可能有多个函数执行上下文以及eval执行上下文。
  • 对一个函数的每次调用,会进入到函数执行上下文中,并对函数代码类型进行求值。
  • 每次对eval函数进行调用,会进入eval执行上下文并对其代码进行求值。

二、一个函数可能会创建无数的上下文,因为对函数的每次调用(即使这个函数自己递归地调用自己),都会生成一个具有新状态的上下文。

  1. function foo(bar) {}
  2. // call the same function
  3. // generate three different
  4. // contexts in each call, with
  5. // different context state(eg: value of the 'bar' argument)
  6. foo(10)
  7. foo(20)
  8. foo(30)

三、一个执行上下文可能会触发另一个上下文。eg:一个函数调用另一个函数(或者在全局上下文中调用一个全局函数)等,从逻辑上来说,这是以栈的形式实现的,它叫做执行上下文栈。
1、一个触发其他上下文的上下文叫做caller,被触发的上下文叫做callee
2、callee在同一时间可能是一些其他callee的caller(比如,一个在全局上下文中被调用的函数,之后调用了一些内部函数)
3、当一个caller触发(调用)了一个callee,这个caller会暂缓自身的执行,然后把控制权传递给callee。这个callee被push到栈中,并成为一个运行中(活动的)执行上下文。在callee的上下文结束后,它会把控制权返回给caller,然后caller的上下文继续执行(它可能触发其他上下文)直到它结束,以此类推。callee可能简单的返回或者由于异常而退出。一个抛出的但是没有被捕获的异常可能退出(从栈中pop)一个或者多个上下文。
(1)换句话说,所有ECMAScript程序的运行时可以用执行上下文(EC)栈来表示,栈顶是当前活跃(active)上下文
image.png
当程序开始的时候它会进入全局执行上下文,此上下文位于栈底并且是栈中的第一个元素。然后全局代码进行一些初始化,创建需要的对象和函数。在全局上下文的执行过程中,它的代码可能触发其他(已经创建完成的)函数,这些函数将会进入它们自己的执行上下文,向栈中push新的元素,以此类推。当初始化完成之后,运行时系统(runtime system)就会等待一些事件(比如,用户鼠标点击),这些事件将会触发一些函数,从而进入新的执行上下文中。
在下个图中,拥有一些函数上下文EC1和全局上下文Global EC, 当ec1进入和退出全局上下文的时候,下面的栈将会发生变化:
image.png
这就是ECMAScript的运行时系统如何真正地管理代码执行的。
像我们所说的,栈中的每个执行上下文都可以用一个对象来表示,让我们来看看它的结构以及一个上下文到底需要什么状态(什么属性)来执行它的代码。

执行上下文

一个执行上下文可以抽象地表示为一个简单的对象,每一个执行上下文拥有一些属性(可以叫做上下文状态),用来跟踪和它相关的代码的执行过程,在下图中展示了一个上下文的结构:
image.png
除了这三个必需的属性(一个变量对象(variable object),一个this值以及一个作用域链(scope chain)以外,执行上下文可以拥有任何附加的状态,这取决于实现。

变量对象

变量对象是与执行上下文相关的数据作用域,它是一个与上下文相关的特殊对象,其中存储了在上下文中定义的变量和函数声明。
注意,函数表达式(与函数声明相对)不包含在变量对象之中。
变量对象是一个抽象概念。对于不同的上下文类型,在物理上,是使用不同的对象,比如,在全局上下文中变量对象就是全局对象本身(这就是为什么我们可以通过全局对象的属性名来关联全局变量)。
让我们再全局执行上下文中考虑下面这个例子:

  1. var foo = 10
  2. function bar() {} // function declaration, FD
  3. (function baz() {}) // function expression, FE
  4. console.log(
  5. this.foo == foo, // => true
  6. window.bar == bar // => true
  7. )
  8. console.log(baz) // ReferenceError, 'baz' is not defined

之后,全局上下文的变量对象(variable object,简称VO)将会拥有如下属性
image.png
再看一遍,函数baz是一个函数表达式,没有被包含在变量对象之中。这就是为什么当我们想要在函数自身之外访问它的时候会出现ReferaneceError
注意,与其他语言(比如C/C++)相比,在ECMAScript中只有函数可以创建一个新的作用域。在函数作用域中所定义的变量和内部函数,在函数外边是不能直接访问到的,而且并不会污染全局变量对象。
使用eval我们也会进入一个新的(eval类型)执行上下文。无论如何,eval使用全局的变量对象或者使用caller(比如eval被调用时所在的函数)的变量对象。
那么函数和它的变量对象是怎么样的?在函数上下文中,变量对象是以活动对象(activation object)来表示的。

活动对象

当一个函数被caller所触发(被调用),一个特殊的对象,叫做活动对象(avtivation object)将会被创建,这个对象中包含形参和那个特殊的arguments对象(是对形参的一个映射,但是值是通过索引来获取)。活动对象知乎会做为函数上下文的变量对象来使用。
换句话说,函数的变量对象也是一个同样简单的变量对象,但是除了变量和函数声明之外,它还存储了形参和arguments对象,并叫做活动对象。
考虑如下例子:

  1. function foo(x, y) {
  2. var z = 30
  3. function bar() {} // FD
  4. (function baz() {}) // FE
  5. }
  6. foo(10, 20)

我们看下函数foo的上下文中的活动对象(avtivation object,简称AO):
image.png
并且函数表达式baz还是没有被包含在变量/活动对象中
注意,在ES5变量对象和活动对象被并入了词法环境模型(lexical environments model)。
在ECMAScript中我们可以使用内部函数,然后再这些内部函数我们可以引用父函数的变量或者全局上下文中的变量。当我们把变量对象命名为上下文的作用域对象,与上面讨论的原型链相似,这里有一个叫做作用域链的东西。

作用域链

作用域链是一个对象列表,上下文代码中出现的标识符在这个列表中进行查找。

闭包

This

image.png
image.png
image.png
image.png

image.png

image.png
image.png

image.png

image.png
image.png
image.png

image.png

image.png