JS系列-趣谈bind

1,484 阅读7分钟

bind简介

我们先来看看MDN上给出的简介

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

是不是看不太懂,没关系我翻译一下,说人话就是 bind 函数执行完会返回一个新的函数,后续我们都称为绑定函数,执行这个绑定函数时, this 的指向就会变成 bind 函数的第一个参数,其他参数会作为返回的绑定函数的参数,在执行绑定函数的时候再传入到这个函数中。 这下明白了吧,什么?你没听明白,管你听没听懂,看就完事。

举个例子:

const module = {
    x42,
    getXfunction (y = 0) {
        return this.x + y;
    }
};

const unboundGetX = module.getX;
console.log(unboundGetX()); // NaN 该函数在全局作用域中被调用,此时this指向的是window, 

const boundGetX = unboundGetX.bind(module); // 这个时候我们将新的函数的this指向通过bind改成了module
console.log(boundGetX()); // 42
console.log(boundGetX(3)); // 45

bind的实现

通过上面的例子我们已经明白了 bind 的功能了,可以我们动手实现 bind 函数了。

初步模拟实现

在写代码前我们需要了解下 bind 有哪些特性,然后我们可以根据它的特性来进行实现

  • bind 是挂载在 function 的原型上的,所以 function 能直接调用
  • bind 会返回一个新的函数
  • bind 传递的第一个参数会绑定为新函数的 this 的指向
  • bind 的其他参数会作为绑定函数的参数
Function.prototype.selfBind = function (context, ...bindArgs) {
    const self = this;
    return function (...args) {
        return self.apply(context, bindArgs.concat(args)); // 利用apply修改指向传入的第一个参数,同时参数拼接给新的函数
    }
}

代码写完了,我们来执行上面的例子来看下是否满足要求

const module = {
    x42,
    getXfunction (y = 0) {
        return this.x + y;
    }
};

Function.prototype.selfBind = function (context, ...bindArgs) {
    const self = this;
    return function (...args) {
        return self.apply(context, bindArgs.concat(args));
    }
}

const unboundGetX = module.getX;
const boundGetX = unboundGetX.selfBind(module); // 使用自己自定义的bind修改this指向
console.log(boundGetX()); // 42,输出的值和原生bind一样
console.log(boundGetX(3)); // 45,输出的值和原生bind一样

new构造函数处理

这样我们就实现...实现好了吗?

我们来看看MDN的原话吧。

绑定函数自动适应于使用 new 操作符去构造一个由目标函数创建的新实例。当一个绑定函数是用来构建一个值的,原来提供的 this 就会被忽略。不过提供的参数列表仍然会插入到构造函数调用时的参数列表之前。

什么意思呢?就是我们可以把 bind 返回的函数当做构造函数去用 new 操作符创建实例的时候,bind 传入的 this 指向会失效,但是传入的参数还是有效的。

我们先来看看原生的 bind 效果吧

const module = {
    x42,
    getXfunction (y = 0) {
        console.log('this.x: 'this.x);
        console.log('y :', y);
        this.property = '我是getX自带的属性';
        return this.x + y;
    }
};
module.getX.prototype.prototypeProperty = '我是getX原型上的属性'// 给getX原型挂载数据

const unboundGetX = module.getX;
const boundGetX = unboundGetX.bind(module); // 注意这里是用的原生的bind
const instance = new boundGetX(3);
// this.x:  undefined
// y: 3
console.log(instance.property); 
// 我是getX自带的属性
console.log(instance.prototypeProperty); 
// 我是getX原型上的属性

这里我们可以看到 this 指向的不是传入的 module 而且 getX ,这其实是 new 操作符带来的影响,下周我会继续出一篇关于 new 操作符的内幕,有兴趣的小伙伴可以点个关注。我知道你们这时候都是

那么我们现在来处理下被 new 的情况

Function.prototype.selfBind = function (context, ...bindArgs) {
    const self = this;
    let fBound = function (...args) {
        // 如果绑定函数作为构造函数使用,通过判断this是否继承原函数,this指向当前实例,self指向需要绑定的函数
        return self.apply(this instanceof self ? this : context, bindArgs.concat(args));
    }
    // 修改绑定函数的prototype为要绑定的函数的prototype,实例就能继承函数的原型,这样上面才能用instanceof判断this是否继承self
    function Fn() {};
    Fn.prototype = this.prototype;
    fBound.prototype = new Fn();
    return fBound;
}

结果验证

const module = {
    x42,
    getXfunction (y = 0) {
        console.log('this.x: 'this.x);
        console.log('y :', y);
        this.property = '我是getX自带的属性';
        return this.x + y;
    }
};
module.getX.prototype.prototypeProperty = '我是getX原型上的属性'// 给getX原型挂载数据

const unboundGetX = module.getX;
const boundGetX = unboundGetX.selfBind(module); // 注意这里是用的原生的bind
const instance = new boundGetX(3);
// this.x:  undefined
// y: 3
console.log(instance.property); 
// 我是getX自带的属性
console.log(instance.prototypeProperty); 
// 我是getX原型上的属性

和原生的 bind 返回一样,这样我们就完整的实现了一个原生的 bind 方法???no no no,这还不够严谨,我们还需要对调用 bind 的对象进行一个类型校验

添加类型校验

为了避免一些特殊情况的发生,比如:

let obj = {};
obj.__proto__ = Function.prototype;
obj.selfBind(module);

我们就需要对调用者进行一个类型校验了,判断 this 类型是否是 function 即可

if (typeof this !== 'function') {
    throw new TypeError('Function.prototype.selfBind - what is trying to be bound is not callable');
}

完整代码

Function.prototype.selfBind = function (context, ...bindArgs) {
    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');
    }

    const self = this;
    let fBound = function (...args) {
        // 如果绑定函数作为构造函数使用,通过判断this是否继承原函数,this指向当前实例,self指向需要绑定的函数
        return self.apply(this instanceof self ? this : context, bindArgs.concat(args));
    }
    // 修改绑定函数的prototype为要绑定的函数的prototype,实例就能继承函数的原型,这样上面才能用instanceof判断this是否继承self
    function Fn() {};
    Fn.prototype = this.prototype;
    fBound.prototype = new Fn();
    return fBound;
}

非常感谢各位能阅读到这里,觉得有帮助的话不妨点个赞,你的支持是对我对最大的鼓励。

新一篇的 new 已经更新了,欢迎各位看官捧场!!

趣谈new