先看一段代码:
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()时报错,接下去的语句就没再执行。