Difficulty: Medium

We often see a line like this in framework-level source code:

var toStr1 = Function.prototype.call.bind(Object.prototype.toString);
Copy the code

This code uses both the call method and the bind method, which at first glance looks a bit confusing. What the hell is this all about?

Let’s call it and try passing in a different type.

console.log(toStr1({}));      // "[object Object]"
console.log(toStr1([]));      // "[object Array]"
console.log(toStr1(123));     // "[object Number]"
console.log(toStr1("abc"));   // "[object String]"
console.log(toStr1("abc"));   // "[object String]"
console.log(toStr1(new Date));// "[object Date]"
Copy the code

You can see from the results that the main function of this method is to detect the type of the object. But in general type detection, we might see more code implementations like this:

var toStr2 = obj= > Object.prototype.toString.call(obj);

console.log(toStr2({}));      // "[object Object]"
console.log(toStr2([]));      // "[object Array]"
console.log(toStr2(123));     // "[object Number]"
console.log(toStr2("abc"));   // "[object String]"
console.log(toStr2("abc"));   // "[object String]"
console.log(toStr2(new Date));// "[object Date]"
Copy the code

If you are familiar with call and bind, you should know that the two methods are essentially the same. However, the second method is more concise. It only takes one call to get what you want.

Prototype is the main reason is to prevent the pollution, such as covered in the business code we write the Object. The prototype. The toString method, the second of writing won’t get the right result, and the first kind of method can still. Let’s try it with code:

var toStr1 = Function.prototype.call.bind(Object.prototype.toString);

var toStr2 = obj= > Object.prototype.toString.call(obj);

Object.prototype.toString = function(){
  return'toString method overridden! ';
}
// Then we call the above method

// The result of toStr1 is as follows:
console.log(toStr1({}));      // "[object Object]"
console.log(toStr1([]));      // "[object Array]"
console.log(toStr1(123));     // "[object Number]"
console.log(toStr1("abc"));   // "[object String]"
console.log(toStr1("abc"));   // "[object String]"
console.log(toStr1(new Date));// "[object Date]"

// The result of toStr2 is as follows:
console.log(toStr2({}));      // "toString method overridden!"
console.log(toStr2([]));      // "toString method overridden!"
console.log(toStr2(123));     // "toString method overridden!"
console.log(toStr2("abc"));   // "toString method overridden!"
console.log(toStr2("abc"));   // "toString method overridden!"
console.log(toStr2(new Date));// "toString method overridden!"

Copy the code

The results were clear. The first method still gets the results right, but the second method doesn’t! So why does this happen? We know that bind returns a function that is inside a function and is delayed, so it’s natural to assume that there might be a closure! Because closures preserve the context state of the internal function as it executes. However, in modern browsers, both call and bind are implemented internally by the JS engine, so there is no way to debug them! However, we can understand the internal logic of the engine with the source code provided by Polly -fill. Here is a demo for this article, you can try it out:

// Simulate the call implementation
/ / ES6 implementation
Function.prototype.mycall = function (context) {
  context = context ? Object(context) : window;
  var fn = Symbol(a); context[fn] =this;

  let args = [...arguments].slice(1);
  letresult = context[fn](... args);delete context[fn]
  return result;
}

// Simulate the implementation of bind
Function.prototype.mybind = function (context) {
  if (typeof this! = ="function") {
    throw new Error("Please call me with a function object, thank you!");
  }

  var self = this;
  var args = Array.prototype.slice.call(arguments.1);

  var fNOP = function () {};var fBound = function () {
    var bindArgs = Array.prototype.slice.call(arguments);
    return self.myapply(this instanceof fNOP ? this : context, args.concat(bindArgs));
  }

  fNOP.prototype = this.prototype;
  fBound.prototype = new fNOP();
  return fBound;
}

// implement apply
/ / ES6 implementation
Function.prototype.myapply = function (context, arr) {
    context = context ? Object(context) : window;
    var fn = Symbol(a); context[fn] =this;
    let result;
    if(! arr) { result = context[fn](); }else{ result = context[fn](... arr); }delete context[fn]
    return result;
}

var toStr1 = Function.prototype.mycall.mybind(Object.prototype.toString);

console.log(toStr1({}));      // "[object Object]"
console.log(toStr1([]));      // "[object Array]"
console.log(toStr1(123));     // "[object Number]"
console.log(toStr1("abc"));   // "[object String]"
console.log(toStr1(new Date));// "[object Date]"
Copy the code

The above implementation leaves out some robust code and only preserves the core logic. The specific implementation details are not explained here. From DevTools we can see that the closure formed by MyBind is indeed in the scope of function object toStr1!

Of course, if you have a good understanding of prototype chains, this interesting code can be written as follows:

var toStr3 = Function.call.bind(Object.prototype.toString);
var toStr4 = Function.call.call.bind(Object.prototype.toString);
var toStr5 = Function.call.call.call.bind(Object.prototype.toString);

// You can even write...
var toStr6 = (() = >{}).call.bind(Object.prototype.toString);
Copy the code

-END-