Understand JavaScript call()/apply()/bind()

Understanding JavaScript this In this paper, the pointing problem of this in JavaScript has been comprehensively analyzed. In one sentence, it is concluded that the pointing of this must be decided at the execution time, pointing to the object of the called function. Of course, the previous article also pointed out that the directions of this can be specified through call ()/ apply ()/ bind () built-in function methods to achieve the developer's expectations, and this article will further discuss this issue.

Let me first review a simple example:

var leo = {
  name: 'Leo',
  sayHi: function() {
    return "Hi! I'm " + this.name;
  }
};

var neil = {
  name: 'Neil'
};

leo.sayHi(); // "Hi! I'm Leo"
leo.sayHi.call(neil); // "Hi! I'm Neil"

Basic Usage

In JavaScript, functions are also objects, so JS functions have some built-in methods, including call(), apply() and bind(), which are defined on the prototype of Function, so each function can call these three methods.

Function. prototype. call (this Arg [, arg1 [, arg2,...]), for call(), its first parameter is the object to be bound, that is, the object to which this points, as in today's quotation.

The first parameter can also be null and undefined, in strict mode this will point to the window object in the browser or the global object in Node.js.

var leo = {
  name: 'Leo',
  sayHi: function() {
    return "Hi! I'm " + this.name;
  }
};

leo.sayHi.call(null); // "Hi! I'm undefined"

_this points to window, window.name is not defined

In addition to the first parameter, call() can choose to receive any number of parameters remaining, which will be used as parameters of the calling function. Let's see:

function add(a, b) {
  return a + b;
}

add.call(null, 2, 3); // 5

_is equivalent to add(2, 3)

The use of apply() is similar to call(), except that they receive parameters in different forms. In addition to the first parameter, call() is passed in as an enumeration, while apply() is passed in as an array.

function add(a, b) {
  return a + b;
}

add.apply(null, [2, 3]); // 5

Note: The second parameter that apply() accepts is an array (or a class array object), but that does not mean that the function calling it receives an array parameter. Here, the add() function is still a and b parameters, assigned to 2 and 3 respectively, instead of a being assigned to [2, 3].

Now let's talk about bind(), which is quite different from the other two.

var leo = {
  name: 'Leo',
  sayHi: function() {
    return "Hi! I'm " + this.name;
  }
};
var neil = {
  name: 'Neil'
};
var neilSayHi = leo.sayHi.bind(neil);
console.log(typeof neilSayHi); // "function"
neilSayHi(); // "Hi! I'm Neil"

Unlike call() and apply(), which directly execute the original function, bind() returns a new function. In short, the function of bind() is to bind this of the original function to the specified object and return a new function to delay the execution of the original function, which has a strong role in asynchronous processes (such as callback functions, event handlers). You can simply understand the bind() process as follows:

function bind(fn, ctx) {
  return function() {
    fn.apply(ctx, arguments);
  };
}

How to achieve

This part should often appear in interviews. The most common is the implementation of bind(), so let's start with how to implement your own bind().

Implementation of_bind()

The previous section has simply implemented a bind(), slightly changed, in order to distinguish it from the built-in bind(), we implemented a function called bound(), first look at:

Function.prototype.bound = function(ctx) {
  var fn = this;
  return function() {
    return fn.apply(ctx);
  };
}

Here bound() simulates the implementation of a basic bind() function, which returns a new function. This new function wraps the original function and binds this to the incoming ctx.

For the built-in bind(), it also has one feature:

var student = { id: '2015' };

function showDetail (name, major) {
    console.log('The id ' + this.id +
                ' is for ' + name +
                ', who major in ' + major);
}

showDetail.bind(student, 'Leo')('CS');
// "The id 2015 is for Leo, who major in CS"

showDetail.bind(student, 'Leo', 'CS')();
// "The id 2015 is for Leo, who major in CS"

In this case, the two invocations of parameters pass in different ways, but with the same result. Next, we will continue to improve our bound() function.

var slice = Array.prototype.slice;

Function.prototype.bound = function(ctx) {
  var fn = this;
  var _args = slice.call(arguments, 1);
  return function() {
    var args = _args.concat(slice.call(arguments));
    return fn.apply(ctx, args);
  };
}

The Array.prototype.slice() method is needed here to convert an array object of the arguments class into an array. We use a variable to save the parameters of the incoming bound() except the first parameter. In the new function returned, we merge the parameters of the incoming bound() with those of the bound().

In fact, up to now, the entire bound() function implementation is inseparable from closures, you can see the article. Understanding JavaScript closures.

In Article Understanding JavaScript this In this article, we mentioned that new can also change the direction of this. If new and bind() appear at the same time, who will this listen to?

function Student() {
  console.log(this.name, this.age);
}

Student.prototype.name = 'Neil';
Student.prototype.age = 20;

var foo = Student.bind({ name: 'Leo', age: 21 });
foo(); // 'Leo' 21

new foo(); // 'Neil' 20

As you can see from the example, using new changes the bind() bound this pointing, while our own bound() function does not:

var foo = Student.bound({ name: 'Leo', age: 21 });
foo(); // 'Leo' 21

new foo(); // 'Leo' 21

So we'll go on to improve the bound() function. To solve this problem, we need to clarify the prototype chain and the principle of new. In the following article, I will analyze it again, and only provide the solution here.

var slice = Array.prototype.slice;

Function.prototype.bound = function(ctx) {
  if (typeof this !== 'function') {
    throw TypeError('Function.prototype.bound - what is trying to be bound is not callable');
  }
  var fn = this;
  var _args = slice.call(arguments);
  var fBound = function() {
    var args = _args.concat(slice.call(arguments));

    // Adding a judgment when binding the original function fn, if this is an example of fBound
    // At this point, the fBound call must be a new call.
    // So, this binds directly to this(fBound instance object)
    // Otherwise, this will still be bound to the ctx we specified
    return fn.apply(this instanceof fBound ? this : ctx, args);
  };

  // Here we have to declare that the prototype of fBound points to the prototype of the original function fn.
  fBound.prototype = Object.create(fn.prototype);

  return fBound;
}

Be accomplished. If you don't understand the last piece of code, you can put it in first. The following article will analyze the prototype chain and the principle of new.

Implementation of_call()

function foo() {
  console.log(this.bar);
}
var obj = { bar: 'baz' };
foo.call(obj); // "baz"

We observe the call calls with the following characteristics:

  • When the function foo calls call and passes in obj, it seems that a foo method is added to the prototype of obj.
  • foo.call() All parameters except the first one should be passed to foo(), which was handled when implementing bind().
  • No changes can be made to foo and obj.

Let's see, to make a difference, the call we implement ourselves is called a call.

Function.prototype.calling = function(ctx) {
  ctx.fn = this;
  ctx.fn();
}

We have completed the first step.

To complete the second step, we need to use eval(), which can execute a string-type JavaScript code.

var slice = Array.prototype.slice;

Function.prototype.calling = function(ctx) {
  ctx.fn = this;
  var args = [];
  for (var i = 1; i < args.length; i++) {
    args.push('arguments[' + i + ']');
  }
  eval('ctx.fn(' + args + ')');
}

Here we avoid using the same method as implementing bind() to get the remaining parameters, because to use call, we use loops here. We need to pass the parameters one by one into ctx.fn(), so we use eval(), where the code in eval() does the + operation, and args converts the type and automatically calls the toString() method.

To achieve this, most of the functions and completions, but we inevitably add a fn method for ctx manually, changing the ctx itself, so we need to delete it. In addition, call should have a return value, and its value is the result of fn execution, and if ctx passes in null or undefined, this should be bound to the global object. We can get the following code:

var slice = Array.prototype.slice;

Function.prototype.calling = function(ctx) {
  ctx = ctx || window || global;
  ctx.fn = this;
  var args = [];
  for (var i = 1; i < args.length; i++) {
    args.push('arguments[' + i + ']');
  }
  var result = eval('ctx.fn(' + args + ')');
  delete ctx.fn;
  return result;
}

Implementation of_apply()

The implementation of apply() is similar to call(), but the processing of parameters is different. Look at the code directly.

var slice = Array.prototype.slice;

Function.prototype.applying = function(ctx, arr) {
  ctx = ctx || window || global;
  ctx.fn = this;
  var result = null;
  var args = [];
  if (!arr) {
    result = ctx.fn();
  } else {
    for (var i = 1; i < args.length; i++) {
      args.push('arr[' + i + ']');
    }
    result = eval('ctx.fn(' + args + ')');
  }
  delete ctx.fn;
  return result;
}

Summary

On the basis of the previous article, this article further discusses the use and implementation of call ()/ apply ()/ bind(). The difference between the three and the implementation of bind() are the common test points for school recruitment interviews. Initial contact may be a little difficult to understand bind(), because it involves closures, new and prototype chains.

I will introduce the object, prototype, prototype chain, inheritance and new implementation principles in the next article. Please look forward to it.

The original article is published in the public number cameraee. Click to view

Article reference

Function.prototype.call() / apply() / bind() | MDN

Invoking JavaScript Functions With 'call' and 'apply' | A Drop of JavaScript

Implement your own - call(), apply() and bind() method in JavaScript | Ankur Anand

JavaScript .call() .apply() and .bind() - explained to a total noob | Owen Yang

JavaScript call() & apply() vs bind()? | Stack Overflow

Learn & Solve: call(), apply() and bind() methods in JavaScript

JavaScript series of articles

Understanding JavaScript this

Understanding JavaScript closures

Understanding JavaScript Execution Stack

Understanding JavaScript scope

Understanding JavaScript data types and variables

Be Good. Sleep Well. And Enjoy.

Front-end Technology | Personal Growth

Keywords: Javascript

Added by tendrousbeastie on Sat, 18 May 2019 10:32:16 +0300