bind的模拟实现

2019-06-15  本文已影响0人  小泡_08f5

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
上一篇下一篇

猜你喜欢

热点阅读