Call, apply, and bind are all essentially about changing the direction of this, and it’s important to always know what this is pointing to during implementation
Consider this scenario first
function show() {
console.log(this.name);
}
const person = {
name: 'white_give'
};
Copy the code
How do I get this in show to point to a Person object without call, apply, and bind?
You can do it like this
const person = {
name: 'white_give'.show: function () {
console.log(this.name); }};Copy the code
If you’re familiar with js, you probably know that this is implicitly bound instead of the default binding for this
This is how we implement call, apply, and bind by hand
call
The implementation of the
We’ll use the show method and person object defined above, but we’ll add some parameters to the show method to verify that the call method implemented is valid
function show(. args) {
console.log(this.name);
console.log(... args); }const person = {
name: 'white_give'
};
Function.prototype.myCall = function (ctx, ... args) {
/** * this refers to the caller of myCall * CTX is the object this refers to */
ctx.fn = this; // This is equivalent to adding a fn attribute to the person object, whose value is show
constresult = ctx.fn(... args);delete ctx.fn;
return result;
}
Copy the code
So the idea of the above code is:
- will
mycall
Function callers (show
Method) to the incomingctx
(this
The object to point to is not inside the functionthis
) - Method to perform mounting
- Deletes the mount method properties
At this point we have a rudimentary implementation of the Call method, but there is another case that we haven’t covered.
What does the following code output?
show.call(null.1.2.3);
show.myCall(null.1.2.3);
// Here is the output of the above two statements
//
/ / 1 2 3
// Uncaught TypeError: Cannot set property 'fn' of null
Copy the code
As we can see from the above code, we do not handle the incoming object when it is null, so our final code is:
Function.prototype.myCall = function (ctx, ... args) {
ctx = ctx || window;
ctx.fn = this;
constresult = ctx.fn(... args);delete ctx.fn;
return result;
}
Copy the code
Aren’t you happy that we’ve implemented the Call method manually at this point? Don’t worry, let’s look at the Apply method again
apply
The implementation of the
The difference between Apply and Call is that the remaining parameters of apply receive an array
Here we borrow the idea from the call implementation of the show function and the Person object and the call method
Function.prototype.myApply = function (ctx, args = []) {
ctx = ctx || window;
ctx.fn = this;
constresult = ctx.fn(... args);delete ctx.fn;
return result;
}
Copy the code
Does this feel like you’ve implemented apply? Before you worry, think about what the code will say?
show.apply(person, 'abc'.'def');
show.myApply(person, 'abc'.'def');
// Here is the output of the above two statements
// Uncaught TypeError: CreateListFromArrayLike called on non-object
// white_give
// a b c
Copy the code
From the above code, we see that there are no restrictions on parameter types. So our final code is:
Function.prototype.myApply = function (ctx, args = []) {
if (args && !Array.isArray(args)) {
throw ('Uncaught TypeError: CreateListFromArrayLike called on non-object');
}
ctx = ctx || window;
ctx.fn = this;
constresult = ctx.fn(... args);delete ctx.fn;
return result;
}
Copy the code
bind
The implementation of the
I’m going to try to explain the bind method
First, bind and call accept arguments in the same way and return a function
Here we borrow the show function and the person object from the call implementation, so the prototype of our bind method is:
Function.prototype.myBind = function (ctx, ... args) {
return () = > {
this.apply(ctx, ...args);
}
}
Copy the code
This allows us to implement a simple bind function, but the native BIND function has a corritic nature, which means the following output is the same:
show.bind(person, 5.6) (2.1); // white_give 5 6 2 1
show.bind(person, 5.6.2.1) ();// white_give 5 6 2 1
show.bind(person)(5.6.2.1); // white_give 5 6 2 1
Copy the code
So our code needs to change to:
Function.prototype.myBind = function (ctx, ... args1) {
return (. args2) = > {
this.apply(ctx, args1.concat(args2)); }}Copy the code
This satisfies the requirements of bind’s curation features. But the most annoying thing is that the function that bind returns can also instantiate objects using the new keyword.
So it’s up to us to determine if the function we’re returning is instantiating the object with the new keyword. If so, this of the returned function will refer to the instantiated object. If not, we’ll use our original logic
Here we add a property to the prototype object of the show method above and use the new keyword to instantiate an object
show.prototype.info = 'Here's the show method';
Fcuntion.prototype.myBind = function (ctx, ... args) {
// do something...
}
const NewBind = show.myBind(person, 5.6);
const newBind = new NewBind(2.1);
console.log(newBind.info);
Copy the code
Next we use instanceof to determine if the object created is an object instantiated with the new keyword
Function.prototype.myBind = function (ctx, ... args1) {
// Use that to save the caller of myBind
const that = this;
const newFn = function (. args2) {
const args = args1.concat(args2);
// If the new keyword is used, this refers to the newFn object
if (this instanceof newFn) {
// Change the myBind caller's this to the newFn object
that.apply(this, args);
} else{ that.apply(ctx, args); }}return newFn;
}
Copy the code
Here we find console.log(newbind.info); We still can’t access the info attribute because we haven’t made a connection between newFn and myBind’s caller (the show function), so our code is changed to:
Function.prototype.myBind = function (ctx, ... args1) {
// Use that to save the caller of myBind
const that = this;
const newFn = function (. args2) {
const args = args1.concat(args2);
// If the new keyword is used, this refers to the newFn object
if (this instanceof newFn) {
// Change the myBind caller's this to the newFn object
that.apply(this, args);
} else {
that.apply(ctx, args);
}
}
newFn.prototype = that.prototype;
return newFn;
}
Copy the code
This allows us to implement bind, but there is also a bit of nonspecification in the code that we have modified the prototype object directly in the code. We can modify our code using the original type inheritance, so the final code for bind is:
Function.prototype.myBind = function (ctx, ... args1) {
const that = this;
const o = function () {};
const newFn = function (. args2) {
const args = args1.concat(args2);
if (this instanceof o) {
that.apply(this, args);
} else {
that.apply(ctx, args);
}
}
o.prototype = that.prototype;
newFc.prototype = new o;
return newFn;
}
Copy the code
Bind: new bind > show bind > implicit bind > default bind
So, do you understand the binding order of this now?