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

callThe 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:

  1. willmycallFunction callers (showMethod) to the incomingctxthisThe object to point to is not inside the functionthis)
  2. Method to perform mounting
  3. 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

applyThe 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

bindThe 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?