美文网首页
手写bind

手写bind

作者: 方_糖 | 来源:发表于2021-03-30 18:04 被阅读0次
    1. 利用call实现一个最基础的bind
    var getName = function(){
        console.log(this.name)
    }
    //call
    getName.call({name:"Jan"})  //直接输出"Jan"
    //bind
    var f = getName.bind({name:"Jan"});
    f();        //执行f才输出"Jan"
    

    bind和call的区别 : bind返回的是函数,函数调用后才运行。而call直接运行。所以得到如下bind实现

    //bind是给所有函数来调用的
    Function.prototype.bind = function(obj){
        var fn = this;
        return function(){
            //返回call的调用形式
            return fn.call(obj)     
        }
    }
    
    2. 传递多个参数

    bind和call一样,都可以传递多个额外参数, 这里我们可以用ES6的...args,也可以用ES5的arguments(通过Array.prototype.slice.call(arguments, 0)arguments转换成普通数组)

    //bind是给所有函数来调用的
    Function.prototype.bind = function(obj, ...args){
        var fn = this;
        return function(){
             //返回call的调用形式
            return fn.call(obj, ...args)    
        }
    }
    

    但是bind除了支持

    var getName = function(age, city){
        console.log(this.name + "," + age + "," + city)
    }
    var f = getName.bind({name:"Jan"}, 11, "BeiJing");
    f();        //Jan,11,BeiJing
    
    

    还支持在f()中传递多个参数(预设

    var f = getName.bind({name:"Jan"}, 11);
    f("Beijing");        //Jan,11,BeiJing
    

    所以我们要把bind函数改写成如下:

    Function.prototype.bind = function(obj, ...args){
        var fn = this;
        return function(...others){
             //返回call的调用形式
             fn.call(obj, ...args, ...others) 
        }
    }
    
    3. 作为构造函数使用

    可以用new操作符创建一个由bind实现的绑定函数,如:

    function Person(name) {
        this.name = name
    }
    Person.prototype.getName = function(){
        console.log(this.name)
    }
    var PersonBind = Person.bind({}, "Jan")
    var p = new PersonBind();
    p.getName()     //Jan
    console.log(p instanceof PersonBind)    //true
    console.log(p instanceof Person)        //true
    

    这是我们要返回的不能是一个简单的函数,而应该是一个继承了Person属性和方法的对象。
    首先我们先使用原型链的方式继承,原型链的写法为B.prototype = new A()(B继承A),所以可以得到如下的bind写法

    Function.prototype.bind = function(obj, ...args){
        var fn = this;
        var res = function(...others){
            //返回call的调用形式
            fn.call(obj, ...args, ...others) 
        }
        res.prototype = new fn(...args);  //通过原型链继承
        return res
    }
    

    乍一看没什么问题,而且结果输出也都是正确的,但是当我们输出var p = new PersonBind();中的p,发现在使用了我们的bind后,浏览器输出是这样的

    我们改写的bind输出
    而原始的bind输出是这样的
    原始的bind输出

    bind要求我们只是继承方法,而传递的参数我们通过借用构造函数来继承,即通过组合继承来实现(原型链继承和组合继承的区别请参考js对象——继承(2)
    使用组合继承实现如下

    Function.prototype.bind = function(obj, ...args){
        var fn = this;
        var res = function(...others){
            fn.call(this, ...args, ...others);  //借用构造函数继承属性
        }
        res.prototype = fn.prototype;   //原型链的方式继承方法
        return res
    }
    

    但是我们突然发现,虽然实现了用new创建,但传统的bind使用方式失效了

    4. 使用组合继承来改造

    构造函数(new创建)和普通函数的调用有什么区别呢?使用构造函数时,内部的this指向的是该对象,而调用普通函数是,内部的this指向的是window,所以我们可以通过这一点来返回不同的内容

    Function.prototype.bind = function(obj, ...args){
        var fn = this;
        var res = function(...others){
            if(this == window){
                return fn.call(obj, ...args, ...others);
            }else{
                return fn.call(this, ...args, ...others);  //借用构造函数继承属性
            }
        }
        res.prototype =new f();   //原型链的方式继承方法
        return res
    }
    

    存疑: return fn.call(this, ...args, ...others)和obj并没有联系

    5. 官方实现

    链接:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

    if (!Function.prototype.bind) (function(){
      var ArrayPrototypeSlice = Array.prototype.slice;
      Function.prototype.bind = function(otherThis) {
        if (typeof this !== 'function') {
          // closest thing possible to the ECMAScript 5
          // internal IsCallable function
          throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
        }
    
        var baseArgs= ArrayPrototypeSlice.call(arguments, 1),
            baseArgsLength = baseArgs.length,
            fToBind = this,
            fNOP    = function() {},
            fBound  = function() {
              baseArgs.length = baseArgsLength; // reset to default base arguments
              baseArgs.push.apply(baseArgs, arguments);
              return fToBind.apply(
                     fNOP.prototype.isPrototypeOf(this) ? this : otherThis, baseArgs
              );
            };
    
        if (this.prototype) {
          // Function.prototype doesn't have a prototype property
          fNOP.prototype = this.prototype;
        }
        fBound.prototype = new fNOP();
    
        return fBound;
      };
    })();
    

    其实变成ES6写法就可以简化成如下:

    Function.prototype.myBind = function(obj, ...args){
        var fn = this;
        //仿制一个Person的构造函数
        var F = function(){};   
        if(fn.prototype){
            //将Person的原型全部拷贝到F
            F.prototype = fn.prototype;     
        }
        var res = function(){
            //this: BindPerson{}对象 / window
            //F: function Person
            var callObj = F.prototype.isPrototypeOf(this) ? this : obj;
            return fn.call(callObj, ...args)
        }
        res.prototype = new F();
        return res
    }
    

    我们用一个例子来说明上面代码的干了什么

    function Person(name){
        this.name = name
    }
    Person.prototype.getName = function(){
        return this.name
    }
    
    var BindPerson = Person.bind({}, "xxx");
    var bp = new BindPerson();
    console.log(bp.getName())
    

    (1)复制Person构造函数,名为F
    (2)创建一个全新构造函数res,通过原型链的方法继承F的方法
    (3)返回构造函数res,BindPerson = res
    (4)当通过bp = new BindPerson();来创建对象时,此时res里面的this指向bp对象,而bp对象是res的实例,res又是继承了Person,所以F.prototype.isPrototypeOf(this) = true。再通过构造函数继承Person的属性fn.call(this)
    (5)当通过普通方式调用时BindPerson (),此时的this指向window,F.prototype.isPrototypeOf(this) = false。就只需要直接调用fn.call(obj)

    6. 总结

    对比官方提供的标准答案我们发现,其实唯一的区别在于:判断是不是通过new创建的,简单的用this == window并不严谨,如果不是通过浏览器调用,而是通过node运行,这时的this就不等于window了

    相关文章

      网友评论

          本文标题:手写bind

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