📜 ⬆️ ⬇️

What is written in this? Hacking JavaScript Objects

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 clarity code. The keyword this makes a significant contribution to this price, around which features a lot of things are collected that can confuse a programmer.



Dynamic method binding


Dynamic binding allows you to specify, during program execution, and not during compilation, the method that needs to be called when executing a certain command. In JavaScript, this mechanism is implemented using the this and a chain of prototypes. In particular, the specific value of this inside the method is determined during the execution of the program, and the rules for determining this value vary depending on how this method was declared.

Let's play one game. I call it "What's in this?". Before you, its first option is the code of the ES6 module:
')
 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 answers array and write down the answers. After you do this - check yourself by answers using console.log() . Did you manage to correctly “decipher” the value of this in each of the cases?

Let us analyze this problem, starting with the first example. The obj.getThis() construction returns undefined . Why? To the arrow function this cannot be tied. Such functions use this from the surrounding lexical scope. The method is called in the ES6 module, in its lexical scope this will be undefined . For the same reason, undefined and return call obj.getThis.call(a) . The value of this when working with pointer functions cannot be reassigned even with .call() or .bind() . This value will always correspond to this from the lexical scope in which such functions are located.

The obj.getThis2() command demonstrates how to work with this when using the usual object methods. If this not tied to a similar method, and provided that this method is not an arrow function, that is, it supports this binding, the this keyword is bound to the object for which the method is called using the property access syntax via point or using square brackets.

With the construction obj.getThis2.call(a) already a little more difficult to figure out. The call() method allows you to call a function with a given value of this , which is specified as an optional argument. In other words, in this case, this is taken from the .call() parameter, as a result, the call obj.getThis2.call(a) returns the object a .

Using the obj.getThis3 = obj.getThis.bind(obj); we are trying to bind to this method, which is a switch function. As we have already found out, this cannot be done. As a result, calls to obj.getThis3() and obj.getThis3.call(a) return undefined .

Methods that are ordinary functions can be bound to this , so obj.getThis4() , as expected, returns obj . Calling obj.getThis4.call(a) returns obj , and not, as one would expect, a . The fact is that before calling this command, we have already tied this command obj.getThis4 = obj.getThis2.bind(obj); . As a result, the execution of obj.getThis4.call(a) takes into account the state of the method in which it was after the first binding.

Using this in classes


Here is the second version of our game - the same task, but now based on classes. It uses the syntax of declaring public class fields (at the moment the proposal for this syntax is in the third stage of approval, it is available by default in Chrome, you can also use it using @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 record your vision of what will fall into the array of answers2 .

Is it done?

Here all method calls, with the exception of obj2.getThis2.call(a) , will return a reference to the object instance. The same call will return object a . Arrow functions still take this from the lexical scope. The difference between this example and the previous one is the difference in scopes from which this is taken.

Namely, here we work with the properties of classes, which determines the peculiarities of 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 switch function is declared inside the context of the constructor function. Since we are working with a class, the only way to create an instance of it is to use the keyword new (if you forget about this keyword, you will get an error message).

The most important tasks solved by the new keyword are to create a new instance of the object and to link this to the constructor. This feature, taking into account what we have already said in the previous section, should help you understand what is happening.

Results


Did you cope with the tasks given in this material? A good understanding of how the this keyword behaves in JavaScript will save you a lot of time when debugging when looking for unclear reasons for incomprehensible errors. If you answer some of the questions incorrectly, it means that it will be useful for you to practice.

Experiment with a code of examples, and then test yourself again, and so on - until you manage to answer all the questions correctly. After you figure it out yourself - find someone ready to listen to you, and tell him about why the methods from the tasks return exactly what they return.

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

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

Apparently, it will be useful to note the main features of classes and turnout functions in terms of using this . Remember that the arrow functions always use this from their lexical scope, and the keyword this in classes is actually tied to a class constructor function. And if you ever feel that you don’t know exactly what this indicates, use the debugger to check your assumptions about this.

Also, remember that a lot can be done in JavaScript without using this in the code. Experience tells me that virtually any JS code can be rewritten as pure functions that take all the arguments with which they work, in the form of an explicitly given list of parameters ( this can be perceived as an implicitly specified parameter with a mutable state). The logic contained in pure functions is deterministic, which improves their testability. Such functions have no side effects, which means that when working with them, in contrast to manipulations with this , you are unlikely to “break” anything outside of them. Whenever you change this , you face a potential problem, which is that something depending on this may stop working correctly.

Notwithstanding the foregoing, it should be noted that this is a useful concept. For example, it can be applied in order to organize the sharing of a certain method by a set of objects. Even in functional programming, this can be useful for calling an object of its other methods from one method, which allows you to create something new on the basis of existing structures.



Source: https://habr.com/ru/post/455527/


All Articles