1. What is this
1.1 What is this
The end of science is theology. The keyword “this” is an extremely complex mechanism in JS, which is often deified by people. It is automatically defined in every function, just like the accompanying product of every function, which is both good and evil. When used properly, a piece of code is often shorter and more powerful, but there are often many unexpected events that make it unstable. Actually, the this keyword isn’t that mysterious, but in the absence of clarity, this can seem like magic. Today, let’s unveil its mystery.
Now declare that this is bound at runtime, and which object it is bound to depends entirely on what it is called at declaration time! The binding to this has nothing to do with where it is declared, only where it is called.
function demo(num){ console.log(num); // Record the number of times demo is called, and the value will be 0 this.count++; }; demo.count = 0; demo(1); //1 demo(2); //2 demo(3); Console. log(demo.count); / / 0?????? what??Copy the code
Demo () has been called three times, but when we output the count, we find that the count is zero, right?
So, it follows that it is wrong to take this literally. This does not refer to itself. When demo.count() is executed, it does add count to the function, but the this.count inside the function does not refer to the outer count. Although the two properties have the same name, they are not the same property. The external count is a global variable, while this.count is a property of demo(). Context explains why this is the case. Without going into too much context here.
So, how can we solve the above problems? Since these are two different counts, why don’t we just make them the same count, and we can solve this?
function demo(num) { console.log(num); // Record the number of times demo is called, and the value will be 0 demo.count++; // Change this.count to demo.count}; demo.count = 0; demo(1); //1 demo(2); //2 demo(3); //3 console.log(demo.count); / / 3Copy the code
In this way, we do solve the above problem in a way, but we also perfectly avoid this. So in a way, we failed.
1.2 Why use this
Given that this mechanism is so complex, why do we use it at all? What does it do? Is it really worth knowing so much about? With that in mind, let me explain why we still insist on using this, which is so complicated.
function upcase() { return this.name.toUpperCase(); }; function test() { let demo = "demo:" + upcase.call(this); console.log(demo); }; let name1 = { name: "demo1"}; let name2 = { name:"demo2" }; test.call(name1); //demo:DEMO1 test.call(name2); //demo:DMEO2Copy the code
If you’re not familiar with this or don’t know much about JS syntax, this code tends to be obscure. Here, by using this, we see that we can use the functionality of upCase () and test() functions without passing an object to them. It’s a myth not to mention this in the past. If you do not use this, you need to explicitly pass a context object to the upCase and test functions, as follows:
function upcase(name) { return name.name.toUpperCase(); }; function test(name) { let demo = "demo:" + upcase(name); console.log(demo); }; let name1 = { name: "demo1"}; let name2 = { name:"dmeo2"}; test(name1); //demo:DEMO1 test(name2); //demo:DMEO2Copy the code
By comparing the two pieces of code, we can see that using this is a much more elegant way to pass a reference to an object, making our code much cleaner and easier to use. In particular, when we use multiple functions calling each other, constantly nesting, and we show objects passing in the context we use, it’s easy to clutter the code and make the code less readable, which is not the case with this. Therefore, using this is more conducive to their own code development and later code maintenance.
1.3 Its scope
It is often understood, correctly or wrongly, that this refers to the scope of the function. This is because of the complexity of this mechanism, which determines that the pointing of this is also extremely complex, so this refers to the scope of the function, not to the scope of the function. To be clear, **this never refers to the lexical scope of the function. ** Scopes are similar to objects in that their internal identifiers are their properties. However, objects access their properties through the JS code, while scopes are inside the JS engine. The difference in access causes them to be inconsistent, and also determines that this does not refer to the scope of the function. The binding of this has nothing to do with where it is declared, only where it is called! The binding of this has nothing to do with where it is declared, only where it is called!! The binding of this has nothing to do with where it is declared, only where it is called!!
2. Binding rules
2.1 Invocation Rules
Before understanding the binding process for this, it is important to understand the call location: the call location is where the function is called in the code (rather than where it is declared). Only careful analysis of the call location can answer the question “what is this referring to?” Or pointing to something.
Looking for the call location is looking for “where the function was called,” but it’s not that easy to do because some programming patterns can hide the actual call location. The most important thing is to analyze the call stack (that is, all the functions called to get to the current execution location). The call location we care about is in the previous call to the currently executing function.
Let’s take a look at what a call stack and call location are:
Function demo1() {// The current call stack is: demo1 // Therefore, the current call location is the global scope console.log("demo1"); dmeo2(); // <-- demo2 call location}; Function dmeo2() {// the current call stack is demo1 -> demo2 // The current call is in demo1 console.log("demo2"); demo3(); // <-- demo3 call location}; Function demo3() {// the current call stack is dmeo1 -> demo2 -> demo3 // The current call stack is console.log("demo3"); }; demo1(); // <-- demo1 call locationCopy the code
Notice how we analyze the actual call location from the call stack, since it determines the binding of this. In terms of call stack, you can simply understand it as a transport chain of function calls, a path formed by constantly embedded calls of different functions, just like a railway line. If you want to go to a place, you need to constantly change trains according to the railway line to get to the place you want to go. Of course, some places can be reached directly. Demo1 can go directly to Demo2, but demo3 requires entering demo2 to enter Demo3. Now that we know how to find the call location, we can start by talking about a few binding rules for this, and this is where the introduction to this begins.
2.2 Default Binding
First up is surely the simplest and most commonly used type of function call: the simple function call. Think of this as a rule with no rules at all, since you have to find an object for this.
function demo() { console.log(this.a); }; var a = 2; demo(); // 2 <-- the actual reference location of demoCopy the code
The first thing to notice is that the variable var a = 2 declared in global scope is a property of the global object. This. A refers to the global variable a, and since demo is declared in global scope, this. A refers to the global object if demo does not find the property a. Some may ask why?
Because in this case, the default binding for this is applied when the function is called, this points to the global object. So how do we know that the _ default binding _ is applied here? We just need to examine how demo() is called. In the code, demo() is called directly, so only the default binding can be used and no other rules can be applied.
If strict mode is used, however, the default binding cannot be used for global objects, so this is bound to undefined:
function foo() { "use strict"; console.log( this.a ); }
var a = 2; foo(); // TypeError: this is undefined
Copy the code
An important detail here is that although the binding rules for this are entirely dependent on the location of the call, only
Default bindings are bound to global objects only if foo() is running in non-strict mode; In strict mode with foo()
The call is position-independent:
function foo() { console.log( this.a ); } var a = 2; (function(){ "use strict"; foo(); / / 2}) ();Copy the code
In general, you should not mix strict mode and non-strict Mode in your code. The whole process is either strict or not. However, sometimes you may use third-party libraries that are not as strict as your own code, so be aware of such compatibility details.
2.3 Implicit binding
Implicit binding is another rule to consider, whether there is a context object at the call location.
function demo() { console.log( this.a ); } var obj = { a: 2, demo: demo }; obj.foo(); / / 2Copy the code
The first thing to notice is how demo() is declared and how it is then added to obj as a reference property. This function is not strictly an obj object. However, the call location references the function using the OBj context, so you can say that the obj object “contains” the function when it is called. When demo() is called, its this does point to the obj object. So when a function reference has a context object, the implicit binding rule binds this in the function call to that context object. Because this is bound to obj when demo() is called, this.a and obj.a are the same.
Since there is implicit binding, is there an implicit loss? The answer, of course, is yes! Implicit loss is one of the most common types of loss and is often used in combination with implicit and default bindings.
function foo() { console.log(this.a); } var obj = { a: 2, foo: foo }; var bar = obj.foo; // Function alias! Var a = "oops, global"; // a is the global object property bar(); // "oops, global";Copy the code
Although bar is a reference to obj.foo, it is actually a reference to the function foo itself, so bar() is actually a function call with no strings attached. Therefore, if the default binding is applied, this will point to a in the scope where bar is located, causing an implicit loss. Implicit loss is also particularly widespread in callback functions, such as the built-in setTimeout() function in JS, which can be abbreviated to see exactly how it is lost.
function setTimeout(fn(),delay){
delay(); // Make the function wait for some time
fn(); //<– the call position waits for the end and executes the function. }
It is very common for callbacks to lose this binding. In addition, there is another case where this can be unexpected: in some popular JS libraries, event handlers often bind the callback’s this function to the DOM element that triggers the event. This can be useful in some situations, but sometimes it can be very frustrating. Unfortunately, these tools often don’t have an option to enable this behavior.
2.4 Explicit binding
What if we want to force a call to a function on an object instead of referring to it as an implicit binding? Almost every function in JS has some useful features: Call (..) And the apply (..) Methods. As we did in our first successful use of this in our visit, they will take a parameter and then bind the function’s this to that parameter, implementing an explicit binding.
function foo() { console.log( this.a ); } var obj = { a:2 }; foo.call( obj ); / / 2Copy the code
We bind this to that object by using the call() method, passing in the object we want to bind to. Sometimes when you pass in a simple data type instead of an object, the call method automatically converts it to the object form of that value and then binds it, a process known in the industry as “autoboxing.”
But while the display binding is good, there is still the implicit missing that just appeared, so how can we solve this problem? Two approaches are commonly used
1. Hard binding
Hard binding is a variant of explicit binding, but solves this problem.
function foo() {console.log( this.a ); } var obj = { a:2 }; var bar = function() { foo.call( obj ); }; bar(); // 2 setTimeout( bar, 100 ); // 2 // Hard bound bar can no longer modify its this bar.call(window); / / 2Copy the code
In function bar(), we call foo.call(obj), which forces foo’s this to be bound to obj. This is bound to obj again no matter how the function bar is called later. This binding is an explicit mandatory binding, so we call it a hard binding.
2.API call context
Many functions in third-party libraries, as well as many of the new built-in functions in the JS language and environment, provide an optional argument, often referred to as a “context,” which functions in conjunction with bind(..) Again, make sure your callback uses the specified this.
function foo(id) { console.log( id, this.id ); } var obj = { id: "dmeo" }; / / calls foo (..) [1, 2, 3]. ForEach (foo, obj); // 1 demo 2 demo3 demoCopy the code
2.5 the new binding
Before we look at new binding, let’s first look at what happens when we use new, so that it’s clear how the binding is done. Using new to call a function, or when a constructor call occurs, automatically does the following.
-
Create (or construct) an entirely new object.
-
The new object will be chained to the execution prototype.
-
This new object is bound to the this function call.
-
If the function returns no other object, then the function call in the new expression automatically returns the new object.
So we know that when we use new, they automatically bind.
3. Priority of binding rules
3.1 Priority Comparison
The next step is to compare the priorities of the above binding rules, see which is higher, and order them once so that we can use them more clearly in the future.
First of all, default bindings were introduced at the beginning, and are used only when there are no rules. So it is the least card!
If implicit loss is the “father” of the implicit binding, then the display binding is the “father” of the implicit binding, so the implicit binding is the brother of the display binding
So the display binding must be greater than the implicit binding! If you really don’t believe me, then let’s, on the code!
function foo() { console.log( this.a ); } var obj1 = { a: 2, foo: foo }; var obj2 = { a: 3, foo: foo }; obj1.foo(); // 2 obj2.foo(); // 3 obj1.foo.call( obj2 ); // 3 obj2.foo.call( obj1 ); / / 2Copy the code
This time, you believe me. So now the rank is show > Implicit > Default, so where do I put new? Let’s compare new and display binding directly first!
function foo(something) {this.a = something; } var obj1 = {}; var bar = foo.bind( obj1 ); bar( 2 ); console.log( obj1.a ); // 2 var baz = new bar(3); console.log( obj1.a ); // 2 console.log( baz.a ); / / 3Copy the code
Now it looks like the winner is won!
The new bind successfully beat the display bind to a good beat, directly in millions of troops, take the explicit dog head!
So we sorted the four rules: New binding > Explicit binding > Implicit binding > Default binding
At the same time, we can draw a set of rules:
- Is the function called in new (new binding)? If so, this binds to the newly created object.
var bar = new foo()
- Are functions invoked by call, apply (explicit binding), or hard binding? If so, this is bound to the specified object.
var bar = foo.call(obj2)
- Is the function called in a context object (implicit binding)? If so, this binds to that context object.
var demo2= obj.foo()
- If neither, use the default binding. If in strict mode, it is bound to undefined, otherwise it is bound to a global object.
demo();
4. Summary
If you want to determine a running this binding, the most important thing is to find where it was called, and then determine the binding object of the this pointer according to the following four rules.
- Whether to bind to a specified object by a new declaration.
- Whether to call, apply, or bind.
- Context call or not
- Default binding, undefined in strict mode, otherwise bound to a global object.
Also be aware that sometimes binding exceptions will be triggered, which requires careful screening. The arrow function in ES6 does not use the above binding rules, but instead determines the binding object for this based on the current lexical scope. In short, the arrow function directly inherits the this binding from the previous function call.
, because of the length (in fact, I very hard, but still be a long document), the only give you a simple for simple this binding rule, this usage is really varied, but mastering the four kinds of rules and priority, can be relatively simple to this binding judgment!