What is this
When a function is called, an execution context is created. This execution context contains information about where the function is called (execution stack), how the function is called, the parameters passed in, and so on. This is an attribute of the execution context that is used during function execution.
The position
Before you understand the binding process for this, you need to understand the call location: the call location is where the function is called in the code (not where it is declared).
The call location we care about is in the previous call to the currently executing function.
function baz() {
// The current stack is: baz
// Therefore, the current call location is global scope
console.log( "baz" );
bar(); // <-- bar call location
}
function bar() {
// The current stack is: baz --> bar
// Therefore, the current call location is in baz
console.log( "bar" );
foo(); // <-- foo's call location
}
function foo() {
// The current stack is: baz --> bar --> foo
// Therefore, the current call location is in bar
console.log( "foo" );
}
baz(); // <-- baz call location
Copy the code
Binding rules
There are five binding rules for this:
- The default binding
- Implicit binding
- According to the binding
new
The binding- Arrow function binding
1. Default binding
Independent function calls: Think of this rule as the default rule when no other rule can be applied.
function foo() {
console.log( this.a );
}
var a = 2;
foo(); / / 2
Copy the code
In strict mode, global objects cannot be used for the default binding; this is bound to undefined.
function foo() {
"use strict";
console.log( this.a );
}
var a = 2;
foo(); // TypeError: Cannot read property 'a' of undefined
Copy the code
2. Implicit binding
When a function references a context object, the implicit binding rule binds this in the function to that context object.
function foo() {
console.log( this.a );
}
var obj = {
a: 2.foo: foo
};
obj.foo(); / / 2
Copy the code
Only the previous or last level in the object attribute reference chain is relevant at the call location.
function foo() {
console.log( this.a );
}
var obj2 = {
a: 42.foo: foo
};
var obj1={
a:2.obj2:obj2
}
obj1.obj2.foo() / / 42
Copy the code
Implicit loss
Implicitly bound functions lose the binding object in certain cases and apply the default binding, binding this to the global object or undefined (depending on whether it is in strict mode).
// Although bar is a reference to obj.foo, it actually refers to foo itself.
// So bar() is an undecorated function call, so the default binding is applied.
function foo() {
console.log( this.a );
}
var obj = {
a: 2.foo: foo
};
var bar = obj.foo; // Function alias!
var a = "oops, global"; // a is an attribute of the global object
bar(); // "oops, global"
Copy the code
A subtler, more common, and more unexpected situation occurs when a callback is passed in:
function foo() {
console.log( this.a );
}
function doFoo(fn){
//fn actually refers to foo
fn();// <-- call location!
}
var obj = {
a: 2.foo: foo
};
var a = "oops, global"; // a is an attribute of the global object
doFoo(obj.foo); // "oops, global"
Copy the code
Argument passing is essentially an implicit assignment, and when we pass in a function we get an implicit assignment.
The result is the same if you pass functions into the language’s built-in functions instead of your own declarations.
function foo() {
console.log( this.a );
}
var obj = {
a: 2.foo: foo
};
var a = "oops, global"; // a is an attribute of the global object
setTimeout(obj.foo,100); // "oops, global"
// The built-in setTimeout() function implemented in the JS environment is similar to the following pseudocode:
function setTimeout(fn, delay) {
// Wait delay milliseconds
fn(); // <-- call location!
}
Copy the code
3. Explicit binding
You can use the call(…) function. And the apply (…). Methods, whose first argument is an object for this, which is then bound to this when the function is called. Because you can specify the binding object for this directly, we call it an explicit binding.
function foo() {
console.log( this.a );
}
var obj = {
a: 2
};
foo.call( obj ); / / 2
Copy the code
Through the foo. Call (…). We can force foo to bind its this to obj when we call it.
Unfortunately, explicit binding still doesn’t solve the problem of missing bindings we mentioned earlier.
Hard binding
But a variant of explicit binding 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
// A hard-bound bar cannot change its this
bar.call( window ); / / 2
Copy the code
We created the function bar() and manually called foo.call(obj) inside it, forcing foo’s this to be bound to obj.
No matter how bar is later called, it will always manually call foo on obj. We call this hard binding.
A typical application scenario is to create a wrapper function that accepts parameters and returns values:
function foo(something) {
console.log( this.a, something );
return this.a + something;
}
var obj = {
a: 2
};
var bar = function() {
return foo.apply( obj, arguments );
};
var b = bar( 3 ); / / 2, 3
console.log( b ); / / 5
Copy the code
Another way to use it is to create a helper function that can be reused:
function foo(something) {
console.log( this.a, something );
return this.a + something;
}
// A simple auxiliary binding function
function bind(fn, obj) {
return function() {
return fn.apply( obj, arguments); }}var obj = {
a: 2
};
var bar = bind( foo, obj );
var b = bar( 3 ); / / 2, 3
console.log( b ); / / 5
Copy the code
ES5 provides built-in function.prototype.bind:
function foo(something) {
console.log( this.a, something );
return this.a + something;
}
var obj = {
a: 2
};
var bar = foo.bind( obj );
var b = bar( 3 ); / / 2, 3
console.log( b ); / / 5
Copy the code
bind(…) Returns a new hard-coded function that sets the argument you specified to the context of this and calls the original function.
The “context” of the API call
Third-party libraries, as well as many new built-in functions in the JavaScript language and host environments, provide an optional argument, often referred to as a “context,” which serves the same purpose as bind(…). Also, make sure your callback uses the specified this.
function foo(el) {
console.log( el, this.id );
}
var obj = {
id: "awesome"
}
let myArr = [1.2.3]
/ / calls foo (..) Bind this to obj
myArr.forEach( foo, obj );
// 1 awesome 2 awesome 3 awesome
Copy the code
These functions are actually called by call(…). Or apply (…). Explicit binding is implemented.
4. The new binding
In Javascript, constructors are simply functions that are called when the new operator is used. They do not belong to a class and do not instantiate a class.
Includes built-in object functions (such as Number(…) ) can be called with new, which is called a constructor call.
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.
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); / / 2
Copy the code
Call foo(…) with new We construct a new object and bind it to foo(…). Call to this.
New is another way to influence the behavior of the this binding when a function is called; we call it a new binding.
priority
function foo1() {
console.log(this.a)
}
function foo2(something) {
this.a = something
}
var obj1 = {
a: 2.foo: foo1
}
var obj2 = {
a: 3.foo: foo1
}
var obj3 = {
foo: foo2
}
var obj4 = {}
obj1.foo(); / / 2
obj2.foo(); / / 3
obj1.foo.call(obj2); / / 3
obj2.foo.call(obj1); / / 2
Explicit binding takes precedence over implicit binding
obj3.foo(4);
console.log(obj3.a); / / 4
obj3.foo.call(obj4, 5);
console.log(obj4.a); / / 5
var bar = new obj3.foo(6);
console.log(obj3.a); / / 4
console.log(bar.a); / / 6
// The new binding takes precedence over the implicit binding
var qux = foo2.bind(obj4);
qux(7);
console.log(obj4.a); / / 7
var quux = new qux(8);
console.log(obj4.a); / / 7
console.log(quux.a); / / 8
// The new binding modifies the hard-bound (to obj4) call to qux(...) In this.
Copy the code
We can now determine which rule is applied to a function at a particular call location based on priority.
- Is the function in
new
In the call (new
Bind), if sothis
The binding is to the newly created object. - Whether the function passes
call
,apply
(explicitly bound) or hard bound call, if sothis
Binds to the specified object. - Whether the function is called in a context object (implicitly bound), and if so
this
It is that context object that is bound. - If neither, use the default binding.
- If in strict mode, bind to
undefined
. - Otherwise, bind to the global object.
- If in strict mode, bind to
Bind the exception
The ignored this
If you pass null or undefined as this binding to call, apply, or bind, these values will be ignored during the call, and the default rules are actually applied.
function foo(){
console.log(this.a);
}
var a = 2;
foo.call(null); / / 2
Copy the code
Null is passed in both cases
- use
apply(...)
To “expand” an array and pass in a function as an argument. bind(...)
Parameters can be currified (pre-set some parameters).
function foo(a, b) {
console.log( "a:" + a + "B:" + b );
}
// Expand the array as an argument
foo.apply( null[2.3]);/ / a: 2, b: 3
/ / use the bind (..) Let's do that
var bar = foo.bind( null.2 );
bar( 3 ); / / a: 2, b: 3
Copy the code
Always using NULL to ignore the this binding can have some side effects.
If a function does use this (say, a function in a third-party library), the default binding rule binds this to the global object, which can have unpredictable consequences (such as modifying the global object).
Safer this
A “safer” approach is to pass in a special object to which binding this will have no adverse effects on your program.
The easiest way to create an empty Object in Javascript is object.create (null). It is similar to {}, but does not create the object. prototype delegate.
function foo(a, b) {
console.log( "a:" + a + "B:" + b );
}
// Our empty object
var ø = Object.create( null );
// Expand the array as an argumentFoo. Apply (ø, [2.3]);/ / a: 2, b: 3
/ / use the bind (..) Let's do that
varThe bar = foo. Bind (ø,2 );
bar( 3 ); / / a: 2, b: 3
Copy the code
Indirect reference
Another thing to note is that it is possible to create an “indirect reference” to a function that applies the default binding rules.
Indirect references are most likely to occur in assignments:
function foo() {
console.log( this.a );
}
var a = 2;
var o = { a: 3.foo: foo };
var p = { a: 4};
o.foo(); / / 3
(p.foo = o.foo)(); / / 2
Copy the code
The return value of the assignment expression p.foo = o.foo is a reference to the target function, so the call location is foo() rather than p.foo() or o.foo().
Soft binding
Hard binding forces this to be bound to the specified object (except when using new), preventing function calls from applying the default binding rules.
The disadvantage is that hard binding greatly reduces the flexibility of the function and makes it impossible to modify this using either implicit or explicit binding.
If you can give the default binding a value other than a global object and undefined, you can achieve the same effect as hard binding, while leaving implicit or explicit binding to modify this.
if(!Function.prototype.softBind) {
Function.prototype.softBind = function(obj) {
var fn = this;
// Capture all curried parameters
var curried = [].slice.call( arguments.1 );
var bound = function() {
return fn.apply(
(!this || this= = = (window || global))? obj :this,
curried.concat.apply( curried, arguments)); }; bound.prototype =Object.create( fn.prototype );
return bound;
};
}
Copy the code
In addition to soft binding, softBind(…) And ES5’s built-in bind(…) Similar.
If this is bound to a global object or undefined, the specified default object obj is bound to this. Otherwise, this is not modified.
function foo() {
console.log("name:" + this.name);
}
var obj = { name: "obj" },
obj2 = { name: "obj2" },
obj3 = { name: "obj3" };
var fooOBJ = foo.softBind( obj );
fooOBJ(); // name: obj
obj2.foo = foo.softBind( obj );
obj2.foo(); // name: obj2 <----
fooOBJ.call( obj3 ); // name: obj3 <----
setTimeout( obj2.foo, 10 ); // name: obj
Copy the code
As you can see, the soft-bound version of foo() can manually bind this to obj2 or obj3, but if the default binding is applied, this will be bound to obj.
Arrow function
The four rules we introduced earlier can contain all normal functions. But ES6 introduces a special type of function that doesn’t use these rules: arrow functions.
Instead of using the four standard rules for this, arrow functions determine this based on the outer (function or global) scope.
function foo() {
Return an arrow function
return (a) = > {
// This inherits from foo()
console.log(this.a);
};
}
var obj1 = {
a:2
};
var obj2 = {
a:3
};
var bar =foo.call(obj1);
bar.call(obj2); //2, not 3!
Copy the code
The arrow function created inside foo() captures the this of foo() when called. Since foo() ‘s this is bound to obj1, and bar (which references the arrow function)’ s this is bound to obj1, the arrow function ‘s binding cannot be fixed. (Neither does New!)
Arrow functions are often used in callback functions, such as event handlers or timers:
function foo(){
setTimeout(() = >{
// This is morphologically inherited from foo()
console.log(this.a);
},100);
}
var obj = {
a:2
};
foo.call(obj) / / 2
Copy the code
This in the arrow function
- Arrow function doesn’t have any
prototype
(prototype), so the arrow function itself does notthis
.
let a = () = > {}
console.log(a.prototype) //undefined
Copy the code
- In the arrow function
this
Inherited from the context in which they are defined (Javascript Authority Guide, version 7 P206), from the first ordinary function in the outer layerthis
.
let foo
let barObj = {
msg: 'Bar this points to'
}
let bazObj = {
msg: 'Baz's this points to'
}
bar.call(barObj) // Bar this points to barObj
baz.call(bazObj) // This of baz points to bazObj
function bar() {
foo = () = > {
console.log(this.'This refers to the first ordinary function in the context in which they are defined.')}}function baz() {
foo()
}
// MSG: "bar this points to ""this points to the first normal function in the context in which they are defined"
Copy the code
- Arrow function
this
Unable to getbind
.call
.apply
todirectlyModification.
let quxObj = {
msg: 'Try modifying the arrow function's this pointer directly'
}
function baz() {
foo.call(quxObj)
}
} "this refers to the first normal function in the context in which they are defined."
Copy the code
Modify the direction of the arrow function indirectly:
bar.call(bazObj) // The normal function bar this points to bazObj, and the inner arrow function also points to bazObj
Copy the code
The this pointer of the inherited normal function changes, and the this pointer of the arrow function also changes.
-
If the arrow function has no outer function, this points to the window
var obj = { i: 10.b: () = > console.log(this.i, this), c: function() { console.log( this.i, this) } } obj.b()//undefined, window obj.c()//10, {i: 10, b: ƒ, c: ƒ} Copy the code
practice
/** * Question 1 */
var name = 'window'
var person1 = {
name: 'person1'.show1: function () {
console.log(this.name)
},
show2: () = > console.log(this.name),
show3: function () {
return function () {
console.log(this.name)
}
},
show4: function () {
return () = > console.log(this.name)
}
}
var person2 = { name: 'person2' }
person1.show1()
person1.show1.call(person2)
person1.show2()
person1.show2.call(person2)
person1.show3()()
person1.show3().call(person2)
person1.show3.call(person2)()
person1.show4()()
person1.show4().call(person2)
person1.show4.call(person2)()
Copy the code
The correct answer is:
person1.show1() // person1, implicit binding
person1.show1.call(person2) // person2, explicitly bound
person1.show2() // window, arrow function binding, no outer function, pointing to window
person1.show2.call(person2) // window, arrow function binding, cannot be changed directly, still pointing to window
person1.show3()() Return an ƒ(){console.log(this.name)} to the global model
Var name = 'window' var name = 'window'
person1.show3().call(person2) // person2, after returning the function, explicitly bind person2, this refers to the person2 object
person1.show3.call(person2)() ƒ(){return function(){console.log(this.name)}} explicitly bind person2,
ƒ(){console.log(this.name)}} is window
person1.show4()() // person1, arrow function binding, this inherits from the context in which the function is defined, i.e. the context in which the outer function is located, and this points to person1
person1.show4().call(person2) // person1, arrow function bindings cannot be modified directly by call
person1.show4.call(person2)() // person2, the higher order function, the outer function this explicitly bind person2, modify the arrow function this pointing, can change the arrow function this pointing
Copy the code
/** * Question 2 */
var name = 'window'
function Person (name) {
this.name = name;
this.show1 = function () {
console.log(this.name)
}
this.show2 = () = > console.log(this.name)
this.show3 = function () {
return function () {
console.log(this.name)
}
}
this.show4 = function () {
return () = > console.log(this.name)
}
}
var personA = new Person('personA')
var personB = new Person('personB')
personA.show1()
personA.show1.call(personB)
personA.show2()
personA.show2.call(personB)
personA.show3()()
personA.show3().call(personB)
personA.show3.call(personB)()
personA.show4()()
personA.show4().call(personB)
personA.show4.call(personB)()
Copy the code
The correct answer is:
personA.show1() After the new binding, the "this" in the constructor Person is bound to the "personA" parameter passed in by Person, so the result is "personA"
personA.show1.call(personB) ƒ(){console.log(this.name)}
PersonA. Show1's this points to the personB instance object, so the result is personB
personA.show2() Person.show2 =>console.log(this.name)
// Then bind the arrow function and call the arrow function. This points to the outer function's this.name, i.e., personA
personA.show2.call(personB) Person.show2 =>console.log(this.name)
// The arrow function cannot be changed directly, so it is still personA
personA.show3()() After binding this in the constructor Person to personA, personA. Show3 () returns the function ƒ(){console.log(this.name)} to the global model
Var name = 'window' var name = 'window
personA.show3().call(personB) // personB, new, this in the constructor Person bound to personA,
PersonA. Show3 () returns an ƒ(){console.log(this.name)} function to global,
// bind personB explicitly, so the final result is personB
personA.show3.call(personB)() // window, new, this in the constructor Person bound to personA,
ƒ(){return function(){console.log(this.name)}}
ƒ(){console.log(this.name)} returns a function to the global model
Var name = 'window' var name = 'window
personA.show4()() // personA, new, this in the Person constructor bound to personA,
ƒ(){return ()=>console.log(this.name)} Return arrow function after running ()=>console.log(this.name), and run the arrow function
// Arrow function binding, inherits the outer normal function this, so the result is personA
personA.show4().call(personB) // personA, new, this in the Person constructor bound to personA,
ƒ(){return ()=>console.log(this.name)} Return arrow function ()=>console.log(this.name),
// The arrow function cannot be changed directly, so the result is still personA
personA.show4.call(personB)() // personB, new, this in the constructor Person bound to personA,
// Explicitly bind the outer function, so the arrow function is also changed to personB
Copy the code
reference
You don’t know JavaScript scroll up
The Definitive Javascript Guide, seventh edition
From these two sets of problems, to understand JS this, scope, closure, object
Explain the difference between the arrow function and ordinary functions, as well as the precautions of the arrow function, not applicable scenarios