携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第14天,点击查看活动详情
之前说,在执行代码时做的预先准备动作,这个准备动作就叫对应的执行上下文。
执行上下文有三个属性: 1.变量对象 2.作用域链 3.this
1.变量对象
变量对象:变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明。
执行上下文分为:全局,函数,eval。
不同上下文的变量对象是不同的。
1)全局上下文
全局变量就是window.a=1或直接var a=1,都是全局下的。
且,在JS中,全局对象有window属性指向自身。
var a = 1;
console.log(window.a);
//两段代码的意义差不多,含义就是指向自身。
this.window.b=2;
console.log(this.b);
在全局上下文中的变量对象就是全局对象。
2)函数上下文
函数执行上下文分为:分析和执行两个阶段,即先进入到执行上下文中,代码执行。(我们用active object ,AO表示变量对象,通过arguments属性初始化)
以下根据这两个阶段做分析。
1---进入执行上下文
刚进入执行上下文中,此时没有执行代码
就是说,例如var a = 1时,由于没有执行,所以只定义了a的变量,取不到a的数值,即此时的a对应值为undefined。
例如var a = function(),同理,只定义了a,对应的仍然是undefined。
例子1
function foo(a) {
var b = 2;
function c() {}
var d = function() {};
b = 3;
}
foo(1);
以上分析:a的值为1先被创建;创建变量b,对应undefined;创建函数c;创建d,对应undefined。还有foo(1)函数执行上下文的变量对象arguments:{0:1,length:1}
例子二
function foo(a) {
var b = 2;
var d = function() {};
c = 2;
function c() {}
}
foo(1);
观察可知c被重复声明,但规定,如果变量对象已经存在相同名称的属性,则完全替换这个属性。
所以b最终的形式是函数。
例子三
function foo(a) {
var b = 2;
function c() {}
c = 2;
var d = function() {};
}
foo(1);
观察可知c被重复声明,但规定,如果变量名称跟已经生命的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。
所以c最终的形式是函数。
!!!!!!!!!!!!!!!!!!!!!!!
注意,例子二与例子三在本地代码是行不通的,因为以上的环境是在刚进入函数执行上下文中的,并没有进行代码执行,所以在本地运行log输出无效,注意注意。
!!!!!!!!!!!!!!!!!!!!!!!
总结:
变量对象会包括:
- 函数的所有形参 (如果是函数上下文)
- 由名称和对应值组成的一个变量对象的属性被创建
- 没有实参,属性值设为 undefined
- 函数声明
- 由名称和对应值(函数对象(function-object))组成一个变量对象的属性被创建
- 如果变量对象已经存在相同名称的属性,则完全替换这个属性
- 变量声明
- 由名称和对应值(undefined)组成一个变量对象的属性被创建;
- 如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性
2---代码执行
在这第二个阶段会按顺序执行代码了,给第一阶段声明的变量赋值。
function foo(a) {
var b = 2;
function c() {}
var d = function() {};
b = 3;
}
foo(1);
这时候的变量对象包括:
a=1;b=3;c:function c();d:FunctionExpression "d"
还有函数执行上下文的变量对象arguments:{0:1,length:1}
总结:
- 全局上下文的变量对象为全局对象。
- 函数执行上下文的变量对象只为Arguments对象
- 在进入执行上下文时会给变量对象添加形参,函数声明,变量声明等初始的属性值,但var变量值都为undefined。
- 在代码执行阶段,会将undefined修改为代码中设置的真实值。
2.作用域链
在执行代码查找变量时,先会在当前上下文中查找,如果没有就去父级查找,再没有,就去全局环境下查找。这个由多个执行上下文的变量对象构成的链表就叫做作用域。
函数的生命有两个过程,先被创建,然后激活被使用。
作用域链在这过程中也会有两种链表:创建和变化。
1)函数创建
函数被创建时,会自动保存父变量到自身其中,这也是可以去父级查找的原因。例如:
function f1(){
var a1 = 1;
function f2(){
var a2 = 2;
}
}
其中f2内部的变量对象 就包含了 f1的变量对象。
f1中父级作用域链有global,
f2中父级作用域链有f1层级与global。
2)函数激活
当函数被激活时,进入函数上下文,创建变量后,就会将活动对象添加到作用链的前端。
例子:
var scope = "global scope";
function checkscope(){
var scope2 = 'local scope';
return scope2;
}
checkscope();
1.(函数创建)创建了checkscope(),checkscope中父级作用域链有global.
2.(创建执行上下文并压入栈)将checkscope函数创建上下文并压入执行上下文栈中,此时栈中情况:
ESC:{
checkscopeContext,
globalContext
}
3.(刚进入执行上下文,没有执行代码)
- 创建checkcope作用域链。
- 初始化变量对象arguments:{length:0};scope2:undefined;和上一步中的checkscope作用域链。
- (函数激活)将活动对象参加到作用域链前端。
4.(代码执行)将属性值填入,即sope2:local scope,return寻找到scope2的值之后成功返回。函数执行完毕。
5.函数执行上下文从栈中弹出,此时栈中情况:
ESC:{
globalContext
}
至此,执行上下文的前两个属性分别讲解后又合并在一起,【变量对象与作用域链】,还有最后一个属性this下一篇再写吧。
参考博客:github大佬👉mqyqingfeng