前端百題斬【015】——快速手撕call、apply、bind

公衆號【執鳶者】提早解鎖3斬
寫該系列文章的初衷是「讓每位前端工程師掌握高頻知識點,爲工做助力」。這是前端百題斬的第15斬,但願朋友們關注公衆號「執鳶者」,用知識武裝本身的頭腦。

在百題斬【014】中已經簡要概述了call、apply、bind三個方法,這三者做用是相同的,都可以改變this指向,從而讓某對象能夠調用自身不具有的方法,本節將深刻理解這三者的實現原理。javascript

15.1 call()

img

15.1.1 基礎

call() 方法使用一個指定的 this 值和單獨給出的一個或多個參數來調用一個函數。其返回值是使用調用者提供的this值和參數調用該函數的返回值,若該方法沒有返回值,則返回undefined。前端

基本用法:java

function.call(thisArg, arg1, arg2, ...)
小試牛刀
function method(val1, val2) {
    return this.a + this.b + val1 + val2;
}

const obj = {
    a: 1,
    b: 2
};

console.log(method.call(obj, 3, 4)); // 10

15.1.2 實現

實現一個call函數,將經過如下幾個步驟:
  1. 獲取第一個參數(注意第一個參數爲null或undefined時,this指向window),構建對象
  2. 將對應函數傳入該對象中
  3. 獲取參數並執行相應函數
  4. 刪除該對象中函數,消除反作用
  5. 返回結果
Function.prototype.myCall = function (context, ...args) {
    // 獲取第一個參數(注意第一個參數爲null或undefined時,this指向window),構建對象
    context = context ? Object(context) : window;
    // 將對應函數傳入該對象中
    context.fn = this;
    // 獲取參數並執行相應函數
    let result = context.fn(...args);
    // 消除反作用
    delete context.fn;
    // 返回結果
    return result;
}
// ……
console.log(method.myCall(obj, 3, 4)); // 10

15.2 apply()

img

15.2.1 基礎

apply() 方法調用一個具備給定this值的函數,以及以一個數組(或類數組對象)的形式提供的參數。其返回值是指定this值和參數的函數的結果。call()apply()的區別是call()方法接受的是參數列表,而apply()方法接受的是一個參數數組數組

基本用法前端工程師

func.apply(thisArg, [argsArray])
小試牛刀
function method(val1, val2) {
    return this.a + this.b + val1 + val2;
}

const obj = {
    a: 1,
    b: 2
};

console.log(method.apply(obj, [3, 4])); // 10

15.2.2 實現

apply和call的區別主要是參數的不一樣,因此其實現步驟的call大致相似,以下所示:
Function.prototype.myApply = function (context, arr) {
    context = context ? Object(context) : window;
    context.fn = this;

    let result = arr ? context.fn(...arr) : context.fun();

    delete context.fn;

    return result;
}
// ……
console.log(method.myApply(obj, [3, 4])); // 10

15.3 bind()

img

15.3.1 基礎

bind() 方法建立一個新的函數,在 bind() 被調用時,這個新函數的 this 被指定爲 bind() 的第一個參數,而其他參數將做爲新函數的參數,供調用時使用。該函數的返回值是一個原函數的拷貝,並擁有指定的this值和初始參數。app

基本用法函數

function.bind(thisArg[, arg1[, arg2[, ...]]])
小試牛刀
function method(val1, val2) {
    return this.a + this.b + val1 + val2;
}

const obj = {
    a: 1,
    b: 2
};

const bindMethod = method.bind(obj, 3, 4);
console.log(bindMethod()); // 10

15.3.2 實現

實現一個bind函數相對較複雜一些,應該注意如下幾點:
  1. 可以改變this指向;
  2. 返回的是一個函數;
  3. 可以接受多個參數;
  4. 支持柯里化形式傳參 fun(arg1)(arg2);
  5. 獲取到調用bind()返回值後,若使用new調用(當作構造函數),bind()傳入的上下文context失效。
Function.prototype.myBind = function (context, ...args) {
    if (typeof(this) !== 'function') {
        throw new TypeError('The bound object needs to be a function');
    }

    const self = this;
    // 定義一箇中裝函數
    const fNOP = function() {};
    const fBound = function(...fBoundArgs) {
        // 利用apply改變this指向
        // 接受多個參數+支持柯里化形式傳參
        // 當返回值經過new調用時,this指向當前實例 (由於this是當前實例,實例的隱士原型上有fNOP的實例(fnop);fnop instanceof fNOP爲true)
        return self.apply(this instanceof fNOP ? this : context, [...args, ...fBoundArgs]);
    }

    // 將調用函數的原型賦值到中轉函數的原型上
    if (this.prototype) {
        fNOP.prototype = this.prototype;
    }
    // 經過原型的方式繼承調用函數的原型
    fBound.prototype = new fNOP();

    return fBound;
}

1.若是以爲這篇文章還不錯,來個分享、點贊吧,讓更多的人也看到this

2.關注公衆號執鳶者,與號主一塊兒斬殺前端百題spa

相關文章
相關標籤/搜索