推薦閲讀地址
掘金 歡迎 Start
思維導圖
大家好,我是林一一。下面的這一篇是關於 JS 中 call,apply,bind 原理和模擬實現和場景的面試題文章,一起開始閲讀吧。🧐
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調用了call,fn的this指向obj,最後fn被執行;this指向的值都是引用類型,在非嚴格模式下,不傳參數或傳遞null/undefined,this都指向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.fn中fn的this指向到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 函數也可以處理基本類型比如上面的熱身1fn.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() 的區別
通過apply和call改變函數的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改變了slice的this指向arguments來遍歷輸出。
參考
JavaScript深入之call和apply的模擬實現
JavaScript深入之bind的模擬實現
MDN bind
結束
感謝閲讀到這裏,如果這篇文章能對你有一點啓發或幫助,歡迎 star, 我是林一一,下次見。