js 执行上下文

259 阅读4分钟

先看一段代码:

console.log(a) //会报 a is not defined 的错误
console.log(a) //会打印出undefined
var a;
console.log(a) //会打印出undefined
var a=110;

第一句报错,a未定义,很正常。第二句、第三句输出都是undefined,说明浏览器在执行console.log(a)时,已经知道了a是undefined,但却不知道a是110(第三句中)。

在一段js代码拿过来真正一句一句运行之前,浏览器已经做了一些“准备工作”,其中就包括对变量的声明,而不是赋值。变量赋值是在赋值语句执行的时候进行的。

接下来继续看一段代码

console.log(f1) //会输出 function(){}
function f1(){} //函数声明
console.log(f2) //会输出undefined
var f2=function(){} //函数表达式

我们总结一下,在“准备工作”中完成了哪些工作:

  • 变量、函数表达式——变量声明,默认赋值为undefined;
  • this——赋值;
  • 函数声明——赋值;

      这三种数据的准备情况我们称之为“执行上下文”或者“执行上下文环境”。们上面所有的例子都是在全局环境下执行的。其实,javascript在执行一个代码段之前,都会进行这些“准备工作”来生成执行上下文。这个“代码段”其实分三种情况——全局代码,函数体,eval代码。

  • 首先,全局代码是一种,这个应该没有非议,本来就是手写文本到<script>标签里面的。

  • 其次,eval代码接收的也是一段文本形式的代码。eval不常用,也不推荐大家用

  • 最后,函数体是代码段是因为函数在创建时,本质上是 new Function(…) 得来的,其中需要传入一个文本形式的参数作为函数体。函数每被调用一次,都会产生一个新的执行上下文环境。 ,另外函数在定义的时候(不是调用的时候),就已经确定了函数体内部自由变量的作用域。

var a=10
function fn(){
    console.log(a) //a是自由变量,函数在创建的时候就确定了a要取值的作用域
}
function bar(f){
    var a=20
    f() // 打印10 而不是20
}
bar(fn)

全局代码的执行上下文环境内容为

  • 普通变量(包括函数表达式),如: var a = 10; 声明(默认赋值为undefined)
  • 函数声明,如: function fn() { } 赋值
  • this 赋值

在 Web 浏览器中,全局执行环境是 window 对象,因此所有全局变量和函数都是作为 window 对象的属性和方法创建的。

如果代码段是函数体,那么在此基础上需要附加:

  • 参数 赋值
  • arguments 赋值
  • 自由变量的取值作用域 赋值

给执行上下文环境下一个通俗的定义——在执行代码之前,把将要用到的所有的变量都事先拿出来,有的直接赋值了,有的先用undefined占个空。

js预解析特殊事项

  • 带var的可以进行预解释,所以在赋值的前面执行不会报错;不带var的是不能 进行预解释的,在前面执行会报错;
console.log(num2);//->Uncaught ReferenceError:num2 is not defined 
num2=12;//不能预解释
  • 预解释的时候不管你的条件是否成立,都要把带var的进行提前的声明。
if(!("num" in  window)){
  var num=12;//这句话会被提到大括号之外的全局作用域:var num;->window.num; 
}
 console.log(num);//undefined
  • 预解释的时候只预解释”=”左边的,右边的值,不参与预解释
fn();//报错
var fn=function (){  //window下的预解释:var fn;
console.log("ok");
};
  • 自执行函数:定义和执行一起完成了。自执行函数定义的那个function在全局作用域下不进行预解释,当代码执行到这个位置的时候定义和执行一起完成了。
(function (num) {
  console.log(num);
})(100);
  • 函数体中return下面的代码虽然不再执行了,但是需要进行预解释;return后面跟着的都是我们返回的值,所以不进行预解释;
    function fn(){

        console.log(num) //可以预解析
         console.log(num2) //不能预解析
        return function(){
            var num2=200
           }
        var num=100
        }

fn()
  • 函数声明和变量声明都会被提升。但是一个值得注意的细节(这个细节可以出现在有多个“重复”声明的代码中)是函数会首先被提升,然后才是变量。在预解释的时候,如果名字已经声明过了,不需要从新的声明,但是需要重新的赋值
var fn = 13;                                       
function fn() {                                    
    console.log('ok');                               
}                                                  
fn(); // Uncaught TypeError: fn is not a function
fn();
function fn(){console.log(1);};
fn();
var fn=10;
fn();
function fn(){console.log(2);};
fn();

①一开始预解释,函数声明和赋值一起来,fn 就是function fn(){console.log(1);};遇到var fn=10;不会重新再声明,但是遇到function fn(){console.log(2);}就会从重新赋值,所以一开始fn()的值就是2 ②再执行fn();值不变还是2 ③fn重新赋值为10,所以运行fn()时报错,接下去的语句就没再执行。