“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:
- This points to change
- 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:
- Set the function to an object property: foo.fn = bar
- Execute function: foo.fn()
- 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:
- Return a function
- 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:
- The function created with BIND inherits the prototype of the original function
- 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.