结合MDN的代码手写bind

bind 是在 ES5 加入的语法,具体如何使用 MDN 都有。但有一点需要额外注意一下:bind 会返回一个新的函数。也就是说:它会修改 this 的指向,并返回新的函数体。

接下来,我来讲一下手写 bind 的思路。

首先我们要明白 bind 的用途:它是为了改变默认 this 的,如:

var obj = { a: 1 }
function yyy(a) {this.a = a}
var xxx = yyy.bind(obj)
xxx(a) = 3
console.log(obj.a) // 3
复制代码

此时 xxx 的代码实际上是下面这样的

function xxx(newA) {
   obj.a = newA 
}
复制代码

了解了如何使用,我们才能往下写,我们来为函数的原型添加一个 myBind 方法

(Function.prototype.myBind = function(otherThis) {
  // 当前的 this 是 yyy,如果 this 的类型不是函数,就返回
  if (typeof this !== 'function') {
    return
  }
  // 获取 yyy 除去第一项后的参数,也就是除去 this 的剩余参数
  var bindArgs = Array.prototype.slice.call(arguments, 1) 
  var bindArgsLength = bindArgs.length  // 这句话很重要,代码后有解释

  var fBind = this // yyy ,也就是要绑定到哪里
  var fNP = function(){}  // 中间量,以便生成的 xxx 和原来函数毫无瓜葛
  var fBound = function() {
    bindArgs.length = bindArgsLength // 必须重置 bindArgs 的长度
    bindArgs.push.apply(bindArgs, arguments) // 组合两次的数据

    return fBind.apply(otherThis, bindArgs) // 用 apply 方法为新的返回值绑定用户指定的 this
  }

  if (this.prototype) { // Function.prototype 没有 prototype
    fNP.prototype = this.prototype
  }

  fBound.prototype = new fNP() 
  
  return fBound
})()

复制代码

需要说明一下重置 bindArgs 那一句话:

因为 bindArgs 是和 fBound 同一级别的,当执行 myBind 这个函数之后,fBound 和 bindArgs 都只有一份,但是可能多次调用 myBind 返回的函数(这里是 xxx)并传参,如果不重置,我们两次调用 xxx('aaa'),那 bindArgs 的长度就变为 2 了,但我们希望两次调用不能被影响,只能受父亲 yyy 传参的影响。由于每次添加都是 push 到后面,所有每次只要恢复成以前的长度即可。

好了,我们已经大致完成了,我们已经可以在函数种使用了。

但是还有一个缺陷:不支持 new,真正的 bind 方法在使用 new 之后的 this 还是指向 new 新生成的对象。

举例来说,我们希望得到下面的效果:

var obj = { a: 1 }
function yyy(a) {this.a = a}
var xxx = yyy.myBind(obj)
var xxxxxx = new xxx('你好')
console.log(obj.a) // 1 
console.log(xxxxxx.a) // 你好
复制代码

而我们现在的代码是如何的呢? 由于我们没做判断,我们返回的函数实际还是:

function xxx(newA) {
   obj.a = newA 
}
复制代码

结果是:xxxxxx.a 未定义,而 obj.a 是你好。

如何解决?只需在执行 apply 的时候绑定更换绑定的 this 就好了 解决的关键是 this 到底指向什么,如果使用 new 操作符,那个时候的 this 就是指向新的对象

(Function.prototype.myBind = function(otherThis) {
  // 当前的 this 是 yyy,如何 this 的类型不是函数,就返回
  if (typeof this !== 'function') {
    return
  }

  var bindArgs = Array.prototype.slice.call(arguments, 1) // 获取 yyy 除去第一项后的参数
  var bindArgsLength = bindArgs.length 

  var fBind = this // yyy ,也是是要绑定到哪里
  var fNP = function(){}  // 中间量,以便生成的 xxx 和原来函数毫无瓜葛

  if (this.prototype) { // Function.prototype 没有 prototype
    fNP.prototype = this.prototype
  } // 放在前面便于理解

  var fBound = function() {
    bindArgs.length = bindArgsLength // 必须重置 bindArgs 的长度
    bindArgs.push.apply(bindArgs, arguments) // 组合两次的数据
      
      // 如果使用 new 操作符,此时 this 指向的是新生成的对象,那么 fNP.prototype.isPrototypeOf(this) === true
      // xxxxxx -> xxx -> FNP
      return fBind.apply( fNP.prototype.isPrototypeOf(this) ? this: otherThis, bindArgs) 
  }

  fBound.prototype = new fNP() 
  
  return fBound
})()
复制代码

写完了,昨天花了一个下午的时间在理解这个问题,我认为理解起来还是有点难度的,希望帮助到你。

(完)

转载于:https://juejin.im/post/5c7f68a8f265da2d97111a91

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值