詳解JavaScript函式模式

trigkit4發表於2014-11-04

JavaScript設計模式的作用是提高程式碼的重用性,可讀性,使程式碼更容易的維護和擴充套件

javascript中,函式是一類物件,這表示他可以作為引數傳遞給其他函式;此外,函式還可以提供作用域。

js函式基礎部分:JavaScript學習總結(四)function函式部分

建立函式的語法

命名函式表示式

//命名函式表示式
var add = function add(a,b){
    return a+b;
};

var foo = function bar() {
    console.log(foo === bar);
};
foo();//true

可見,他們引用的是同一函式,但這隻在函式體內有效。

var foo = function bar() {};
console.log(foo === bar);//ReferenceError: bar is not defined

但是,你不能通過呼叫bar()來呼叫該函式。

var foo = (function bar() {
    console.log(foo === bar);
})();//false

函式表示式

//又名匿名函式
var add = function(a,b){
    return a+b;
};

為變數 add 賦的值是函式定義本身。這樣,add 就成了一個函式,可以在任何地方呼叫。

函式的宣告

function foo(){
    //code here
}  //這裡可以不需要分號

在尾隨的分號中,函式表示式應總是使用分號,而函式的宣告中並不需要分號結尾。

宣告式函式與函式表示式的區別在於:在JS的預編譯期,宣告式函式將會先被提取出來,然後才按順序執行js程式碼:

console.log(f1);//[Function: f1]
console.log(f2);//undefined,Javascript並非完全的按順序解釋執行,而是在解釋之前會對Javascript進行一次“預編譯”,在預編譯的過程中,會把定義式的函式優先執行

function f1(){
    console.log("I am f1");
}
var f2 = function (){
    console.log("I am f2");
};

由於宣告函式都會在全域性作用域構造時候完成,因此宣告函式都是window物件的屬性,這就說明為什麼我們不管在哪裡宣告函式,宣告函式最終都是屬於window物件的原因了。

javascript語言裡任何匿名函式都是屬於window物件。在定義匿名函式時候它會返回自己的記憶體地址,如果此時有個變數接收了這個記憶體地址,那麼匿名函式就能在程式裡被使用了,因為匿名函式也是在全域性執行環境構造時候定義和賦值,所以匿名函式的this指向也是window物件

var f2 = function (){
    console.log("I am f2");
};
console.log(f2());//I am f2

(function(){
   console.log(this === window);//true
})();

函式宣告與表示式

函式的提升(hoisting)

函式宣告的行為並不等同於命名函式表示式,其區別在於提升(hoisting)行為,看下面例子:

<script type="text/javascript">
    //全域性函式
    function foo(){alert("global foo!");}
    function bar(){alert('global bar');}
    
    function hoist(){
        console.log(typeof foo);//function
        console.log(typeof bar);//undefined
        
        foo();//local foo!
        bar();//TypeError: 'undefined' is not a function  

        //變數foo以及實現者被提升
        function foo(){
            alert('local foo!');
        }
        
        //僅變數bar被提升,函式實現部分 並未被提升
        var bar = function(){
            alert('local bar!');
        };
    }
    hoist(); 
</script>

對於所有變數,無論在函式體的何處進行宣告,都會在內部被提升到函式頂部。而對於函式通用適用,其原因在於函式只是分配給變數的物件。

提升,顧名思義,就是把下面的東西提到上面。在JS中,就是把定義在後面的東西(變數或函式)提升到前面中定義。 從上面的例子可以看出,在函式hoist內部中的foobar移動到了頂部,從而覆蓋了全域性foobar函式。區域性函式barfoo的區別在於,foo被提升到了頂部且能正常執行,而bar()的定義並沒有得到提升,僅有它的宣告被提升,所以,當執行bar()的時候顯示結果為undefined而不是作為函式來使用。

即時函式模式

函式也是物件,因此它們可以作為返回值。使用自執行函式的好處是直接宣告一個匿名函式,立即使用,省得定義一個用一次就不用的函式,而且免了命名衝突的問題,js中沒有名稱空間的概念,因此很容易發生函式名字衝突,一旦命名衝突以最後宣告的為準。

模式一:

<script>
    (function () {
        var a = 1;
        return function () {
            alert(2);
        };
    }()());//彈出2,第一個圓括號自執行,第二個圓括號執行內部匿名函式
</script>

模式二:自執行函式變數的指向

<script type="text/javascript">
        var result = (function () {
            return 2;
        })();//這裡已執行了函式
 
        alert(result);//result 指向了由自執行函式的返回值2;如果彈出result()會出錯
</script>

模式三:巢狀函式

<script type="text/javascript">
        var result = (function () {
            return function () {
                return 2;
            };
        })();
 
 alert(result());//alert(result)的時候彈出function(){return 2}
</script>

模式四:自執行函式把它的返回值賦給變數

    var abc = (function () {
            var a = 1;
            return function () {
                return ++a;
            }
        })();//自執行函式把return後面的函式返回給變數
   alert(abc());//如果是alert(abc)就會彈出return語句後面的程式碼;如果是abc(),則會執行return後面的函式

模式五:函式內部執行自身,遞迴

// 這是一個自執行的函式,函式內部執行自身,遞迴
function abc() { abc(); }

回撥模式

回撥函式:當你將一個函式write()作為一個引數傳遞給另一個函式call()時,那麼在某一時刻call()可能會執行(或者呼叫)write()。這種情況下,write()就叫做回撥函式(callback function)

非同步事件監聽器

回撥模式有許多用途,比如,當附加一個事件監聽器到頁面上的一個元素時,實際上是提供了一個回撥函式的指標,該函式將會在事件發生時被呼叫。如:

document.addEventListener("click",console.log,false);

上面程式碼示例展示了文件單擊事件時以冒泡模式傳遞給回撥函式console.log()

javascript特別適用於事件驅動程式設計,因為回撥模式支援程式以非同步方式執行。

超時

使用回撥模式的另一個例子是,當使用瀏覽器的window物件所提供的超時方法:setTimeout()setInterval(),如:

<script type="text/javascript">
    var call = function(){
        console.log("100ms will be asked…");
    };
    setTimeout(call, 100);
</script>

庫中的回撥模式

當設計一個js庫時,回撥函式將派上用場,一個庫的程式碼應儘可能地使用可複用的程式碼,而回撥可以幫助實現這種通用化。當我們設計一個龐大的js庫時,事實上,使用者並不會需要其中的大部分功能,而我們可以專注於核心功能並提供“掛鉤形式”的回撥函式,這將使我們更容易地構建、擴充套件,以及自定義庫方法

Curry化

Curry化技術是一種通過把多個引數填充到函式體中,實現將函式轉換為一個新的經過簡化的(使之接受的引數更少)函式的技術。———【精通JavaScript】

簡單來說,Curry化就是一個轉換過程,即我們執行函式轉換的過程。如下例子:

<script type="text/javascript">
    //curry化的add()函式
    function add(x,y){
        var oldx = x, oldy = y;
        if(typeof oldy == "undefined"){
            return function(newy){
                return oldx + newy;
            };
        }
        //完全應用
        return x+y;
    }
    //測試
    typeof add(5);//輸出"function"
    add(3)(4);//7
    //建立並儲存一個新函式
    var add2000 = add(2000);
    add2000(10);//輸出2010
</script>

當第一次呼叫add()時,它為返回的內部函式建立了一個閉包。該閉包將原始的x和y值儲存到私有變數oldx和oldy中。

現在,我們將可使用任意函式curry的通用方法,如:

<script type="text/javascript">
    //普通函式
    function add(x,y){
        return x + y;
    }
    //將一個函式curry化以獲得一個新的函式
    var newadd = test(add,5);
    newadd(4);//9
    
    //另一種選擇,直接呼叫新函式
    test(add,6)(7);//輸出13
</script>

何時使用Curry化

當發現正在呼叫同一個函式時,並且傳遞的引數絕大多數都是相同的,那麼該函式可能是用於Curry化的一個很好的候選引數

相關文章