14

js implements these five layers of bind, which layer are you on?

Recently, I was reviewing the basic knowledge of JS for a friend, and she would come to ask me if she encountered a problem.

image-20210511013512798

Isn't it simple? Three under five divided by two, resolved in minutes.

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

So I posted this code

image-20210511013602940

At this time, the girlfriend was immediately subjected to a series of soul torture.

image-20210511013907433

At this time, my teacher Ma couldn’t sit still, I was not convinced, so I went to review the bind, and found that it took too long to write the basic code, it would still take a little time to review, this time I have to write a deep bind, deep The true biography of Mr. Ma of Mr. Ma is divided into five layers of shorthand.

dasima

The first layer-the method bound to the prototype

This layer is very simple, thanks to the characteristics of the JS prototype chain. Since the prototype chain of function xxx points to Function.prototype , when we call xxx.bind, we call the method on Function.prototype.

Function.prototype._bind = function() {}

In this way, we can directly call our bind method on a constructor ~ for example like this.

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

If you want to understand this in detail, you can see this picture and this article ( https://github.com/mqyqingfeng/blog/issues/2 )

js-prototype

Second layer-change the direction of this

This can be said to be the core feature of bind, which is to change the point of this and return a function. And to change this, we can implement it through the known apply and call, here we will use apply for the time being to simulate. First self , which is the function passed in. Because we know that this has rule implicitly bound ( from "you do not know JavaScript (a)" 2.2.2 ),

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

Through the above features, we can write our _bind function.

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

Many friends may stop here, because in general interviews, especially in some school recruitment interviews, you may only need to know the first two to be almost the same. However, it is still not enough to surprise everyone in the interview. Next, we will continue our exploration and research.

The third layer-support currying

Function currying is a common topic, so I will review it here.

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

It is not difficult to find that Currying uses a closure. When we execute fn1, the x of the outer function is used in the function, thus forming a closure.

And our bind function is similar. We get the arguments current external function, remove the bound object, save it as a variable args , and finally return method, once again get the current function arguments , and finally merge it finalArgs .

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

Through the above code, let us bind method more and more robust.

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

Generally, when you get to this level, it can be said to be very good, but if you stick to it, it becomes a perfect answer sheet.

The fourth layer-consider the call of new

You know, our approach, by then bind binding, still can be instantiated by new new new priority higher than bind ( from "you do not know JavaScript (a)" 2.3 priority ) .

For this, we verify and compare the native bind and our fourth layer _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

Note that the bind method

Therefore, we need to process new in bind. The new.target attribute is used to detect whether the construction method is called by the new operator.

Next we also need to implement a new by ourselves,

According to MDN , new keywords will perform the following operations:

1. Create an empty simple JavaScript object (ie {} );

2. Link the object (set the constructor the object) to another object;

3. Use the newly created object in step 1 as the context of this

4. If the function does not return an object, it returns 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);
    }
  }
}

Seeing this, your attainments are already innocent, but there is one small detail at the end.

Fifth layer-keep function prototypes

There is no problem with the above method in most scenarios, but when our constructor has a prototype attribute, there is a problem. Therefore, we need to add to the prototype, and the calling object must be a function.

Function.prototype._bind = function (thisObj) {
  // 判断是否为函数调用
  if (typeof this !== 'function' || Object.prototype.toString.call(this) !== '[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;
};

The above is a relatively complete bind implementation. If you want to know more detailed practices, you can check it out. (Also recommended by MDN)

https://github.com/Raynos/function-bind

Although this exploration is relatively simple, there are still some precautions for careful and in-depth exploration, and the front-end veterans may not answer fully.

Conclusion

+Like+Favorite+Comment+ , original is not easy, I encourage the author to create better articles

Pay attention to the notes of the Autumn Wind, a front-end public account that focuses on front-end interviews, engineering, and open source


程序员秋风
3k 声望3.9k 粉丝

JavaScript开发爱好者,全栈工程师。