JavaScript深入浅出-执行上下文
-
- 全局
-
- 函数
-
- eval
每次调用函数都是重新生成一个作用域
概念-执行上下文
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 )是一个抽象概念中的对象,它用于存储执行上下文中的:变量,函数声明,函数参数
执行上下文与变量对象
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并且存储函数中的变量和形参。
全局执行上下文(浏览器)
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拿一个变量
函数中的激活对象
VO(functionContext)===AO;
AO={
arguments:<Arg0>
};
arguments={
callee,
length,
properties-indexes
}
可以理解为 不同执行阶段下的一个不同东西…AO在函数调用的时候会有一个arguments,会放置在VO对象中,在arguments定义后AO就等于VO对象
变量初始化阶段
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了也就是函数参数,所以给你忽略掉了这个隐式声明当函数声明与参数冲突了,后者会覆盖前者比如
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中拿到.
代码执行阶段
初始化阶段
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(){});如果是立即执行的,他又会有一个自己的执行上下文
测试
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) -形参 -函数声明 -变量 -..
环境记录初始化
词法环境例子
如图,那么分析下
全局初始化阶段
AO(global){
x:undefined,
foo:<ref to func 'foo'>,
bar:undefined,
outer:null
}
全局执行阶段
AO(global)['x']=10;
AO(global)['bar']= run func 'foo'
foo初始化阶段
AO(foo){
y:20,
bar:<ref to func 'bar'>
z:undefined
outer:global //为了理解写了个outer 意思就是上层作用域
}
foo执行阶段
AO(foo)['z']=30;
AO(foo)return bar, go to global and set bar
全局执行阶段-1
AO(global)['bar']=<ref to func 'bar'>;
AO(global) bar(40)// run bar
bar初始化阶段
AO(bar){
q:40,
outer:foo
}
bar执行阶段
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)
这样整个函数就执行完毕了
带名称的函数表达式
(function A(){
A=1;
console.log(A)//function A
})()
上述说过 函数表达式不会出现在AO里面,那么这个例子
全局初始化阶段
AO(global){
outer:null
}
全局执行阶段
AO(global) run IIFE
A初始化阶段
AO(A){
A:<ref to func 'A'> //无法被修改
outer:global
}
A执行阶段
AO(A)['A']=1 //因为无法被修改 所以A还是等于本身