JavaScript學習筆記(十) call、apply、bind

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
}

【 閱讀更多 JavaScript 系列文章,請看 JavaScript學習筆記

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章