JS语言理解13 bind函数的实现

定义

bind的作用和callapply类似,区别在于使用上。bind的执行的结果返回的是绑定了一个对象的新函数

先看一个使用例子:

var obj = { name: 'Jay' };

function sayName(){
  console.log(this.name);
}

var fn = sayName.bind(obj) ;
fn() 
// 'Jay'

MDN上的定义:

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

从上面的定义来看,bind函数的功能包括:

  1. 改变原函数的this指向,绑定this
  2. 返回原函数的拷贝,并预传参数

要注意的,当使用new调用bind返回的参数时,bind绑定this失效。这是由于上面提到过的this绑定的优先级,new的优先级高于bind

简易版

在面试过程中,无数次遇到过这个问题,如何自己实现一个bind函数,一般情况下,都知道了用apply/call来实现

Function.prototype.myBind = function (thisArg) {
  if (typeof this !== 'function') {
    return;
  }
  const self = this;
  const args1 = [].slice.call(arguments, 1);
  return function () {
    const args2 = [].slice.call(arguments, 0);
    self.apply(thisArg, args1.concat(args2))
  }
};

const obj = {
  age: 1,
};

function foo(a, b) {
  console.log(this.age, a, b);
}

foo.bind(obj, 2)(3);
// 1 2 3

foo.myBind(obj, 2)(3);
// 1 2 3

貌似没有问题了,实现了bind上面提到的绑定this和复制函数的功能,还附带实现了传参的实现。

但是有一点,对于绑定优先级的处理还有一些问题,this有4种绑定规则:

  1. 默认绑定
  2. 隐式绑定
  3. 显示绑定
  4. new绑定

四种绑定规则的优先级从上到下依次递增,默认绑定的优先级最低,new绑定的优先级最高,而bind方法就是显示绑定的一种,优先级应该低于new绑定

先复习一下new调用构造函数时的过程:

  1. 创建一个全新的对象
  2. 这个对象被执行[[Prototype]]连接
  3. 将这个对象绑定到构造函数中的this
  4. 如果函数没有返回其他对象,则new操作符调用的函数则会返回这个对象

先看一下正常的bind后的函数遇到new操作符,表示是如何的:

const obj = {};

function foo(name) {
  this.name = name;
}

const Person = foo.bind(obj);
Person('jay');
console.log(obj.name);
// 'jay'

let p2 = new Person('chow');
console.log(obj.name); // jay
console.log(p2.name); // chow

首先直接调用bind后的参数,这时候this指向obj,所以obj.name变成了jay,然后通过new操作符调用Person,根据上面的优先级,new的优先级更高,所以this会绑定为创建的新对象p2,所以对name的更改是对p2name的更改,objname保持不变

再来看看我们的myBind方法遇到this后的表现:

const obj = {};

function foo(name) {
  this.name = name;
}

const Person = foo.myBind(obj);
Person('jay');
console.log(obj.name);
// 'jay'

let p2 = new Person('chow');
console.log(obj.name); // 'chow'
console.log(p2.name); // undefined

而我们的简易版bind方法,一旦绑定了this后,当遇到new操作符后也不会更改,而是固定在obj上,所以在new的过程中绑定的this仍然是我们myBind绑定的对象obj,所以p2.name

所以对简易版的bind还需要优化

优化版

想要解决上面的问题,关键点就是在于处理new的第三步,我们的myBind函数识别是new调用,那么就不再将this绑定到我们传入的thisArg对象,而是绑定为函数本身调用的this

那么问题就转换为了,如何判断一个函数是普通调用,还是通过new操作符调用。在new的过程中,会首先建立新建对象的原型继承,然后绑定新建对象到构造函数的this,也就是下面的过程

let p1 = new Person();

// 1 let p1 = {};
// 2 p1.__proto__ === Person.prototype;
// 3 p1 === this;
// 4 return p1;

通过第二步和第三步,this就成为了Person的一个实例,可以通过this insetanceof Person来判断,所以可以对上面的myBind进行优化:

Function.prototype.myBind = function (thisArg) {
  if (typeof this !== 'function') {
    return;
  }
  const self = this;
  const args1 = [].slice.call(arguments, 1);
  return function fBound() {
    // 判断是否 new 调用
    const target = this instanceof fBound ? this : thisArg;
    const args2 = [].slice.call(arguments, 0);
    self.apply(target, args1.concat(args2))
  };
};

再来验证一下效果:

const obj = {};

function foo(name) {
  this.name = name;
}

const Person = foo.myBind(obj);
Person('jay');
console.log(obj.name);
// 'jay'

let p2 = new Person('chow');
console.log(obj.name); // 'jay'
console.log(p2.name); // 'chow'

myBind的效果与原生bind的效果一致,new操作符改变了bindthis指向。

MDN上的bind实现,增加了维护原型关系的步骤,但是我并不是太理解这样做的必要性,我理解new的实现就直接将thisfBound的原型链进行了关联,可以通过instanceof判断

if (!Function.prototype.bind) {
  Function.prototype.bind = function(oThis) {
    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');
    }

    var aArgs   = Array.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP    = function() {},
        fBound  = function() {
          // this instanceof fBound === true时,说明返回的fBound被当做new的构造函数调用
          return fToBind.apply(this instanceof fBound
                 ? this
                 : oThis,
                 // 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    // 维护原型关系
    if (this.prototype) {
      // 当执行Function.prototype.bind()时, this为Function.prototype 
      // this.prototype(即Function.prototype.prototype)为undefined
      fNOP.prototype = this.prototype; 
    }
    // 下行的代码使fBound.prototype是fNOP的实例,因此
    // 返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的实例
    fBound.prototype = new fNOP();

    return fBound;
  };
}

这个疑问先放在这里,看以后有没有机会弄明白,或者哪位大神来指点吧。(2019-09-23)

参考

已标记关键词 清除标记
相关推荐
<p> <span style="color:#4d4d4d;">当前课程中博客项目的实战源码是我在 GitHub上开源项目 My-Blog,目前已有 2000 多个 star:</span> </p> <p> <span style="color:#4d4d4d;"><img src="https://img-bss.csdnimg.cn/202103310649344285.png" alt="" /><br /> </span> </p> <p> <span style="color:#4d4d4d;">本课程是一个 Spring Boot 技术栈的实战类课程,课程共分为 3 大部分,前面两个部分为基础环境准备和相关概念介绍,第三个部分是 Spring Boot 个人博客项目功能的讲解,<span style="color:#565656;">通过本课程的学习,不仅仅让你掌握基本的 Spring Boot 开发能力以及 Spring Boot 项目的大部分开发使用场景,同时帮你提前甄别和处理掉将要遇到的技术难点,认真学完这个课程后,你将会对 Spring Boot 有更加深入而全面的了解,同时你也会得到一个大家都在使用的博客系统源码,你可以根据自己的需求和想法进行改造,也可以直接使用它来作为自己的个人网站,这个课程一定会给你带来巨大的收获。</span></span> </p> <p> <span style="color:#4d4d4d;"><span style="color:#565656;"> </span></span> </p> <p> <span style="color:#e53333;"><span style="color:#e53333;"><strong>课程特色</strong></span></span> </p> <p> <span style="color:#e53333;"><span style="color:#e53333;"><strong> </strong></span></span> </p> <p> <span style="color:#4d4d4d;"><span style="color:#565656;"> </span></span> </p> <ol> <li> <span style="color:#565656;">课程内容紧贴 Spring Boot 技术栈,涵盖大部分 Spring Boot 使用场景。</span> </li> <li> <span style="color:#565656;">开发教程详细完整、文档资源齐全、实验过程循序渐进简单明了。</span> </li> <li> <span style="color:#565656;">实践项目页面美观且实用,交互效果完美。</span> </li> <li> <span style="color:#565656;">包含从零搭建项目、以及完整的后台管理系统和博客展示系统两个系统的功能开发流程。</span> </li> <li> <span style="color:#565656;">技术栈新颖且知识点丰富,学习后可以提升大家对于知识的理解和掌握,对于提升你的市场竞争力有一定的帮助。</span> </li> </ol> <p> <strong>实战项目预览</strong> </p> <p> <span style="color:#4d4d4d;"><span style="color:#565656;"><span style="color:#e53333;"><strong> </strong></span></span></span> </p> <p> <span style="color:#4d4d4d;"><img src="https://img-bss.csdn.net/202005150303066258.png" alt="" /><br /> </span> </p> <p>   </p> <p> <span style="color:#4d4d4d;"> </span> </p> <p> <span style="color:#4d4d4d;"><img src="https://img-bss.csdn.net/202005150305396930.png" alt="" /><br /> </span> </p> <p> <span style="color:#4d4d4d;"> </span> </p> <p> <span style="color:#4d4d4d;"><img src="https://img-bss.csdn.net/202005150305528842.png" alt="" /><br /> </span> </p> <p> <span style="color:#4d4d4d;"> </span> </p> <p> <span style="color:#4d4d4d;"><img src="https://img-bss.csdn.net/202005150306056323.png" alt="" /><br /> </span> </p>
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页