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

最近在帮敌人温习 JS 相干的基础知识,遇到不会的问题,她就会来问我。

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

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

于是我就将这段代码发了过来

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

这个时候,我马老师就坐不住了,我不服气,我就去温习了一下 bind,发现太久不写根底代码,还是会须要一点工夫温习,这一次我得写一个有深度的 bind,深的马老师的真传,给他分成了五层速记法。

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

这一层十分的简略,得益于 JS 原型链的个性。因为 function xxx 的原型链 指向的是 Function.prototype , 因而咱们在调用 xxx.bind 的时候,调用的是 Function.prototype 上的办法。

Function.prototype._bind = function() {}

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

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

想要具体了解这方面的能够看这张图和这篇文章(https://github.com/mqyqingfen...

第二层 - 扭转 this 的指向

这能够说是 bind 最外围的个性了,就是扭转 this 的指向,并且返回一个函数。而扭转 this , 咱们能够通过已知的 apply 和 call 来实现,这里咱们就暂且应用 apply 来进行模仿。首先通过 self 来保留以后 this,也就是传入的函数。因为咱们晓得 this 具备 隐式绑定的规定(摘自 《你不晓得的JavaScript(上)》2.2.2 ),

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

通过以上个性,咱们就能够来写咱们的 _bind 函数。

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// 第四层的 bindvar 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...

本次摸索尽管比较简单,然而认真深刻探索还是有一些注意事项,前端老鸟也可能答复不全。

结语

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

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