首先我们先来说一下js的解析顺序。js引擎读取一段js代码,首先会进行预解析,也就是从上往下逐行读取代码,寻找所有的var和function(这个后面会详细解释)。当预解析完成后,js引擎在从第一行开始逐行运行js代码。
JS预解析的定义:
在当前作用域下,js运行之前,会把带有var和function关键字的事先声明,并在内存中安排好。然后再从上到下执行js语句。预解析只会发生在通过var定义的变量和function上。
1.var
通过var关键字定义的变量进行预解析的时候:都是声明declare,不管它有没有赋值,都会赋值undefined。
alert(a); //undefined
var a = 1;
alert(b); //undefined
var b = function(){
}
alert(c); //undefined
var c;
只要是通过var定义的,不管是变量,还是函数,都是先赋值undefined,如果是变量,也不管变量有没有赋值,在预解析阶段,都是会被赋值为undefined,在真正执行的时候才会为它赋值。
2.function
function进行预解析的时候,不仅是声明而且还定义(define)了,但是它存储的数据的那个空间里面存储的是代码是字符串,没有任何意义。
alert(a); //弹出的是下面的function
function a(){
alert("预解析function")
}
我们知道,定义函数的方式有函数声明和函数表达式两种。解析器在向执行环境中加载数据时,对函数声明和函数表达式并非一视同仁。解析器会率先读取函数声明,并使其在执行任何代码之前可用(可以访问);至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。
alert(sum(10,10));
function sum(num1,num2)
{
return num1+num2;
}
以上代码完全可以正确执行,因为在代码开始执行之前,解析器就已经通过一个名为函数声明提升(function declaration hoisting)的过程,读取并将函数声明添加到执行环境中。对代码求值时,JavaScript引擎在第一遍会声明函数并将它们放到源代码树的顶部。所以,即使声明函数的代码在调用它的代码后面,JavaScript引擎也能把函数声明提升到顶部。如果像下面的例子所示,把上面的函数声明改为等价的函数表达式,就会在执行的时候出现错误。
alert(sum(10,10));
var sum=function(num1,num2)
{
return num1+num2;
};
以上代码会在运行的时候出现错误,原因在于函数位于一个初始化语句中,而不是一个函数声明。换句话讲,在执行到函数所在的语句之前,变量sum中不会保存有对函数的引用。
注: 在JS解析器进行预解析的时候会遵循一个原则,变量声明和函数声明重名的话会优先存储函数声明,与先后顺序无关。
提醒!!! 这里有一个特例,函数声明只能出现在程序或函数体内。从句法上讲,它们 不能出现在Block(块)({ … })中,例如不能出现在 if、while 或 for 语句中。
function f(){
console.log("outside f()");
}
+function (){
if(false){
function f(){
console.log("inside f()");
}
}
f(); // Uncaught TypeError: f is not a function
}();
此处调用f会报错,甚至连外层的f也取不到,这是为什么呢?
条件语句块中声明的函数,其效果等同于在此处声明的函数表达式。
在你这里,由于变量提升,以+开头的匿名函数里面,首先声明变量f,然后到了条件语句内部才会定义其函数,而你的条件语句是永远失败的,所以这里你只会找到未初始化的变量f,而不是函数f。所以报的错也是TypeError,一个变量怎么能通过 f(); 这种函数的方式调用呢。
报错代码相当于:
function() {
if (false) {
var f = function() { console.log("inside f()"); };
}
}
或:
var f;
if(false){
f = function(){
console.log("inside f()");
}
}
//这两段代码效果是一样的,就相当于:
if(false){
var a=1;
}
console.log(a); //undefined 而不是a is not defined,因为变量提升
总结:
Block(块) 中只能包含Statement语句, 而不能包含函数声明这样的源元素。另一方面,仔细看一看规则也会发现,唯一可能让表达式出现在Block(块)中情形,就是让它作为表达式语句的一部分。但是,规范明确规定了表达式语句不能以关键字function开头。而这实际上就是说,函数表达式同样也不能出现在Statement语句或Block(块)中(因为Block(块)就是由Statement语句构成的)。
函数声明在条件语句内虽然可以用,但是没有被标准化,也就是说不同的环境可能有不同的执行结果。所以严格来讲,或者按照新的标准,这种写法是不规范的,所以大家还是不要这样写。非要这样用的话,就写成函数表达式吧……
var foo;
if (true) {
foo = function() {
return 'first';
};
}
foo();
最后,再说一下最开始那个function前面的+ 吧。
平时我们可能对添加括号来调用匿名函数的方式更为习惯:
(function(){alert('iifksp')})() // true
//或
(function(){alert('iifksp')}()) // true
虽然上述两者括号的位置不同,不过效果完全一样。
那么加了括号,让整个语句合法做的事情只有一件,就是让一个函数声明语句变成了一个表达式。
function a(){alert('iifksp')} // undefined
这是一个函数声明,如果在这么一个声明后直接加上括号调用,解析器自然不会理解而报错:
function a(){alert('iifksp')}()
// SyntaxError: unexpected_token
因为这样的代码混淆了函数声明和函数调用,以这种方式声明的函数a,就应该以 a(); 的方式调用。
但是括号则不同,它将一个函数声明转化成了一个表达式,解析器不再以函数声明的方式处理函数a,而是作为一个函数表达式处理,也因此只有在程序执行到函数a时它才能被访问。
所以,任何消除函数声明和函数表达式间歧义的方法,都可以被解析器正确识别。比如:
!function(){alert('iifksp')}() // true
var i = function(){return 10}(); // undefined
1 && function(){return true}(); // true
1, function(){alert('iifksp')}(); // undefined
赋值,逻辑,甚至是逗号,各种操作符都可以告诉解析器,这个不是函数声明,它是个函数表达式。并且,对函数一元运算可以算的上是消除歧义最快的方式,感叹号只是其中之一,如果不在乎返回值,这些一元运算都是有效的:
!function(){alert('iifksp')}() // true
+function(){alert('iifksp')}() // NaN
-function(){alert('iifksp')}() // NaN
~function(){alert('iifksp')}() // -1
甚至下面这些关键字,都能很好的工作:
void function(){alert('iifksp')}() // undefined
new function(){alert('iifksp')}() // Object
delete function(){alert('iifksp')}() // true
所以,括号做的事情也是一样的,消除歧义才是它真正的工作,而不是把函数作为一个整体,所以无论括号括在声明上还是把整个函数都括在里面,都是合法的。