循序渐进的用js实现一个bind()

如果对call,apply,bind的应用和区别还不了解,可以去看我之前的文章了解下。 让你弄懂 call、apply、bind的应用和区别

如果出现错误,请在评论中指出,我也好自己纠正自己的错误

author: thomaszhou

bind实现

一般我们会直接使用bind函数,但是这次我们通过原生js来尝试实现这个函数

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

由此我们可以首先得出 bind 函数的三个特点:

  • (1)返回一个函数:我们可以使用 call 或者 apply 实现
  • (2)可以传入参数:我们用 arguments 进行处理
var foo = { value: 1 };

function bar(name, age) {
    console.log(this.value);
    console.log(name);
    console.log(age);
}

var bindFoo = bar.bind(foo, 'daisy'); // (1)bindFoo是一个函数
bindFoo('18'); // (2)此处可以再次传入参数
// 1
// daisy
// 18
复制代码
先实现前两个特点:实现代码(version 1.0):
Function.prototype.bind2 = function (context) {
    var self = this;
    // 获取bind2函数从第二个参数到最后一个参数
    var args = Array.prototype.slice.call(arguments, 1);
    return function () {
        // 这个时候的arguments是指bind返回的函数bindFoo调用时传入的参数
        var bindArgs = Array.prototype.slice.call(arguments);
        self.apply(context, args.concat(bindArgs));
    }
}
复制代码
  • (3)一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 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'); // this 已经指向了 obj
// undefined
// daisy
// 18
console.log(obj.habit);
console.log(obj.friend);
// shopping
// kevin
复制代码

注意:尽管在全局和 foo 中都声明了 value 值,最后依然返回了 undefind,说明绑定的 this 失效了

先实现第三个特点:实现代码(version 2.0):
Function.prototype.bind2 = function (context) {
    let self = this;
//    self --> ƒ bar(){}
    let args = Array.prototype.slice.call(arguments, 1);
    let fbound = function () {
      let bindArgs = Array.prototype.slice.call(arguments); 
       // (1) 当作为构造函数时,this --> 实例(fbound创建的的实例),self --> 绑定函数bar,结果为true,那么self指向实例
      // (2) 当作为普通函数时,this -->window,self -->绑定函数,此时结果为false,那么 self指向绑定的 context。
      self.apply(this instanceof self ? this : context, args.concat(bindArgs));
    };
   // 为了要让 this instanceof self为true,就要使创建的实例继承绑定函数bar,
   // 唯一办法就是让创建实例的函数fbound的prototype指向bar函数的prototype
    fbound.prototype = this.prototype;
    return fbound;
};
复制代码
优化:实现代码(version 3.0):

version 2.0 直接将 fbound.prototype = this.prototype,我们直接修改fbound.prototype 的时候,也会直接修改函数bar的 prototype。这个时候,我们可以通过一个空函数来进行中转,然后利用组合继承的方式来实现

Function.prototype.bind2 = function (context) {
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);
    
    var fNOP = function () {}; // // 定义一个中间函数,用于作为继承的中间值

    var fbound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        self.apply(this instanceof self ? this : context, args.concat(bindArgs));
    }
    // 先让 fNOP 的原型方法指向 this 即函数bar的原型方法,继承 this 的属性
    fNOP.prototype = this.prototype;
    // 再将 fbound 即要返回的新函数的原型方法指向 fNOP 的实例化对象
    // 这样,既能让 fBound 继承 this 的属性,在修改其原型链时,又不会影响到 this 的原型链
    fbound.prototype = new fNOP();
    return fbound;
}
复制代码

在上面的代码中,我们引入了一个新的函数 fun,用于继承原函数的原型,并通过 new 操作符实例化出它的实例对象,供 fBound 的原型继承,至此,我们既让新函数继承了原函数的所有属性与方法,又保证了不会因为其对原型链的操作影响到原函数

-------------优化三点-------------------------
  • (兼容性)当 Function 的原型链上没有 bind 函数时,才加上此函数
Function.prototype.bind = Function.prototype.bind || function () {
    ……
};
复制代码
  • 只有函数才能调用 bind 函数,其他的对象不行。即判断 this 是否为函数。
if (typeof this !== 'function') {
    throw new TypeError("NOT_A_FUNCTION -- this is not callable");
}
复制代码
  • 对于参数传递的代码,可以用ES6的拓展运算符来替换

最终版本!!!

Function.prototype.bind = Function.prototype.bind || function (context, ...formerArgs) {
    let self = this;

    if (typeof this !== 'function') {
			throw new TypeError("NOT_A_FUNCTION -- this is not callable");
	}
    let fNOP = function () {}; 

    let fbound = function (...laterArgs) {
      self.apply(this instanceof self ? this : context, formerArgs.concat(laterArgs)); 
      // es6 : formerArgs.concat(laterArgs)
    };
   
    fNOP.prototype = this.prototype;
    
    fbound.prototype = new fNOP();
    return fbound;
  };

复制代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值