學好js,這些js函式概念一定要知道

weixin_33728268發表於2016-09-27

函式建立方式###

1.宣告方式 例如:function consoleTip (){ console.log("tip!"); }
 2.表示式方式 例如:var consoleTip = function(){ console.log("tip!"); }

兩種方式的區別
 1.表示式方式適合用來定義只使用一次的函式,宣告方式定義的函式沒有這個限制,當然也不是絕對的,這個區別只適用於編碼規範上;
 2.宣告方式定義的函式可以在函式呼叫之前定義也可以在函式呼叫之後定義,而表示式方式定義的函式只能在函式呼叫之前定義;

函式引數###

函式引數包括形參,實參,形參就是函式定義時的引數;實參就是函式呼叫時傳入的引數。由於js是弱型別語言,所以js函式的形參不指定型別。

js函式的形參和實參個數可以不一致。形參個數小於實參時,未傳的形參值都是undefined,注意形參可以指定預設值,但是隻能在函式體內部指定;形參個數小於實參時,在函式體內引用多餘實參,必須通過實參物件arguments來獲取實參,在函式體內部arguments就是實參物件的引用,並且此時的實參物件是一個陣列物件,陣列物件每一項對應著函式呼叫時傳入的引數。

PS:實參物件有兩個特殊屬性callee和caller,其中callee屬性代指當前正在執行的函式,caller屬性代指呼叫當前正在執行的函式的函式,caller屬性不是標準屬性,不是所有瀏覽器都支援。使用callee屬性的典型例子就是匿名函式的遞迴呼叫,例如定義一個階乘函式:

var fact = function(x){ 
    if(x <= 1) {
        return 1;
    }else{
        return arguments.callee(x-1)*x;
    }
};

函式作用域###

在函式中宣告的變數(包括函式形參)在整個函式體內都是可見的,包括巢狀的函式中,在函式外部是不可見的;函式體內部定義的變數會覆蓋同名的全域性變數;

函式作用域中有個特性很重要,就是“宣告提前”,意思就是在函式內部任意位置宣告的變數,在函式體內部任意位置都是可見的,這是因為js引擎在預編譯js時會把函式中所有的變數宣告都提前至函式體頂部。
例如:

var scope = "outter";
!function(){
    console.log(scope);  //undefined
    var scope = "inner";
    console.log(scope); //inner
}();
console.log(scope);  //outter

說明
undefined 由於函式作用域的宣告提前特性,這裡的scope已經在函式頂部宣告,但是沒有被賦值,所以scope值為undefined
inner scope在函式體內部宣告,並且有賦值
outter  函式體內部定義的變數會覆蓋同名的全域性變數,但是不影響全域性變數的值

建構函式##

建構函式的用處就是用來初始化新建立的物件,例子:var ary = new Array();

建構函式與普通函式的區別
 1.函式命名上有區別,建構函式命名時通常是首字元大寫,普通函式命名時首字元小寫;
 2.呼叫方式的區別,建構函式是通過new關鍵字呼叫,而普通函式直接呼叫。

立即執行函式###

把函式定義和函式執行結合到一起就是立即執行函式,也叫自執行函式。

這裡需要注意兩點:1.函式定義僅限於表示式方式定義的函式;2.函式執行實際上就是對函式表示式做一次運算,所以一元運算子都可以讓函式執行。

這樣的話,立即執行函式就會有多種寫法:

(function(){console.log("IIFE");}());
(function(){console.log("IIFE");})();
!function(){console.log("IIFE");}();
void function(){console.log("IIFE");}();
~function(){console.log("IIFE");}();
....

立即執行函式可以接受引數,上面的寫法都是可以的,但是編碼規範推薦第一種寫法,jQuery庫使用的就是第一種寫法。

那麼自執行函式的用處有哪些呢?總結起來常用也就兩種:1.儲存引數上下文環境2.作為名稱空間

用處1的適用場景:迴圈中執行非同步函式,並且函式引數隨迴圈變化。

/**
* 例項一
* 錯誤寫法,因為jQuery的post方法是非同步的,迴圈十次,post方法並行跑十次,
* 迴圈比post方法執行要快,最終傳過去的i值都變成了10,即服務端收到index的都是10
*/
for(var i=0; i<10; i++){
    $.post(url,{index:i},function(){});
}
/**
* 正確寫法,這樣對於迴圈體中的立即執行函式來說,每次迴圈得到的引數都不同。立即執行函式
* 每執行一次都會建立一個函式上下文環境,在這個上下文環境中的變數值不受外界影響,
* 迴圈十次就會建立十個上下文環境,並且每個上下文環境的i值都不一樣。這樣的話,
* 雖然post方法是非同步方法,但是是在每一個上下文環境中執行的,也就是說迴圈十次,
* post方法在十個上下文環境中分別執行了一次,post方法中使用的index引數每次都不一樣,
* 最終服務端收到的index值就是從0到9十個數值
*/
for(var i=0; i<10; i++){
    (function(index){$.post(url,{index:index},function(){});}(i));
}

/**
* 例項二   
* 錯誤寫法,最終會輸出十個10,因為迴圈體的語句會延時執行
*/
for(var i=0; i<10; i++){
    setTimeout(function(){console.log(i);},100);
}
        
//正確寫法,最終會輸出0到9十個數值,原理同上
for(var i=0; i<10; i++){
    (function(x){setTimeout(function(){console.log(x);},100);}(i));
}

用處2的適用場景:你需要寫一個公共模組,這個公共模組在很多地方都會使用,但是要保證公共模組中使用的變數和函式不會對其它模組造成汙染,這樣的話這個公共模組就需要一個單獨的不同於其它模組的名稱空間。

案例1:建立jQuery外掛,保證建立的jQuery外掛在jQuery的名稱空間內都是有效的,這樣每個jQuery物件才可以使用。

(function($){
    $.fn.changeStyle = function(colorStr){
        this.css("color",colorStr);        
        return this;
    }
}(jQuery));

更多jQuery外掛知識,請參見這篇文章

案例2:建立一個帶有私有變數和私有方法的物件。

var obj = (function(){
    var privateAttr,
        publicAttr;     
    function _setPriAttr(){
        privateAttr = "private";
    }       
    function getPriAttr(){
        return privateAttr;
    }       
    return {
        attr:publicAttr,            
        getAttr:function(){
            getPriAttr();
        }
    }
}());

通過這種方式建立的物件,利用立即執行函式的return語句對外暴露屬性以及方法,並且可以保證沒有對外暴露物件的屬性和方法,在物件外邊是無法訪問到的。

總結:其實用處1和用處2的原理都是一樣的,都是利用了函式作用域的概念,請仔細體會。

js閉包詳見下一篇文章,靜待!

參考資料: js權威指南

相關文章