JavaScript學習之路--命名函數表達式

函數表達式和函數聲明

在ECMAjavascript中,建立函數最經常使用的兩個方法是函數表達式和函數聲明,這二者的區別並非很明顯。由於ECMA規範只明確了一點:函數聲明必須帶有標識符(identifier,也就是常說的函數名稱),而函數表達式能夠省略標識符。javascript

//函數聲明
  function 函數名稱:必須 (函數參數:可選){};
  
  //函數表達式
  function 函數名稱:可選(函數參數:可選){}

也就是說,若是沒有函數名稱,那麼確定是函數表達式,經常使用於:java

var foo = function(){};

那麼若是有函數名稱的時候,就要根據上下文來判斷了。
若是是做爲賦值表達式的一部分的話,那就是表達式。
若是是被包含在一個函數體內,或者位於程序最頂部,那麼就是聲明。json

var foo = function(){};// 函數表達式 

function foo (){}; // 函數聲明

new function(){}; // 匿名類表達式

(function(){
  function foo(){}; // 聲明 由於位於函數體內
})();

還有一種不太常見的表達式寫法,就是被括號括住的(function(){}),這是由於在括號是一個分組操做符,在它內部只能是表達式。瀏覽器

function foo(){};//函數聲明

(function foo(){}) //函數表達式,由於位於分組操做符內部

try{
    (var temp = 3);
}
catch(error){
    alert(error);
}
//這裏會拋出異常,由於在()中只能放入表達式,而var是聲明。

一樣的,在使用eval方法,將string類型的json字符串轉換爲對象的時候,使用eval('('+str +')')也是這個緣由。把str放在括號內,就可使編譯器把"{}"解析成表達式而不是代碼塊。ide

表達式和聲明還有一個很大的差別就是:聲明會在同一個做用域內,最先的表達式執行前執行解析。即便函數聲明在最後一行,也會優先解析。函數

alert(foo()); 

 function foo(){
    return "abc";
 }
 
 //這也就是爲何結果是"abc"的緣由,聲明雖然寫在下面,可是在alert表達式以前已經被執行了。
 
 // 可是若是foo不是以聲明的方式定義,而是以表達式的方式定義的話,就會出現問題了:
 
 alert(foo());
 var foo = function(){
    return "abc";
 };
 
 //Uncaught TypeError: foo is not a function 會出現這個錯誤,由於在alert調用foo的時候,foo並無被解析。

另外,雖然在條件語句內部可使用函數聲明,可是並無人測試過兼容性,所以,若是須要「重載」函數,最好仍是使用表達式:測試

if(true){
    function foo(){
    }
  }
  else{
    function foo(){
    }
  }
  //這種寫法存在必定風險,最好使用如下表達式的寫法
  var foo;
  if(true){
    foo = function(){};
  }
  else{
  }

命名函數表達式

上面說了一堆函數聲明和函數表達式的區別,如今終於言歸正傳說到正題了。能夠看到,上面函數表達式的例子裏面,都是沒有名字的。調試

//常規寫法:
var foo = function(){};

//命名函數表達式:
var foo = function f(){};
f就是這個表達式的名字,可是,它的做用域僅僅在於函數內部

var foo = function f(){
    alert(typeof f);
};
foo();
alert(f);//Uncaught ReferenceError: f is not defined

既然如此,命名還有什麼用呢?
答案就是在調試的時候,能夠很爽。。。。code

var f1 = function (){
    return "abc";
};

var f2 = function(){
    return f1() + "edf";
};

var f3 = function(){
    return f2();
};

alert(f3());

表達式不命名時的堆棧信息

命名錶達式時的堆棧信息

能夠看到,當使用命名錶達式時,Call Stack會使用該名稱。不然會自動起一個名字。能夠確定的是,當狀況複雜的時候,解析器並不會返回你指望的那個名字,這也就是爲何要使用命名錶達式的緣由。對象

早期版本下,命名函數表達式的bug

  1. 表達式標識符的做用域泄露到外部做用域,也就是說在函數外部,該標識符也是可見的
  2. 將函數表達式同時看成了函數聲明,即將函數表達式的解析也提到最前面了
  3. 命名函數表達式會建立兩個不一樣的對象。
var foo = function f(){};
alert(foo === f);// 結果是false,固然這個只有在低版本的ie瀏覽器下才會看到,正常狀況下f應該是undefined

瞭解到這些bug之後,最好的解決方案就是:當標識符不存在。
由於自己就是方便調試的,那麼代碼裏面直接無視就是最好的辦法。

替代方案

若是不想使用「命名」的方式的話,能夠還有一種簡單的方式來替代,就是在函數表達式中,定義一個函數聲明,而後將這個函數聲明返回。

var hasClassName = (function(){
    var cache = {};
    
        var _className = '(?:^|\\s+)' + className + '(?:\\s+|$)';
      var re = cache[_className] || (cache[_className] = new RegExp(_className));
      return re.test(element.className);
    } 
    
    return hasClassName;
    
 })();
相關文章
相關標籤/搜索