You can check out this article on my blog at 😉

preface

This is one of the most confusing mechanics in Javascript. Even seasoned developers may not fully grasp this magic. Today I reorganize this with some grammar of ES6+, hoping it will be helpful to you.

This definition of

The ECMAScript specification (5.1) describes this like this:

The this keyword evaluates to the value of the ThisBinding of the current execution context.

ThisBinding for the current execution context is this. This is an object that is associated with the execution context, or you can call this a context object that activates the execution context.

Execution context

What is an execution context?

In short, an execution context is an abstraction of the context in which JavaScript code is evaluated and executed. Whenever Javascript code is running, it is running in an execution context.

There are three types of execution context: global execution context, function execution context, and eval execution context.

This for the global execution context

1. Browser:

console.log(this);
// window
Copy the code

2. The Node:

console.log(this)
// global
Copy the code

Summary: In global scope its this executes the current global object (Window on the browser side, global in Node).

Knowledge supplement:

In the Web, global objects can be fetched through Window, self, or frames, but in Web Workers, only self can. In Node.js, they are not available and you must use global.

In loose mode, you can return this in a function to get a global object, but in strict mode and module environments, this returns undefined.

GlobalThis aims to consolidate the increasingly fragmented methods of accessing global objects by defining a standard global attribute. The proposal is currently in phase four, which means it is ready to be included in the ES2020 standards. All popular browsers, including Chrome 71+, Firefox 65+, and Safari 12.1+, already support this feature. You can also use it in Node.js 12+.

This of the function execution context

This in the execution context of a function is a bit complicated, mainly because it is not statically bound to a function, but rather depends on how the function is called, meaning that even the same function may have a different this each time it is executed.

In JavaScript, this refers to the context in which the current function is being executed. There are four types of JavaScript function calls:

  • Function call mode;alert('Hello World! ')
  • Method call mode;console.log('Hello World! ')
  • Constructor mode;new RegExp('\\d')
  • Indirect invocation (apply/call);alert.call(undefined, 'Hello World')

In each of these calls, it has its own separate context, which makes this refer to differently. In addition, strict patterns can have an impact on the execution environment.

The following examples are in the browser environment

1. Function calls

This in 1.1 function calls

When a function is not a property of an object, it is called as a function. In this case, the function is called in the global context, where this refers to the global object.

var funA = function() {
  return this.value;
}
console.log(funA()); //undefined
var value = 233;
console.log(funA()); / / 233
Copy the code

1.2 Function calls in strict mode

To use strict mode, simply place ‘use strict’ at the top of the function body. This in the context will be undefined.

function strictFun() {  
    'use strict';
    console.log(this= = =undefined); // => true
}
Copy the code

Error prone: this in an inner method of an object method:

var numbers = {  
    numberA: 5.numberB: 10.sum: function() {
        console.log(this === numbers); // => true
        function calculate() {
            console.log(this === numbers); // => false
            // This is window, not numbers
            // Strictly, this is undefined
            return this.numberA + this.numberB;
        }
        returncalculate(); }}; numbers.sum();/ / NaN; Throws TypeError in strict mode
Copy the code

2. Method calls

When a method is called in an object, this refers to the object itself.

var obj = {
  value:233.funA:function(){
    return this.value;// When called through obj.funa (), this points to obj}}console.log(obj.funA());/ / 233
Copy the code

The obj.funa () call means that the context execution environment is in the OBj object.

We can also write:

function funA() {
  console.log(this.value);
}
var obj = {
  value: 233.foo: funA
}
obj.funA();/ / 233
Copy the code

Error-prone: Executed after assigning an object’s method to a variable

var obj = {
  value:233.funA:function(){
    return this.value;// When called with funB(), this points to the global object}}var value = 1;
var funB = obj.funA;
funB(); / / 1
Copy the code

The above method is called as a normal function, so this refers to the global object.

Fallible: this in the callback

var obj = {
  funA: function() {
    console.log(this);
  },
  funB: function() {
    console.log(this);
    setTimeout(this.funA, 1000);
  }
}
obj.funB(); 
// obj
// window
Copy the code

FunA is passed as an argument to a setTimeout method (fun = this.funa). When funB is executed, only fun() is executed. The execution context is now independent of the OBj object.

Error-prone: Strict mode under setTimeout

'use strict';
function foo() {
  console.log(this); // window
}
setTimeout(foo, 1);
Copy the code

In the previous example, this in foo refers to a global object. In strict mode, this is undefined. This is a global object. Is the strict mode broken?

There is a note in the MDN documentation on SetTimeout:

Even in strict mode, this in the setTimeout() callback still points to the window object by default, not undefined. This feature also applies to setInterval.

Constructor calls

When the constructor is called, this refers to the object instantiated when the constructor is called;

function Person(name) {
  this.name = name;
  console.log(this);
}
var p = new Person('Eason');
// Person {name: "Eason"}
Copy the code

The same is true when using the class syntax (in ES6), where initialization occurs only in its constructor method.

class Person {
  constructor(name){
    this.name = name;
    console.log(this)}}var p = new Person('Eason');
// Person {name: "Eason"}
Copy the code

When new Person() is executed, JavaScript creates an empty object and its context is the constructor method.

4. Indirect invocation

A function is called indirectly when it uses either.call() or.apply() methods.

In indirect calls, this refers to the first argument passed by.call() and.apply()

  • call
fun.call(thisArg[, arg1[, arg2[, ...]]])
Copy the code
  • apply
fun.apply(thisArg[, [arg1, arg2, ...]])
Copy the code

Indirect calls are useful when the execution of a function needs to specify a context, to solve the context problem in a function call (this refers to window or in strict mode to undefined), and to simulate a method call object.

var eason = { name: 'Eason' };  
function concatName(str) {  
  console.log(this === eason); // => true
  return `${str} The ${this.name}`;
}
concatName.call(eason, 'Hello ');  // => 'Hello Eason'  
concatName.apply(eason, ['Bye ']); // => 'Bye Eason' 
Copy the code

Another practical example is to call the parent constructor in class inheritance in ES5.

function Animal(name) {  
  console.log(this instanceof Cat); // => true
  this.name = name;  
}
function Cat(name, color) {  
  console.log(this instanceof Cat); // => true
  // Call the parent constructor indirectly
  Animal.call(this, name);
  this.color = color;
}
var tom = new Cat('Tom'.'orange');  
tom; // { name: 'Tom', color: 'orange' }
Copy the code

Animal.call(this, name) indirectly calls the parent method in Cat to initialize the object.

Note that if this function is in non-strict mode, the first argument is automatically replaced with pointing to a global object if it is not passed or if it is specified as null or undefined.

5. Bind function calls

The.bind() method creates a new function, executed in the context of the first argument passed to.bind(thisArg), which allows the creation of a function with this set in advance.

fun.bind(thisArg[, arg1[, arg2[, ...]]])
Copy the code

thisArg

The value passed to the target function as the this parameter when the binding function is called.

If you construct a binding function using the new operator, the supplied this value is ignored, but the leading argument is still supplied to the mock function.

arg1, arg2, ...

Arguments that are preset into the argument list of the binding function when the target function is called.

Compare the.apply() and.call() methods, which both execute the function immediately, while.bind() returns a new method with a pre-specified this and can be called later.

var students = {  
  arr: ['Eason'.'Jay'.'Mayday'].getStudents: function() {
    return this.arr; }};// Create a binding function
var boundGetStudents = students.getStudents.bind(students);  
boundGetStudents(); // => ['Eason', 'Jay', 'Mayday'] 
Copy the code

.bind() creates a permanent context that cannot be modified. A binding function that repasses a different context with.call() or.apply() does not change the context to which it was previously bound and has no effect. The binding function can change context only when the constructor is called.

function getThis() {
  'use strict';
  return this;
}
var one = getThis.bind(1);  
// Bind the function call
one(); / / = > 1
// bind functions with.apply() and.call()
one.call(2);  / / = > 1
one.apply(2); / / = > 1
// rebind
one.bind(2) ();/ / = > 1
// call the binding function using the constructor method
new one(); // => Object  
// ES6 provides a new way to instantiate
Reflect.construct(one,[]);   // => Object
Copy the code

Summary: The priority of this binding

New Bind (Constructor) > Explicit Bind (Method Call) > Implicit Bind (Method Call) > Default Bind (Function Call)

6. This in the arrow function

The this object inside the function is the object at which it is defined, not used.

function foo() {
  setTimeout((a)= > {
    console.log('id:'.this.id);
  }, 100);
}
var id = 21;
foo.call({ id: 42 }); // id: 42
Copy the code

The arrow function can make this point immobilized, which is useful for encapsulating callback functions.

This is fixed, not because the arrow function has a mechanism to bind this, but because the arrow function does not have its own this, so the inner this is the outer code block’s this. Because it does not have this, it cannot be used as a constructor.

In addition, since the arrow function does not have its own this, it cannot of course use call(), apply(), or bind() to change the direction of this.

Fallibility: Object method definition

const cat = {
  lives: 9.jumps: (a)= > {
    this.lives--; // This points to the global object}}Copy the code

Because the object does not constitute a separate scope, the scope that causes the jump arrow function to be defined is the global scope.

Fallibility: Event callback dynamic this

var button = document.getElementById('press');
button.addEventListener('click', () = > {this.classList.toggle('on');
});
Copy the code

When the code above runs, clicking on the button will cause an error because button’s listener is an arrow function, resulting in this being the global object. If changed to a normal function, this dynamically points to the button object being clicked.

Eval Execution context

1. Direct call

This refers to the run context in which Eval is called.

eval("console.log(this);"); // window
var obj = {
  method: function () {
    eval('console.log(this === obj)'); // true
  }
}
obj.method(); 
Copy the code

2. Indirect invocation

No matter where it is called, its run context copies the global run context, so this is a global object.

var evalcopy = eval;
evalcopy("console.log(this);"); // window
var obj = {
  method: function () {
    evalcopy('console.log(this)'); //window
  }
}
obj.method();
Copy the code

What is this?

This is not a writer-time binding, but a run-time binding. It depends on the context of the function call. The this binding has nothing to do with where the function is declared and everything to do with how the function is called.

When a function is called, an active record is created, also known as the execution environment. This record contains information about where the function was called from (call-stack), how it was called, what arguments were passed, and so on. This is an attribute of the record that is used during function execution.

supplement

2. Scattered fragments of knowledge:

There are also apis that support binding this:

Array.prototype.every( callbackfn [ , thisArg ] )
Array.prototype.some( callbackfn [ , thisArg ] )
Array.prototype.forEach( callbackfn [ , thisArg ] )
Array.prototype.map( callbackfn [ , thisArg ] )
Array.prototype.filter( callbackfn [ , thisArg ] )
Copy the code
  • Array.from() is used to convert two types of objects into true arrays: array-like objects and iterable objects. Its second argument, similar to the Map method, is used to process each element, putting the processed value into the returned array. Its third argument can be bound to this in the map method.

  • The array instances find() and findIndex() support a second argument to bind this to the first function argument.

  • The flatMap() method performs a function on each member of the original Array (equivalent to array.prototype.map ()), and then flat() on the Array of returned values. This method returns a new array, leaving the original array unchanged. The flatMap() method can also have a second parameter that binds this to the traversal function.

Reflect 中的 this

  • Reflect the apply method is equivalent to the Function. The prototype. Apply. Call (func, thisArg, args), used for binding after this object to perform a given Function. In general, if you want to bind this to a function, you can write fn.apply(obj, args), but if the function defines its own apply method, . Can only be written in the Function prototype. Apply. Call (fn, obj, args), using Reflect object can simplify the operation.

  • Reflect.get(target, name, receiver) and reflect. set(target, name, value, receiver), if the name attribute deploys a getter/setter, The function’s this binding receiver is read.

var myObject = {
  foo: 1.bar: 2,
  get baz() {
    return this.foo + this.bar; }};var myReceiverObject = {
  foo: 4.bar: 4};Reflect.get(myObject, 'baz', myReceiverObject) / / 8
Copy the code

Proxy

  • The Apply method takes three parameters: the target object, the target object’s context object (this), and the target object’s parameter array.
var handler = {
  apply (target, ctx, args) {
    return Reflect.apply(...arguments);
  }
};
Copy the code
  • This problem with Proxy

In the Proxy case, the this keyword in the target object points to the Proxy.

const target = {
  m: function () {
    console.log(this=== proxy); }};const handler = {};
const proxy = new Proxy(target, handler);
target.m() // false
proxy.m()  // true
Copy the code

In the code above, once the proxy represents target.m, this inside the latter refers to the proxy, not target.

This in ES6 external module scripts

  • In modules, the top-level this keyword returns undefined instead of pointing to the window. That is, using the this keyword at the top of a module is meaningless. Using the top-level syntax point this equals undefined, you can detect whether the current code is in an ES6 module.
<script type="module" src="./foo.js"></script>
Copy the code
  • ES6 modules should be generic, and the same module can be used in both browser and server environments without modification. To achieve this, Node does not use CommonJS module specific internal variables in ES6 modules. First, the this keyword. In ES6 modules, the top layer of this refers to undefined; The top layer of the CommonJS module, this, points to the current module, which is a major difference between the two.

The super keyword

ES6 adds a new keyword super, which refers to the prototype object of the current object. The super keyword, when used to represent a prototype object, can only be used in the object’s methods and will cause an error if used elsewhere. Currently, only the shorthand for object methods allows the JavaScript engine to validate that it defines object methods.

const proto = {
  x: 'hello',
  foo() {
    console.log(this.x); }};const obj = {
  x: 'world',
  foo() {
    super.foo(); }}Object.setPrototypeOf(obj, proto);
obj.foo() // "world"
Copy the code

In the code above, super.foo refers to the foo method of the prototype object proto, but the bound this is the current object obj, so the output is world.

The Class of this

  • A class must have a constructor method; if it is not explicitly defined, an empty constructor method is added by default. The constructor method returns the instance object (that is, this) by default; you can specify to return another object at all.
  • As in ES5, the attributes of an instance are defined on the stereotype unless they are explicitly defined on themselves (that is, on this object).
  • Class methods that contain this inside point to instances of the class by default. If used separately, this refers to the environment in which the method was run (this actually refers to undefined because of the strict mode inside the class).
  • Static methods include the this keyword, which refers to the class, not the instance.
  • Class inheritance:
  1. Subclasses must call the super method from the constructor method or they will get an error when creating a new instance. This is because the subclass’s this object must be molded by the parent’s constructor to get the same instance attributes and methods as the parent, and then processed to add the subclass’s instance attributes and methods. If you don’t call super, your subclasses don’t get this.

  2. ES5 inheritance essentially creates an instance object of the subclass, this, and then adds the Parent class’s methods to this (parent.apply (this)). ES6 has a completely different inheritance mechanism, essentially adding the properties and methods of the superclass instance object to this (so the super method must be called first), and then modifying this with the constructor of the subclass.

  3. In the constructor of a subclass, the this keyword can only be used after super is called, otherwise an error will be reported. This is because subclass instances are built on superclass instances, and only super methods can call superclass instances.

  4. ES6 states that when a method of a parent class is called through super in a subclass normal method, this inside the method refers to the current subclass instance. Since this refers to the subclass instance, if you assign a value to a property via super, which is this, the assigned property becomes the property of the subclass instance.

class A {
  constructor() {
    this.x = 1; }}class B extends A {
  constructor() {
    super(a);this.x = 2;
    super.x = 3;
    console.log(super.x); // undefined
    console.log(this.x); / / 3}}let b = new B();
Copy the code

In the code above, assigning super.x to 3 is the same as assigning this.x to 3. When super.x is read, a.prototype. x is read, so return undefined.

  1. When a method of a parent class is called through super in a static method of a subclass, the this inside the method refers to the current subclass, not the instance of the subclass.
class A {
  constructor() {
    this.x = 1;
  }
  static print() {
    console.log(this.x); }}class B extends A {
  constructor() {
    super(a);this.x = 2;
  }
  static m() {
    super.print();
  }
}
B.x = 3;
B.m() / / 3
Copy the code

conclusion

If there are any mistakes or additions, please feel free to comment.

reference

  • MDN

  • ES6 – nguyen