从执行上下文对象入手看声明提前
资料
- js 中的活动对象 与 变量对象 什么区别? 答主:貘吃馍香 的回答
- 汤姆大叔的博客
涉及到的关键词
- 执行上下文(执行环境)
- 活动对象、变量对象
- arguments、声明提前
- 作用域链
一、执行上下文(Execution Contexts)
(1) 为什么要有执行上下文
- 每个函数都有对应的执行环境,它定义了变量或者函数有权访问的数据,决定他们各自的行为
- 执行上下文(执行环境)是存在的对象,是规范中的存在和引擎上实现,JS无法访问,无法操作,只不过大家都这么叫,上一篇讲的是函数环境,是内存上的实现,要注意区分
- 每个函数有自己的函数环境,函数环境都有对应的执行上下文(执行环境),函数环境是栈数据结构,那么执行上下文也应该是栈数据结构的,因为当恢复函数环境时,自然也要恢复执行上下文(执行环境)
(2) 什么时候开始建立执行上下文
每次执行函数前,会对函数进行预编译,这个时间段就在建立执行上下文。
执行上下文包含:
- 变量对象(Variable Objecct),存储声明的变量和函数的具体位置
- this value,确定this的值
- 作用域链,确定能够访问的变量和函数
执行上下文也分:
- 全局执行上下文
- 函数执行上下文
(3) 建立执行上下文的过程
声明变量 var0
函数 A() {
声明变量 var1
函数 B()
声明变量 var2
}
函数 B() {
声明变量 var3
函数 C()
声明变量 var4
}
函数 C() {
声明变量 var5
}
//执行函数 A
函数A()
- 代码一开始,先是全局执行上下文对象
globalExecutionContent = {
VO: {
arguments: undefined //默认是 undefined
},
Scope: [globalContent.VO],
this: window
}
- 调用函数A,创建A函数的执行上下文对象
AExecutionContent = {
VO: {
arguments: undefined //默认是 undefined
},
Scope: [AExecutionContent.VO, globalExecutionContent.VO],
this: window
}
- 其它函数的执行上下文对象也是如此,注意作用域链的最左端是当前执行上下文对象的变量对象,其后依次的是上层的执行上下文对象的变量对象
(4) 变量对象和活动对象
前面说过每个执行上下文都有一个对应的变量对象,但是注意执行上下文也是栈新式的结构,当前函数调用另外一个函数时,肯定也要保存当前的执行上下文,并创建新的上下文及其变量对象
此时,最上层的执行上下文和它的变量对象都是被 “激活的”,因为要执行当前环境代码必须访问执行上下文和它的变量对象
这样看其它的下层的执行上下文和它的变量对象都是 “休息状态”,在当前环境不需要访问它们
二、变量对象怎么创建的
这一小节涉及声明提前
进入函数并创建执行上下文对象时,对函数进行一扫描
-
扫描函数的所有形参,并将形参名称 和对应值组成的键值对作为变量对象VO的属性。如果没有传递对应的实参,将undefined作为对应值。如果形参名为arguments,将覆盖arguments(这里和上面执行环境对应)
-
扫描函数代码中所有的函数声明(注意是函数声明,函数表达式不算)
将函数名和对应值(指向内存中该函数的引用指针)组成组成的键值对作为变量对象VO的属性
如果变量对象VO已经存在同名的属性,则覆盖这个属性 -
扫描函数代码中所有的变量声明
由变量名和对应值(此时为undefined) 组成,作为变量对象的属性
如果变量名与已经声明的形参或函数相同,此时什么都不会发生,变量声明不会干扰已经存在的这个同名属性。
三、作用域和作用域链
(1) 作用域
- 变量(或者说标识)的作用域是程序源代码中定义这个作用域的区域
- 作用域是描述这个变量起作用的代码范围
(2) 作用域链
JS是静态作用域,这意味这一旦代码确定,作用域确定,并且不会改变
this不是作用域的一部分,可以从上一小节的执行上下文的作用域链可以看到 this和变量对象和作用域链是同级的
JS函数内部还能定义函数,内部函数还能访问上层函数的变量和函数(注意这个上层,是定义函数时的代码的上层环境或者说上层函数)
作用域链就像一个数组一样,它的最前端表示当前函数的作用域,往后就是上层函数的作用域,一直到全局环境,当需要访问变量或者函数时,查找方式就是按照这样的路径查询