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

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

技术杂谈小彩虹2021-07-18 5:22:05180A+A-

如果对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;
  };

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1

联系我们