What is this here? Inner Operation of JavaScript Objects

Original author: Eric Elliott
  • Transfer


Photo: Curious Liliana Saeb (CC BY 2.0)


JavaScript is a multi-paradigm language that supports object-oriented programming and dynamic linking. Dynamic linking is a powerful concept that allows you to change the structure of JavaScript code at runtime, but these additional power and flexibility are achieved at the cost of some confusion, most of which is related thisto JavaScript behavior .


Dynamic linking


With dynamic binding, the definition of the method to call occurs at run time, and not at compile time. JavaScript does this with a thisprototype chain as well. In particular, inside the method it thisis determined during the call, and the value thiswill be different depending on how the method was defined.


Let's play a game. I call her "What is here this?"


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)
];

Think about what the values ​​in the array will be answersand check your answers with console.log(). Guessed?


Let's start with the first case and continue in order. obj.getThis()returns undefined, but why? Arrow functions never have their own this. Instead, they always take thisfrom the lexical scope ( approx. Lexical this ). For the root of the ES6 module, the lexical region will have an undefined ( undefined) value this. obj.getThis.call(a)also not defined for the same reason. For arrow functions thisit cannot be redefined, even with .call()or .bind(). thiswill always be taken from the lexical field.


obj.getThis2()Gets a binding during a method call. If before this there was no binding thisfor this function, then it can be bound this(since this is not an arrow function). In this case, it thisis an object objthat is bound at the time the method is called using .or [squareBracket]property access syntax. ( note implicit binding )


obj.getThis2.call(a)a little harder. The method call()calls a function with the given this value and optional arguments. In other words, the method gets the binding thisfrom the parameter .call(), so it obj.getThis2.call(a)returns an object a. ( note explicit binding )


In the case obj.getThis3 = obj.getThis.bind(obj);we are trying to get an arrow function with an attached one this, which, as we have already figured out, will not work, so we get undefinedfor obj.getThis3()and, obj.getThis3.call(a)respectively.


For regular methods, you can bind, so it obj.getThis4()returns objas expected. He already got his binding here obj.getThis4 = obj.getThis2.bind(obj);, and obj.getThis4.call(a)takes into account the first binding and returns objinstead a.


Twisted ball


We’ll solve the same problem, but this time we use classpublic fields to describe the object ( Stage 3 innovations at the time of this writing are available in Chrome by default and with @babel/plugin-offer-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)
];

Think about the answers before continuing.


Ready?


All calls except obj2.getThis2.call(a)return an instance of the object. obj2.getThis2.call(a)returns a. Arrow functions are still derived thisfrom the lexical environment. There is a difference in how it thisis defined from the lexical environment for class properties. Inside, the initialization of class properties looks something like this:


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

In other words, the arrow function is defined within the constructor context. Since this is a class, the only way to create an instance is to use the keyword new(omission newwill result in an error). One of the most important things a keyword does newis create a new instance of the object and bind thisto it in the constructor. This behavior, combined with other behaviors that we mentioned above, should explain the rest.


Conclusion


Did you succeed? A good understanding of how thisJavaScript behaves will save you a lot of time debugging complex problems. If you made a mistake in the answers, this means that you need to practice a little. Practice with the examples, then go back and check yourself again until you can run the test and explain to someone else why the methods return what they return.


If it was harder than you expected, then you are not alone. I asked a lot of developers on this topic and I think that so far only one of them has coped with this task.


What started as a search for dynamic methods that you could redirect using .call(), .bind()or .apply(), has become much more difficult with the addition of class methods and arrow functions. Perhaps you should once again focus on this. Remember that arrow functions are always taken thisfrom the lexical scope, and classthisare actually lexically limited by the class constructor under the hood. If you ever doubt it this, then remember that you can use the debugger to check its value.


Remember that you can do without solving many JavaScript tasks this. In my experience, almost everything can be redefined in terms of pure functions that take all the arguments used as explicit parameters ( thisyou can imagine it as an implicit variable). The logic described through pure functions is deterministic, which makes it more testable. Also, with this approach there are no side effects, therefore, unlike the moments of manipulation with this, you are unlikely to break something. Each time it is thisinstalled, something depending on its meaning may break.


However, sometimes thisuseful. For example, to exchange methods between a large number of objects. Even in functional programming, thisyou can use it to access other methods of the object in order to implement the algebraic transformations necessary to build new algebras on top of existing ones. So, universal .flatMap()can be obtained using this.map()and this.constructor.of().




Thanks for the help with translating wksmirnowa and VIBaH_dev


Also popular now: