自己实现bind

2019-08-14  本文已影响0人  临安linan

bind会创建一个新的函数返回, 这个函数被调用的时候, this指向bind的第一个参数, 之后的一系列参数都会传入作为函数的参数

  1. 返回一个函数
  2. 指定这个函数的this
  3. 传入参数的实现

1. 实现返回函数和指定this

Function.prototype.bind = function(context){
  return () => {  // 返回新函数
    return this.apply(context)  // 新函数内部执行原函数, 并指定this, 将执行结果返回
  }
}

2. 实现传参

bind的传参机制有点奇特, 它支持分两次传参, 也就是所需参数不用一次传完, 可以分两次传

var foo = {value: 1};
function bar(name, age) {
    console.log(this.value);
    console.log(name);
    console.log(age);
}
var bindFoo = bar.bind(foo, 'daisy');  // 实现参数固化, 每次调用barFoo时, 原本所需的第一个参数固定了下来, 只用指定第二个
bindFoo('18');
// 1
// daisy
// 18

实现思路: 分别用arguments接收, 然后拼接起来

Function.prototype.bind = function(context){
  let arg = Array.from(arguments).slice(1)
  return () => {  // 返回新函数
    let arg2 = Array.from(arguments)
    return this.apply(context, arg.concat(arg2))  // 拼接参数数组返回
  }
}

以上就基本实现了bind, 但还差点东西:

  1. 如果调用bind的是一个构造函数, bind指定的this会失效
  2. 返回函数的prototype应指向原函数的prototype, 使原型链继承下来

3. 实现构造函数的判断

因为需要获取到实例的this, 所以这里就不用箭头函数了

Function.prototype.bind = function(context){
  let _this = this  // 这里的this指向调用bind的函数
  let arg = Array.from(arguments).slice(1)
  let bindFn = function(){
    let arg2 = Array.from(arguments)
    // 只需判断this的原型是否为返回函数.prototpye即可
    // 如果返回函数被用作构造函数, 内部的this指向实例, this.__proto__ === bindFn.prototype, 下面三元表达式结果为true, 此时不改变this指向
    // 如果返回函数被用作普通函数, 内部this指向window, this.__proto__ === Window.prototype, 下面三元表达式结果为false, 将this绑定为指定的context
    return _this.apply(this instanceof bindFn ? this : context, arg.concat(arg2))
  }
  return bindFn
}

4. 保持函数的原型

因为调用bind的函数可能是用作构造函数, 所以原函数上可能有些属性需要提供给实例访问
举个例子, 上面代码, 原有函数bind之前, 原有函数new出来的实例的原型指向原函数.prototype
但bind之后, 返回了bindFn, new出来的实例的原型指向bindFn.prototype, 丢失了原函数.prototype这一原型

Function.prototype.bind = function(context){
  let _this = this
  let arg = Array.from(arguments).slice(1)
  let bindFn = function(){
    let arg2 = Array.from(arguments)
    return _this.apply(this instanceof bindFn ? this : context, arg.concat(arg2))
  }
  // 这里不能直接写 bindFn.prototype = _this.prototype
  // 如果这样写, 那 bindFn.prototype 和 原函数.prototype 会持有同一个对象的引用, 一旦我们修改了其中一个, 另一个也会改变
  // 所以我们用一个函数中转
  let bus = function(){}
  bus.prototype = _this.prototype
  bindFn.prototype = new bus()  // 此时, new bindFn()实例的 __proto__ 指向这个new bus()实例, 这个new bus()实例的 __proto__ 又指向原函数.prototype, 原型链构建成功(*^▽^*)
  return bindFn
}

5. 边界情况考虑

假如 bind 的不是一个函数呢?
拒绝。

if(typrof this !== "function"){
  throw new Error("bind必须是函数调用")
  return
}

最终代码:

Function.prototype.bind = function(context){
  if(typrof this !== "function"){
    throw new Error("bind必须是函数调用")
    return
  }
  let _this = this
  let arg = Array.from(arguments).slice(1)
  let bindFn = function(){
    let arg2 = Array.from(arguments)
    return _this.apply(this instanceof bindFn ? this : context, arg.concat(arg2))
  }
  let bus = function(){}
  bus.prototype = _this.prototype
  bindFn.prototype = new bus()
  return bindFn
}

参考: https://github.com/mqyqingfeng/Blog/issues/12

上一篇下一篇

猜你喜欢

热点阅读