Write apply, call and bind~ again based on cat vs. Dog ultraman
I’ll never see you rip my head off again!
Today, I saw a strange description about call and apply when I was brushing the question. I felt quite interesting, so I wrote the logic of call and apply again by hand, and learned something new by reviewing the past
It goes something like this:
- Cats eat fish, dogs eat meat, ultraman fights little monsters
- Dog eats fish: cat eats fish. Call (dog, fish)
- Cat fighting little monster: Ultraman. Call a little monster.
Apply and bind apply and bind apply and bind
The premise
First prepare three objects: cat, dog, ultraman:
let cat = {
name: "Cat".eatFish() {
console.log(`The ${this.name}Eat fish! `); }};let dog = {
name: "Dog".eatMeat() {
console.log(`The ${this.name}Eat meat! `); }};let ultraman = {
name: "Tiga".fight() {
console.log(`The ${this.name}Fight the little monster! `); }};Copy the code
Once we’re ready, let’s implement call.
call
Dogs eating fish need to be used like this: cat. Call (dog, fish); call(cat, fish); call(dog, fish);
cat.eatFish.call(dog, "Dog");
Copy the code
For the call method, the general logic looks like this:
- The first parameter passed in is treated as the context, in this case the dog
- Dog adds a fish-eating method that points to cat’s fish-eating method, which is cat’s this
- Dogs can eat all kinds of fish, too
- After eating, the dog deletes this method of eating fish, because this does not belong to it, but just borrows it
Following the above logic, we can write:
Function.prototype.defineCall = function (context, ... args) {
// Do not pass dog, default is window
var context = context || window;
// Dog adds a method that points to the cat's fish-eating method, i.e., this
context.fn = this;
// Dogs can eat all kinds of fish, which can have multiple parameters
letresult = context.fn(... args);// delete dog eating fish
delete context.fn;
return result;
};
Copy the code
Now, a custom call is almost complete! Now put it to the test:
cat.eatFish.defineCall(dog, "Dog");
ultraman.fight.defineCall(cat, "Cat");
// output:
// The dog eats the fish!
// The cat fights the little monster!
Copy the code
Now the dog can eat the fish, and the cat can fight the little monster!
Now let’s get the dog to eat more fish. Let’s change the cat’s fish diet briefly:
let cat = {
name: "Cat".eatFish(. args) {
console.log(`The ${this.name}Eat fish! To eat is:${args}`); }};Copy the code
Then we call it like this:
cat.eatFish.defineCall(dog, "Salmon".Tuna fish."Shark");
/ / the output:
// The dog eats the fish! Food: salmon, tuna, shark
Copy the code
Arguments can be used as arguments.
apply
The use of apply and call is similar except that the second argument is an array. We can write it like this:
Function.prototype.defineApply = function (context, arr) {
var context = context || window;
let result;
context.fn = this;
if(! arr) {// If no argument is passed, execute directly
result = context.fn();
} else {
// Execute if there are argumentsresult = context.fn(... arr); }delete context.fn;
return result;
};
Copy the code
Now call it again to see if it is correct:
cat.eatFish.apply(dog, ["Dog"]);
ultraman.fight.apply(cat, ["Cat"]);
/ / the output:
// The dog eats the fish!
// The cat fights the little monster!
Copy the code
Success! 🎉
bind
Now that call and apply are implemented, we can use bind, which is a little bit more difficult.
Let’s take a look at what bind has:
bind
It’s also used to convertthis
Pointing to.bind
Instead of executing immediately like these two, a binding is returnedthis
To execute, you need to call it again.bind
Support for currification of functions.bind
Of the new functionthis
It cannot be changed,call
å’Œapply
You can’t.
Let’s do it step by step. Let’s start with the simplest one:
Function.prototype.defineBind = function (obj) {
// If this is not stored, this may refer to window during execution
let fn = this;
return function () {
fn.apply(obj);
};
};
Copy the code
Then add the parameter pass function to it and it looks like this:
Function.prototype.defineBind = function (obj) {
// Bit 0 is this, so you have to crop from the first
let args = Array.prototype.slice.call(arguments.1);
// If this is not stored, this may refer to window during execution
let fn = this;
return function () {
fn.apply(obj, args);
};
};
Copy the code
And then we add cremation to it:
Function.prototype.defineBind = function (obj) {
// Bit 0 is this, so you have to crop from the first
let args = Array.prototype.slice.call(arguments.1);
let fn = this;
return function () {
// Second call we also grab arguments objects
let params = Array.prototype.slice.call(arguments);
// Note the order of concat
fn.apply(obj, args.concat(params));
};
};
Copy the code
DefineBind is pretty much starting to look like a bind now, so to upgrade it to a bind, there is one more detail:
The returned callback function can also be constructed as new, but its this is ignored during construction, and the returned instance still inherits the constructor and stereotype attributes of the constructor, and can receive attributes as normal (i.e., only this is missing, otherwise normal).
The constructor of an instance of a constructor refers to the constructor itself:
function Fn(){};
let o = new Fn();
console.log(o.constructor === Fn);
//true
Copy the code
And when the constructor is run, the inner this points to the instance (whoever calls it points to this), so this.constructor refers to the constructor:
function Fn() {
console.log(this.constructor === Fn);
//true
};
let o = new Fn();
console.log(o.constructor === Fn);
//true
Copy the code
Is it possible to change archetypal inheritance by changing the orientation of this.contructor?
Of course the answer is right! When returning a function as a constructor, this should refer to the instance; when returning a function as a normal function, this should refer to the current context:
Function.prototype.defineBind = function (obj) {
let args = Array.prototype.slice.call(arguments.1);
let fn = this;
let bound = function () {
let params = Array.prototype.slice.call(arguments);
// Call from constructor: true this points to the instance, otherwise obj
fn.apply(this.constructor === fn ? this : obj, args.concat(params));
};
// Prototype chain inheritance
bound.prototype = fn.prototype;
return bound;
};
Copy the code
This way, a bind is basically done, and the constructor is not affected by the instances generated by the returned constructor.
But! Modifying the instance stereotype directly affects the constructor!
What about this? If only there were nothing in the constructor prototype, then there would be no interaction… Blablabla…
Write a small example of using a mediation so that the prototype of a constructor affects only the instance and nothing else:
function Fn() {
this.name = "123";
this.sayAge = function () {
console.log(this.age);
};
}
Fn.prototype.age = 26;
// create a blank function Fn1, just copy Fn's prototype
let Fn1 = function () {};
Fn1.prototype = Fn.prototype;
let Fn2 = function () {};
Fn2.prototype = new Fn1();
Copy the code
– Added a __proto__ layer to Fn2 so that the prototype of Fn2 points to an instance of Fn, so that Fn2 changes don’t affect Fn (__proto__.__proto__ can still be modified)!
Function.prototype.defineBind = function (obj) {
let args = Array.prototype.slice.call(arguments.1);
let fn = this;
// Create a mediation function
let fn_ = function () {};
// Fn2 is bound here
let bound = function () {
let params = Array.prototype.slice.call(arguments);
// Call from constructor: true this points to the instance, otherwise obj
fn.apply(this.constructor === fn ? this : obj, args.concat(params));
};
fn_.prototype = fn.prototype;
bound.prototype = new fn_();
return bound;
};
Copy the code
Add a final touch with an error:
Function.prototype.defineBind = function (obj) {
if (typeof this! = ="function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
};
let args = Array.prototype.slice.call(arguments.1);
let fn = this;
// Create a mediation function
let fn_ = function () {};
// Fn2 is bound here
let bound = function () {
let params = Array.prototype.slice.call(arguments);
// Call from constructor: true this points to the instance, otherwise obj
fn.apply(this.constructor === fn ? this : obj, args.concat(params));
};
fn_.prototype = fn.prototype;
bound.prototype = new fn_();
return bound;
};
Copy the code
Handwritten bind finished!
Finally, let’s test it with a dog eating a fish:
let cat = {
name: "Cat".eatFish(. args) {
console.log(`The ${this.name}Eat fish! To eat is:${args}`); }};let dog = {
name: "Dog"
};
cat.eatFish.defineBind(dog, "Salmon".Tuna fish) ("Shark");
// output:
// The dog eats the fish! Food: salmon, tuna, shark
Copy the code
Bind (es6) : Bind (es6)
Function.prototype.defineBind = function (context, ... rest) {
if (typeof this! = ="function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
var self = this;
return function F(. args) {
if (this instanceof F) {
return newself(... rest, ... args); }return self.apply(context, rest.concat(args));
};
};
Copy the code
References:
- Js manual implementation of bind method, ultra detailed thinking analysis!
My public number: the front end stack in the road, share the front end knowledge, chewing the feeling is wonderful ~ for attention