This article is taken from my public account [Sun Wukong, Don’t talk nonsense]
If you know anything about lexical scope, the this keyword seems to have nothing to do with lexical scope (lexical scope is defined at runtime, whereas the reference to this is dynamically determined at runtime). Furthermore, this has more in common with dynamic scope, the opposite of lexical scope.
The this keyword is one of the most complex mechanics in the JS world. The magic of JavaScript”. Maybe you know you don’t know it and try to avoid it. You may be using it, but you don’t have to pat your chest and say, “This is easy. I know everything about it.” If you are one of the above two situations, then this article is for you.
Ps: Most of the content of the article is from JavaScript you don’t know < Volume 1 >, because the book is so good for this part of the content, so Sun Wukong absorbed and exported it. If you are interested, we recommend reading this book.
Benefits of using this
One of the words that goes along with this is “context object” (not “scope”). The advantage of using this, in short, is that it is intended to be dynamic, so that functions can automatically reference appropriate context objects. (In everyday coding, this is often used together with apply and call. We will explain its usage in the following sections.
Here’s an example of using this versus not using this:
/ / use thisfunction identify() {return this.name.toUpperCase();
}
function speak(){
var greeting = "Hello, I am " + identify.call(this);
console.log(greeting);
}
var me = { name : "Frank"};
var you = {name : "NiuNiu"}; speak.call(me); // FRANK speak.call(you); // NIUNIU // For comparison, instead of using this, the following code explicitly uses context to do the same thingfunction identify(context){
return context.name.toUpperCase();
}
function speak(context){
var greeting = "Hello, I am " + identify(context);
console.log(greeting);
}
var me = { name : "Frank"};
var you = {name : "NiuNiu"};
speak(me); // FRANK
speak(you); // NIUNIU
Copy the code
Both pieces of code do the same thing. If you’re not familiar with this, the second code is easier to understand. However, this provides a more elegant way to implicitly “pass” an object reference, so code can be designed to be cleaner and easier to reuse.
As your usage patterns become more complex, passing context objects explicitly will clutter up your code, not using this. If you’re familiar with objects and stereotypes in JavaScript, it’s important that functions can automatically reference the appropriate context object.
A lot of the best source code on the web uses this, and when we read it, it can be a little embarrassing if we don’t understand what this means.
This is not what
Most of the time, our understanding of a thing starts from a misunderstanding. Remove the false sense of what it is, and you’ll know more about what it is. Let’s look at a few misconceptions about this.
Myth # 1: This refers to itself
Because it’s this, there’s nothing wrong with pointing to the function itself, in the sense of the English word. So we might ask: when do we need a function to point internally to itself? The answer is recursion.
Let’s use chestnuts to show that this is not true:
function foo(num){
console.log(" foo : "+ num); // The count variable wants to count the number of times foo is called this.count ++; } foo.count = 0; // Initialize var I;for(i = 0; i<3; i++){ foo(i); } // foo: 0 // foo: 1 // foo: 2 // let's call Kangkang foo how many times? console.log(foo.count); // 0 -- Nani? !Copy the code
We see that the log outputs three entries to prove that foo was called three times, but that the value of the foo.count variable is still 0. This shows that we are wrong to think that this refers to ourselves. In fact, this in function foo refers to the global object Window.
Myth # 2: This refers to the scope of a function
Many people might recognize that this refers to its scope. This situation is a bit complicated because in some cases it is true and in others it is false.
It must be made clear that this does not in any case refer to the lexical scope of a function (see the article for a description of scope). Inside JavaScript, the scoped “object” is not accessible through JavaScript code; it exists inside the JavaScript engine.
Let’s take the classic example of a failed attempt to use this to implicitly reference the lexical scope of a function:
function foo(){
var a = 1;
this.bar();
}
function bar(){
console.log(this.a);
}
foo(); // ReferenceError: a is not defined
Copy the code
First, the code attempts to reference the bar() function via this.bar(). This call succeeded by accident, and we’ll see why in a moment. In addition, the code tries to use this to connect the lexical scopes of foo() and bar() so that bar() can access variable A in the scope of foo(). It’s impossible to do that. You can’t look anything up in the lexical scope with this.
This definition of
With that out of the way, let’s take a look at how this works.
Earlier we said that this is bound at runtime, not at code time. Its context depends on the various conditions under which the function is called. The binding of this has nothing to do with where the function is declared, except how the function is called.
When a function is called, an active record (also known as the execution context) is created. This record contains information about where the function was called (call stack), how the function was called, the parameters passed in, and so on. This is an attribute of the record that is used during function execution.
Four binding rules for this
In this section, we’ll look at how to find where a function is called based on specific rules to determine how the function will bind this during execution. There are four rules to follow, and we’ll look at them one by one.
The default binding
I’ll start with the most common type of function call: the stand-alone function call. You can think of this rule as the default rule when no other rule can be applied.
Here’s an example:
function foo(){ console.log( this.a ); } var a = 2; foo(); / / 2Copy the code
The declared scope of the variable var a = 2 is global. To be clear, a variable declared in global scope is an attribute of the same name of the global object. In other words, the variable A is a property of the global variable. The default binding rule is: in the absence of other binding rules, this refers to the global object. So in this case, you can output normally.
In Strict mode, you cannot use a global object for the default binding. In actual coding, this point is worth noting.
So how can we tell if the default binding is applied here? As simple as that, we will go on to learn the following three binding rules, and you will find that none of them apply to this chestnut, so the remaining rule is the default binding.
Implicit binding
Another rule to consider is whether the calling location has a context object, or whether it is owned or contained by an object. Let’s look at chestnuts:
function foo(){ console.log( this.a ); } var obj = { a : 1, foo: foo }; obj.foo(); / / 1Copy the code
The first thing to notice is how foo() is declared and then added to OBj as a reference property. But whether defined directly in OBj or defined first and then added as a reference property, this function is not strictly an OBj object.
However, the calling location references the function using the OBJ context, so you can say that the OBJ object “owns” or “contains” the function reference when the function is called.
When a function reference has a context object, the implicit binding rule binds this from the function call to that context object.
There are two things to note about implicit binding:
- Only the previous or last level in the object attribute reference chain is at play at the call location.
- When parameter passing occurs, implicit binding will fail, that is, implicit loss.
For point 1, plain code can understand:
function foo(){ console.log( this.a ); } var obj2 = { a : 2, foo: foo }; var obj1 = { a : 1, obj2: obj2 } obj1.obj2.foo(); // Output 2, only the last layer worksCopy the code
For the implicit loss of point 2, let’s look at a chestnut:
function foo() {
console.log( this.a );
}
function doFoo(fn){
fn();
}
var obj = {
a: 2,
foo: foo
}
var a = "hello, I am frank"; // a is an attribute of the global objectdoFoo( obj.foo ); // "hello, I am frank"
Copy the code
The process of passing parameters here is the process of assigning values. Thus, the argument passed in function doFoo() refers to function foo() itself.
Explicitly bound
With implicit binding, we must bind this to the object indirectly by including an attribute inside the object that refers to the function and by referring to the function indirectly through this attribute.
So what if we want to force a function call on a function instead of containing a function reference inside an object?
Well, yes, that’s the call(…) we talked about at the beginning of this article. And the apply (…). Methods. Both methods are included in “all” functions in JavaScript, so they can both be called for explicit binding. The first function of the two methods is an object (for this), which is then bound to this when the function is called.
Or chestnuts:
function foo() { console.log( this.a ); } var obj = { a: 2 } foo.call(obj); / / 2Copy the code
This example makes it clear that foo binds this to obj. Apply () and call() are used similarly and will not be expanded here.
For the problem of implicit binding loss, a variant of explicit binding can provide a solution: hard binding.
function foo() {
console.log( this.a );
}
var obj = {
a: 2
}
var bar = function(){ foo.call(obj); } // If the bar is hard bound, no operation can change its' this' bar(); / / 2setTimeout(bar, 100); // 2 bar.call(window); / / 2Copy the code
We created the function bar() and manually called hardbound foo.call(obj) inside it, forcing foo’s this to be bound to obj. No matter how bar is later called, it always manually calls foo on obj. This binding is a display of forced binding, which we call hard binding.
Bind via the new keyword
Here’s an interesting point. It is easy to confuse the new operator with the this keyword when discussing it together without a good foundation.
Let’s look at the operator new. In traditional class-oriented programming languages, “constructors” are methods in a class that are called when the class is initialized with new. The usual form is something = new MyClass(..) ;
JavaScript also has a new, which uses methods that look like those in class-oriented languages. However, the mechanics of new in JavaScript are actually quite different from class-oriented languages.
Let’s start by redefining “constructors” in JavaScript. In JavaScript, constructors are simply functions that are called when the new operator is used. They do not belong to a class, nor do they instantiate a mine. In fact, they are not even a special type of function; they are just ordinary functions called by the new operator.
In fact, there is no such thing as a “constructor”, only a “constructor call” to a function.
When a function is called with new, or when a constructor call occurs, the following operations are performed automatically.
- Create (or construct) a brand new object.
- The new object will be connected by [[Prototype]].
- This new object will be bound to the function call
this
. - If the function returns no other object, then
new
The function call in the expression automatically returns the new object.
Let’s skip step 2 for a moment and consider the following code:
functionfoo( a ){ this.a = a; } var bar = new foo(233); console.log( bar.a ); / / 233Copy the code
Call foo(..) with new , we create a new object and bind it to foo(..) Call to this.
Rule ordering
What is the priority of each of the above four rules?
Its sorting priority is:
New Binding > Show Binding > Implicit Binding > Default binding.
If you want to validate, you can design your own validation logic, or see the corresponding content in JavaScript you Don’t Know < Volume 1 >.
Arrow function and this
A new special class of functions introduced in ES6: arrow functions. The arrow function is very popular, partly because of its more concise syntax and partly because of its use and qualification of this. By understanding this of the arrow function, we can understand the nature of this better.
Instead of using the four standard rules for this, arrow functions determine this based on the outer (function or global) lexical scope.
Let’s look at the lexical scope of the arrow function:
function foo(){return an arrow functionreturn(a)=> {//this inherits from foo() console.log(this.a); } } var obj1 = {a : 2}; var obj2 = {a : 3}; var bar = foo.call( obj1 ); bar.bind(obj2); // 2, not 3!Copy the code
The arrow function created inside foo() captures foo() ‘s this when called. Since foo() ‘s this is bound to obj1, and bar(which references the arrow function)’ s this is bound to obj1, the binding of the arrow function cannot be modified. (New bindings don’t work either!)
conclusion
If you want to determine the this binding of a running function, you need to find where the function is called directly. Once found, we can apply the following four rules in sequence to determine the object bound to this.
- by
new
Call? Bind to the newly created object. - by
call
orapply
Call? Binds to the specified object. - Called by a context object? Bind to that context object.
- Default: bind to undefined in strict mode, otherwise bind to global object.
Instead of using the four standard binding rules, arrow functions in ES6 determine this based on the current lexical scope, specifically: the arrow function inherits the this binding of the outer function call (whatever this is bound to).
JavaScript in-depth series:
“var a=1;” What’s going on in JS?
Why does 24. ToString report an error?
Here’s everything you need to know about “JavaScript scope.
There are a lot of things you don’t know about “this” in JS
JavaScript is an object-oriented language. Who is for it and who is against it?
Deep and shallow copy in JavaScript
JavaScript and the Event Loop
From Iterator to Async/Await
Explore the detailed implementation of JavaScript Promises