JavaScript 函數表達式

  JavaScript中建立函數主要有兩種方法:函數聲明和函數表達式。這兩種方式都有不一樣的適用場景。這篇筆記主要關注的是函數表達式的幾大特色以及它的使用場景,下面一一描述。ide

  主要特色

  • 可選的函數名稱

  函數名稱是函數聲明的必需組成部分,這個函數名稱至關於一個變量,新定義的函數會複製給這個變量,之後函數的調用都須要經過這個變量進行。而對於函數表達式來講,函數的名稱是可選的,例以下面的例子:模塊化

var sub = function(a1,a2){
    return a1-a2;
}

  這個例子中函數表達式沒有名稱,屬於匿名函數表達式。再看下面的例子:函數

var sub = function f(a1,a2){
    return a1-a2;
}
console.log(f(5,3));   //錯誤調用方式
console.log(sub(5,3));   //正確調用方式

  在這個例子中,函數表達式的名稱爲f,這個名稱f實際上變成了函數內部的一個局部變量,而且指代函數對象自己,在函數遞歸的時候有很大用處,後面會詳細講到。this

  • 在執行階段建立(區別於函數聲明)

   這個特色是函數表達式明顯區別於函數聲明的地方。對象

  解釋器在解析JavaScript代碼時對於這兩種方式並非一視同仁的。解釋器會首先讀取函數聲明,並使其在執行任何代碼以前可用;而對於函數表達式,則必須等到解釋器執行到它所在的代碼行,纔會被真正解析執行。例如:遞歸

console.log(add(1,2));   //"3"
console.log(sub(5,3));   //"unexpected identifier",報錯
function add(a1,a2){
    return a1+a2;
}
var sub = function(a1,a2){
    return a1-a2;
}

  第一條語句徹底能夠正常執行。對代碼求值時,JavaScript引擎在第一遍就會聲明函數並經過一個名爲函數聲明提高的過程將它們放到源代碼樹的頂部。也就是說在執行環境的建立階段(函數被調用但尚未開始執行)就會對函數聲明進行"hosting"操做。因此,即便聲明函數的代碼在調用它的代碼後面,JavaScript引擎也會把函數聲明提高到頂部。可是若是把函數聲明更改成函數表達式,就會在執行期間報錯。緣由在於在執行到函數所在的語句以前,變量sub中並不會包含對函數的引用。也就是說在代碼執行階段,變量sub纔會被賦值。除了以上不一樣,在其它方面函數聲明和函數表達式的語法是等價的。接口

  • 不會影響變量對象
var sub = function f(a1,a2){
    console.log(typeof f);  //"function"
    return a1-a2;
}
console.log(typeof f);   //"Uncaught ReferenceError: f is not defined(…)"

  經過上面的例子能夠看到,函數名稱f只能在函數對象內部使用,函數表達式的函數名稱並不存在於變量對象中。ip

  使用場景

  函數表達式的使用場景不少。下面主要描述的是函數遞歸以及代碼模塊化方面的應用。作用域

  • 函數遞歸

  看下面的例子:get

function factorial(num){
    if(num <= 1){
         return 1; 
    }else{
         return num * factorial(num - 1);
    }
}

  這是一個經典的階乘函數,可是這個例子存在的一個問題是函數名稱factorial與函數體緊密耦合在一塊兒,執行下面的語句就會報錯:

var anotherFactorial = factorial;
factorial = null;
console.log(anotherFactorial(5));   //"Uncaught TypeError: factorial is not a function"

  報錯的緣由在於在函數體內部會調用factorial函數,而變量factorial對函數的引用已經被解除因此報錯。這種狀況的解決方法通常可使用arguments.callee來解決,arguments.callee始終指向當前的函數,例如:

function factorial(num){
    if(num <= 1){
         return 1; 
    }else{
         return num * arguments.callee(num - 1);
    }
}

  這樣在此執行anotherFactorial函數就能夠獲得正確結果了。可是在嚴格模式"strict"下,arguments.callee是不能經過腳本訪問的,這是就可使用函數表達式來解決這個問題了,例如:

var factorial = (function f(num){
    if(num <= 1){
         return 1; 
    }else{
         return num * f(num - 1);
    }
});
console.log(factorial(5));   //"120"
  • 代碼模塊化

  JavaScript中沒有塊級做用域,但咱們可使用函數表達式模塊化JavaScript代碼。模塊化代碼中能夠封裝沒必要讓使用者知道的細節,只暴露給使用者相關接口,同時能夠避免對全局環境的污染,例如:

var person = (function(){
    var _name = "";
    return{
        getName:function(){
             return _name;
        },
        setName:function(newname){
             _name = newname;
        }
    };
}());
person.setName("John");
person.getName();   //"John"

  這個例子中建立了一個匿名函數表達式,這個函數表達式中包含了模塊自身的私有變量和函數;這個函數表達式的執行結果返回一個對象,對象中包含了模塊暴露給使用者的公共接口。代碼模塊化的具體形式還有不少,例如在一些經常使用的JavaScript庫中一般都會使用相似下面例子的當即執行函數:

(function(){
    var _name = "";
    var root = this;
    var person = {
        getName: function(){
            return _name;
        },
        setName: function(newname){
            _name = newname;
        }
    };
    root.person = person;
}.call(this));
person.setName("John");
person.getName();   //"John"

  這種方式直接將包含模塊公共接口的對象做爲全局對象的一個屬性,這樣在其它地方直接可使用全局對象的這個屬性來使用這個模塊了。

  這篇筆記只包含了JavaScript中函數表達式的一部份內容,可能存在不許確或錯誤的地方,但願可以指出!

相關文章
相關標籤/搜索