一篇读懂 call、apply 和 bind 方法以及对应模拟实现🎈

113 阅读3分钟

哈喽,我是前端菜鸟JL😄 下面分享一下call、apply、bind这个专题

概念

在javascript中,这些方法都是为了改变某个函数运行时的上下文(context)

即改变函数内部this的指向

函数原型上的方法

function test() {} 
test.prototype = { 
    color: 'red', 
    say: function() { 
        console.log('my color is' + this.color) 
    } 
} 
const check = new test() 
check.say() // My color is red const 
test1 = { color: 'yellow' } 
check.say.call(test1) // My color is yellow 
check.say.apply(test1) // My color is yellow

可以看出,this被改变,当test1没有say方法时候,可以借助其他函数方法

用法

apply、call用法类似,主要区别在参数方面

  1. call()方法接受的是参数列表
  2. apply()方法接受的是一个参数数组
fn.apply(thisArg, [argsArray]) // apply
fn.call(thisArg, arg1, arg2, arg3...) // call
// 例子
const fn = function(arg1, arg2) {}
fn.call(this, arg1, arg2)
fn.apply(this, [arg1, arg2]) // 按顺序传递

参数解析:

  • thisArg:必选值,需要fn运行时改变指向的this值,在非严格模式下,该参数指定为null或undefined时会自动替换为指向全局对象
  • argsArray:可选的,一个数组或者类数组对象,即相当于实参传进fn函数中。如果该参数值为null或undefined,表示不需要传入任何参数。

bind

bind和其他两个主要区别是call、apply会立即执行函数,而bind会创建一个函数但是不会立即执行。

const obj = { a: 1 }
const fn = {
    say: function() {
        return this.x    
    }
}

console.log(foo.getX.bind(obj)()) // 1
console.log(foo.call(obj)) // 1
console.log(foo.apply(obj)) // 1

注意:bind还有个主要区别是,bind参数列表可以分多次传入,因为不是立即执行,而其他两个需要一次性传入

const arr = [1,2,3,4]
const max = Max.max.bind(null, arr[0], arr[1], arr[2])
console.log(max(arr[3]))

应用场景

数组之间追加

const arr1 = [1,2,3]
const arr2 = [4,5,6]
Array.prototype.push.apply(arr1, arr2)
arr1 // [1,2,3,4,5,6]

获取数组中的最大值或最小值

const arr = [1,2,3,4,5,6]
const MaxNum = Math.max.apply(Math, arr)
const MaxNum = Math.max.call(Math, 1, 2, 3, 4, ,5 ,6)

判断数据类型(特别常用!)

const test = Object.prototype.toString
test.call('') // '[oboject String]'
test.call(1) // '[object Number]'
test.call(true) // '[object Boolean]'
test.call(null) // '[object Null]'
test.call(undefined) '[object Undefined]'
test.call([]) 'object Array'
test.call(function() {}) 'object Function'
test.call({}) 'object Object'

手撕来了

call

思路:

  1. 将函数变成对象的属性
  2. 执行该函数
  3. 删除该函数
Function.prototype.newCall = function (context) {
    // 判断传入this,为null/undefined时要赋值为window或global
    // 浏览器为window、其他环境(node等)为global
    if (!context) {
        context = typeof window === 'undefined' ? global : window    
    }
    // 将调用call的函数变成需要修改this指向的对象的属性
    context.fn = this // this获取调用call的函数
    const rest = [...arguments].slice(1) // 获取参数
    const result = context.fn(...rest) // 隐式绑定,传入参数
    delete context.fn
    return result
}

apply

apply和call类似,区别在于参数

Function.prototype.applytest = function (context, rest) {
    if (!context) {
        context = typeof window === 'undefined' ? global : window            
    }
    context.fn = this
    let result
    if (rest === null || rest === undefined) { // 不是类数组或者数组不能...扩展运算符
        result = context.fn()    
    } else {
        result = context.fn(...rest)    
    }
    delete context.fn
    return result
}

bind

区别于其他两个:

  • 创建的是一个新函数,不会立即执行
  • bind参数列表可以分多次传入,call、apply只能一次性传入所有参数
Function.prototype.bindtest = function (context) {
    if (typeof this !== "function") {
        throw new TypeError("not a function")    
    }
    let self = this
    let args = [...argument].slice(1) // 获取第一次的参数
    function Fn() {}
    Fn.prototype = this.prototype
    const bound = function () {
        const res = [...args, ...arguments] // 拼接第二次参数
        // 注意,这里this和上面this不一样,是新方法bound的执行this
        // 两种情况,原型指向Fn(构造函数),原型不指向Fn(普通函数,this指向window)
        context = this instanceof Fn ? this : context || this
        // 通过apply改变this指向,call也行
        return self.apply(context, res)
    }
    // 原型链继承
    bound.prototype = new Fn()
    return bound
}

结语

希望能给你带来帮助✨~

分享不易,点赞鼓励🤞