手寫call,apply和bind(分析三者的用法與區別)

它們有什麼用及區別?

在闡述它們如何使用以前,咱們有必要整理清楚this的用法,簡單的說thisJavaScript語言的一個關鍵字,它是函數運行時,在函數體內部自動生成的一個對象,只能在函數體內部使用。數組

那麼問題又來了,this的值是什麼呢?bash

由於this是在函數運行時,函數內部自動生成的一個對象,那麼接下來咱們經過函數來對this進行分析。 首先JavaScript中的函數能夠分爲兩類:app

  • 常規函數:函數聲明式,函數表達式,構造函數
  • 箭頭函數:(ES6引入使用)

接下來分別分析this在這些函數中到底是什麼?函數

理解常規函數中的this

1.純粹的函數調用學習

function test(name) {
    console.log(name)
    console.log(this)
}
test('Jerry')  //調用函數
複製代碼

以上函數調用的方式是很是常見的,然而這只是一種簡寫的形式,完整的寫法應該以下:ui

function test(name) {
    console.log(name)
    console.log(this)
}
test.call(undefined, 'Tom')
複製代碼

這裏面便出現了咱們將要學習的call,先不討論它的做用,咱們繼續討論this的用處,call方法接受的第一個參數就是this,可是咱們這裏是undefined,按照規定,若是你傳的contextnull 或者 undefined,那麼 window對象就是默認的context(嚴格模式下默認contextundefined)。this

2.對象中函數的調用spa

const obj = {
    name: 'Jerry',
    greet: function() {
        console.log(this.name)
    }
}
obj.greet()  //第一種調用方法
obj.greet.call(obj) //第二種調用方法
複製代碼

從上面的例子中,咱們發現此次call方法的第一個參數爲obj,此時說明函數greet內部的this指向了obj對象,這顯而易見便知call方法的做用是改變this的指向,又由於上面兩種調用方式結果同樣可知函數的this指向能夠理解爲誰調用便指向誰。prototype

3.構造函數中的thiscode

每一個構造函數在new以後都會返回一個對象,這個對象就是this,也就是context上下文。

理解箭頭函數中的this

在使用箭頭函數的時候,箭頭函數會默認綁定外層的this值,因此在箭頭函數中this的值和外層的this是同樣的。由於箭頭函數沒有this,因此須要經過查找做用域鏈來肯定this的值。

這就意味着若是箭頭函數被非箭頭函數包含, this綁定的就是最近一層非箭頭函數的 this

注意:多層對象件套裏面的this是和最外層保持一致的。

由於今天的重點是講解callapplybind的用法及實現,然而箭頭函數是沒有這些方法的,因此箭頭函數的使用僅限於此。

首先說明callapply是ES5中的語法,bind是ES6新引入的,它們三者的類似之處爲:

  • 都是用來改變函數的this對象的指向
  • 第一個參數都是this要指向的對象
  • 均可以利用後續參數進行傳參

不一樣之處使用一個例子進行說明:

const personOne = {
    name: "張三",
    age: 12,
    say: function () {
        console.log(this.name + ',' + this.age);
    }
}

const personTwo = {
    name: "李四",
    age: 24
}

personOne.say();    //張三,12
複製代碼

對於以上的結果,咱們應該都很是清楚,那麼問題來了,若是咱們想要知道personTwo對象的信息如何實現呢?

分別使用callapply以及bind方法實現,並從中獲得它們三者的區別:

personOne.say.call(personTwo);       //李四,24
personOne.say.apply(personTwo);      //李四,24
personOne.say.bind(personTwo);       //沒有輸出任何東西
複製代碼

修改以上代碼對比可知:callapply都是對函數的直接調用,而bind方法返回的仍然是一個函數,所以咱們須要執行它纔會有結果。

personOne.say.call(personTwo);       //李四,24
personOne.say.apply(personTwo);      //李四,24
personOne.say.bind(personTwo)();       //李四,24
複製代碼

接着繼續討論其他參數

const personOne = {
    name: "張三",
    age: 12,
    say: function (gender, phone) {
        console.log(this.name + ',' + this.age + ',' + gender + ',' + phone);
    }
}

const personTwo = {
    name: "李四",
    age: 24
}

personOne.say("女", "123");
複製代碼

這個例子的區別於上面的即爲say函數須要傳遞參數,咱們分別使用這三種方法實現傳遞參數:

personOne.say.call(personTwo, "女", "123");       //李四,24,女,123
personOne.say.apply(personTwo, ["女", "123"]);    //李四,24,女,123
personOne.say.bind(personTwo, "女", "123")();     //李四,24,女,123
複製代碼

顯而易見的區別callbind除了第一個參數外,以後的參數均爲一一傳遞,而apply除了第一個參數外,只有一個參數即爲一個數組,數組中的每一項爲函數須要的參數。

說明它們的用法以及區別以後,咱們就要本身嘗試着剖析它的原理,本身書寫這三個方法啦~~~~~

call實現

在知道了它的使用即原理以後,想必直接看實現方法應該也能夠理解的,那麼先上代碼:

Function.prototype.myCall = function (obj) {
    const object = obj || window; //若是第一個參數爲空則默認指向window對象
    let args = [...arguments].slice(1); //存放參數的數組
    object.func = this;
    const result =  object.func (...args);
    delete object.func; //記住最後要刪除掉臨時添加的方法,不然obj就平白無故多了個fn
    return result;
}
複製代碼

代碼很是簡短,一步步進行說明解釋:

由於call方法是每個函數都擁有的,因此咱們須要在Function.prototype上定義myCall,傳遞的參數obj即爲call方法的第一個參數,說明this的指向,若是沒有該參數,則指向默認爲window對象。

args爲一個存放除第一個參數之外的其他參數的數組(arguments爲函數中接收到的多有參數,[...arguments]能夠將arguments類數組轉換爲真正的數組,詳細講解能夠查看ES6語法)。

解釋object.func=this以前,咱們先使用示例使用一下本身定義的myCall函數:

const personOne = {
    name: "張三",
    age: 12,
    say: function (gender, phone) {
        console.log(this.name + ',' + this.age + ',' + gender + ',' + phone);
    }
}

const personTwo = {
    name: "李四",
    age: 24
}

Function.prototype.myCall = function (obj) {
    const object = obj || window; //若是第一個參數爲空則默認指向window對象
    let args = [...arguments].slice(1); //存放參數的數組
    object.func = this;
    const result =  object.func (...args);
    delete object.func; //記住最後要刪除掉臨時添加的方法,不然object就平白無故多了個func
    return result;
}

personOne.say.myCall(personTwo,"女",18333669807);   //李四,24,女,18333669807
複製代碼

根據示例,咱們進行解釋,myCall裏面的this指的是personOne.say這個方法(由於myCall是一個方法,上面所說的,誰調用它,它的this便指向誰),object.func=this至關於給object這個對象克隆了一個personOne.say方法,讓object在調用這個方法,至關於object.personOne.say,達到了call的效果。(記住最後要刪除掉臨時添加的方法,不然object就平白無故多了個func

object.func (...args)裏面的參數即爲傳入的其他參數。

apply實現

經過上面的分析,想必你們應該已經基本明白了它是如何實現的了,那麼接下來實現apply就很是簡單了,由於二者的區別主要就是參數的傳遞方式不一樣,和上面同樣,先直接看一下代碼:

Function.prototype.myApply = function (obj) {
    const object = obj || window; //若是第一個參數爲空則默認指向window對象
    if (arguments.length > 1) {
        var args = arguments[1]; //存放參數的數組
    } else {
        var args = []; //存放參數的數組
    }
    object.func = this;
    const result = object.func(...args);
    delete object.func; //記住最後要刪除掉臨時添加的方法,不然obj就平白無故多了個fn
    return result;
}

personOne.say.myApply(personTwo, ["女", 24]);
複製代碼

主要區別就是獲取參數不一樣,由於apply的帶二個參數爲數組,數組中包含函數須要的各項參數值,其他內容實現myCall相同,此處就不在作解釋。

bind實現

話很少說,依舊是先上代碼

Function.prototype.myBind = function (obj) {
    const object = obj || window; //若是第一個參數爲空則默認指向window對象
    let self = this;
    let args = [...arguments].slice(1); //存放參數的數組

    return function () {
        let newArgs = [...arguments]
        return self.apply(object, args.concat(newArgs))
    }
}

personOne.say.myBind(personTwo, "女", 24)();
複製代碼

前面的知識不重複說,return function是由於bind返回的是一個函數,而且這個函數不會執行,須要咱們再次調用,那麼當咱們調用的時候,咱們依舊能夠對這個函數進行傳遞參數,即爲支持柯里化形式傳參,因此須要在返回的函數中聲明一個空的數組接收調用bind函數返回的函數時傳遞的參數,以後對兩次的參數使用concat()方法進行鏈接,調用ES5中的apply方法。

到此爲止,這三種方法的使用,區別以及實現已經都講述完了,但願這篇文章對你們有所幫助~~
相關文章
相關標籤/搜索