What is written in this? Behind the scenes of JavaScript objects

Original author: Eric Elliott
  • Transfer
JavaScript is a multi-paradigm language that supports object-oriented programming and dynamic method binding - a powerful concept that allows the structure of JavaScript code to change during program execution. This gives developers serious opportunities, it makes the language flexible, but you have to pay for everything. In this case, you have to pay with the clarity of the code. A significant contribution to this price is made by the keyword this, around the features of the behavior of which a lot has been collected that can confuse the programmer.



Dynamic Method Binding


Dynamic binding allows you to specify, during program execution, and not during compilation, the method that must be called when executing a certain command. In JavaScript, this mechanism is implemented using a keyword thisand a prototype chain. In particular, the specific value thisinside the method is determined at runtime, and the rules for determining this value change depending on how the method was declared.

Let's play one game. I call it "What is written in this?". Here is her first option - ES6 module code:

const a = {
  a: 'a'
};
const obj = {
  getThis: () => this,
  getThis2 () {
    return this;
  }
};
obj.getThis3 = obj.getThis.bind(obj);
obj.getThis4 = obj.getThis2.bind(obj);
const answers = [
  obj.getThis(),
  obj.getThis.call(a),
  obj.getThis2(),
  obj.getThis2.call(a),
  obj.getThis3(),
  obj.getThis3.call(a),
  obj.getThis4(),
  obj.getThis4.call(a)
];

Before reading further, think about what will fall into the array answersand write down the answers. After you do this - test yourself by listing the array answerswith console.log(). Did you manage to correctly “decipher” the meaning thisin each of the cases?

We will analyze this problem, starting with the first example. The construct obj.getThis()returns undefined. Why? It is thisimpossible to bind to the arrow function . Such functions are used thisfrom the surrounding lexical scope. The method is called in the ES6 module, in its lexical scope it thiswill matter undefined. For the same reason, it will undefinedreturn the call obj.getThis.call(a). The value thiswhen working with arrow functions cannot be reassigned even with .call()or.bind(). This value will always correspond thisfrom the lexical scope to which such functions are located.

The command obj.getThis2()demonstrates how to work with thisusing the usual methods of the object. If you thisdid not bind to a similar method, and provided that this method is not an arrow function, that is, it supports binding this, the keyword thisis bound to the object for which the method is called using the syntax of accessing the properties of the object through a point or with using square brackets.

It’s obj.getThis2.call(a)already a little harder to deal with the design . The method call()allows you to call a function with a given valuethis, which is indicated as an optional argument. In other words, in this case it thisis taken from the parameter .call(), as a result, the call obj.getThis2.call(a)returns an object a.

With the help of the command, obj.getThis3 = obj.getThis.bind(obj);we are trying to bind to a thismethod that is an arrow function. As we have already found out, this cannot be done. As a result, calls obj.getThis3()and obj.getThis3.call(a)returns undefined.

You thiscan bind methods that are ordinary functions, so obj.getThis4()it returns as expected obj. The call obj.getThis4.call(a)returns obj, and not, as one might expect a. The fact is that, before calling this command, we already bound the thisteam obj.getThis4 = obj.getThis2.bind(obj);. As a result, when executedobj.getThis4.call(a) takes into account the state of the method in which it was after completing the first binding.

Using this in classes


Here is the second version of our game - the same task, but now based on classes. Here we use the syntax for declaring public class fields (at the moment, the proposal for this syntax is at the third stage of approval, it is available by default in Chrome, you can use it with the help @babel/plugin-proposal-class-properties).

class Obj {
  getThis = () => this
  getThis2 () {
    return this;
  }
}
const obj2 = new Obj();
obj2.getThis3 = obj2.getThis.bind(obj2);
obj2.getThis4 = obj2.getThis2.bind(obj2);
const answers2 = [
  obj2.getThis(),
  obj2.getThis.call(a),
  obj2.getThis2(),
  obj2.getThis2.call(a),
  obj2.getThis3(),
  obj2.getThis3.call(a),
  obj2.getThis4(),
  obj2.getThis4.call(a)
];

Before reading further, think about the code and write down your vision of what will fall into the array answers2.

Are you done?

Here, all method calls, except obj2.getThis2.call(a), will return a reference to an instance of the object. The same call will return an object a. Arrow functions are still taken thisfrom the lexical scope. The difference between this example and the previous one is the difference in the visibility areas from which it is taken this.

Namely, here we work with class properties, which determines the behavior of this code.

The fact is that during the preparation of the code for execution, the values ​​are written to the properties of the classes like this:

class Obj {
  constructor() {
    this.getThis = () => this;
  }
...

In other words, it turns out that the arrow function is declared inside the context of the constructor function. Since we are working with a class, the only way to instantiate it is to use a keyword new(if you forget about this keyword, an error message will be displayed).

The most important tasks solved by the keyword neware to create a new instance of the object and to bind thisto the constructor. This feature, taking into account what we already talked about in the previous section, should help you understand what is happening.

Summary


Have you completed the tasks outlined in this article? A good understanding of how the keyword behaves in JavaScript thiswill save you a lot of time when debugging, when looking for unobvious causes of obscure errors. If you answered some of the questions incorrectly, it means that it will be useful for you to practice.

Experiment with the sample code, and then try yourself again, and so on, until you can answer all the questions correctly. After you figure it out yourself, find someone ready to listen to you and tell him why the methods from the tasks return exactly what they return.

If all this seems to you more complicated than you expected, then know that you are not alone in this. I tested for knowledge of the featuresthisquite a few developers, and I think that only one of them was absolutely accurate in all of their answers.

That subsystem of the language, which, at the very beginning, looked like a dynamic search for methods that could be influenced by help .call(), .bind()or .apply()began to look much more complicated after the appearance of arrow functions and classes.

Apparently, it will be useful to note the main features of classes and arrow functions in terms of use this. Remember that arrow functions always use thisfrom their lexical scope, and the keyword thisin the classes is, in fact, tied to the constructor function of the class. And if you ever feel that you don’t know exactly whatthis, use the debugger to check your assumptions on this subject.

Also, keep in mind that you can do a lot in JavaScript without using it thisin your code. Experience tells me that almost any JS code can be rewritten in the form of pure functions that take all the arguments that they work with, in the form of an explicitly specified list of parameters ( thisyou can perceive an implicitly specified parameter with a mutable state). The logic contained in pure functions is deterministic, which improves their testability. Such functions do not have side effects, which means that when working with them, unlike manipulating with this, you are unlikely to “break” anything outside of them. Always when you changethis, you are faced with a potential problem, which is that something that depends on thisit may stop working correctly.

Notwithstanding the foregoing, it should be noted that thisthis is a useful concept. For example, it can be used in order to organize the sharing of a certain method by a variety of objects. Even in functional programming, it thiscan come in handy for calling other methods of an object from one method, which allows you to create something new based on existing structures.




Also popular now: