「前端每日一问(41)」手写 bind 函数

302 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第4天,点击查看活动详情

本题难度:⭐ ⭐ ⭐

答:

Function.prototype.myBind = function (context) {
  const fn = this
  const args = [...arguments].slice(1)
  return function newFn () {
    if (this instanceof newFn) { 
      return new fn(...args, ...arguments) 
    }
    return fn.call(context, ...args, ...arguments)
  }
}

或者

Function.prototype.myBind = function (context) {
  const fn = this
  const args = [...arguments].slice(1)
  return function newFn () {
    if (this instanceof newFn) { 
      return new fn(...args, ...arguments) 
    }
    return fn.apply(context, [...args, ...arguments])
  }
}

这两者的区别仅仅只是改变内部函数 this 指向时,一个用了 call、一个用了 apply,参数传递方式不同,其他都一模一样。

实现 this 指向改变和返回函数

Function.prototype.myBind = function (context) {
  const fn = this // this 为调用方法 比如:fn.bind,this 就是 fn
  return function () { // 返回一个函数,这样才能在外部调用
    return fn.call(context) // 显式改变 this 指向 context
  }
}

测试一下:

var lastName = 'xxx'
function fn() {
  console.log(this.lastName)
}
const obj = {
  lastName: 'lin'
}

fn.myBind(obj)() // 'lin'

考虑参数

因为 bind 可以实现类似这样的代码 f.bind(obj, 1)(2),所以我们需要像下面这样处理参数:

Function.prototype.myBind = function (context) {
  const fn = this
  const args = [...arguments].slice(1) // 取 myBind 函数内的参数
  return function () {
    return fn.call(context, ...args, ...arguments) // 把 myBind 函数内的参数和要返回的函数的参数拼接起来
  }
}

测试一下:

function sum(x, y, z) {
  console.log(x + y + z) 
}

sum.myBind(null, 1, 2, 3)() // 6
sum.myBind(null, 1, 2)(3)   // 6
sum.myBind(null, 1)(2, 3)   // 6

兼容 new 调用的情况

对于函数来说有两种方式调用,一种是直接调用,一种是通过 new 的方式。

既然 bind 返回一个函数,那么这个返回的函数就可以被 new 调用,如下面代码所示:

function Person (name) {
  this.name = name
}

const fn = Person.myBind()
const p = new fn('lin')
console.log('p :>> ', p)
console.log('p.name :>> ', p.name)

最终输出结果:

image.png

我们知道,构造函数中的 this 指向这个构造函数的实例,很显然,这里的输出不对,p.name 应该输出 'lin',继续改造 myBind 函数:

Function.prototype.myBind = function (context) {
  const fn = this
  const args = [...arguments].slice(1)
  return function newFn () {
    if (this instanceof newFn) { // 如果用了 new 的调用方式,this instanceof F 为 true
      return new fn(...args, ...arguments) // 这里不需要传 this
    }
    return fn.call(context, ...args, ...arguments)
  }
}

对于 new 的情况来说,this 总是指向构造函数的实例,不会被任何方式改变 this,所以对于这种情况我们需要忽略传入的 this。

function Person (name) {
  this.name = name
}

const fn = Person.myBind()
const p = new fn('lin')
console.log('p :>> ', p)
console.log('p.name :>> ', p.name)

再次执行刚才出问题的代码,这次正常了。

image.png

我们知道,如果构造函数内部返回了一个对象,实例指向这个对象,测试一下这种情况:

function Person (name) {
  this.name = name
  return {
    name: 'xxx'
  }
}

const fn = Person.myBind()
const p = new fn('lin')
console.log('p :>> ', p)
console.log('p.name :>> ', p.name)

测试结果:

image.png

至此, myBind 函数就写完了,如果还有细节可以完善,欢迎评论区补充。

结尾

阿林水平有限,文中如果有错误或表达不当的地方,非常欢迎在评论区指出,感谢~

如果我的文章对你有帮助,你的👍就是对我的最大支持^_^

你也可以关注《前端每日一问》这个专栏,防止失联哦~

我是阿林,输出洞见技术,再会!

上一篇:

「前端每日一问(40)」手写 apply 函数

下一篇:

「前端每日一问(42)」this 指向谁?