彻底搞懂call和apply的原理

297 阅读2分钟

callapply 的作用

他们都是改变this的指向,区别在于call可以传递多个参数,apply只能传递一个数组

语法

fn.call(context, param1, param2, ...)
fn.apply(context, [param1, param2, ...])

相关知识点

  • property 原型连方法
  • this 的指向问题

函数都继承于 Function.prototype,(Function.prototype 中包含 call, apply, bind等方法),
原型链如下: f ---> Function.prototype ---> Object.prototype ---> null。 property可以参考MDN this可以参考MDN

疑问

  1. 为什么我们定义的函数可以使用.(点)操作运算符来调用call或者apply方法
  2. 他们是如何实现改变this 的指向

实现call方法

// 给Function原型定义一个myCall方法
Function.prototype.myCall = function (context) {
   var context = context || window
   
   var args = [...arguments].slice(1)
   // 此处的this 其实就是 调用myCall的那个函数(谁调用myCall,this就指向谁,这句话只适用与一般情况,这里这么说是为了便于理解this,还有其他特殊情况)
   context.fn = this
   
   var result = context.fn(...args)
   
   delete context.fn
   
   return result
}

以下是对上面代码的解释:

  1. Function的原型连上定义一个myCall的方法,这样我们定义的函数就都能通过.(点)操作符来直接调用myCall这个方法
  2. 获取当前函数执行上下文
  3. 获取myCall函数除过第一个参数的剩余参数
  4. 给当前上下文添加一个fn临时属性,并将this赋值给fn,注意,context是当前执行函数的上下文,这一句的意思就是说,把要执行的函数赋值给当前执行上下文中的fn临时属性
  5. 将参数传入绑定到上下文的函数(即context.fn(...args))并执行,将执行结果赋值给变量result
  6. 因为当前上下文本身并不存在我们指定的fn,我们只是为了执行调myCall的函数,所以,在前一步将其赋值给result变量后需要删除contextfn
  7. 返回执行函数result

注意第4点中:
这里的this 指向的就是myCall的调用者,也就是fn.call(context, param1, param2)中的fn一般情况下,谁调用了这个函数,那么这个函数的this就指向谁),了解更多关于this

实现apply方法

原理同上,只是获取参数时,获取的是myApply第二个参数

// 给Function原型定义一个myApply方法
Function.prototype.myApply = function (context) {
   var context = context || window
   
   // 此处的this 其实就是 调用myCall的那个函数(谁调用myCall,this就指向谁,这句话只适用与一般情况,这里这么说是为了便于理解this,还有其他特殊情况)
   context.fn = this
   
   var result = null
   
   if (arguments[1]) {
   	result = context.fn(...args)
   } else {
   	result = context.fn()
   }
   
   delete context.fn
   
   return result
}

本文代码来源于前端进阶之道