js 实现 bind 的这五层,你在第几层?

js 实现 bind 的这五层,你在第几层?

最近在帮朋友复习 JS 相关的基础知识,遇到不会的问题,她就会来问我。前端

image-20210511013512798

这不是很简单?三下五除二,分分钟解决。git

function bind(fn, obj, ...arr) {
    return fn.apply(obj, arr)
}

因而我就将这段代码发了过去github

image-20210511013602940

这时候立马被女友进行了一连串的灵魂拷问。面试

image-20210511013907433

这个时候,我马老师就坐不住了,我不服气,我就去复习了一下 bind,发现过久不写基础代码,仍是会须要一点时间复习,这一次我得写一个有深度的 bind,深的马老师的真传,给他分红了五层速记法。segmentfault

dasima

第一层 - 绑定在原型上的方法

这一层很是的简单,得益于 JS 原型链的特性。因为 function xxx 的原型链 指向的是 Function.prototype , 所以咱们在调用 xxx.bind 的时候,调用的是 Function.prototype 上的方法。闭包

Function.prototype._bind = function() {}

这样,咱们就能够在一个构造函数上直接调用咱们的bind方法啦~例如像这样。app

funciton myfun(){}
myfun._bind();

想要详细理解这方面的能够看这张图和这篇文章(https://github.com/mqyqingfen...函数

js-prototype

第二层 - 改变 this 的指向

这能够说是 bind 最核心的特性了,就是改变 this 的指向,而且返回一个函数。而改变 this , 咱们能够经过已知的 apply 和 call 来实现,这里咱们就暂且使用 apply 来进行模拟。首先经过 self 来保存当前 this,也就是传入的函数。由于咱们知道 this 具备 隐式绑定的规则(摘自 《你不知道的JavaScript(上)》2.2.2 ),this

function foo() {console.log(this.a)}
var obj = {a: 2, foo};
obj.foo(); // 2

经过以上特性,咱们就能够来写咱们的 _bind 函数。spa

Function.prototype._bind = function(thisObj) {
    const self = this;
    return function () {
    self.apply(thisObj);
  }
}
var obj = {a:1}
function myname() {console.log(this.a)}
myname._bind(obj)(); // 1

可能不少朋友都止步于此了,由于在通常的面试中,特别是一些校招面试中,可能你只须要知道前面两个就差很少了。可是想要在面试中惊艳全部人,仍然是不够的,接下来咱们继续咱们的探索与研究。

第三层 - 支持柯里化

函数柯里化是一个老生常谈的话题,在这里再复习一下。

function fn(x) {
    return function (y) {
        return x + y;
    }
}
var fn1 = fn(1);
fn1(2) // 3

不难发现,柯里化使用了闭包,当咱们执行 fn1 的时候,函数内使用了外层函数的 x, 从而造成了闭包。

而咱们的 bind 函数也是相似,咱们经过获取当前外部函数的 arguments ,而且去除了绑定的对象,保存成变量 args,最后 return 的方法,再一次获取当前函数的 arguments, 最终用 finalArgs 进行了一次合并。

Function.prototype._bind = function(thisObj) {
    const self = this;
  const args = [...arguments].slice(1)
    return function () {
    const finalArgs = [...args, ...arguments]
    self.apply(thisObj, finalArgs);
  }
}

经过以上代码,让咱们 bind 方法,愈来愈健壮了。

var obj = { i: 1}
function myFun(a, b, c) {
  console.log(this.i + a + b + c);
}
var myFun1 = myFun._bind(obj, 1, 2);
myFun1(3); // 7

通常到了这层,能够说很是棒了,可是再坚持一下下,就变成了完美的答卷。

第四层 - 考虑 new 的调用

要知道,咱们的方法,经过 bind 绑定以后,依然是能够经过 new 来进行实例化的, new 的优先级会高于 bind摘自 《你不知道的JavaScript(上)》2.3 优先级)。

这一点咱们经过原生 bind 和咱们第四层的 _bind 来进行验证对比。

// 原生
var obj = { i: 1}
function myFun(a, b, c) {
  // 此处用new方法,this指向的是当前函数 myFun 
  console.log(this.i + a + b + c);
}
var myFun1 = myFun.bind(obj, 1, 2);
new myFun1(3); // NAN

// 第四层的 bind
var obj = { i: 1}
function myFun(a, b, c) {
  console.log(this.i + a + b + c);
}
var myFun1 = myFun._bind(obj, 1, 2);
new myFun1(3); // 7

注意,这里使用的是 bind方法

所以咱们须要在 bind 内部,对 new 的进行处理。而 new.target 属性,正好是用来检测构造方法是不是经过 new 运算符来被调用的。

接下来咱们还须要本身实现一个 new ,

而根据 MDNnew 关键字会进行以下的操做:

1.建立一个空的简单JavaScript对象(即{});

2.连接该对象(设置该对象的constructor)到另外一个对象 ;

3.将步骤1新建立的对象做为this的上下文 ;

4.若是该函数没有返回对象,则返回this

Function.prototype._bind = function(thisObj) {
    const self = this;
  const args = [...arguments].slice(1);
    return function () {
    const finalArgs = [...args, ...arguments];
        // new.target 用来检测是不是被 new 调用
    if(new.target !== undefined) {
      // this 指向的为构造函数自己
      var result = self.apply(this, finalArgs);
      // 判断改函数是否返回对象
      if(result instanceof Object) {
        return reuslt;
      }
      // 没有返回对象就返回 this
      return this;
    } else {
      // 若是不是 new 就原来的逻辑
      return self.apply(thisArg, finalArgs);
    }
  }
}

看到这里,你的造诣已经如火纯情了,可是最后还有一个小细节。

第五层 - 保留函数原型

以上的方法在大部分的场景下都没有什么问题了,可是,当咱们的构造函数有 prototype 属性的时候,就出问题啦。所以咱们须要给 prototype 补上,还有就是调用对象必须为函数。

Function.prototype._bind = function (thisObj) {
  // 判断是否为函数调用
  if (typeof target !== 'function' || Object.prototype.toString.call(target) !== '[object Function]') {
    throw new TypeError(this + ' must be a function');
  }
  const self = this;
  const args = [...arguments].slice(1);
  var bound = function () {
    var finalArgs = [...args, ...arguments];
    // new.target 用来检测是不是被 new 调用
    if (new.target !== undefined) {
      // 说明是用new来调用的
      var result = self.apply(this, finalArgs);
      if (result instanceof Object) {
        return result;
      }
      return this;
    } else {
      return self.apply(thisArg, finalArgs);
    }
  };
  if (self.prototype) {
    // 为何使用了 Object.create? 由于咱们要防止,bound.prototype 的修改而致使self.prototype 被修改。不要写成 bound.prototype = self.prototype; 这样可能会致使原函数的原型被修改。
    bound.prototype = Object.create(self.prototype);
    bound.prototype.constructor = self;
  }
  return bound;
};

以上就是一个比较完整的 bind 实现了,若是你想了解更多细节的实践,能够查看。(也是 MDN 推荐的)

https://github.com/Raynos/fun...

本次探索虽然比较简单,可是仔细深刻探究仍是有一些注意事项,前端老鸟也可能回答不全。

结语

❤️关注+点赞+收藏+评论+转发❤️ ,原创不易,鼓励笔者创做更好的文章

关注公众号秋风的笔记,一个专一于前端面试、工程化、开源的前端公众号

相关文章
相关标签/搜索