美文网首页
自己实现bind

自己实现bind

作者: 临安linan | 来源:发表于2019-08-14 13:59 被阅读0次

    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

    相关文章

      网友评论

          本文标题:自己实现bind

          本文链接:https://www.haomeiwen.com/subject/dcygjctx.html