Writing in the front
When used well, this can make your code clean and flexible, but when used poorly, it can make your bugs hard to track. Recently, I always meet the problem about this direction when doing the problem, but I still can’t do it correctly.
I spent a day summing up my troubles in order to make a final decision. This article is based on the first volume of JavaScript you Don’t Know.
Listen to not understand all give a thumbs-up 😄
A test question
As the title suggests, there are four ways to bind this. Perhaps you can rely on experience to use the daily use of very 6, but really start a topic, may not be handy, even with a guess. Let’s take a look at a topic:
var name = 'global';
function foo () {
this.name = 'foo';
}
var obj = {
name: 'local', bar: foo.bind(window), foo: foo }; var baz = new obj.bar(); console.log(window.name); // global console.log(baz.name); // foo(new binding) obj.bar() console.log(window.name) // foo(display binding) obj.foo() console.log(obj.name) // foo(implicit binding)Copy the code
If you can answer the question quickly and correctly, you can go. If not, move on.
The default binding
The default binding works when the function is called directly, where this refers to a global object, but strictly this refers to undefined.
function foo() {console.log(this)} foo() // => window // strict modefunction bar () {
'use strict'
console.log(this)
}
bar() // => undefined
Copy the code
Note that the function is called directly without a prefix, which can be confused with implicit binding.
Implicit binding
This refers to its caller, that is, it refers to whoever calls the function.
Example:
function foo() {console.log(this)} const obj = {foo: foo} obj.foo() // => obj calls foo, this refers to obj foo() // => window. Is this a default or implicit binding?Copy the code
If you call foo directly, the result will be window. Does this count as a default binding, or is it an implicit binding? Take a look below:
const obj = {
a: function() {consooe.log(this)function b() {the console. The log (this) / / (2) the Windows (the default)} b ()}} obj. () a.Copy the code
Surprisingly, the result at â‘¡ is Window, not obj. The reason is simple, because b() is called directly (without a prefix).
Distinguish between implicit and default bindings
Call foo() directly in the first code. This points to window because foo is called directly and does not attach to anyone.
But foo() is equivalent to window.foo() because of the special nature of global variables (which are properties of Windows), so there are two ways to interpret this:
- Window calls foo(), and this refers to window. Implicit binding
- Foo is called directly, and this is bound to window by default. The default binding
But in the second code, the result is the default binding
Implicit binding this loss problem
This is common when passing a callback function. Such as:
const obj = {
foo: function () {
console.log(this)
}
}
setTimeout(obj.foo, 0) // => window
setTimeout(function () {
obj.foo() // => obj
}, 10)
Copy the code
Why is it wrong to call a callback? What’s the difference between these two pieces of code?
Functions are also reference types, and when obj.foo is passed as an argument to another function, it is passing a reference, so it is passing the anonymous function that corresponds to the identity itself, regardless of its position, whether it is in OBj or not, even if it is nested at n levels.
The first setTimeout, obj.foo, refers to the anonymous function itself, which is actually an undecorated function call, so the default binding is applied, causing us to lose this unexpectedly.
Since this will be lost, it greatly limits our application scenarios, but we can use explicit binding to solve this problem, such as:
setTimeout(obj.foo.bind(obj), 0) // => obj
Copy the code
According to the binding
As we have just seen, when analyzing implicit binding, we must bind this indirectly (implicitly) to an object by including a property that points to a function inside the object and by referring to the function indirectly through this property.
So what if we want to force a function call on an object instead of including a function reference inside the object? The answer is through call, apply, or bind.
function foo() {
console.log( this.a );
}
var obj = {
a: 2
};
foo.call( obj ); // => 2
foo.apply( obj ); // => 2
var tmp = foo.bind(obj)
tmp() // => 2
Copy the code
All of this is routine, but there are three things to note:
- Call and apply execute immediately, bind returns a new function with this attached, and you only call the target function if you call the new function
- The bind function has the problem of binding this multiple times. If bind this multiple times, the first time will prevail.
- The bind function is actually a variation of the show bind (call, apply) called hard binding. Because hard binding is a very common pattern, built-in methods are provided in ES5
Function.prototype.bind
Hard binding
Briefly explain the second point, why the first one
function foo() {
console.log( this.a );
}
var obj1 = {
a: 'obj1'
};
var obj2 = {
a: 'obj2'
}
var tmp = foo.bind(obj1).bind(obj2)
tmp() // => 'obj1'
tmp.call(obj2) // => 'obj1'
Copy the code
Why take the first time as the criterion? For the actual bind function, refer to MDN’s polyfill. To make it easier to understand, bind can be simplified as:
Function.prototype.bind = function(context, ... Args) {const fn = this // This is the function we bound to, as in fooreturn function(... props) { fn.call(context, ... args, ... props) } }Copy the code
As you can see, bind is simply putting a layer of functions on a function, using currization to set up context objects in advance. And by this principle, you know that no matter how many layers you wrap around it, the target function doesn’t change. And because of the closure, the original context object remains the same, so when the layers of functions are removed, you end up using the same context object that you first bound to.
The bind function can only be bound once, and multiple bindings are useless. The bind function this cannot be changed, even if it is called call.
But there are always exceptions to the rule, and look at the new binding.
The new binding
In traditional class-oriented languages, “constructors” are special methods in a class that are called when a class is initialized with new. JavaScript also has a new operator that looks the same as those used in class-oriented languages, and most developers assume that the new mechanism in JavaScript is the same as in those languages. However, the mechanics of new in JavaScript are actually quite different from class-oriented languages.
We actually do four things when we use the new operator:
- Create a brand new object
- Performing prototype chaining
- The new object will be bound to this in the constructor
- The constructor is executed to determine the value returned, if it is an object, otherwise the object created by default is returned
priority
What if more than one rule can be applied to an invocation location? To solve this problem, these rules must be prioritized, which is what we’ll cover next.
There is no doubt that the default binding has the lowest precedence, and explicit and implicit binding, as evidenced by the above example, have higher precedence than implicit. So the current order is: explicit > implicit > Default
Let’s test the priority order of display binding and new binding. Since call/apply cannot be used with new, we can use bind to verify.
function foo() {
this.a = 'Hahaha';
}
var obj = {
a: 'obj'
};
var tmp = foo.bind(obj)
var result = new tmp()
console.log(obj.a) // => 'obj'
console.log(result.a) // => 'Hahaha'
Copy the code
Obviously, new takes precedence over display binding. Final order: New > Explicit > Implicit > Default.
So we have an order for this:
- Is the function called in new?
- Is it called by call, apply, bind, etc?
- Is it called in a context object?
- None is the default binding. Bind to undefined in strict mode.
For normal function calls, you can understand that this is far away, but again, there are always exceptions.
exceptions
Null or undefined
If null or undefined is used as the first parameter of call or apply, then the actual call will be ignored and the default binding rule will be applied, i.e. binding to the window. This can be done when we do not care about the context and only the parameters.
However, there is a potential risk that binding to Window may inadvertently add or modify global variables, causing hidden bugs. So to prevent this, you can bind the first parameter to an empty object. Of course, it depends on the demand. This is just a suggestion.
Soft binding
For soft binding, which is used to solve the problem that this cannot be modified after hard binding, see this article on soft binding. The implementation is as follows (from JavaScript You Don’t Know, Volume 1) :
Function.prototype.softBind = function(obj){
var fn = this;
var args = Array.prototype.slice.call(arguments,1);
var bound = function() {returnfn.apply( (! this || this === (window || global)) ? obj : this, args.concat.apply(args,arguments) ); }; bound.prototype = Object.create(fn.prototype);return bound;
};
Copy the code
Focus on this line:
// If this is bound to a global object or undefined, null, // change this to the obj passed in, otherwise do nothing. (! this || this === (window || global)) ? obj : thisCopy the code
What’s the point?
var name = 'global'
const obj = {
name: 'obj',
foo: function () {
console.log(this.name)
}
}
const obj1 = {
name: 'obj1'} obj.foo() // => obj // normalsetTimeout(obj.foo, 0) // this is lost, global // Now we use soft binding const softFoo = obj.foo.softbind (obj)setTimeout(softFoo, 0) // obj softfoo.call (obj1) // obj1, you can use call to explicitly change this obj1.foo = softFoo obj1.foo() // ob1, you can also change this implicitlyCopy the code
In setTimeout(softFoo, 0), softFoo will also lose this, but after losing this, we will take the obj bound in the previous step by internal judgment.
If it is hard bound, softFoo’s this will be fixed to OBj, fixed to always apply the context passed in the first time.
However, when applying the implicit or explicit rule, this is false because it is neither global nor undefined or null, and this is changed to obj1 when we call softfoo.call (obj1). And then it works.
The design here and here is very clever and needs to be carefully savoured. Note that our softFoo is the internal bound function returned after calling softBind. Note also that this in the first line of softBind and this in bound are not equal.
As you can see, this criterion prevents the default binding rule from being applied, and takes the former when the default rule is applied. The former is the target context we pass in. Otherwise we use the context passed in by soft binding, which is our OBj.
With soft binding, the order is new > Explicit > Implicit > Soft binding > Default
Copy the code
Arrow function
- This inside a function is the object on which it is defined, not called, as opposed to a normal function.
- Arrow functions cannot be used as constructors, that is, they cannot be called with new
- You cannot use the Arguments object, which does not exist in the function.
- The yield command cannot be used as a Generator function.
The first of these is particularly noteworthy. The reason this is fixed is that the arrow function itself does not have this; the arrow function’s this is not its own. It can’t be modified, and because it doesn’t have this, it can’t be used as a constructor. These limitations are caused by the absence of this.
conclusion
There are mainly the following steps to judge this:
- Is the function called in new?
- Is it called by call, apply, bind, etc?
- Is it called in a context object?
- None is the default binding. Bind to undefined in strict mode.
Also note the specificity of arrow functions and the fact that undefined and NULL are ignored.
If there are mistakes, please also criticize correct, thank you!