call,apply,bind详解&&手写实现

32 阅读5分钟

一、引言

在 JavaScript 中,this 的值取决于函数的调用上下文。它的指向会根据不同的调用方式发生变化。call、apply 和 bind 是三个可以显式设置 this 值的方法,它们是 Function.prototype 上的内置函数,常用于控制函数执行时的 this 指向,方便我们灵活地改变 this 的指向对象。

本文将详细介绍这三个方法的使用场景、区别以及手写实现,帮助你更好地理解 this 的绑定机制。

二、this绑定的几种方式

JavaScript 中,this 的绑定有几种常见方式,理解这些方式有助于我们更好地使用 call、apply 和 bind。

  • 默认绑定:如果函数在没有被任何东西访问的情况下被调用,将指向globalThis(在浏览器中即window对象,在node中即{})

  • 隐式绑定: 在典型的函数调用中,this通过函数的前缀隐式传递的

  • 显示绑定:通过call,apply,bind设置的this值为显示绑定

  • new绑定:通过new关键字将函数作为构造函数时,this值会绑定到正在构造的函数上

  • 箭头函数没有自己的 this,它的 this 值是从外部上下文中继承的。因此,箭头函数中的 this 绑定时机是固定的,在定义时就已确定。

三、call,apply的使用

call 和 apply 函数几乎相同,唯一的区别在于它们传递参数的方式不同

  • 函数调用

1.call与apply会以给定的this和提供的参数调用该函数,第一个参数为调用函数是要绑定this对象

function fn() {
  console.log(this)
}
fn.call({}) // {}
fn.apply({}) // {}

2.call与apply的区别在于:函数的参数以列表的形式逐个传递给 call(),而在 apply() 中它们被组合在一个对象中,通常是一个数组

function fn(num1, num2) {
  console.log(this, num1, num2)
}
fn.call({}, 1,2) // {} 1,2
fn.apply({}, [1,2]) // {} 1,2
  • 函数返回

返回函数调用之后的返回值

function fn(num1, num2) {
  return num1 + num2 
}
console.log(fn.call({}, 1,2)) // 3
console.log(fn.apply({}, [1,2])) // 3

四、bind的使用

bind 与 call 和 apply 不同,它不会立即执行函数,而是返回一个新的函数,允许你永久绑定 this 和部分参数。

  • 函数调用

    bind函数会以给定的this和提供的参数返回一个新的函数

function fn() {  
console.log(this)
}
const fn2 = fn.bind('fn2')
const fn3 = fn.bind('fn3')
fn2() // 'fn2'
fn3() // 'fn3'
  • 函数参数传递

    bind函数在传入其他参数时,会将args插入到返回返回到参数前

    function fn(num1, num2) { console.log(num1, num2) } const fn2 = fn.bind(1)fn2(2) // 1,2

五、手写实现call,apply与bind

call函数:

我们了解call函数后,要自己实现call函数时就会遇到以下问题:

  • call函数是js内置提供给函数调用的方法,要怎么让自己myCall方法能让所有的函数都能调用myCall呢?

  • call函数是怎么将函数调用时的this绑定在传入的参数呢

  • call函数怎么将传入的其余参数放入到调用函数里呢

    // 在Function的原型上挂载myCall,所有Funciton实例都可调用
    Function.prototype.myCall = funciton (_this, ...args) {
      const fn = this
      // myCall方法中的thi是绑定在fn1上的(上文提到的隐式绑定)
      _this.fn = fn  _this.fn(...args)
      // 执行完函数之后,将fn属性去除
      delete _this.fn
    }
    funciton fn1 () {
      console.log(this)
    }
    fn1.myCall({name: 'test'}) // { name: 'test' }
    

上诉代码实现之后,我们会发现有一些新的问题:

  • 如果传入的this绑定对象是number,boolean或者是其他基础类型,不存在fn的熟悉,代码即会报错,假设传入的是null 或者是 undefined,call应该函数是指向全局对象的

因此我们要添加一些异常场景的兜底

Function.prototype.myCall = funciton (_this, ...args) {
  // 对null 和 undefined进行处理
  const isGlobalValue = _this === null || _this === undefined
  // 对基础类型数据处理
  _this =  isGlobalValue ? window : Object(_this)
  const fn = this  // myCall方法中的thi是绑定在fn1上的(上文提到的隐式绑定)
  _this.fn = fn  _this.fn(...args)  delete _this.fn
}
funciton fn() {
  console.log(this)
}
fn.myCall(null) // windowfn.myCall('test') // test 此处为Object

尽管还有一些异常场景的情况未能考虑到但目前足以应对面试官了QAQ

apply:

apply函数与call函数一致,只是args传参存在差异所以只需将args传入调用函数的格式修改下即可

上诉代码第一行改为
Function.prototype.myApply = funciton (_this, arg) {
上诉代码第9行改为
_this.fn(...arg)

bind:

bind函数除了上诉问题解决和call,apply相似,还需要考虑两个问题

  • 如何返回一个已经绑定了this的函数?

  • 如何将bind传入的额外参数,与调用返回函数的参数合并在一块?

    Function.prototype.myBind = funciton (_this, ...args) { // 对null 和 undefined进行处理 const isGlobalValue = _this === null || _this === undefined // 对基础类型数据处理 _this = isGlobalValue ? window : Object(_this) const fn = this // 直接返回一个函数 return function (...arg2) { _this.fn = fn // 将arg 与arg2 合并 return _this.fn(...[...args, ...arg2]) }} funciton fn(num1, num2) { console.log(this, num1, num2) } const fn2 = fn.myBind('test', 123)fn2(456) // 'test'此处为Object 123, 456

六、总结

  • call,apply和bind都用于显式绑定 this,它们的差异在于参数传递方式。

  • call 逐个传递参数

  • apply 通过数组传递参数

  • bind 返回一个新的函数,可以在后续调用时传入参数

  • 理解这些方法的工作原理,可以帮助你更灵活地控制函数的执行上下文。

希望本文能帮助你更好地理解和掌握 call、apply 和 bind 的使用!如果有任何问题,欢迎留言讨论。