Detailed explanation of this pointing and binding of JavaScript

  • 2021-08-12 01:56:51
  • OfStack

Note: This article belongs to the basic article, please take a detour. If you don't know enough, or if you don't know it completely, you can review it through this article.

Types pointed to by this

At the beginning of learning JavaScript, this is always the most confusing. Let's take a look at how to determine the direction of this in JavaScript.

this is determined when the function is called, and its direction depends entirely on where the function is called, not where it is declared (except for the arrow function). When a function is called, an execution context will be created, which contains information such as where the function is called (call stack), how the function is called, and the parameters passed in. this is an attribute of this record, which will be used in the process of function execution.

this has the following scenarios in the direction of functions:

Called by new as a constructor Use as a method of an object Call directly as a function Called by call, apply, bind this in Arrow Function

Let's discuss the pointing of this in these scenarios.

1. new binding

If a function is called as a constructor using new, this binds an instance of the newly created constructor.


function Foo() {
 console.log(this)
}

var bar = new Foo() //  Output : Foo  Example, this  Is  bar

In fact, when the constructor is called using new, the following actions are performed in turn:

Create 1 new object The constructor's prototype is assigned to the new object's __proto__ Assign a new object to the current this Execute constructor If the function returns no other object, the function call in the new expression will automatically return the new object, and if it does not return an object, it will be ignored

2. Explicit binding

Through call, apply and bind, we can modify this of function binding to make it our specified object. With the first parameter of these methods, we can explicitly bind this.


function foo(name, price) {
 this.name = name
 this.price = price
}

function Food(category, name, price) {
 foo.call(this, name, price) // call  Mode call 
 // foo.apply(this, [name, price])  // apply  Mode call 
 this.category = category
}

new Food(' Food ', ' Hamburg ', '5 Nineteen dollars ')

//  Output in browser : {name: " Hamburg ", price: "5 Nineteen dollars ", category: " Food "}

The difference between call and apply is that the call method accepts a parameter list, while the apply method accepts an array of parameters.


func.call(thisArg, arg1, arg2, ...)    // call  Usage 
func.apply(thisArg, [arg1, arg2, ...])   // apply  Usage 

The bind method sets this to a given value and returns a new function. When calling the new function, the given parameter list is taken as the first few items of the parameter sequence of the original function.


func.bind(thisArg[, arg1[, arg2[, ...]]])  // bind  Usage 

For example:


var food = {
 name: ' Hamburg ',
 price: '5 Nineteen dollars ',
 getPrice: function (place) {
  console.log(place + this.price)
 },
}

food.getPrice('KFC ') //  Output in browser : "KFC 5 Nineteen dollars "

var getPrice1 = food.getPrice.bind({ name: ' Chicken leg ', price: '7 Nineteen dollars ' }, ' Ken Beat Chicken  ')
getPrice1() //  Output in browser : " Ken Beat Chicken  7 Nineteen dollars "

Regarding the principle of bind, we can use apply method to implement an bind by ourselves:


// ES5  Mode 
Function.prototype.bind =
 Function.prototype.bind ||
 function () {
  var self = this
  var rest1 = Array.prototype.slice.call(arguments)
  var context = rest1.shift()
  return function () {
   var rest2 = Array.prototype.slice.call(arguments)
   return self.apply(context, rest1.concat(rest2))
  }
 }

// ES6  Mode 
Function.prototype.bind =
 Function.prototype.bind ||
 function (...rest1) {
  const self = this
  const context = rest1.shift()
  return function (...rest2) {
   return self.apply(context, [...rest1, ...rest2])
  }
 }

ES6 uses some knowledge of ES6, such as rest parameters and array deconstruction.

Note: If you pass null or undefined into call, apply, bind as the binding object of this, these values will be ignored when calling, and the default binding rules are applied.


var a = 'hello'

function foo() {
 console.log(this.a)
}

foo.call(null) //  Output in browser : "hello"

3. Implicit binding

Function is called in a context object, and if so, this is bound to that context object.


var a = 'hello'

var obj = {
 a: 'world',
 foo: function () {
  console.log(this.a)
 },
}

obj.foo() //  Output in browser : "world"

In the above code, the foo method is called as a property of the object, so when the foo method executes, this points to the obj object. That is, at this point this points to the object calling this method, or, if multiple objects are nested, to the last object calling this method:


var a = 'hello'

var obj = {
 a: 'world',
 b: {
  a: 'China',
  foo: function () {
   console.log(this.a)
  },
 },
}

obj.b.foo() //  Output in browser : "China"

The last object is b on obj, so when the foo method executes, the this points to the b object.

4. Default binding

Functions are called independently and directly using function references without any modification, which is also an alternative to the above binding approaches. this is bound to global objects in non-strict mode (winodw in browser, global in node environment), and this is bound to undefined in strict mode (because strict mode does not allow this to point to global objects).


var a = 'hello'

function foo() {
 var a = 'world'
 console.log(this.a)
 console.log(this)
}

foo() //  Equivalent to executing  window.foo()

//  Output in browser : "hello"
//  Output in browser : Window  Object 

In the above code, the variable a is declared in the global scope and becomes a property with the same name of the global object window. When the function foo is executed, this points to the global object at this time, so the printed a is a property of the global object.

Note that there is one situation:


function foo(name, price) {
 this.name = name
 this.price = price
}

function Food(category, name, price) {
 foo.call(this, name, price) // call  Mode call 
 // foo.apply(this, [name, price])  // apply  Mode call 
 this.category = category
}

new Food(' Food ', ' Hamburg ', '5 Nineteen dollars ')

//  Output in browser : {name: " Hamburg ", price: "5 Nineteen dollars ", category: " Food "}
0

Why does the bar function, that is, the foo method on obj, point to the global object at this time? Because the bar method is called independently as a function at this time, the scene at this time belongs to the default binding, not the implicit binding. This situation is similar to the scenario where the method is used as a callback function:


function foo(name, price) {
 this.name = name
 this.price = price
}

function Food(category, name, price) {
 foo.call(this, name, price) // call  Mode call 
 // foo.apply(this, [name, price])  // apply  Mode call 
 this.category = category
}

new Food(' Food ', ' Hamburg ', '5 Nineteen dollars ')

//  Output in browser : {name: " Hamburg ", price: "5 Nineteen dollars ", category: " Food "}
1

Parameter passing is actually an implicit assignment, except that the obj. foo method is implicitly assigned to the formal parameter fn of the function func, while the previous scenario is self-assignment, and the two scenarios are actually similar. In this scenario, we encounter setTimeout and setInterval. If the callback function is not an arrow function, then this points to the global object.

In fact, we can regard the default binding as a special case of implicit binding, such as bar () above, and we can regard it as being called in the way of window. bar (). At this time, this in bar points to window according to the implicit binding scenario.

Priority of this binding

this has multiple usage scenarios, so how should this point when multiple scenarios appear at the same time? There is a concept of a priority, and this determines the direction according to the priority. Priority: new binding > Display binding > Implicit binding > Default binding

Therefore, the judgment order of this:

new binding: Is the function called in new? If so, this binds to the newly created object; Explicit binding: Is the function called through bind, call, apply? If so, this binds to the specified object; Implicit binding: Is the function called in a context object? If so, this binds to that context object; If none, use the default binding. If it is in strict mode, it is bound to undefined, otherwise it is bound to global objects.

this in Arrow Function

The arrow function determines this according to where it is declared, which is the knowledge point of ES6.

The this binding of the arrow function cannot be modified through call, apply and bind, and because the arrow function has no constructor constructor, it cannot be called by new, that is, it cannot be used as a constructor, otherwise an error will be reported.


function foo(name, price) {
 this.name = name
 this.price = price
}

function Food(category, name, price) {
 foo.call(this, name, price) // call  Mode call 
 // foo.apply(this, [name, price])  // apply  Mode call 
 this.category = category
}

new Food(' Food ', ' Hamburg ', '5 Nineteen dollars ')

//  Output in browser : {name: " Hamburg ", price: "5 Nineteen dollars ", category: " Food "}
2

We can look at the description of arrow function in ECMAScript standard.

Original:

An Arrow Function does not define local bindings for arguments, super, this, or new.target. Any reference to arguments, super, this, or new.target within an ArrowFunction must resolve to a binding in a lexically enclosing environment. Typically this will be the Function Environment of an immediately enclosing function.

Translation:

The arrow function does not define a local binding for arguments, super, this, or new. target. Any reference to arguments, super, this, or new. target in the arrow function resolves to the binding in the current lexical scope. Typically, this is the function scope where the arrow function is located.

-ECMAScript Language Specification-Arrow Function ECMA standard-arrow function

1 small exercise of this

Use a small exercise to actual combat 1 time:


var a = 20

var obj = {
 a: 40,
 foo: () => {
  console.log(this.a)

  function func() {
   this.a = 60
   console.log(this.a)
  }

  func.prototype.a = 50
  return func
 },
}

var bar = obj.foo() //  Output in browser : 20
bar() //  Output in browser : 60
new bar() //  Output in browser : 60

Explain a little bit:

1) The sentence var a = 20 creates an attribute a on the global variable window and assigns it to 20;

2) First of all, obj. foo () is executed, which is an arrow function. The arrow function does not create a new function scope and directly follows the scope outside the statement. Therefore, when obj. foo () is executed, this in the arrow function is a global window. First, print out the value of a on window. The arrow function returns a function object func with an attribute a with a value of 50 on the prototype to bar;

3) Continuing execution is bar (), where the closure func just returned by the arrow function is executed, and its internal this points to window, so this. a modifies the value of window. a to 60 and prints it out;

4) Then new bar () is executed. According to the previous statement, the new operator will create an instance object in the func function that inherits the prototype of func and point to it with this. Then this. a = 60 creates an attribute a on the instance object. In the subsequent printing, the attribute a has been found on the instance, so I don't continue to look for the prototype of the object, so I print the third 60.

If the arrow function in the above example is replaced by an ordinary function, what will be the result?


function foo(name, price) {
 this.name = name
 this.price = price
}

function Food(category, name, price) {
 foo.call(this, name, price) // call  Mode call 
 // foo.apply(this, [name, price])  // apply  Mode call 
 this.category = category
}

new Food(' Food ', ' Hamburg ', '5 Nineteen dollars ')

//  Output in browser : {name: " Hamburg ", price: "5 Nineteen dollars ", category: " Food "}
4

This example will not be explained in detail.

If you understand the principle of the above two examples, basically the direction of this is almost mastered ~

The above is the detailed explanation of JavaScript this pointing and binding details, more about JavaScript this pointing and binding information please pay attention to other related articles on this site!


Related articles: