JavaScript深入浅出-执行上下文

  • JavaScript作用域
    • 全局
    • 函数
    • eval

每次调用函数都是重新生成一个作用域

概念-执行上下文

概念-执行上下文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
console.log('EC0');
function funcEC1(){
console.log('EC1');
var funcEC2=function(){
console.log('EC2');
var funcEC3=function(){
console.log('EC3');
}
funcEC3()
}
funcEC2()
}
funcEC1();
//EC0 EC1 EC2 EC3

EC类似一个栈的结构,如图EC0是在Global下,当输出EC3的时候实际上当前是在EC3的这个作用域,等EC3调用结束完毕后退回到EC2-EC1-EC0

概念-变量对象

JS解释器如何找到我们定义的函数和变量?
变量对象(variable Object )是一个抽象概念中的对象,它用于存储执行上下文中的:变量,函数声明,函数参数

执行上下文与变量对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
activeExecutionContext={
VO:{
data_var,
data_func_declaration,
data_func_arguments
}
};
GlobalContentVO (VO===this===global)
var a=10;
function test(x){
var b=20;
test(30);
}
VO(globalContext)={
a:10,
test:<ref to function>
};
VO(test functionContext){
x:30,
b:20
}

全局VO即为global
在定义test函数作用域时,
首先会定义一个全局VO,存储全局变量,函数声明.
当调用函数的时候会再定义一个函数VO并且存储函数中的变量和形参。

全局执行上下文(浏览器)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
VO(globalContext)===[[global]];
[[global]]={
Math:<..>,
String:<..>,
isNaN:function(){[native code]},
...
...
...
window:global // applied by browser(host)
}
GlobalContentVO (VO===this===global)
String(10);//[[global]].String(10);
window.a=10;//[[global]].window.a=10
this.b=20;//[[global]].b=20

浏览器中全局中的VO即等于global,全局下有个window指向global 所以能够window.window.window.Math;
在全局中调用与声明间接的与VO去接触,在函数中去拿一个变量实际上也去函数VO拿一个变量

函数中的激活对象

1
2
3
4
5
6
7
8
9
VO(functionContext)===AO;
AO={
arguments:<Arg0>
};
arguments={
callee,
length,
properties-indexes
}

可以理解为 不同执行阶段下的一个不同东西…
AO在函数调用的时候会有一个arguments,会放置在VO对象中,在arguments定义后AO就等于VO对象

变量初始化阶段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function test(a,b){
var c=10;
function d(){};
var e=function _e(){};
(function x(){});
b=20;
}
test(10)
AO(test)={
a:10,
b:undefined,
c:undefined,
d:<ref to func 'd'>,
e:undefined
}

对于函数来讲AO和VO是一个东西
第一个阶段是变量初始化阶段.
VO按照如下顺序填充

  • 函数参数(若未传入,初始化该参数值为undefined)
  • 函数声明(若发生命名冲突,会覆盖)
  • 变量声明(初始化变量值为undefined,若发生命名冲突,会忽略)

test(10)只传递了一个10,b未传递初始化为undefined,变量初始化为undefined和变量不存在是有区别的,如果是声明了,值是undefined,alert一下会弹出undefined,如果未声明,那么alert会报错 xx is not defined.
为什么函数声明会被前置?
在变量初始化阶段也就是在执行之前会把函数声明放到VO里面
在处理变量声明中,c=10是一个赋值语句,目前只是一个初始化阶段,也就是仅仅声明一个var c并不会赋值.
当声明有冲突的时候比如 b与b=20,虽然隐式声明了,但是因为存在b了也就是函数参数,所以给你忽略掉了这个隐式声明
当函数声明与参数冲突了,后者会覆盖前者
比如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function foo(x,y,z){
function x(){};
console.log(x);
}
foo(100);// function x(){} 函数声明会覆盖
function foo(x,y,z){
function func(){};
var func;
console.log(func)
foo(100)//function func(){} 变量声明 冲突会忽略
function foo(x,y,z){
function func(){};
var func=1;
console.log(func); 第二阶段执行
}
foo(100)//1

函数表达式不会影响VO,比如var e=function _e(){},这个_e不会记录到VO里,在第二阶段会赋值给e 这样就能在VO中拿到.

代码执行阶段

代码执行阶段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
初始化阶段
AO(test){
a:10,
b:undefined,
c:undefined,
d:<ref to func 'd'>
e:undefined
}
function test(a,b){
var c=10;
function d(){};
var e=function _e(){};
(function x(){});
b=20;
}
test(10)
代码执行阶段
VO['c']=10;
VO['e']=function _e(){};
VO['b']=20;
AO(test)={
a:10,
b:20,
c:10,
d:<ref to func 'd'>,
e:function _e(){}
}

在代码执行阶段就指向了一个 var c=10; 那么相当于执行了VO[‘c’]=10;当var e=function _e(){};相当于执行了VO[‘e’]=10;
匿名函数是函数表达式 是不会影响VO 所以就忽略了
(function x(){});如果是立即执行的,他又会有一个自己的执行上下文

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
alert(x);//function 函数声明提前
var x=10;
alert(x);//10 执行阶段赋值并且覆盖了function
x=20; //重新赋值
function x(){} //声明提前
alert(x);//20
if(true){
var a=1;
}else{
var b=true;
}
alert(a);//1 因为true 所以执行阶段赋值了
alert(b);//undefined 未赋值仅定义所以是undefined

词法环境

词法环境

  • 环境记录 (VO)
    -形参
    -函数声明
    -变量
    -..

环境记录初始化

环境记录初始化

词法环境例子

环境记录初始化
如图,那么分析下

全局初始化阶段

1
2
3
4
5
6
AO(global){
x:undefined,
foo:<ref to func 'foo'>,
bar:undefined,
outer:null
}

全局执行阶段

1
2
AO(global)['x']=10;
AO(global)['bar']= run func 'foo'

foo初始化阶段

1
2
3
4
5
6
AO(foo){
y:20,
bar:<ref to func 'bar'>
z:undefined
outer:global //为了理解写了个outer 意思就是上层作用域
}

foo执行阶段

1
2
AO(foo)['z']=30;
AO(foo)return bar, go to global and set bar

全局执行阶段-1

1
2
AO(global)['bar']=<ref to func 'bar'>;
AO(global) bar(40)// run bar

bar初始化阶段

1
2
3
4
AO(bar){
q:40,
outer:foo
}

bar执行阶段

1
AO(bar) return x(find to outer...in global)+y(find to outer in foo)+z (find to outer in foo)+q(find to outer in bar)

这样整个函数就执行完毕了

带名称的函数表达式

1
2
3
4
(function A(){
A=1;
console.log(A)//function A
})()

上述说过 函数表达式不会出现在AO里面,那么这个例子

全局初始化阶段

1
2
3
AO(global){
outer:null
}

全局执行阶段

1
AO(global) run IIFE

A初始化阶段

1
2
3
4
AO(A){
A:<ref to func 'A'> //无法被修改
outer:global
}

A执行阶段

1
AO(A)['A']=1 //因为无法被修改 所以A还是等于本身