“This is the 12th day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”

preface

In javascript, call, apply, and bind all exist to change the context in which a function is run; in other words, to change the orientation of this within the function body. Call is described in one sentence as calling a function or method with a specified this value and several specified parameter values. The difference between call() and apply() is that the call() method accepts a list of several parameters, while the apply() method accepts an array of multiple parameters. The bind() method creates a new function. When the new function is called, the first argument to bind() will be this when it runs, and the subsequent sequence of arguments will be passed as its arguments before the arguments passed. In general, call and apply are automatically executed, while bind is not. Here’s an example:

var foo = {
    value: 1
};
function bar(name, age) {
    console.log(this.value);
    console.log(name);
    console.log(age);
}
bar.call(foo, 'vision'.25);
bar.apply(foo,['vision'.25]);
var bindFoo = bar.bind(foo, 'vision'.25);
bindFoo()
Copy the code

I. Simulated implementation of Call and Apply

Step 1 of simulation

From the above example, we can see the following two main points:

  1. This points to change
  2. The function executes

One way to change the “this” reference is to mount the function to an object so that the “this” of the function refers to the object, but in order not to affect the original object, we need to use delete to remove the function mounted to the object after executing the function. Example:

var foo = {
    value: 1.bar: function() {
        console.log(this.value)
    }
};
foo.bar() / / 1
delete foo.bar
Copy the code

So there are three steps:

  1. Set the function to an object property: foo.fn = bar
  2. Execute function: foo.fn()
  3. Delete function: delete foo.fn

Packaged, the first edition reads as follows:

/ / the first edition
Function.prototype.call2 = function(context) {
    context.fn = this;  // Mount the function to the specified object
    context.fn();  / / execution
    delete context.fn;  / / delete
}

// Open a browser to verify
var foo = {
    value: 1
};
function bar() {
    console.log(this.value);
}

bar.call2(foo); / / 1
Copy the code

ok!

Simulation Step 2

Arguments can be passed in call. The Arguments passed are undefined, so I can get Arguments using the Arguments object, which is an array object corresponding to the Arguments passed to the function. In the first example our Arguments object looks like this.

// arguments = {
// 0: foo
// 1: 'vision',
/ / 2:25,
// length: 3
// }
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
    args.push(arguments[i]);
}
Copy the code

In this way, the parameter of uncertain length is obtained, and we will implement our second version using ES6 notation

/ / the second edition
Function.prototype.call2 = function(context) {
    context.fn = this;
    let args = [];
    for(let i = 1, len = arguments.length; i < len; i++) {
        args.push(arguments[i]); } context.fn(... args);delete context.fn;
}
Copy the code

Simulation Step 3

There are two other details to note: 1. This can be passed as null or undefined, in which case this refers to window. 2. This can be passed as primitive data, which native Call automatically converts to Object()

/ / the third edition
Function.prototype.call2 = function(context) {
    context = context ? Object(context) : window;
    context.fn = this;
    let args = [];
    for(let i = 1, len = arguments.length; i < len; i++) {
        args.push(arguments[i]);
    }
    letresult = context.fn(... args);delete context.fn;
    return result;
}
Copy the code

The difference between Apply and call is the difference in passing parameters, so the simulation of Apply

Function.prototype.apply2 = function(context, arr) {
    context = context ? Object(context) : window;
    context.fn = this;

    let result;
    if(! arr) { result = context.fn(); }else{ result = context.fn(... arr); }delete context.fn;
    return result;
}
Copy the code

A mock implementation of BIND

Features of BIND:

  1. Return a function
  2. You can pass in parameters

For specifying this, we can refer to the Call or apply implementation

/ / the first edition
Function.prototype.bind2 = function (context) {
    var self = this;
    return function () {
        returnself.apply(context); }}Copy the code

Returns a function that uses apply to change this.

Parameter emulation of bind

The bind method can pass in arguments when bind() is called, or other arguments when the function is executed

var foo = {
    value: 1
};

function bar(name, age) {
    console.log(this.value);
    console.log(name);
    console.log(age);
}
var bindFoo = bar.bind(foo, 'vision');
bindFoo('25'); // 1 vision 25
Copy the code

Bind (‘ name ‘); bind (‘ age ‘); bind (‘ name ‘);

/ / the second edition
Function.prototype.bind2 = function (context) {
    // The first argument is context, so start with the second argument, which gets the arguments passed in by the call to bind()
    var aArgs   = Array.prototype.slice.call(arguments.1),
        self = this;
    return function () {
        // Get the parameters passed in by the function and concat the two parameters
        var bindArgs = Array.prototype.slice.call(arguments);
        returnself.apply(context, aArgs.concat(bindArgs)); }}Copy the code

Constructor property

Now that we’re done with changing this and parameters, the hard part is coming. Bind also has a feature called

A binding function can also use the new operator to create objects: this behavior is like treating the original function as a constructor. The supplied this value is ignored, and the arguments to the call are supplied to the mock function.

var foo = {
    value: 1
};
function bar(name, age) {
    this.habit = 'shopping';
    console.log(this.value);
    console.log(name);
    console.log(age);
}
bar.prototype.friend = 'kevin';

var bindFoo = bar.bind(foo, 'vision');
var obj = new bindFoo('25');  // Use new to create an object. The specified this binding is invalid
// undefined
// vision
/ / 25
console.log(obj.habit);  // shopping
console.log(obj.friend); // kevin
Copy the code

Tidy it up:

  1. The function created with BIND inherits the prototype of the original function
  2. This is ignored when new is used to create objects

If we want to create a function that inherits from the original, we can change the return function’s prototype to the binding function’s prototype, which solves the first problem

Function.prototype.bind2 = function (context) {
    var aArgs = Array.prototype.slice.call(arguments.1),
        self = this,
        fBound = function () {
            var bindArgs = Array.prototype.slice.call(arguments);
            return self.apply(context, aArgs.concat(bindArgs));
        };
    // Change the return function's prototype to the binding function's prototype, and the instance inherits the value from the binding function's prototype
    fBound.prototype = this.prototype;
    return fBound
}
Copy the code

There’s also the “this” problem, where “this” is still referring to the context, so we have to make a judgment

/ / the third edition
Function.prototype.bind2 = function (context) {
    var aArgs = Array.prototype.slice.call(arguments.1),
        self = this,
        fBound = function () {
            var bindArgs = Array.prototype.slice.call(arguments);
            // Use instanceof to determine if it is currently treated as a constructor. If so, point this to the instance to get the value from the binding function
            return self.apply(this instanceof fBound ? this : context, aArgs.concat(bindArgs));
        };
    // Change the return function's prototype to the binding function's prototype, and the instance inherits the value from the binding function's prototype
    fBound.prototype = this.prototype;
    return fBound
}
Copy the code

Fbound. prototype = this.prototype; When you change fbound. prototype, this. Prototype also changes. So let’s optimize.

Function.prototype.bind2 = function (context) {
    var aArgs = Array.prototype.slice.call(arguments.1),
        self = this,
        fNOP  = function() {},
        fBound = function () {
            var bindArgs = Array.prototype.slice.call(arguments);
            // Use instanceof to determine if it is currently treated as a constructor. If so, point this to the instance to get the value from the binding function
            return self.apply(this instanceof fBound ? this : context, aArgs.concat(bindArgs));
        };
    // Maintain prototype relationships
    if (this.prototype) {
      fNOP.prototype = this.prototype;
    }
    // The descending code makes fbound. prototype an instance of fNOP, so
    // Return fBound as the constructor of new, and pass the new object as this. The __proto__ of the new object is an instance of fNOP
    fBound.prototype = new fNOP();
    return fBound;
}
Copy the code

We have now completed the emulation of the BIND method.