JS系列-趣谈bind

bind简介

咱们先来看看MDN上给出的简介markdown

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

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

举个例子:oop

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 函数了。post

初步模拟实现

在写代码前咱们须要了解下 bind 有哪些特性,而后咱们能够根据它的特性来进行实现ui

  • 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修改指向传入的第一个参数,同时参数拼接给新的函数
    }
}
复制代码

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

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构造函数处理

这样咱们就实现...实现好了吗?spa

咱们来看看MDN的原话吧。prototype

绑定函数自动适应于使用 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

相关文章
相关标签/搜索