模拟实现Javascript中的bind函数

WillemWei
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

从MDN对于bind的描述来看:

  • 返回值是一个函数,而不是执行结果
  • this值会指向第一个参数
  • 其余参数会作为新函数的参数

看个例子:

function test(name, age) {
    console.log(this.name);
    this.name = name;
    this.age = age;
    console.log(this.name, this.age);
}

var obj = {
    name: 'Willem'
};

var bindFn = test.bind(obj, 'Wei');
bindFn(18);
// Willem
// Wei, 18

从上面的代码就可以看出来,bind函数执行之后,bindFn的this值指向了obj,并且在bind的时候传入的参数和在执行bindFn时传入的参数都成功的传入了test函数。

那代码就呼之欲出了啊。

Function.prototype.wbind = function() {
    var context = [].shift.call(arguments);
    var args = [].slice.call(arguments);
    var self = this;

    return function() {
        var innerArgs = [].slice.call(arguments);
        
        self.apply(context, args.concat(innerArgs));
    }
}

相关:模拟实现Javascript中的call和apply

既然bind返回的是一个函数,那我有一个大胆的想法,如果我把这个返回的函数作为一个构造函数会怎样呢?改造一下上面的那个例子:

function test(name, age) {
    console.log(this.name);
    this.name = name;
    this.age = age;
    console.log(this.name, this.age);
}

test.prototype.sayHi = function() {
    console.log('Hi, ' + this.name);
}

var obj = {
    name: 'Willem'
};

var bindFn = test.bind(obj, 'Wei');
var instance = new bindFn(18);
// undefined
// Wei,18
instance.sayHi(); // Hi, Wei
console.log(obj.name); // Willem

咦,obj对象里面明明是有name属性的啊,为啥第一次输出的是undfined呢?明明传入了name属性为"Wei",为啥obj.name还是"Willem"呢?

其实是因为this并没有指向obj了,而是指向了instance。总结一下,将返回的函数作为普通函数使用时,函数的this指向bind执行时传入的第一个参数;将返回的函数作为构造函数使用时,函数的this指向实例,并且该实例将会继承原函数原型上的属性和方法。

这时候,我们再来改一改wbind函数

Function.prototype.wbind = function() {
    var context = [].shift.call(arguments);
    var args = [].slice.call(arguments);
    var self = this;

    var fBound = function() {
        var innerArgs = [].slice.call(arguments);
        // 做构造函数时,this指向实例
        self.apply(this instanceof fBound ? this : context, args.concat(innerArgs));
    }
    
    // 实例需要继承原函数原型上的方法和属性
    // 使用fNOP中转一次是因为直接将this.prototype赋值到fNOP.prototype时
    // 当修改fNOP的prototype时,this.prototype也会被修改
    var fNOP = function() {}
    if (this.prototype) {
        fNOP.prototype = this.prototype;
    }
    
    // fBound.prototype = { __proto__: { this.prototype } }
    // 相当于是中间多了一个__proto__,因为原型链的缘故,所以多一层__proto__没有什么影响
    fBound.prototype = new fNOP(); 
    
    return fBound;
}

相关:
模拟实现js中的new操作符
简单说说原型和原型链
使用ts模拟实现js中的一些函数和属性

以上,就是bind的相关内容。

阅读 849
452 声望
29 粉丝
0 条评论
你知道吗?

452 声望
29 粉丝
宣传栏