JS预解析

  首先我们先来说一下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

所以,括号做的事情也是一样的,消除歧义才是它真正的工作,而不是把函数作为一个整体,所以无论括号括在声明上还是把整个函数都括在里面,都是合法的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章