利用apply提升編程效率的方法總結

合理的使用apply/call函數可以幫助咱們極大地簡化代碼,高效地解決問題。本文嘗試總結apply方法的幾種用法,並發現規律,以便在須要時可以想到該方法。因apply和call方法的用法幾乎相同,差異僅在於參數的傳入方法,故本文僅以apply方法爲例。編程

方法介紹

apply是函數對象原型的一個方法(Function.prototype.apply),它可以改變函數在運行時的this指向,即,可以改變函數運行時的執行環境。該函數最多接受兩個參數,第一個參數指定函數運行時的this指向,決定了執行環境,第二個參數爲,將要傳入函數的參數組成的數組或者類數組對象(若是是call函數,則參數直接傳入)。設計模式

下面是一個常見的例子:數組

var dog = {
    sound: 'wang',
    makeSound: function() {
        console.log(this.sound);
    }
}

var cat = {
    sound: 'miao',
}

dog.makeSound.apply(cat);複製代碼

例子中分別定義了一個dog對象和一個cat對象,它們都有sound變量,可是dog對象有一個makeSound方法,而cat沒有,若是這裏要求cat也要可以發聲(makeSound),有兩種直觀的解決方案:bash

第一,給cat直接賦予一個makeSound方法併發

var cat = {
    sound: 'miao',
    makeSound: function() {
        console.log(this.sound);
    }
}複製代碼

第二,利用原型繼承app

var Pets = function(sound) { this.sound = sound; }
Pets.prototype.makeSound = function() { console.log(this.sound); }

var dog = new Pets('wang');
var cat = new Pets('miao');
cat.makeSound(); // miao複製代碼

可是,有了apply,一切都變得很簡單:函數

dog.makeSound.apply(cat);// miao性能

cat既不須要有本身的makeSound方法,也不須要和dog繼承自同一個父類,只要makeSound本身能用,那就能夠拿來用。
如下的全部用法,其實都是針對apply方法兩個方面的特性而來的:ui

  1. 能夠傳入this,改變函數運行時的執行環境。
  2. apply的第二個參數是函數參數組成的數組或者類數組,且被借用函數是以散列形式傳參。

幾種用途

1. 類數組借用數組方法

類數組雖然能和數組同樣使用下標索引,可是它不具備數組擁有的內置方法,沒法直接使用這些方法,幸運的是,類數組的特性決定了數組的方法也能在其對象上使用,這就給apply方法發揮的空間:this

// 錯誤例子
var addOne = function() {
    return argument.map(function(a) {return a+1;});
}
var arr = addOne(1,2,3,4) // Uncaught TypeError: arguments.map is not a function

// 正確的例子
var addOne = function(){
    var arr = Array.prototype.slice.apply(arguments);
    return arr.map(function(a){return a+1;});
}
addOne(1,2,3,4); // [2,3,4,5]
// 這裏使用call更加簡單
var addOne = function() {
    return Array.prototype.map.call(arguments, function(a){ return a+1; })
}複製代碼

上面的addOne函數將全部傳入的參數分別加1,而後組成數組返回,函數的arguments就是由參數構成的一個類數組,咱們無需取出這些參數再一個個加1,再push進數組,而是將類數組先轉化爲數組,而後使用數組的map方法。第一個錯誤的示例代表了類數組不具備數組方法,因此報錯。

在DOM操做中,如document.getElementsByClassName, document.querySelectAll等方法拿到的對象都是類數組,通常來說,只要轉化成數組類型就會極大地方便咱們的操做。

2. 求數組的最大最小值

求數組中的最大最小值操做是很常見的,可是,數組並無爲咱們實現這樣的操做,最直觀的方法就是遍歷數組,查找最大最小值,無疑,這種方法不只笨拙低效,並且性能極差。可是,若是給你的不是一個數組,而就是一些數字呢?你可能會當即想到Math.max方法。這兩種形式參數的關係剛好就符合咱們以前提到的兩個特性之二:函數要求以散列形式傳參,apply又要求以數組或類數組傳參。

Math.max.apply(null, [1,6,5,3,5]) // 6
Math.min.apply(null, [1,6,5,3,5] ) // 1複製代碼

3. 準確判斷對象類型

判斷對象類型,咱們有typeof函數可用,可是它的判斷並不可靠,好比,對數組進行typeof操做,返回的倒是"object"。而在Object的原型對象上,有一個toString方法,它做用在不一樣類型的對象上,返回特定的字符串,根據返回值能夠準確地判斷對象類型。

Object.prototype.toString.apply([]) // "[object, Array]"
Object.prototype.toString.apply({}) // "[object, Object]"
Object.prototype.toString.apply(undefined) // "[object, Undefined]"
Object.prototype.toString.apply(function(){}) // "[object, Function]"
Object.prototype.toString.apply(document.getElementsByClassName(div)) // "[object, NodeList ]"複製代碼

該判斷方法能夠支持以下類型的判斷:NodeList,Window, Object, String, Infinity, Number(NaN), Function, HTMLDocument, Undefined, Boolean。須要特別注意的是Number類型的判斷,NaNInfinity 也會被識別爲Number類型(可用以下規則判斷:1/0 === Infinity, 1/-0 === -Infinity, NaN != NaN)。這裏由於不涉及第二個參數的問題,因此使用call也徹底是能夠的。

4. 二維數組的扁平化

先來看看數組的concat方法的用法:

var a = [1,2,3];
var b = [4,5,6];
var c = a.concat(7,8,9) // [1,2,3,7,8,9]
var d = a.concat(b) // [1, 2, 3, 4, 5, 6]
var f = a.concat(7,8,b,9) //[1, 2, 3, 7, 8, 4, 5, 6, 9]
a // [1,2,3]
b // [4,5,6]複製代碼

能夠發現,concat既能夠接受數組也能夠接受散列參數,並且最終生成的結果都是同樣的,都是一個一維的數組,同時,不改變原來的數組。若是將計算f的參數組成一個數組,那麼就是一個二維數組,再結合apply接受數組做爲第二個參數的特性,就能夠實現一個二維數組的扁平化功能了:

var twoDemArr = [[1,2,3], [4,5,6], 7,8,9]
var arr = Array.prototype.concat.apply([],twoDemArr);
arr // [1,2,3,4,5,6,7,8,9]複製代碼

進一步,咱們看看那些接受散列參數的數組方法,若是結合apply會有什麼樣的做用:

push也接受散列參數,它將參數推入數組,而且改變了原數組,那麼它就能夠實現,將一個數組的元素推入另一個數組,而且改變被推入數組:

var a = [1,2,3]
var b = [4,5,6]
Array.prototype.push.apply(a, b);
a // [1,2,3,4,5,6]複製代碼

unshiftpush同樣,不過是將元素加在數組前面:

var a = [1,2,3]
var b = [4,5,6]
Array.prototype.unshift.apply(a, b);
a // [4,5,6,1,2,3]複製代碼

5. 修正內部函數的this指向

在函數內部定義的函數,若是直接調用,則該內部函數的this並不指向外層函數的this,而是指向全局執行環境,因此調用內部函數必須指明其this的指向

// 問題代碼示例
document.getElementById('div1').onclick = function() {
    alert(this.id) // div1
    var func = function() {
        alert(this.id);
    }
    func(); // undefined
}
// 正確代碼示例
document.getElementById('div1').onclick = function() {
    alert(this.id) // div1
    var func = function() {
        alert(this.id);
    }
    func.apply(this); // div1
}複製代碼

固然,在外層保存this變量,而後在內部函數定義中直接使用保存的變量也能夠達到相同的效果,可是使用apply的方法相對而言,保持了內部函數的獨立性。

6. 給既有方法打補丁

看下面一段代碼:

// 保存原函數
var originalfoo = someobject.foo;
someobject.foo = function() {
    // 在這裏添加須要在原函數調用前執行的操做
    console.log(arguments);
    // 調用原函數
    originalfoo.apply(this, arguments);
    // 在這裏添加須要在原函數調用後執行的操做
}複製代碼

上面的例子,在調用原來的方法以前或者以後,執行了新的操做,補充加強了原來的方法,並且不改變原來的操做。這也是設計模式中裝飾者模式的實現思路

舉一個應用場景:假如你從上一位開發者手中接過了一個項目,你須要在不改動原來功能的基礎上開發一個新功能,你找到了這個功能的函數位置,可是由於代碼組織很糟糕,你幾乎看不懂這段代碼作了什麼,因此也不敢輕易改動。怎麼辦?也許上面利用apply打補丁的方法值得一試。只需在調用先後添加新操做,而後按照其調用方法調用,徹底不用關心原函數的實現細節如何。

結語

善用applycall方法,能夠大幅提升咱們編程的效率,提高程序性能。這裏僅僅總結了一部分apply的用法,apply的強大之處確定遠遠不止如此,更多的用法還有待咱們進一步的發現和總結。

你有什麼好的用法,歡迎留言,我繼續補充。

相關文章
相關標籤/搜索