preface

This is the fifth installment of our source code analysis for underscore. If you are interested in the series, please click here

Underscore – Analysis/watch for dynamic updates at any time

Things start from this in JS. Do you often have a feeling that you can’t control and know it? For beginners, this is like a callback to hell, so miraculous that people can’t fathom it. But with the bind method in native JS, we can display the this scope of the binding function without worrying if the runtime changes to suit our expectations. Bind, of course, mimics the underscore function and can achieve similar results.

ctx

A brief review of BIND

Let’s review the use of BIND from the introduction on MDN.

The bind method creates a new function whose this keyword is set to the supplied value when called.

grammar

fun.bind(thisArg[, arg1[, arg2[, ...]]])Copy the code

Take a quick look at what these parameters mean

  1. thisArg

    This parameter is referred to as this when the binding function is called, and is invalid when the binding function is called using the new operator.

  2. arg1, arg2, …

    When the binding function is called, these arguments are passed to the bound method before the arguments.

Bind the this scope example

window.name = 'windowName'

let obj = {
  name: 'qianlongo',
  showName () {
    console.log(this.name)
  }
}

obj.showName() // qianlongo

let showName = obj.showName
  showName() // windowName

let bindShowName = obj.showName.bind(obj)
  bindShowName() // qianlongoCopy the code

From the simple example above, we know that the first parameter is used to bind the this reference to the function at runtime

The second parameter starts the usage example

let sum = (num1, num2) = > {
  console.log(num1 + num2)
}

let bindSum = sum.bind(null.1)
bindSum(2) / / 3Copy the code

Bind allows a function to have preset initial arguments. These arguments (if any) follow this (or any other object) as the second argument to bind, and they are inserted at the beginning of the argument list of the target function, followed by the arguments passed to the binding function.

Now that we’re done with arguments, let’s see what happens when you use new to call bind.

function Person (name, sex) {
  console.log(this) // Person {}
  this.name = name
  this.sex = sex
}
let obj = {
  age: 100
}
let bindPerson = Person.bind(obj, 'qianlongo')

let p = new bindPerson('boy')

console.log(p) // Person {name: "qianlongo", sex: "boy"}Copy the code

If you notice that this in bindPerson is no longer the first argument to bind, obj no longer works.

The use of bind is limited and does not work in some older browsers. Would you like to see how to implement a compatible BIND in the underline? come on

The bind implementation is underlined

The source code


 _.bind = function(func, context) {
  // If native supports bind, use native and pass in the corresponding parameters
  if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments.1));
  // Throw an exception if the func passed in is not a function type
  if(! _.isFunction(func))throw new TypeError('Bind must be called on a function');
  // Save the value after the third argument. Next look at executeBound
  var args = slice.call(arguments.2);
  var bound = function() {
    return executeBound(func, bound, context, this, args.concat(slice.call(arguments)));
  };
  return bound;
};Copy the code

ExecuteBound implementation

var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) {
  // If the call method is not new func, call sourceFunc directly and give the corresponding argument
  if(! (callingContextinstanceof boundFunc)) return sourceFunc.apply(context, args); 
  // Handle the form of the new call
  var self = baseCreate(sourceFunc.prototype);
  var result = sourceFunc.apply(self, args);
  if (_.isObject(result)) return result;
  return self;
};Copy the code

The above source code is commented, so let’s focus on the implementation of executeBound

So let’s look at what these parameters mean

  1. SourceFunc: original function, function to bind
  2. BoundFunc: post-binding function
  3. Context: function after bindingthisPointing context
  4. CallingContext: The execution context of the function after binding, usually this
  5. Args: parameters required for the execution of the bound function

Ok, let’s look at the first sentence

if(! (callingContextinstanceof boundFunc)) return sourceFunc.apply(context, args);Copy the code

This sentence is used to determine whether the bound function is called with the new keyword or as a normal function call, for example


function Person () {
  if(! (this instanceof Person)) {
    return console.log('Non-new call method')}console.log('New call method')
}

Person() // Non-new calls
new Person() // New call methodCopy the code

So if you want your constructor to work with or without new, you can use the following code


function Person (name, sex) {
  if(! (this instanceof Person)) {
    return new Person(name, sex)
  }

  this.name = name
  this.sex = sex
}

new Person('qianlongo'.'boy') // Person {name: "qianlongo", sex: "boy"}

Person('qianlongo'.'boy') // Person {name: "qianlongo", sex: "boy"}Copy the code

Let’s go back to executeBound parsing

if(! (callingContextinstanceof boundFunc)) return sourceFunc.apply(context, args);Copy the code

CallingContext is the this scope of the bound function, and boundFunc is the bound function, so if determines that a non-new call is handled by apply.

But what if it’s called with new? Let’s look at the following code, don’t look at the short four lines of code inside the knowledge quite a lot of it!

// Get an empty object that inherits from the function's prototype chain
var self = baseCreate(sourceFunc.prototype);
// The value returned after the constructor is executed
// Normally there is no return value, i.e. undefined
// But sometimes constructors explicitly return an obj
var result = sourceFunc.apply(self, args);
// Determine if the result is object. If so, return the object returned by the constructor
if (_.isObject(result)) return result;
// If no return object is displayed, return the instance after execution of the original function
return self;Copy the code

Okay, so here I have a question, what the hell is baseCreate?

var Ctor = function(){};

var baseCreate = function(prototype) {
  // If prototype is not of type object, return an empty object
  if(! _.isObject(prototype))return {};
  // Use native if native supports create
  if (nativeCreate) return nativeCreate(prototype); 
  // Assign prototype to the Ctor constructor's prototype
  Ctor.prototype = prototype; 
  // Create a Ctor instance object
  var result = new Ctor; 
  // Empty the prototype for next use
  Ctor.prototype = null; 
  // Finally return the instance
  return result; 
};Copy the code

Create implements the native Object. Create to do some inheritance.

At the end

The article is brief, but you only need to know how to implement a native BIND. If you’re interested in Apply, Call, and this, check it out

Js for call, apply, and bind

This – It’s hard to say I love you