call、apply、bind 都是定義在函數原型上的,也就是說每個函數都能調用這些方法
那麼它們都有什麼作用呢?它們之間存在什麼異同呢?下面讓我們一起來探讨一下
1、call
call 可以用于改變函數的執行環境,簡單來說就是可以改變函數内部 this 的指向
使用 call 可以讓一個對象借用另外一個對象的方法,可以借此實作繼承
第一個傳入的參數是上下文執行環境,即函數運作時 this 的指向,之後傳入的參數将會直接傳遞給調用函數
在 call 調用完成後,傳回調用函數的傳回值
// 借用方法
let apple = {
color: 'red',
getColor: function() { return this.color }
}
let banana = {
color: 'yellow'
}
let color = apple.getColor.call(banana)
console.log(color)
/*
* 執行結果:
* yellow
**/
// 實作繼承
function Parent(age, name) {
this.age = age
this.name = name
this.getName = function() { return this.name }
this.setName = function(name) { this.name = name }
}
function Child(age, name) {
Parent.call(this, age, name)
}
let child = new Child(18, 'Peter')
child.setName('Steve')
let name = child.getName()
console.log(name)
/*
* 執行結果:
* Steve
**/
2、apply
apply 的作用與 call 完全一樣,都能用于改變函數的執行環境,兩者的差別僅僅在于傳入的參數
第一個參數傳入的都是上下文執行環境,即函數運作時 this 的指向,參數的差別在于之後傳入的參數
之後傳入的參數是調用函數執行所需的參數,call 是按照順序直接傳入,apply 是将參數放在數組中再傳入
// 判斷類型
let number = 0
let string = ''
let boolean = true
let object = {}
let array = []
function typeOf(value) {
return Object.prototype.toString.apply(value).slice(8, -1)
}
console.log(typeOf(number))
console.log(typeOf(string))
console.log(typeOf(boolean))
console.log(typeOf(object))
console.log(typeOf(array))
/*
* 執行結果:
* Number
* String
* Boolean
* Object
* Array
**/
// 數值求和
function addNumber() {
let isNumber = function(value) { return typeof value === 'number' }
let numbers = Array.prototype.filter.apply(arguments, [isNumber])
let sum = numbers.reduce(function(prev, curr) {
return prev + curr
})
return sum
}
let result = addNumber(1, 'a', 2, 'b', 3, 'c')
console.log(result)
/*
* 執行結果:
* 6
**/
3、bind
傳入 bind 的參數與 call 完全相同,作用也與 call 大緻一樣,但它們還是有所差別的
call 在調用後馬上執行函數,bind 不會,調用 bind 傳回一個改變了上下文的新函數,可以在需要的時候再調用
// 借用方法
let apple = {
color: 'red',
getColor: function() { return this.color }
}
let banana = {
color: 'yellow'
}
let getColorForBanana = apple.getColor.bind(banana)
console.log(getColorForBanana)
let color = getColorForBanana()
console.log(color)
/*
* 執行結果:
* ƒ () { return this.color }
* yellow
**/
// 解決回調函數 this 指向的問題
let object = {
value: 0,
asyncPrint: function() {
setTimeout(function() { console.log(this.value) }, 2000)
},
asyncPrintWithThat: function() {
let that = this
setTimeout(function() { console.log(that.value) }, 2000)
},
asyncPrintWithBind: function() {
setTimeout(function() { console.log(this.value) }.bind(this), 2000)
}
}
object.asyncPrint()
object.asyncPrintWithThat()
object.asyncPrintWithBind()
/*
* 執行結果:
* undefined
* 0
* 0
**/
4、手動實作三個函數
- call
Function.prototype.myCall = function(cxt, ...params) {
// 處理傳入的上下文執行環境
// 若為 null 或 undefined,則要轉換成全局對象
// 若為 原始值,也要轉換成對應的執行個體對象
const context = (cxt !== null && cxt !== undefined) ? Object(cxt) : window
// 建立一個臨時屬性
// 為了避免屬性沖突,這裡使用 Symbol 資料類型
const property = Symbol('property')
// 設定臨時屬性的值為調用函數
context[property] = this
// 通過對象方法調用函數,此時 this 指向 context,也就是指向了傳入的上下文對象
let result = context[property](...params)
// 調用完成之後,删除方法,避免污染傳入對象
delete context[property]
// 傳回執行結果
return result
}
- apply
Function.prototype.myApply = function(cxt, arr) {
// 處理傳入的上下文執行環境
const context = (cxt !== null && cxt !== undefined) ? Object(cxt) : window
// 建立一個臨時屬性
const property = Symbol('property')
// 設定臨時屬性的值為調用函數
context[property] = this
// 聲明執行結果
let result = null
// 用于檢測傳入的參數是否為類數組
function isArrayLike(value) {
if (value &&
typeof value === 'object' &&
isFinite(value.length) &&
value.length >= 0 &&
value.length === Math.floor(value.length) &&
value.length < 4294967296) {
return true
} else {
return false
}
}
// 是否傳入第二個參數
if (arr) {
// 第二個參數是否為類數組
if (isArrayLike(arr)) {
let params = Array.from(arr)
result = context[property](...params)
} else {
throw new TypeError()
}
} else {
result = context[property]()
}
// 調用完成之後,删除方法,避免污染傳入對象
delete context[property]
// 傳回執行結果
return result
}
- bind
Function.prototype.myBind = function(cxt, ...outerParams) {
let self = this
let bound = function(...innerParams) {
// 判斷綁定好的函數是否通過 new 調用
// 如果通過 new 調用,則綁定到 this;否則就綁定到傳入的上下文執行環境
const context = (this instanceof bound) ? this : cxt
// 通過 call 模拟實作
return self.call(context, ...outerParams, ...innerParams)
}
// 使得綁定好的函數與調用 bind 的函數處于同一原型鍊上
bound.prototype = Object.create(this.prototype)
// 傳回綁定好的函數
return bound
}