1. 程式人生 > >淺談JS中的高階函式

淺談JS中的高階函式

在JavaScript中,函式的功能十分強大。它們是第一類物件,也可以作為另一個物件的方法,還可以作為引數傳入另一個函式,不僅如此,還能被一個函式返回!可以說,在JS中,函式無處不在,無所不能,堪比孫猴子呀!當你運用好函式時,它能助你取西經,讓程式碼變得優雅簡潔,運用不好時,那就遭殃了,要大鬧天宮咯~
除了函式相關的基礎知識外,掌握一些高階函式並應用起來,不僅能讓JS程式碼看起來更為精簡,還可以提升效能。以下是博主總結的一些常用的、重要的高階函式,加上了一些個人見解,特此記錄下來。如果您是JS初學者,也不要被“高階”兩個字嚇到,因為文中穿插講解了一些原型、this等基礎知識,相信並不難理解。如果您是JS大牛,也可以把本文用來查漏補缺。

正文

作用域安全的建構函式

function Person(name,age){
    this.name = name;
    this.age = age;
}
var p1 = new Person("Claiyre",80);

相信您對上面的建構函式一定不陌生,但是,,如果某個粗心的程式猿呼叫這個建構函式時忘記加new了會發生什麼?

var p3 = Person("Tom",30);
console.log(p3);              //undefined
console.log(window.name);     //Tom

由於使用了不安全的建構函式,上面的程式碼意外的改變了window的name,因為this

物件是在執行時繫結的,使用new呼叫建構函式時this是指向新建立的物件的,不使用new時,this是指向window的。
由於window的name屬性是用來識別連結目標和frame的,所在這裡對該屬性的偶然覆蓋可能導致其他錯誤。

作用域安全的建構函式會首先確認this物件是正確型別的例項,然後再進行更改,如下:

function Person(name,age){
    if(this instanceof Person){
        this.name = name;
        this.age = age;
    } else {
        return new
Person(name,age); } }

這樣就避免了在全域性物件上意外更改或設定屬性。
實現這個安全模式,相當於鎖定了呼叫建構函式的環境,因此借用建構函式繼承模式可能會出現問題,解決方法是組合使用原型鏈和建構函式模式,即組合繼承。
如果您是一個JS庫或框架的開發者,相信作用域安全的建構函式一定對您非常有用。在多人協作的專案中,為了避免他們誤改了全域性物件,也應使用作用域安全的建構函式。

惰性載入函式

由於瀏覽器間的行為差異,程式碼中可能會有許多檢測瀏覽器行為的if語句。但使用者的瀏覽器若支援某一特性,便會一直支援,所以這些if語句,只用被執行一次,即便只有一個if語句的程式碼,也比沒有要快。
惰性載入表示函式執行的分支僅會執行一次,有兩種實現惰性載入的方式,第一種就是在函式第一次被呼叫時再處理函式,用檢測到的結果重寫原函式。

function detection(){
    if(//支援某特性){
        detection = function(){
            //直接用支援的特性
        }
    } else if(//支援第二種特性){
        detection = function(){
            //用第二種特性
        }
    } else {
        detection = function(){
            //用其他解決方案
        }
    }
}

第二種實現惰性載入的方式是在宣告函式時就指定適當的函式

var detection = (function(){
    if(//支援某特性){
        return function(){
            //直接用支援的特性
        }
    } else if(//支援第二種特性){
        return function(){
            //用第二種特性
        }
    } else {
        return function(){
            //用其他解決方案
        }
    } 
})();

惰性載入函式的有點是在只初次執行時犧牲一點效能,之後便不會再有多餘的消耗效能。

函式繫結作用域

在JS中,函式的作用域是在函式被呼叫時動態繫結的,也就是說函式的this物件的指向是不定的,但在一些情況下,我們需要讓某一函式的執行作用域固定,總是指向某一物件。這時怎麼辦呢?
噹噹噹~~可以用函式繫結作用域函式呀

function bind(fn,context){
    return function(){
        return fn.apply(context,arguments);
    }
}

用法:

var person1 = {
    name: "claiyre",
    sayName: function(){
        alert(this.name);
    }
}
var sayPerson1Name = bind(person1.sayName,person1);
sayPerson1Name();  //claiyre

call函式和apply函式可以臨時改變函式的作用域,使用bind函式可以得到一個綁定了作用域的函式

函式柯里化(curry)

curry的概念很簡單:只傳遞部分引數來呼叫函式,然後讓函式返回另一個函式去處理剩下的引數。可以理解為賦予了函式“載入”的能力。
許多js庫中都封裝了curry函式,具體使用可以這樣。

var match = curry(function(what,str){
    return str.match(what)
}); 

var hasNumber = match(/[0-9]+/g);
var hasSpace = match(/\s+/g)

hasNumber("123asd");       //['123']
hasNumber("hello world!");  //null

hasSpace("hello world!");  //[' '];
hasSpace("hello");         //null

console.log(match(/\s+/g,'i am  Claiyre'));  //直接全部傳參也可: [' ','  ']

一旦函式經過柯里化,我們就可以先傳遞部分引數呼叫它,然後得到一個更具體的函式。這個更具體的函式通過閉包幫我們記住了第一次傳遞的引數,最後我們就可以用這個更具體的函式為所欲為啦~

一個較為簡單的實現curry的方式:

function curry(fn){
    var i = 0;
    var outer = Array.prototype.slice.call(arguments,1);
    var len = fn.length;
    return function(){
        var inner = outer.concat(Array.prototype.slice.call(arguments));
        return inner.length === len?fn.apply(null,inner):function (){
                var finalArgs = inner.concat(Array.prototype.slice.call(arguments));
                return fn.apply(null,finalArgs);
            }
    }
}

debounce函式

debounce函式,又稱“去抖函式”。它的功能也很簡單直接,就是防止某一函式被連續呼叫,從而導致瀏覽器卡死或崩潰。用法如下:

var myFunc = debounce(function(){
    //繁重、耗效能的操作
},250);
window.addEventListener('resize',myFunc);

像視窗的resize,這類可以以較高的速率觸發的事件,非常適合用去抖函式,這時也可稱作“函式節流”,避免給瀏覽器帶來過大的效能負擔。
具體的實現時,當函式被呼叫時,不立即執行相應的語句,而是等待固定的時間w,若在w時間內,即等待還未結束時,函式又被呼叫了一次,則再等待w時間,重複上述過程,直到最後一次被呼叫後的w時間內該函式都沒有被再呼叫,則執行相應的程式碼。
實現程式碼如下:

function debounce(fn,wait){
    var td;
    return function(){
        clearTimeout(td);
        td= setTimeout(fn,wait);
    }
}

once函式

顧名思義,once函式是僅僅會被執行一次的函式。具體實現如下:

function once(fn){
    var result;
    return function(){
        if(fn){
            result = fn(arguments);
            fn = null;
        }
        return result;
    }
}

var init = once(function(){
    //初始化操作
})

在被執行過一次後,引數fn就被賦值null了,那麼在接下來被呼叫時,便再也不會進入到if語句中了,也就是第一次被呼叫後,該函式永遠不會被執行了。

還可以對上述once函式進行改進,不僅可以傳入函式,同時還可以給傳入的函式繫結作用域u,同時實現了bind和once。

function once(fn,context){
    var result;
    return function(){
        if(fn){
            result = fn.apply(context,arguments);
            fn = null;
        }
        return result;
    }
}

結語

通過以上的閱讀,不難發現很多“高階函式”的實現其實並不複雜,數十行程式碼便可搞定,但重要的是能真正理解它們的原理,在實際中適時地應用,以此效能提升,讓程式碼簡潔,邏輯清晰