preface

There are default bindings, implicit bindings, explicit bindings, new operations, arrow functions, and the methods that we use to change this are call, apply, bind, all of these methods that we use a lot and you’re curious about how they’re implemented? It’s not that hard to figure that out, but I think we need to know what’s going on inside the function, what the problem is, and then we can explore it.

new

The first is the most common new, which is not a function, but an operator that creates an instance of an object, which should be familiar. Usage, new is followed by a constructor:

new constructor[([arguments])]
Copy the code

Look at the explanation on MDN and the new keyword does the following:

  1. Create an empty simple JavaScript object (that is {});
  2. Linking this object (setting its constructor) to another object;
  3. Use the new object created in Step 1 as the context for this;
  4. If the function does not return an object, this is returned.

In this case, we will start a myNew function around the above four points

Function myNew() {// Create an empty simple JavaScript object (that is {}); Let obj = new Object() // link this Object(set its constructor) to another Object --> change the point to obj prototype // arguments is a class array, Shift let con = [].shift.call(arguments) obj.__proto__ = con.prototype // Use the new object created in Step 1 as this context; // If the function does not return an object, return this. Return typeof res == 'object'? Return typeof res == 'object'? res : obj } function person(name) { this.name = name } let p = myNew(person, 'jack')Copy the code

Interjection: Why can’t arrow functions be used as constructors?

  1. There is no single this
  2. Do not bind the arguments
  3. Arrow functions have no prototype attribute

Combined with the above code implementation, isn’t it a little clearer?

We can also optimize the code. Changing and inheriting properties in this way is very bad for performance and affects all objects that inherit from this [[Prototype]]. The object.create () method creates a new Object, using an existing Object to provide the __proto__ of the newly created Object.

let obj = Object.create(con.prototype) //
Copy the code

instanceOf

From MDN:

The instanceof operator is used to test whether constructor. Prototype exists on the prototype chain of the parameter Object

Instanceof’s judgment logic is: if you can find a prototype from the current referenced Proto layer by layer up the prototype chain, can you find it? Returns true if found. It’s pretty simple to implement a simple Instanceof

/ / function _instanceOf(obj,con) {let _obj = obj.__proto__ let _con = con.prototype while(true) { if(_obj === null) { return false } if(_obj === _con) { return true } _obj = _obj.__proto__ } }Copy the code

Disadvantages: Not completely accurate to determine the specific data types of complex types

  • [] instanceof Array; //true
  • [] instanceof Object; //true

Bind

The bind() method creates a new function, and when bind() is called, this of the new function is specified as the first argument to bind(), and the remaining arguments will be used as arguments to the new function.

Let’s start with the official example:

const module = { x: 42, getX: function() { return this.x; }}; const unboundGetX = module.getX; console.log(unboundGetX()); // The function gets invoked at the global scope // expected output: undefined const boundGetX = unboundGetX.bind(module); console.log(boundGetX()); // expected output: 42Copy the code

As you can see from the above example, using bind does two things:

  1. Return a new function and add run-time arguments to the function so that a function has preset initial arguments, much like es6’s default arguments.
  2. Displays the bind this value, which resolves the bind implicit loss problem, where this loses the bind object in the function. For example, using setTimeout, it is common to execute this pointing to the window. When using methods on objects that reference this, you may need to explicitly bind this to the callback function to continue using the object. Such as:setInterval(obj.fn.bind(obj), 1000);

Based on this, we can try a simple bind:

Function _bind() {let fn = this // let args = array. from(arguments) // class Array -> Array let obj = args return function () { fn.apply(obj, Array.from(args).concat(Array.from(arguments))) } } Function.prototype._bind = _bind function fn(a, b) { console.log(this); // obj console.log(a + b); // 3 } let obj = { name: 'violetrosez' } let _fn = fn._bind(obj, 1) _fn(2)Copy the code

The above code seems to satisfy the requirement, but when the returned function is used as a constructor, the original function calls this to the object specified by bind, and cannot be bound to the object created by new based on the call to new. When called as a constructor, we need to assign the scope of the binding function to the new object and set the binding function to inherit the prototype of the target function. Modified as follows:

Function _bind() {let fn = this if (typeof fn! == 'function') { throw new TypeError('what is trying to be bound is not callable'); } let args = Array.from(arguments) let obj = args.shift() let fNOP = Object.create(fn.prototype) let fBound = function () {// If the constructor test points to obj and does not point to the new instance p, then p.name == undefined fn. Apply (this instanceof fn? This: Array.from(args).concat(array.from (arguments)))} // Make fBound. Prototype an instance of fN. Prototype = fNOP return fBound} function test(name) {this.name = name} let obj = {} let _fn = test._bind(obj) _fn('violetrosez') console.log(obj.name); // violetrosez let p = new _fn('zzzzz') console.log(obj.name); // violetrosez console.log(p.name); // zzzzzCopy the code

Reference: implementation of the bind method mdN-bind

call

The call() method calls a function with a specified this value and one or more arguments given separately. Call () allows different objects to assign and call functions/methods belonging to one object.

Now that we’ve got the basics, we’re a little bit more comfortable writing this, it’s all the same idea, processing parameters

function _call(ctx, ... args) { if (typeof this ! == 'function') { throw new TypeError('what is trying to be bound is not callable'); } ctx = ctx || window ctx.fn = this let res = ctx.fn(... Return res} function.prototype. _call = _callCopy the code

If CTX does not exist, we make CTX point to the global object, and then execute the function as a method of the object to bind to, and then delete it. This time we’ll use the remaining ES6 operators to handle the arguments, which is a little more concise than we wrote above.

apply

The only difference between Apply and Call is that the rest of the arguments receive an array, so modify the code above. I had been confused about which array was accepted, but I simply remembered the initial letter a of apply as array.

function _apply(ctx, args = []) { if (typeof this ! == 'function') { throw new TypeError('what is trying to be bound is not callable'); } if(args && ! Array.isArray(args)) { throw new TypeError('apply need accept array object'); } ctx = ctx || window ctx.fn = this let res = ctx.fn(... Return res} function.prototype. _apply = _applyCopy the code

Afterword.

Now that we’re done implementing call, apply, and bind, all of which essentially change the orientation of this, it’s important to always know when to use the arrow function and when to display and bind this. Once you understand how this works, you can see why the binding order for this is new > show binding > implicit binding > default binding. If you have any questions or mistakes, please correct them and make progress together. Ask for “like” three times QAQ