参考文章:
1.什么是“上下文”
先看demo
console.log(a);//undefined
console.log(b);//undefined
console.log(fun1);//fun1函数体
console.log(fun2);//undefined
var c="ccc";
fun1(c);//ccc
fun2(c);//报错:fun2不是个函数
var a="aaa";
var b;
function fun1(x){
console.log(x);
var x=100;
}
var fun2=function(x){
console.log(x);
var x=100;
}
将输出变量a语句写在定义变量a之前,运行没有报错说a变量未定义,而是打印变量a值为undefined。这说明浏览器在执行console.log(a)时,已经知道了a是undefined。所以在执行代码之前有个预编译过程,也就是执行上下文。
2.全局执行上下文环境过程
2.1执行过程:
1)把所有var方式定义的变量(包括函数)全部存入上下文,赋值为undefined; 如案例中的fun2
2)把所有函数声明方式定义的函数名和函数体存入上下文; 如案例中的fun1
3)this运行时确定(this相当于上下文)
2.2 函数重名覆盖问题:1)声明函数方式覆盖:后面的会覆盖前面的
2)后面的函数如果是表达式方式定义的,和属性var方式定义是一样的性质,值为undefined不会覆盖声明的函数体
3.全局预编译完毕后,执行代码过程
3.1所做的事情:
1)给上下文环境中的变量附上值。
(非严格模式下,如果变量在上下文环境中不存在,则往上下文环境中加入变量并赋值)
2)声明方式定义的函数不会再管了,只会处理表达式方式定义的函数
3.2 执行阶段覆盖问题
1)后赋值的变量会覆盖先赋值变量内容
2)执行阶段不会再处理声明方式定义的函数,也就是说声明方式定义函数不会覆盖任何定义的函数和变量,如下方案例
var fun="字符串变量 ";
function fun(){
console.log("声明定义函数")
};
console.log(fun);//字符串变量
var fun=function(){
console.log('变量赋值函数');
};
function fun(){
console.log("声明定义函数")
};
fun()//变量赋值函数
4.函数中执行上下文过程
function fun(a,b){
console.log(a); //1
console.log(b);//函数b的函数体
console.log(arguments);//[1,function b]
var a=20;
function b(){
console.log("funB");
}
}
fun(1,2);
4.1 执行时间:调用函数的时候。每调用一次函数,产生一个词法环境(lexical environment)-函数上下文
4.2 执行过程:
1)先初始化函数的参数到上下文环境中,保存其参数名和值
2)内部声明方式定义的函数初始化到上下文环境中
3)初始化arguments属性,此时argument的参数值会被与参数同名的声明函数覆盖,所以argument的值不一定与传进来的参数值相同,如案例中的console.log(arguments);//[1,function b]
4)内部var方式定义的变量初始化到词法环境中,赋值为undefined
4.3 预编译后,执行代码过程
同全局执行代码过程。
注意: 非严格模式下,如果函数中变量在上下文环境中不存在(即没有用var定义),则往全局上下文环境中加入变量并赋值。
5.上下文和作用域区别
作用域只是一个“地盘”,一个抽象的概念,其中没有变量。要通过作用域对应的执行上下文环境来获取变量的值。同一个作用域下,不同的调用会产生不同的执行上下文环境,继而产生不同的变量的值。所以,作用域中变量的值是在执行过程中产生的确定的,而作用域却是在函数创建时就确定了。
所以,如果要查找一个作用域下某个变量的值,就需要找到这个作用域对应的执行上下文环境,再在其中寻找变量的值。
具体参考文章《深入理解javascript原型和闭包(13)-【作用域】和【上下文环境】》中的案例