- What is this? The Inner Workings of JavaScript Objects
- By Eric Elliott
- The Nuggets translation Project
- Permanent link to this article: github.com/xitu/gold-m…
- Translator: fireairforce
- Proofread by: Ezioyuan, Baddyo
JavaScript is a multi-paradigm language that supports object-oriented programming and dynamic binding. Dynamic binding is a powerful concept that allows the structure of JavaScript code to change at run time, but this extra functionality and flexibility comes at the cost of some confusion, and much of it focuses on how JavaScript behaves.
Dynamic binding
Dynamic binding refers to the process of determining which method to call at run time, not compile time. JavaScript implements this through this and the prototype chain. In particular, the reference to this within a method is confirmed at runtime and can change depending on the definition of the method.
Now let’s play a game. The name of the game is “What is this?”
const a = {
a: 'a'
};
const obj = {
getThis: (a)= > 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)
];
Copy the code
Please write down your answers before you read on. Use console.log() to check if your answer is correct. Did you get that right?
Let’s start with the first example and explain the following examples in turn. Obj.getthis () returns undefined, but why? Because arrow functions never have their own this binding. Instead, it always delegates to lexical scopes. In the ES6 module’s root scope, the lexical scope in this example will have undefined this. Obj.getthis. call(a) will also return undefined. For arrow functions, this cannot be modified even with.call() or.bind(). It will always delegate this in the lexical scope.
Obj.getthis2 () gets this binding through the normal method call procedure. If the function was not previously bound to this, then it can have this binding (which is not an arrow function), and this will use. Or square brackets [] property access syntax to bind to the object calling the change method.
Obj.getthis2.call (a) is a little hard to analyze. The call() method calls the function with the given this value and optional arguments. In other words, this function is bound to this by getting the.call() argument, so obj.getThis2.call(a) returns an A object.
We are trying to pass obj.getthis3 = obj.getthis. bind(obj); To bind an arrow function, we already know that this doesn’t work, so obj.getthis3 () and obj.getthis3.call (a) both return undefined.
We can bind regular methods, so the obj.getthis4 () method will return obj as expected, since it has already bound this with obj.getthis4 = obj.getthis2.bind (obj), So obj.getthis4.call (a) will return first binding obj instead of A.
A curveball
Same challenge, but this time, we use a class with a common field syntax (which at the time of writing has advanced to TC39 Committee phase 3, with default support for Chrome and @babel/plugin-proposal-class-properties) :
class Obj {
getThis = (a)= > 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)
];
Copy the code
Write down your answers before proceeding.
Are you ready to check your answers?
Except for obj2.getThis2.call(a), each of these calls returns an instance of the object. Obj2.getthis2.call (a) returns the a object. The arrow function will still unbind this from the lexical scope. The only difference is the this attribute of the morphology. In this case, the class property copy is compiled to something like the following:
class Obj {
constructor() {
this.getThis = (a)= > this; }...Copy the code
In other words, arrow functions are defined in the context of constructors. Since it is a class, the only way to create an instance is to use the new keyword (ignoring new throws an error).
One of the most important uses of the new keyword is to instantiate a new object and bind this in the constructor. This behavior, combined with the other behaviors we’ve mentioned, should explain the rest of the examples.
conclusion
What do you make of that? Did you make any sense of that? A good understanding of this in JavaScript can greatly reduce the time it takes to debug difficult problems. If you get any wrong answers above, then you should practice them. Mull over the examples, and then test yourself again until you can pass the tests completely and explain to others why the methods return what they do.
If you think this is harder than you think, you’re not alone. I’ve tested quite a few developers on these issues, and so far only one developer has been able to explain them well.
Dynamic method lookup using.call(),.bind(), or.apply() redirects becomes more complicated as class or arrow functions are added. A little division might help. Remember that the arrow function always delegates this to the lexical scope, whereas this in class is actually lexicographically scoped to the constructor. If you’re still wondering what this is, remember to use the modality tool to verify that the object is what you think it is.
Also keep in mind that in JavaScript, you can do a lot of things without using this. In my experience, pretty much anything can be reimplemented using pure functions that take the arguments to which they apply as explicit arguments (you can think of this as an implicit argument with mutable state). The logic encapsulated in a pure function is deterministic, which makes it easier to test and has no side effects, which means that unlike operating on this, you’re less likely to break something else. Every time you convert this, you run the risk that anything dependent on this will crash.
Even so, this is sometimes useful. For example, sharing methods between a large number of objects. Even in functional programming, this can be used to access other methods on an object to implement algebraic derivation, thereby building new algebras from existing ones. For example, a generic.flatmap () can be derived by accessing this.map() and this.constructive.of ().
If you find any mistakes in your translation or other areas that need to be improved, you are welcome to the Nuggets Translation Program to revise and PR your translation, and you can also get the corresponding reward points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.
The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.