既然講到bind,我們就不得不說call 和 apply 。在Javascript中,涉及到函數(shù)式語言風(fēng)格的代碼,都離不開call 和apply。那么我們在講bind之前,就先好好分析一下call 和 apply。
call和 apply 方法是ECMAScript 3 給Function 的原型定義的2個方法。分別是Function.prototype.call 和 Function.prototype.apply。
1: call 和apply 的作用
call 的mdn定義: call() 方法使用一個指定的 this 值和單獨(dú)給出的一個或多個參數(shù)來調(diào)用一個函數(shù)。
apply的mdn定義:apply()方法調(diào)用一個具有給定this值的函數(shù),以及作為一個數(shù)組(或類似數(shù)組對象)提供的參數(shù)。
通過以上的定義,我們可以看出,call 和 apply 的相同點(diǎn)都是執(zhí)行一個函數(shù),并指定this。
唯一的區(qū)別就是:
- apply 函數(shù)第二個參數(shù)為一個帶下標(biāo)的集合,這個集合可以是數(shù)組,也可以是類數(shù)組。apply 把這個集合中的元素作為參數(shù)傳遞給被調(diào)用的函數(shù)。
- call 函數(shù)的參數(shù)數(shù)量不固定,從第一個參數(shù)以后, 每一個參數(shù)被依次傳入函數(shù)。
請看下面2個??
var func = function (a,b,c) {
console.log([a,b,c]) // 輸出 [1,2,3]
}
func.apply(null, [1,2,3])
var func = function (a,b,c) {
console.log([a,b,c]) // 輸出[1,2,3]
}
func.call(null, 1,2,3)
其實Javascript參數(shù)在內(nèi)部就是用一個類數(shù)組來表示的。我們可以通過arguments類數(shù)組來接受。call其實是包裝了apply上的一個語法糖。如果我們明確的知道函數(shù)接受多少個參數(shù),而且想一目了然的表達(dá)形參和實參的對應(yīng)關(guān)系,可以使用call 來傳遞參數(shù)。
有一點(diǎn)需要注意的是:當(dāng)使用call 或apply時,第一個參數(shù)為null, 函數(shù)體內(nèi)的this會指向默認(rèn)的宿主對象。
// 在瀏覽器環(huán)境下
var func = function (a,b,c) {
console.log(this === window) // true
}
func.apply(null, [1,2,3])
Math.min.apply(null, [1,2,3,34,4]) // 1
2: 說清楚apply和call ,我們開始講講bind。
bind 用來改變this的指向,并返回一個改變了this指向的新函數(shù)。
我們首先實現(xiàn)一個比較簡單的bind。
Function.prototype.bind = function (context) {
// this 指的是需要綁定的函數(shù)
let self = this
return function () {
// context 指需要綁定的this
return self.apply(context, arguments)
}
}
來一個??演示一下
let obj = {name: 'jack'}
var func = function () {
console.log(this.name) // jack
}.bind(obj)
func()
以上代碼的實現(xiàn)原理是,我們先把func函數(shù)的引用保存了起來。然后返回一個新函數(shù)。當(dāng)我們將來執(zhí)行func函數(shù)時。實際上是執(zhí)行這個返回的新函數(shù)。在新函數(shù)內(nèi)部,我們執(zhí)行了self.apply(context, arguments)這一句才是執(zhí)行了原來func函數(shù)。并指定了context為func函數(shù)體內(nèi)的this。
說清楚了原理,我們實現(xiàn)一個更完善的bind實現(xiàn):
Function.prototype.bind = function () {
let self = this
// 獲取bind第一個參數(shù),即需要綁定的對象。
context = [].shift.call(arguments)
// 獲取bind剩余的參數(shù),并轉(zhuǎn)換為數(shù)組
args = [].slice.call(arguments)
// 返回一個函數(shù),當(dāng)執(zhí)行的時候,執(zhí)行self.apply
return function () {
// arguments 是執(zhí)行返回的函數(shù)時,傳入的參數(shù)。
return self.apply(context, [].concat.call(args, [].slice.call(arguments)))
}
}
我們再給一個??演示下:
var obj = {
name: 'jack',
}
var func = function (a,b,c,d) {
console.log(this.name) // jack
console.log([a,b,c,d]) // 輸出 [1,2,3,4]
}.bind(obj, 1,2)
func(3,4)
我們用es6 來改造下這個寫法:
Function.prototype.bind = function (context, ...res) {
let self = this
// 獲取bind第一個參數(shù),即需要綁定的對象。
context = context
// 獲取bind剩余的參數(shù),并轉(zhuǎn)換為數(shù)組
args = res
// 返回一個函數(shù),當(dāng)執(zhí)行的時候,執(zhí)行self.apply
return function () {
// arguments 是執(zhí)行返回的函數(shù)時,傳入的參數(shù)。
return self.apply(context, [...args, ...arguments])
}
}
幾行代碼就可以解決,是不是簡潔了許多??