[重點]call、apply和bind的區別以及源碼解析

程序員節,給本身準備點什麼禮物好呢?準備‘加班’吧。javascript

前言:在前端面試中,最多見的面試題前端

  1. this的指向問題?
  2. 如何改變this指向?
  3. call、apply和bind的區別以及源碼解析?

若是面試官問到this的指向問題,那麼你去引導面試官,讓他問你如何改變this指向,call、apply和bind的區別以及源碼解析,這樣主動權就掌握在本身的手中,自古深情留不住,老是套路得人心,或者你回答中提到改變this指向及call、apply和bind的區別以及源碼解析,是否是就能夠給你本身加分了。嘻嘻~java

咱們知道在javascript中call和apply以及bind均可以改變this指向,彼此之間有什麼區別呢?那麼它們是怎麼實現的呢? 首先,咱們先說下call、apply和bind的用法程序員

1、call、apply和bind的用法

  • call(this,...arguments);
  • apply(this,[arguments]);
  • bind(this, ...arguments)(...newarguments);

再聊聊區別面試

2、區別

  • call() 和apply()的第一個參數相同,就是指定的對象。這個對象就是該函數的執行上下文。數組

  • call()和apply()的區別就在於,二者之間的參數。call()在第一個參數以後的 後續全部參數就是傳入該函數的值。apply() 只有兩個參數,第一個是對象,第二個是數組,這個數組就是該函數的參數。bash

  • bind() 方法和前二者不一樣在於:bind()方法會返回執行上下文被改變的函數而不會當即執行,而前二者是直接執行該函數。他的參數和call()相同。app

最後看看源碼解析函數

3、源碼解析

一、call()源碼解析ui

Function.prototype.myCall = function(context) {
    if (typeof this !== 'function') {
        throw new TypeError('Error')
    }
    context = context || window
    // 誰調用call方法,this就指向誰
    context.fn = this  
    // 去掉全部參數中第一參數(指定的對象)
    const args = [...arguments].slice(1)
    const result = context.fn(...args)
    delete context.fn
    return result
}
複製代碼

如下是對call()實現的分析:

  1. 首先 context 爲可選參數,若是不傳的話默認上下文爲 window;
  2. 接下來給 context 建立一個 fn 屬性,並將值設置爲須要調用的函數,即this;
  3. 由於 call 能夠傳入多個參數做爲調用函數的參數,因此須要將參數剝離出來,去掉全部參數中第一參數(指定的對象);
  4. 而後調用函數並將對象上的函數fn刪除;

二、apply()源碼解析

apply 的實現和 call 相似,區別在於對參數的處理。

Function.prototype.apply = function (context) {
    if (typeof this !== 'function') {
        throw new TypeError('Error')
    }
    context = context || window
    context.fn = this
    let result
    // 處理參數和 call 有區別,apply只有兩個參數,第一個是對象,第二個是數組
    if (arguments[1]) {
        result = context.fn(...arguments[1])
    } else {
        result = context.fn()
    }
    delete context.fn
    return result
}

複製代碼

如下是對apply()實現的分析:

  1. 首先 context 爲可選參數,若是不傳的話默認上下文爲 window;
  2. 接下來給 context 建立一個 fn 屬性,並將值設置爲須要調用的函數,即this;
  3. apply第二個參數是數組,這是apply和call的區別,區別在於對參數的處理;
  4. 而後調用函數並將對象上的函數fn刪除;

三、bind()源碼解析

bind 的實現對比其餘兩個函數略微地複雜了一點,由於 bind 須要返回一個函數,須要判斷一些邊界問題,如下是 bind 的實現

Function.prototype.bind = function(context) {
    if (typeof this !== 'function') {
        throw new TypeError('Error')
    }
    const _this = this
    const args = [...arguments].slice(1)
    // 返回一個函數
    return function F() {
        // 由於返回了一個函數,咱們能夠 new F(),因此須要判斷
        if (this instanceof F) {
            // 忽略傳入的this
            return new _this(...args, ...arguments)
        }
        // 直接調用,將兩邊的參數拼接起來
        return _this.apply(context, args.concat(...arguments))
    }
}

複製代碼

如下是對bind()實現的分析:

  1. 首先 context 爲可選參數,若是不傳的話默認上下文爲 window;
  2. 接下來給 context 建立一個 fn 屬性,並將值設置爲須要調用的函數,即this;
  3. bind和call參數相同,能夠傳入多個參數做爲調用函數的參數,因此須要將參數剝離出來,去掉全部參數中第一參數(指定的對象);
  4. bind 返回了一個函數,對於函數來講有兩種方式調用,一種是直接調用,一種是經過 new的方式;
  5. 對於直接調用來講,這裏選擇了 apply 的方式實現,可是對於參數須要注意如下狀況:由於 bind 能夠實現相似這樣的代碼 f.bind(obj, 1)(2)因此咱們須要將兩邊的參數拼接起來,因而就有了這樣的實現 args.concat(...arguments);
  6. 經過 new 的方式,對於 new 的狀況來講,不會被任何方式改變 this,因此對於這種狀況咱們須要忽略傳入的this;

it's over.

結語:感謝您的閱讀,但願此篇文章對您有所幫助,以上有不足的地方,歡迎指正呀!!!文章有些內容來自 前端面試之道,若有侵權,請聯繫做者刪除,小冊很不錯喲~!最後在此佳節,祝全部程序員無BUG,向可愛的程序員們致敬!

相關文章
相關標籤/搜索