美文网首页
bind的模拟实现

bind的模拟实现

作者: 小泡_08f5 | 来源:发表于2019-06-15 09:19 被阅读0次

    bind() 方法会创建一个新函数。 当这个函数被调用时, bind()的第一个参数将作为它运行时的this, 之后的一序列参数将会在传递的实参前传入作为它的参数

    由此得出bind函数的两个特点:

    1. 返回一个函数
    2. 可以传入参数

    举个例子:

    var foo = {
        value: 1
    };
    
    function bar() {
        console.log(this.value);
    }
    
    // 返回了一个函数
    var bindFoo = bar.bind(foo); 
    
    bindFoo(); // 1
    

    以上返回了一个函数,同时也指定了this的指向,可以用call和apply来实现

    var foo = {
                value: 1
            };
            
            function bar() {
                console.log(this.value);
            }
            Function.prototype.bind2 = function(context){
                // console.log(this);
                var self = this;
                return function(){
                    return self.call(context);
                }
            }
            bar.bind2(foo)();
    

    这里之所以 return self.call(context); 是考虑到绑定函数可能是有返回值的,

    var foo = {
        value: 1
    };
    
    function bar() {
        return this.value;
    }
    
    var bindFoo = bar.bind(foo);
    
    console.log(bindFoo()); // 1
    

    传参的模拟实现

    var foo = {
        value: 1
    };
    
    function bar(name, age) {
        console.log(this.value);
        console.log(name);
        console.log(age);
    
    }
    
    var bindFoo = bar.bind(foo, 'daisy');
    bindFoo('18');
    // 1
    // daisy
    // 18
    

    说明在bind的时候可以传参,在执行bind返回函数的时候,还可以传参。

    思路:第一次绑定返回的是一个函数,接收的参数用arguments 正常接收,可以用Array.prototype.slice.call(arguments,1) 接收从第二个参数截取。也可以用for循环遍历arguments获取
    第二次运行bind返回的函数传参, 因为返回的是一个函数,也可以利用arguments来接收全部参数,Array.prototype.slice.call(arguments,0),然后把第一次传入的参数concat () 连接起来一并传入。
    来试试

    var foo = {
                value: 1
            };
            
            function bar(name,age) {
                console.log(this.value);
                console.log(name);
                console.log(age);
            }
            Function.prototype.bind2 = function(context){
                // console.log(arguments);
                var self = this;
                var args = Array.prototype.slice.call(arguments,1);
                return function(){
                    var args1 = Array.prototype.slice.call(arguments,0);
                    console.log(args.concat(args1));
                    var args2 = args.concat(args1);
                    return self.apply(context,args2);
                }
            }
            var bindFoo = bar.bind2(foo,'Ailse');
            bindFoo('18');
    
    
    image.png

    构造函数效果的模拟实现

    一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的this值被忽略,同时调用时的参数被提供给模拟函数
    bind的另外一个特点
    也就是说当bind返回的函数作为构造函数的时候,bind时指定的this值会失效,但传入的参数依然生效:

    var value = 2;
    
    var foo = {
        value: 1
    };
    
    function bar(name, age) {
        this.habit = 'shopping';
        console.log(this.value);
        console.log(name);
        console.log(age);
    }
    
    bar.prototype.friend = 'kevin';
    
    var bindFoo = bar.bind(foo, 'daisy');
    
    var obj = new bindFoo('18');
    // undefined
    // daisy
    // 18
    console.log(obj.habit);
    console.log(obj.friend);
    // shopping
    // kevin
    

    尽管在全局和foo中都声明了value值, 最后依然返回了undefined, 说明绑定的this失效了,此时的this已经指向了obj

    // 第四步 如果一个构造函数使用new操作符创建对象
            var foo = {
                value: 1
            };
            
            function bar(name,age) {
                this.habit = 'shopping';
                console.log(this.value);
                console.log(name);
                console.log(age);
            }
            bar.prototype.friend = 'kevin';
    
            Function.prototype.bind2 = function(context){
                var self = this;
                var args = Array.prototype.slice.call(arguments,1);
                var fBound = function(){ // 因为要return回去,我们这里用函数表达式的形式定义函数。
                    // 当作为构造函数时,this 指向实例,此时结果为 true,将绑定函数的 this 指向该实例,可以让实例获得来自绑定函数的值
                    // 以上面的是 demo 为例,如果改成 `this instanceof fBound ? null : context`,实例只是一个空对象,将 null 改成 this ,实例会具有 habit 属性
                    // 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
                    console.log(this instanceof fBound);
                    var args1 = Array.prototype.slice.call(arguments,0);
                    var args2 = args.concat(args1);
                    if(this instanceof fBound){ // 说明obj是'构造函数'fBound  new出来的实例, 用instanceof可判断一个对象是否是构造函数的实例
                        return self.apply(this,args2); // 将绑定函数的this指向该实例对象obj,可以让实例获得来自绑定函数的值
                    }else{
                        return self.apply(context,args2)
                    }
                }
                // 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值
                fBound.prototype = this.prototype; // 因为fBound是新创建的函数,new出来的实例的原型指向的是fBound的原型对象,让'构造函数'fBound的原型指向最初bar函数的原型,则fBound new出来的实例继承了bar
                return fBound;
            }
            var bindFoo = bar.bind2(foo,'Ailse');
            var obj = new bindFoo('18');
            console.log(obj.habit); // shopping
            console.log(obj.friend); // Ailse //18
    
    // 第五步 ,
            // 在第四步中,我们直接将 fBound.prototype = this.prototype,我们直接修改 fBound.prototype 的时候,也会直接修改绑定函数的 prototype。这个时候,我们可以通过一个空函数来进行中转:
            // 如果是修改空函数的prototype, 其实也会修改绑定函数的 prototype 吧, 那为什么还是要用一个空函数中转???
            // 空函数不习惯被操作?
            var foo = {
                value: 1
            };
            
            function bar(name,age) {
                this.habit = 'shopping';
                console.log(this.value);
                console.log(name);
                console.log(age);
            }
            bar.prototype.friend = 'kevin';
    
            Function.prototype.bind2 = function(context){
                var self = this;
                var args = Array.prototype.slice.call(arguments,1);
                var FNOP = function(){};
                var fBound = function(){
                    // 当作为构造函数时,this 指向实例,此时结果为 true,将绑定函数的 this 指向该实例,可以让实例获得来自绑定函数的值
                    // 以上面的是 demo 为例,如果改成 `this instanceof fBound ? null : context`,实例只是一个空对象,将 null 改成 this ,实例会具有 habit 属性
                    // 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
                    console.log(this instanceof fBound);
                    var args1 = Array.prototype.slice.call(arguments,0);
                    var args2 = args.concat(args1);
                    if(this instanceof fBound){ // 说明obj是'构造函数'fBound  new出来的实例, 用instanceof可判断一个对象是否是构造函数的实例
                        return self.apply(this,args2); // 将绑定函数的this指向该实例对象obj,可以让实例获得来自绑定函数的值
                    }else{
                        return self.apply(context,args2)
                    }
                }
                // 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值
                // fBound.prototype = self.prototype; // 因为fBound是新创建的函数,new出来的实例的原型指向的是fBound的原型对象,让'构造函数'fBound的原型指向最初bar函数的原型,则fBound new出来的实例继承了bar
                // fBound.prototype.friend = '123'
                FNOP.prototype = self.prototype;
                fBound.prototype = new FNOP();
                // FNOP.prototype.friend = '6666'
                return fBound;
            }
            var bindFoo = bar.bind2(foo,'Ailse');
            var obj = new bindFoo('18');
            console.log(obj.habit); // shopping
            console.log(obj.friend); // Ailse //18
    

    相关文章

      网友评论

          本文标题:bind的模拟实现

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