1. 程式人生 > >詳解call(),apply()和bind()

詳解call(),apply()和bind()

  之前看了點es6的箭頭函式,為了搞懂箭頭函式的this,看了很多文章,也順便看了幾個繫結函式,發現很多以前沒注意的問題,收穫不少。

  之前就在網上的筆試題中看過用js實現bind()函式,沒怎麼在意,以為既然都是用來進行上下文繫結的,用call或者apply應該就能實現。現在看,我還是圖樣圖森破。

  先來講一下call()和apply()吧,對於這兩個函式,我是看自己的書學習的,學的時候沒覺的有什麼問題,但是我查了一下網上的關於call()和apply()的文章,尼瑪啊,這說都是些什麼啊!!看著真費勁。

  其實,call()和apply()就是改變函式的執行上下文,也就是this值。他們兩個是Function物件的方法,每個函式都能呼叫。他們的第一個引數就是你要指定的執行上下文,第二個用來傳遞引數(說第二個不準確,應該說第二部分,因為引數可以傳多個),也就是傳給呼叫call和apply方法的函式的引數。說白了,就是呼叫函式,但是讓它在你指定的上下文下執行,這樣,函式可以訪問的作用域就會改變。下面看點程式碼:

function apply1(num1, num2){
    return sum.apply(this, [num1, num2]);
    }
function call1(num1, num2){
    return sum.call(this, num1, num2);

    }

這裡,我們執行環境傳的是this,也就是說沒改變函式的執行上下文。這兩段程式碼,只是想告訴你call和apply的區別。

call的第二部分引數要一個一個傳,apply要把這些引數放到陣列中。這就是他們的區別,真的就這麼點區別!!!

然後,不得不說的一點:它們的第二個引數都可以傳arguments。

—————————————————————————————————————————————————————————————————————————————

下面來講bind()函式,bind()是es5中的方法,他也是用來實現上下文繫結,看它的函式名就知道。bind()和call與apply不同。bind是新建立一個函式,然後把它的上下文繫結到bind()括號中的引數上,然後將它返回。

所以,bind後函式不會執行,而只是返回一個改變了上下文的函式副本,而call和apply是直接執行函式。

下面程式碼可以反映出這點,而且也顯示了bind的用法(後面的程式碼皆取自張鑫旭大神的部落格)

var button = document.getElementById("button"),
    text = document.getElementById("text");
button.onclick = function() {
    alert(this.id); // 彈出text
}.bind(text);

但由於ie6~ie8不支援該方法,所以若想在這幾個瀏覽器中使用,我們就要模擬該方法,這也是面試常考的問題,模擬的程式碼如下:

if (!function() {}.bind) {
    Function.prototype.bind = function(context) {
        var self = this
            , args = Array.prototype.slice.call(arguments);
            
        return function() {
            return self.apply(context, args.slice(1));    
        }
    };
}
就是這段程式碼,糾正了我長久以來的一個誤區。下面來講一下這段程式碼

首先,我們判斷是否存在bind方法,然後,若不存在,向Function物件的原型中新增自定義的bind方法。

這裡面var self = this這段程式碼讓我很困擾,按理說,prototype是一個物件,物件的this應該指向物件本身,也就是prototype,但真的是這樣嗎。看看下面的程式碼:

function a(){};

a.prototype.testThis = function(){console.log(a.prototype == this);};

var b = new a();

b.testThis();//false

顯然,this不指向prototype,而經過測試,它也不指向a,而指向b。所以原型中的this值就明朗了。指向呼叫它的物件。

Array.prototype.slice.call(arguments);
接下來就是上面這段程式碼,它會將一個類陣列形式的變數轉化為真正的陣列。為啥呢,其實書上並沒有說slice還有這樣的用法,也不知道是誰發明的。slice的用法可以順便上網查一下,就能查到。但要更正一點,網上的介紹說slice有兩個引數,第一個引數不能省略。然而我不知道是我理解的問題還是咋地,上面這段程式碼tmd不就是典型的沒傳引數嗎!!!arguments是傳給call的那個上下文,前面講過,不要弄混(由於arguments自己沒有slice方法,這裡屬於借用Array原型的slice方法)。而且經過測試,若果你不給slice傳引數,那就等於傳了個0給它,結果就是返回一個和原來陣列一模一樣的副本。

這之後的程式碼就很好理解,返回一個函式,該函式把傳給bind的第一個引數當做執行上下文,由於args已經是一個數組,排除第一項,將之後的部分作為第二部分引數傳給apply,前面講過apply的用法。

如此,我們自己的這個bind函式的行為就同es5中的bind一樣了。