博客 / 詳情

返回

面試 |call, apply, bind的模擬實現和經典面試題

推薦閲讀地址

掘金 歡迎 Start

思維導圖

大家好,我是林一一。下面的這一篇是關於 JS 中 call,apply,bind 原理和模擬實現和場景的面試題文章,一起開始閲讀吧。🧐

callandapplyandbind.png

call,apply,bind 都可以改變 this 的指向

關於this 指向問題可以看看這篇 面試 | 你不得不懂的 JS this 指向

一、call 格式 [function].call([this], [param]...),一句話概括:call() 將函數的 this 指定到 call() 的第一個參數值和剩餘參數指定的情況下調用某個函數或方法。

原理:[function].call([this]),執行 call() 會將函數 [function] 中的 this 綁定到第一個參數。而函數 call() 中的 this 是來源於 [function] 的,[function] 是在 call() 函數內部執行的,是 call() 通過操控 this 來執行函數 [function],同時給 [function] 傳遞剩餘的參數。

思考

1. 熱身題1

function fn(a, b) {
    console.log(this, a, b)
}

var obj = {
    name: '林一一'
}

fn.call(obj, 20, 23)   // {name: "林一一"} 20 23

fn.call(20, 23) // Number {20} 23 undefined

fn.call()   //Window {0: global, window: …} undefined undefined     | 嚴格模式下為 undefined

fn.call(null)   //Window {0: global, window: …} undefined undefined       | 嚴格模式下為 null

fn.call(undefined)  //Window {0: global, window: …} undefined undefined     | 嚴格模式下為 undefined
fn調用了callfnthis 指向 obj,最後 fn 被執行;this 指向的值都是引用類型,在非嚴格模式下,不傳參數或傳遞 null/undefinedthis 都指向 window。傳遞的是原始值,原始值會被包裝。嚴格模式下,call 的一個參數是誰就指向誰

2. 熱身題 2

var obj1 = {
    a: 10,
    fn: function(x) {
        console.log(this.a + x)
    }
}

var obj2 = {
    a : 20,
    fn: function(x) {
        console.log(this.a - x)
    }
}

obj1.fn.call(obj2, 20) //40
稍微變量一下,原理不變obj1.fnfnthis 指向到 obj2,最後還是執行 obj1.fn 中的函數。

二、apply 和 call 基本一致

兩者唯一不同的是:apply 的除了一個this指向的參數外,第二個參數是數組[arg1, arg2...],call的第二參數是列表(arg1, arg2...)

var name = '二二'
var obj = {
    name: '林一一',
    fn: function() {
        return `${this.name + [...arguments]}`
    }
}
obj.fn.apply(window, [12, 23, 34, 56])    // "二二12,23,34,56"
apply 第二個參數接收的是數組

面試題

1. 模擬實現內置的 call(),apply()方法。

  • call 的模擬實現
模擬實現 call 需要明白 call 的原理,1. this 的指向改變,call 函數中執行調用的函數。
下面代碼參考來自 訝羽大佬的
Function.prototype.myCall = function (context, ...args){
    context = context || window
    // 這裏的 this 是指向 fn 的,通過 this 就可以獲取 fn,context 是我們的 obj,可以直接給 obj 添加一個函數屬性
    context.fn = this
    delete context.fn(...args)
    return
}

var name = '二二'
var obj = {
    name: '林一一',
}

function fn() {
    console.log(this.name, ...arguments)
}

fn.myCall(null)
fn.myCall(obj, 12, 23, 45, 567)
上面的模擬 call 其實並沒有考慮基本類型的情況,原生的 call 函數也可以處理基本類型比如上面的熱身1 fn.call(20, 23) 輸出並不會報錯。但是這裏的 myCall 會直接報錯,提供一個更加全面模擬 call 有興趣的可以看看 徹底搞懂閉包,柯里化,手寫代碼,金九銀十不再丟分!
  • apply 的模擬實現
Function.prototype.myApply = function (context, args){
    context = context || window
    context.fn = this
    delete context.fn(args)
    return
}
類似上面的模擬 call 寫法

2. call 和 apply 區別

call 方法的語法和作用與 apply 方法類似,只有一個區別,就是 call() 方法接受的是一個參數列表,而 apply() 方法接受的是一個包含多個參數的數組。
var name = '二二'
var obj = {
    name: '林一一'
}

function fn(){
    console.log(this.name, ...arguments)
}

fn.apply(obj, [12, 34, 45, 56]) //fn(12, 23, 45, 56) 林一一   12 34 45 56
要注意的是,剩餘的數組參數會以單個參數的形式傳遞給函數,fn.apply(obj, [12, 34, 45, 56]) ==> fn(12, 23, 45, 56)

三、bind

引用MDN的話: bind() 方法會創建一個新函數。當這個新函數被調用時,bind() 的第一個參數將作為它運行時的 this,之後的一序列參數將會在傳遞的實參前傳入作為它的參數。
  • 返回一個新函數,這個新函數執行時的 this 才指定到 bind 的第一個參數
  • bind 的剩餘參數,傳遞給新的函數
  • 返回後的新函數是自我調用的

小思考

1. 上面説的這個新函數是啥?

其實這個新函數就是調用 bind 的函數,bind 調用後會將調用 bind 的函數拷貝一份返回。

一個小栗子

var name = '二二'
var obj = {
    name: '林一一'
}

function fn(){
    return `${this.name} ` + [...arguments]
}

let f = fn.bind(obj, 12, 23, 45, 67, 90)
f() // "林一一 12,23,45,67,90"
上面的新函數就是 f()f() 就是 bind 拷貝函數 fn後返回的。

2. bind 是怎麼實現拷貝 fn 的?

簡單地説:通過 this 的獲取,再 return 回這個this獲取的函數,參考 call

面試題

1. bind() 和 call()、apply() 的區別

通過 applycall 改變函數的 this 指向,他們兩個函數的第一個參數都是一樣的表示要
改變指向的那個對象,第二個參數,apply 是數組,而 call 則是 arg1,arg2... 這種形式。通
bind 改變 this 作用域會返回一個新的函數,這個函數不會馬上執行

2. 模擬實現內置的 bind() 方法。

下面的代碼來自 JavaScript深入之bind的模擬實現

Function.prototype.bind2 = function (context) {

    if (typeof this !== "function") {
      throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fNOP = function () {};

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
}
模擬實現 api,最重要的是思維過程,這是 copy 不了的。

思考題

1. 求數組中的最大值和最小值

  • 使用 Math 的 max/min 求最大最小值
    let arr = [12, 45, 65, 3, 23, 11, 76, 8, 9, 56, 70]
    let max = Math.max(...arr)  // 76
    let min = Math.min(...arr)  // 3
  • 使用數組 sort 方法求最大最小值
    let arr = [12, 45, 65, 3, 23, 11, 76, 8, 9, 56, 70]
    let list = arr.sort(function(a, b) {
        return b - a
    })
    let max = list[0]   // 76
    let min = list[list.length - 1] // 3
  • 使用 apply 求數組最大值最小值
    let arr = [12, 45, 65, 3, 23, 11, 76, 8, 9, 56, 70]
    let max = Math.max.apply(null, arr) // 76
    let min = Math.max.apply(null, arr) // 3

2. 如何判斷一個數組

Object.prototype.toString.call()instanceof。特別要注意 typeof 不可以判斷數組類型
let arr = []
Object.prototype.toString.call(arr)

3.Object.prototype.toString.call() 為什麼可以用來判斷類型

因為 Object.prototype.toString() 方法會返回對象的類型字符串,輸出 "[object Object]" 其中第二個 Object 是傳入參數的構造函數。所以使用 call 就可以指定任意的值和結合 toString 將組成的構造函數類型返回來判斷類型。同樣道理換成 apply/bind 同樣也可以判斷
Object.prototype.toString.call('str')   // "[object String]"
Object.prototype.toString.call(123)   // "[object Number]"
Object.prototype.toString.call({})      //  "[object Object]"
Object.prototype.toString.call([])      //  "[object Array]"

Object.prototype.toString.apply({})      //  "[object Object]"
Object.prototype.toString.apply([])      //  "[object Array]"

var f = Object.prototype.toString.bind({})
f()     //  "[object Object]"
var fn = Object.prototype.toString.bind([])
fn()   //  "[object Array]"

4.使用 call() 實現將類數組轉化成數組

使用 call(),[].slice / Array.prototype.slice()
let array = [12, 23, 45, 65, 32]
function fn(array){
    var args = [].slice.call(arguments)
    return args[0]
}
fn(array)   // [12, 23, 45, 65, 32]
上面利用 call 改變了 slicethis 指向 arguments 來遍歷輸出。

參考

JavaScript深入之call和apply的模擬實現

JavaScript深入之bind的模擬實現

MDN bind

結束

感謝閲讀到這裏,如果這篇文章能對你有一點啓發或幫助,歡迎 star, 我是林一一,下次見。
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.