I believe that this in Javascript will make many students confused in work and study. The author also does the same. After reading various materials and practical work applications, the author makes the following sorting out, mainly including people’s wrong understanding of this for a long time and the binding rules of this. Arrow function, the actual work scene encountered problems, I hope to have this confusion you can help.

Two kinds of misconceptions

Point to their own

The first misconception about this is that it is easy to interpret this as referring to the function itself. In fact, the direction of this is not determined at the function definition stage. Only when the function is executed can we determine who this refers to.

In the following example, declare the function foo() and add an attribute count to function object foo when foo. Count =0. But the this in function foo’s this.count code does not refer to that function object. Foo (I) in the for loop uses its object as window, equivalent to window.foo(I), So this in foo refers to window.

function foo(num){

  this.count++; // Records how many times foo is called

}

foo.count = 0;

window.count = 0;

for(let i=0; i<10; i++){

  if(i > 5) {

    foo(i);

  }

}

console.log(foo.count, window.count); / / 0. 4

Copy the code

Point to the scope of the function

The second misconception about this is that this refers to the scope of a function

The following code attempts to call the bar function in foo, depending on the context.

  • Browser: In the browser environment, there is no problem. The globally declared function is placed under the window object, and the this generation in foo refers to the window object. In the global environment, the variable a is not declared, so this. The output is undefined.

  • Node.js: In node. js, the declared function will not be placed under the global object, so calling this.bar in foo will raise TypeError: This. Bar is not a function error. To run error-free, omit the preceding this when calling the bar function.

function foo(){

  var a = 2;

  this.bar();

}

function bar(){

  console.log(this.a);

}

foo();

Copy the code

This four binding rules

The default binding

When a function call is a stand-alone call (without a function reference) that cannot invoke other binding rules, we call it a “default binding”, binding to global objects in non-strict mode, and binding to undefined in use strict mode.

Strict mode call down

'use strict'

function demo(){

  // TypeError: Cannot read property 'a' of undefined

  console.log(this.a);

}

const a = 1;

demo();

Copy the code

Non-strict mode call down

Binding a to window.a in the browser environment, the following code uses var to declare variable A to print 1.

function demo(){

  console.log(this.a); / / 1

}

var a = 1;

demo();

Copy the code

The following code uses let or const to declare variable a and outputs undefined

function demo(){

  console.log(this.a); // undefined

}

let a = 1;

demo();

Copy the code

The default binding of this is intended to be emphasized in the examples, but you will notice that the above two types of code have different results because they use var and let declarations respectively. The reason for this is related to the concept of top-level objects

On the Issue: Nodejs-roadmap /issues/11 introduces the concept of the top-level object. The attributes of the top-level object (the browser environment refers to window, the Node.js environment refers to Global) and the assignment of Global variable attributes are equivalent. Var and function are used to declare top-level object properties, while let belongs to ES6 specification. However, global variables declared in ES6 specification, such as let, const, and class, do not belong to top-level object properties.

Implicit binding

Is contained by an object at the call location of a function and has a context, as shown in the following example:

function child({

  console.log(this.name);

}

let parent = {

  name'zhangsan'.

  child,

}

parent.child(); // zhangsan

Copy the code

The function is called using the parent object context to refer to the child function.

The pitfalls of implicit binding

Implicitly bound functions, because some careless operation will lose the bound object, will apply the default binding from the original binding rule, look at the following code:

function child({

  console.log(this.name);

}

let parent = {

  name'zhangsan'.

  child,

}

let parent2 = parent.child;

var name = 'lisi';

parent2();

Copy the code

Assigning the parent. Child function itself to parent2, calling parent2() is actually an undecorated function call, so the default binding is applied.

According to the binding

Explicit binding and implicit binding are literally opposite, with one being more direct and the other more euphemistic. Here’s what the two rules mean:

  • Implicit binding: Bind this implicitly to the function referred to by an attribute inside an object (for example, function child(){}).

  • Show binding: Enforce binding calls when you need to reference an object. Js provides call(), apply() and the built-in function.prototype.bind method in ES5.

Call () and apply() both set this as their first arguments. The difference is that apply passes the arguments as arrays, while call passes them one by one.

function fruit(. args){

  console.log(this.name, args);

}

var apple = {

  name'apple'

}

var banana = {

  name'banana'

}

fruit.call(banana, 'a'.'b')  // [ 'a', 'b' ]

fruit.apply(apple, ['a'.'b']) // [ 'a', 'b' ]

Copy the code

The following is an example of the bind bind binding, which simply binds a value to the function this and returns the bound function. Output is only given when the fruit function is executed, as in:

function fruit(){

  console.log(this.name);

}

var apple = {

  name'apple'

}

fruit = fruit.bind(apple);

fruit(); / / apple

Copy the code

Call, apply, and bind can also be used in a context, for example:

function fruit(name){

  console.log(`The ${this.name}${name}`);

}

const obj = {

  name'This is fruit'.

}

const arr = ['apple'.'banana'];

arr.forEach(fruit, obj);

// This is fruit: apple

// This is fruit: bananas

Copy the code

The new binding

The new binding can also affect this call, which is a constructor, and each new binding creates a new object.

function Fruit(name){

  this.name = name;

}



const f1 = new Fruit('apple');

const f2 = new Fruit('banana');

console.log(f1.name, f2.name); // apple banana

Copy the code

priority

This has precedence if more than one binding rule is applied to the place where it is called: new binding -> display binding -> Implicit binding -> default binding.

Arrow function

Arrow functions are not defined using the function keyword and do not use the four standard specifications for this described above. Arrow functions inherit from the this binding of the outer function call.

When fruit.call(apple) is executed, the arrow function this is bound and cannot be modified again.

function fruit(){

  return (a)= > {

    console.log(this.name);

  }

}

var apple = {

  name'apple'

}

var banana = {

  name'banana'

}

var fruitCall = fruit.call(apple);

fruitCall.call(banana); / / apple

Copy the code

Several pits in use of This

Simulate classes through functions and prototype chains

In the following example, you define the function Fruit, then you define the info method on the prototype chain, instantiate the object F1 and define the object F2 to call the info method, respectively.

function Fruit(name{

  this.name = name;

}

Fruit.prototype.info = function({

  console.log(this.name);

}

const f1 = new Fruit('Apple');

f1.info();

const f2 = { name'Banana' };

f2.info = f1.info;

f2.info()

Copy the code

After the output, the result is different because the this in the info method corresponds not to the context in which it was defined, but to the context in which it was called, according to the implicit binding rules we discussed above.

Apple

Banana

Copy the code

Use arrow functions on the prototype chain

If you use constructors and prototype chains to emulate classes, you cannot define arrow functions on the prototype chain because the this in the arrow function inherits the this binding of the outer function call.

function Fruit(name{

  this.name = name;

}

Fruit.prototype.info = (a)= > {

  console.log(this.name);

}

var name = 'Banana'

const f1 = new Fruit('Apple');

f1.info();

Copy the code

Use in events

As an example of node.js, when our listener is called, this will point to an EventEmitter instance attached to the listener if we declare a normal function. This will not point to an EventEmitter instance if we use the arrow function.

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {

  constructor() {

    super(a);

    this.name = 'myEmitter';

  }

}

const func1 = (a)= > console.log(this.name);

const func2 = function (console.log(this.name); };

const myEmitter = new MyEmitter();

myEmitter.on('event', func1); // undefined

myEmitter.on('event', func2); // myEmitter

myEmitter.emit('event');

Copy the code

This may have more questions than the ones listed above, feel free to comment in the comments section if you have any other questions.

Reference

  • JavaScript you Don’t Know (Volume 1)

About the author: May Jun, software designer, author of the public account “Nodejs Technology Stack”.

– END –