预解释(hoisting),或者有的地方翻译为变量提升,是指在当前作用域下,JS代码从上到下执行之前,浏览器会默认先把带var和function关键字的进行提前声明或者定义。
声明(declare):只声明,没有定义,如var num,此时num的默认值是undefined
定义(defined):即赋值操作。
一、var的预解释
var:预解释的时候只提前的声明,在代码执行的过程中才定义赋值。
1 var num=15;
2 console.log(num); //15
上面的代码,很显然输出15,那么看看下面的代码:
1 console.log(num); //undefined
2 var num = 15;
3 console.log(num);//15
num经过了预解释,当代码执行到第一行时,输出num的默认值undefined,执行到第二行时,才给num定义即赋值为15,执行到第三行时,输出num为15 。再看看下面的代码:
1 console.log(num);//Error:num is not defined
2 num = 12;
不带var的变量是不进行预解释的,所以代码执行到第一行就会报错。
二、function的预解释
注意,function的预解释和var的预解释是不太一样的
function:预解释的时候,声明和定义都完成了。
1 var num = 12;
2 function fn() {
3 console.log(num); //undefined
4 var num = 13;
5 console.log(num); //13
6 }
7 fn();
先进行全局作用域(window)下的预解释,var num,fn声明和定义,之后代码从上到下执行,给num赋值=12,因为fn在预解释的时候已经声明+定义过了,所以可以跳过声明和定义这段,直接到第7行的fn(),执行fn,此时fn函数执行,会形成一个私有作用域,在这个私有作用域中,又开始了预解释,var num,然后这个私有作用域中的代码从上到下执行,第一个次输出只声明未定义的num为undefined,第二次输出13 。
下面总结一下预解释的一些情况:
1.不管条件是否成立,都会进行预解释:
1 if (!("a" in window)) {
2 var a = 1;
3 }
4 console.log(a);//-->undefined
window下的预解释,var a,相当于给window增加一个属性-->window.a,,代码执行,if判断"a" in window为true,取反,所以条件不成立,输出undefined。
2.只对"="左边的进行预解释,右边的是值,不进行预解释:
1 fn();//-->undefined() Error:fn is not a function
2 var fn = function () {
3 console.log(1);
4 };
3.window下对自执行函数是不进行预解释的,自执行函数的定义和执行是一起完成的了。但是在自执行函数执行的时候,在它的私有作用域中,是要进行预解释的:
1 ;(function (num) {
2 //在私有的作用域中是要进行预解释的
3 })(100);
4.函数体中return后面的返回值不预解释,但是return后面的代码,虽然不执行,但是要预解释(有点无节操啊)
1 function fn() {
2 //私有作用域下的预解释:
3 //var num;
4 console.log(num);//undefined
5 return function () {
6
7 };
8 var num = 12;
9 }
10 fn();
5.关于重名变量和预解释
1 function a(x) {}
2 var a;
3 alert(a); //function a(x) {}
重名变量a又被声明了一次,浏览器在解释的时候发现已经有一个同名变量a了,就不再去重复声明了,并且在声明的时候也没有被赋值,则变量a还是保持原来的值不变,即弹出"function a(x) {}"
如果预解释的时候发现重名了,不重新的声明,但是需要重新的定义。(即后面发现是重名的变量,如果该重名变量只是声明没有定义时,可以忽略,但是该重名变量即声明,执行时又定义赋值的话,该变量的值就等于此时的赋值,比如下面的代码)
1 function b(x){return x*2; };
2 var b=9;
3 var b="abcd";
4 alert(b);//"abcd"
犀牛书上是这样说明的:
使用var语句多次声明一个同名变量是合法的并且也没有什么害处。如果重复声明语句中含有初始值,那么就会被按照普通的赋值表达式来处理。