模拟实现call、apply和bind

145 阅读2分钟

原文博客地址 欢迎讨论、star

这三个函数存在于函数的原型链上,callapply用来改变函数的this指向,也就是执行上下文对象。bind函数也是用来修改执行上下文对象,只是它返回一个修改后的一个新函数,不会自动执行。

call

首先看一下这样的代码:

let obj = {
  a: 1
}

function fn() {
  console.log("@@ this.a", this.a)
}

obj.fn = fn
obj.fn() // 1

如上可以看到,如果单独去执行fn函数的时候,this是指向默认执行window对象的。当把fn挂载到obj上,然后通过obj去访问的时候,fn这时候的this就会指向obj了。这种情况是基本知识,应该都是知道的。那么重点就是利用这种方式来模拟call的实现。

function call(fn, obj, ...args) {
  if (typeof fn !== 'function') {
    return
  }
  let ctx = obj || {}
  let key = Symbol()
  ctx[key] = fn
  let rst = ctx[key](...args)
  delete ctx[key]
  return rst
}
  1. 首先判断传入的fn是否为函数
  2. 如果传入的obj为空,这里其实可以默认为window,默认为一个对象是为了好让fn绑定到这个对象的某一个属性上。
  3. 使用Symbol创建一个不会重复的key
  4. fn绑定到对象上,然后执行得到结果,删除刚添加的属性,将值返回。

以上就是怎么实现call的思路了,至于apply的实现,几乎就是一样的,只是因为传入的参数有所不同,所以在执行传参的时候差别,如下:

function apply(fn, obj, args){
  ...
  if (!Array.isArray(args)) {
    args = [args]
  }
  ...
  let rst = ctx[key](...args)
  ...
}

bind

主要重点就是bind是返回一个新的函数,并且可以绑定传入参数。

function bind(fn, obj, ...args1) {
  if (typeof fn !== 'function') {
    return fn
  }
  let self = obj || {}
  return function (...args2) {
    return fn.apply(self, [].concat(args1).concat(args2))
  }
}
  1. 返回一个函数形成一个闭包,将传入的obj进行一下判断复制。
  2. 调用fnapply方法修改this指向,然后拼接两次传入的参数为一个数组。注意这里的拼接顺序

对于bind函数,网上还有对fn函数为构造函数时候的情况进行判断。主要是使用判断当使用new关键字的时候再去new fn(...args)

function bind(fn, obj, ...args1) {
  ...
  return function bindFn(...args2) {
     // 判断是否用于构造函数
     if (this instanceof bindFn) {
          return new self(...args1, ...args2)
      }
    ...
  }
}

小结

理解了这三个函数的作用,就可以利用this指向的原理去模拟这些方法,以上都用使用es6的一些方法,代码看起来简洁不少。

参考文章 awesome-coding-js