Execution Context 执行期上下文
在java或c语言中,都有块级作用域这个概念,而js中则没有。
在js中,作用域只有一种,即函数级作用域。
而执行期上下文,可以理解为函数的作用域或执行环境。
在代码层面,执行期上下文是嵌套存在的
在js引擎内,执行期上下文是以栈的形式进行存放
栈的最底部存放的global上下文,每次执行一个函数,则会创建一个上下文放入栈中,执行结束后再pop移除。
(function foo(i) {
if (i === 3) {
return;
}
else {
foo(++i);
}
}(0));
当前的执行环境则永远使用存放在栈顶的上下文对象。
参考博文:
- 深入理解Javascript之执行上下文(Execution Context) -- JavaScript -- IT技术博客大学习 -- 共学习 共进步!
- 深入理解JavaScript系列(10):JavaScript核心(晋级高手必读篇) - 汤姆大叔 - 博客园
Function 执行原理
js中有在function上面有很多的用法和概念,就比如作用域链,闭包这些。
其实问题归结到了根本,都在于Function在执行时做了什么。
这个是以下内容的一个思维导图:
以Function的生命周期划分,分为:创建阶段 和 执行阶段
这里从执行阶段开始,以 执行函数内部代码 为时间点分成3个阶段:
- 执行前
- 执行时
- 执行后
执行前
当执行一个函数时,会创建一个函数的执行环境,即执行期上下文对象(execution context)。
ExecutionContext = {
VO:{
// Variable Object(全局上下文独有)
// 变量 (var, 变量声明);
// 函数声明 (FunctionDeclaration, 缩写为FD);
},
AO:{
// Activation Object (函数上下文独有)
// 变量 (var, 变量声明);
// 函数声明 (FunctionDeclaration, 缩写为FD);
// arguments
},
scopeChain:{..},
this:{..}
}
该对象主要做了三件事情:
-
确定函数内所有的变量 (AO | VO)
- AO:通过上下文栈中的当前上下文,获取参数,创建arguments对象
- AO & VO:扫描代码,创建所有函数声明(hoist作用域提升的原因,这些函数将进入创建阶段)和变量声明(值为undefined)
- 创建作用域链 (scopeChain = (AO | VO) + [[Scope]])
- 确定this指向 (由当前所处的执行期上下文提供)
VO & AO
VO 和 AO 的作用是存储函数中执行时需要用到的所有函数和变量。
执行期上下文分为两种:
- 全局上下文 (global,在上下文栈最底部,这个对象只存在一份,它的属性在程序中任何地方都可以访问)
- 函数上下文 (每次在函数调用时进行创建)
这两种上下文的区别就在于其创建方式和VO的访问性。
全局上下文的VO可以直接访问,VO对象指向的是global自身
var name = {};
name === this.name // true
name === window.name // true
函数上下文的VO不能直接访问,创建活动对象AO来代替VO。
因此函数上下文只有AO,VO没有。
创建作用域
函数生命周期分为两个阶段:
- 创建阶段 (创建
[[scope]]
) - 执行阶段 (创建
scopeChain
)
在函数创建阶段,[[scope]]就已经被创建了。
function say(){
var words = "hello";
hello(); // 输出:hello
function hello(){
console.log(words);
}
}
say.[[scope]] = [
GlobalExecutionContext.VO
]
遵循 [[scope]] = superExecutionContext.scopeChain + superFn.[[scope]]
这个规则。
该变量[[scope]]
是一种静态变量,一直存在,直至函数被delete或垃圾回收。
进入执行阶段,执行期上下文初始化作用域链scopeChain。
say.ExecutionContext = {
AO : {
words : undefined,
arguments : [...]
},
scopeChain : say.[[scope]].concat(this.AO)
}
即
say.ExecutionContext = {
AO : {
words : undefined,
arguments : [...]
},
scopeChain : [
say.ExecutionContext.AO, // 创建自身活动对象
GlobalExecutionContext.VO
]
}
遵循 scopeChain = (AO | VO) + [[Scope]]
这个规则。
闭包也遵循这个规则,在say函数执行时,内部函数hello进入创建阶段。
hello.[[scope]] = [
say.scopeChain,
GlobalExecutionContext.VO
]
在执行hello()
时,进入执行阶段
hello.ExecutionContext = {
AO : {
arguments : [...]
},
scopeChain : [
hello.ExecutionContext.AO,
say.scopeChain,
GlobalExecutionContext.VO
]
}
参考博文:
- js 中的活动对象 与 变量对象 什么区别? - JavaScript - 知乎
- 深入理解JavaScript系列(12):变量对象(Variable Object) - 汤姆大叔 - 博客园
- 深入理解JavaScript系列(14):作用域链(Scope Chain) - 汤姆大叔 - 博客园
确定this执向
this由该函数的执行环境所确定
function person = {
say : function(){
console.log(this);
}
}
person.say() // this指向person对象
var say = person.say;
say() // this指向window
参考博文:
执行时
函数的内部代码执行时,由于在执行前将函数声明(function关键字)和变量声明(var关键字)全部创建到了AO中。
因此会存在一种hoist,即作用域提升的问题。
执行后
在函数的内部代码执行后,会销毁函数的执行期上下文。
与此同时AO也将被销毁,除非有引用的情况。
function say(){
var words = "hello";
hello(); // 输出:hello
function hello(){
console.log(words);
}
return hello; // 将hello抛出
}
var hello = hello.say();
此时的作用域情况如下
window.hello.[[scope]] = [
say.scopeChain : [
say.ExecutionContext.AO,
GlobalExecutionContext.VO
]
]
由于 hello.[[scope]]
中留有 say.scopeChain
即 say.ExecutionContext.AO
的引用,所有不会被删除。