[Translation] What is written in this? Hacking JavaScript Objects

[Translation] What is written in this? Hacking JavaScript Objects


JavaScript is a multiparadigm language that supports object-oriented programming and dynamic method binding — a powerful concept that allows the structure of a 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 key word 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 keyword and the prototype chain. In particular, the specific value of this inside the method is determined during program execution, 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 ES6-module code:

  const a = {
  a: 'a'
 };
 const obj = {
  getThis: () = & gt;  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 goes into the answers array and write down the answers. After you do this, check yourself by displaying the answers array with console.log () . Did you manage to “decode” the value of this in each case correctly?

Let us analyze this problem, starting with the first example. The obj.getThis () function returns undefined . Why? 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 return and call obj.getThis.call (a) . The this value when working with pointer functions cannot be reassigned even with .call () or .bind () . This value will always match 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 was not tied to a similar method, and provided that this method is not an arrow function, that is, it supports the binding of this , the keyword this is bound to the object for which the method is invoked using the syntax for accessing the properties of the object through a dot or using square brackets.

The construction of obj.getThis2.call (a) is a little more difficult to understand. 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 to obj.getThis2.call (a) returns the object < code> a .

Using the command obj.getThis3 = obj.getThis.bind (obj); we are trying to bind to this a method that represents 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 .

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

Use this in classes


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

  class Obj {
  getThis = () = & gt;  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 answers2 array.

Ready?

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 the a object. 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 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 = () = & gt;  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.

Totals


Did you cope with the tasks given in this material? A good understanding of how the keyword this behaves in JavaScript will save you a lot of time 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 tested quite a few developers for the knowledge of this , 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 method search, which could be influenced by .call () , .bind () or . apply () , it became much more difficult 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 this keyword in classes is actually tied to the class constructor function. And if you ever feel that you don’t know exactly what this indicates, use the debugger to test your assumptions on this score.

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 they work with, in the form of an explicitly given list of parameters ( this can be taken as an implicitly specified parameter with 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, unlike manipulating 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 based on existing constructions.

<< a>

Source text: [Translation] What is written in this? Hacking JavaScript Objects