JS Series (4) – this points to related

This is a keyword in JavaScript, but it is a relatively special keyword. Unlike keywords such as function, var, for, and if, you can clearly understand how it is used.

this will bind an object in the execution context, but under what conditions is it bound? Different objects will be bound under different execution conditions, which is also confusing.

This time, let’s figure out how this is bound!

1. Understand this

1.1. Why use this

In almost all common programming languages, this keyword is used (self is used in Objective-C), but this in JavaScript is different from this in common object-oriented languages:

  • In common object-oriented programming languages, such as Java, C++, Swift, Dart, and a series of other languages, this usually only appears in class methods.

  • That is to say, you need to have a class. Among the methods in the class (especially instance methods), this represents the current calling object.

  • But this in JavaScript is more flexible, both in where it appears and what it means.

What’s the point of using this? In the following code, we create an object through object literals. When we call the object’s method, we hope to print the name of the object together.

If there is no this, then our code will be written as follows:

  • In the method, in order to obtain the name, it must be obtained through the reference of obj (variable name).

  • But there is a big disadvantage in doing this: if I change the name of obj to info, then obj in all methods needs to be replaced with info.

var obj = {
  name: "why",
  running: function() {
    console.log(obj.name + "running");
  },
  eating: function() {
    console.log(obj.name + " eating");
  },
  studying: function() {
    console.log(obj.name + "studying");
  }
}

In fact, in the actual development of the above code, we will use this for optimization:

  • When we call running, eating, and studying methods through obj, this is the obj object pointed to.

var obj = {
  name: "why",
  running: function() {
    console.log(this.name + "running");
  },
  eating: function() {
    console.log(this.name + " eating");
  },
  studying: function() {
    console.log(this.name + "studying");
  }
}

So we will find that when writing certain functions or methods, this allows us to refer to objects in a more convenient way. When designing some APIs, the code is more concise and easier to reuse.

Of course, the above is just one scenario of applying this. This is used everywhere in development scenarios, which is why it is not easy to understand.

1.2. What does this point to

Let’s talk about the simplest one first, what does this point to in the global scope?

  • This question is very easy to answer. To test in the browser, point to window

  • Therefore, in the global scope, we can think that this is the window pointed to

console.log(this); // window

var name = "why";
console.log(this.name); // why
console.log(window.name); // why

However, in development, this is rarely used directly in the global scope, and is usually used in functions.

All functions create an execution context when called:

  • This context records the function call stack, function calling method, incoming parameter information, etc.;

  • this is also one of the attributes;

Let’s first look at a confusing question:

  • Define a function, we call it in three different ways, and it produces three different results

//Define a function
function foo() {
  console.log(this);
}

// 1. Calling method one: direct call
foo(); // window

// 2. Calling method two: Put foo into an object and then call
var obj = {
  name: "why",
  foo: foo
}
obj.foo() // obj object

// 3. Calling method three: calling through call/apply
foo.call("abc"); // String {"abc"} object

What kind of enlightenment can the above case give us?

  • 1. When a function is called, JavaScript will bind a value to this by default;

  • 2. The binding of this has nothing to do with the location of definition (the location of writing);

  • 3. The binding of this is related to the calling method and the calling location;

  • 4.this is bound at runtime;

So what exactly is this binding rule? Let’s learn together

2. this binding rules

We now know that this is nothing more than an object that is bound when a function is called. We just need to know its binding rules in different scenarios.

2.1. Default binding

When should I use default binding? Standalone function call.

  • Independent function calls can be understood as functions that are not bound to an object for calls;

Case 1: Ordinary function call

  • This function is called directly without any object association;

  • This independent function call will use default binding. Usually when bound by default, this in the function points to the global object (window);

function foo() {
  console.log(this); // window
}

foo();

Case 2: Function call chain (one function calls another function)

  • All function calls are not bound to an object;

// 2. Case 2:
function test1() {
  console.log(this); // window
  test2();
}

function test2() {
  console.log(this); // window
  test3()
}

function test3() {
  console.log(this); // window
}
test1();

Case 3: Pass the function as a parameter into another function

function foo(func) {
  fun()
}

function bar() {
  console.log(this); // window
}

foo(bar);

Let’s make some modifications to the case and consider whether the printing results will change:

  • The result here is still window, why?

  • The reason is very simple. At the location of the real function call, there is no object binding, it is just a call to an independent function;

function foo(func) {
  fun()
}

var obj = {
  name: "why",
  bar: function() {
    console.log(this); // window
  }
}

foo(obj.bar);

2.2. Implicit binding

Another common calling method is to call through an object:

  • That is, its calling position is a function call initiated through an object.

Case 1: Calling a function through an object

  • The calling location of foo is called by obj.foo()

  • Then when foo is called, this will be implicitly bound to the obj object.

function foo() {
  console.log(this); // obj object
}

var obj = {
  name: "why",
  foo: foo
}

obj.foo();

Case 2: Changes in Case 1

  • We reference the obj1 object through obj2, and then call the foo function through the obj1 object;

  • Then obj1 is actually bound to this at the location where foo is called;

function foo() {
  console.log(this); // obj object
}

var obj1 = {
  name: "obj1",
  foo: foo
}

var obj2 = {
  name: "obj2",
  obj1: obj1
}

obj2.obj1.foo();

Case 3: Implicit loss

  • The result is window in the end. Why window?

  • Because the final location where foo is called is bar, and bar is not bound to any object when it is called, there is no implicit binding;

  • Equivalent to a default binding;

function foo() {
  console.log(this);
}

var obj1 = {
  name: "obj1",
  foo: foo
}

//Assign foo of obj1 to bar
var bar = obj1.foo;
bar();

2.3. Display binding

Implicit binding has a prerequisite:

  • There must be a reference to the function (such as a property) inside the calling object;

  • If there is no such reference, when calling, an error that the function cannot be found will be reported;

  • It is through this reference that this is indirectly bound to this object;

What should we do if we don’t want to include a reference to this function within the object but at the same time we want to make a forced call on this object?

  • All JavaScript functions can use the call and apply methods (this is related to Prototype).

    • The differences between the two will not be expanded upon here;

    • In fact, it is very simple. The first parameter is the same, the subsequent parameters, apply is an array, and call is a parameter list;

  • The first parameter of both functions is required to be an object. What is the role of this object? It’s just for this.

  • When this function is called, this will be bound to the passed in object.

Because of the above process, we explicitly bind the object pointed to by this, so it is called display binding.

2.3.1. call, apply

Bind this object through call or apply

  • After displaying the binding, this will clearly point to the bound object.

function foo() {
  console.log(this);
}

foo.call(window); // window
foo.call({name: "why"}); // {name: "why"}
foo.call(123); // Number object, 123 when stored
2.3.2. bind function

If we want a function to always be explicitly bound to an object, what can we do?

Solution 1: Write an auxiliary function by hand (understand)

  • We manually wrote an auxiliary function for bind

  • The purpose of this auxiliary function is to always have its this bound to the obj object when foo is executed.

function foo() {
  console.log(this);
}

var obj = {
  name: "why"
}

function bind(func, obj) {
  return function() {
    return func.apply(obj, arguments);
  }
}

var bar = bind(foo, obj);

bar(); // obj object
bar(); // obj object
bar(); // obj object

Option 2: Use Function.prototype.bind

function foo() {
  console.log(this);
}

var obj = {
  name: "why"
}

var bar = foo.bind(obj);

bar(); // obj object
bar(); // obj object
bar(); // obj object
2.3.3. Built-in functions

Sometimes, we will call some built-in functions of JavaScript or built-in functions in some third-party libraries.

  • These built-in functions will require us to pass in another function;

  • We will not explicitly call these functions ourselves, but JavaScript or a third-party library will help us execute them;

  • How is this in these functions bound?

Case 1: setTimeout

  • A function will be passed in setTimeout. This in this function is usually window.

setTimeout(function() {
  console.log(this); // window
}, 1000);

Why is this a window?

  • This is related to the internal call of the setTimeout source code;

  • Inside setTimeout is the this object bound through apply, and it is bound to the global object;

Case 2: forEach of array

Arrays have a higher-order function forEach, which is used for function traversal:

  • The function passed in forEach also prints the Window object;

  • This is because by default the function passed in is an automatically called function (default binding);

var names = ["abc", "cba", "nba"];
names.forEach(function(item) {
  console.log(this); //Three times window
});

Can we change the this point of the function?

Picture

forEach parameter

var names = ["abc", "cba", "nba"];
var obj = {name: "why"};
names.forEach(function(item) {
  console.log(this); //Three obj objects
}, obj);

Case 3: Click on div

If we have a div element:

  • Note: Some codes are omitted

 <style>
    .box {
      width: 200px;
      height: 200px;
      background-color: red;
    }
  </style>

  <div class="box"></div>

Get the element node and listen for clicks:

  • In the callback of the click event, who does this point to? box object;

  • This is because when a click occurs, when the callback function passed in is called, the box object will be bound to the function;

var box = document.querySelector(".box");
box.onclick = function() {
  console.log(this); // box object
}

So how to determine the callback function this passed to the built-in function?

  • For some built-in functions, it is difficult for us to determine how the passed callback function is called internally;

  • On the one hand, it can be determined by analyzing the source code, on the other hand, we can determine it through experience (well-informed);

  • But in any case, it is usually determined by the rules we talked about before;

2.4. new binding

Functions in JavaScript can be used as constructors of a class, that is, using the new keyword.

When using the new keyword to call a function, the following operations will be performed:

  • 1. Create a brand new object;

  • 2. This new object will be connected by prototype;

  • 3. This new object will be bound to this of the function call (the binding of this is completed in this step);

  • 4. If the function does not return other objects, the expression will return this new object;

//Create Person
function Person(name) {
  console.log(this); // Person {}
  this.name = name; // Person {name: "why"}
}

var p = new Person("why");
console.log(p);

2.5. Rule priority

After learning the four rules, in the next development we only need to find out which rule is applied to the function call. But if multiple rules are applied to a function call position, who has higher priority?

1. The default rule has the lowest priority

There is no doubt that the priority of the default rule is the lowest, because when other rules exist, this will be bound by other rules.

2. Explicit binding has higher priority than implicit binding

Which one has higher priority, explicit binding or implicit binding? We can test this:

  • The result is obj2, indicating that the binding has taken effect.

function foo() {
  console.log(this);
}

var obj1 = {
  name: "obj1",
  foo: foo
}

var obj2 = {
  name: "obj2",
  foo: foo
}

// Implicit binding
obj1.foo(); // obj1
obj2.foo(); // obj2

//Implicit binding and explicit binding exist at the same time
obj1.foo.call(obj2); // obj2, indicating that explicit binding has higher priority

3.New binding has higher priority than implicit binding

  • The result is foo, which means that new binding has taken effect.

function foo() {
  console.log(this);
}

var obj = {
  name: "why",
  foo: foo
}

new obj.foo(); // foo object, indicating that new binding has a higher priority

4.new binding has higher priority than bind

New binding, call, and apply are not allowed to be used at the same time, so there is no one with higher priority.

function foo() {
  console.log(this);
}

var obj = {
  name: "obj"
}

var foo = new foo.call(obj);

Picture

Use new and call at the same time

But can new binding be used at the same time as the function after bind? Can

  • The result is displayed as foo, which means that the new binding has taken effect.

function foo() {
  console.log(this);
}

var obj = {
  name: "obj"
}

// var foo = new foo.call(obj);
var bar = foo.bind(obj);
var foo = new bar(); // Print foo, indicating that new binding is used

Summary of priorities:

  • new binding > explicit binding (bind) > implicit binding > default binding

Three. Outside this rule

The rules we mentioned are sufficient for daily development, but there are always some grammars that are beyond our rules. (There are always characters like this in mythological stories and anime)

3.1. Ignore display binding

If we pass in a null or undefined in explicit binding, then the explicit binding will be ignored and the default rules will be used:

function foo() {
  console.log(this);
}

var obj = {
  name: "why"
}

foo.call(obj); // obj object
foo.call(null); // window
foo.call(undefined); // window

var bar = foo.bind(null);
bar(); // window

3.2. Indirect function reference

Alternatively, create an indirect reference to a function, in which case the default binding rules are used.

Let’s first look at the result of the following case?

  • The result of (num2 = num1) is the value of num1;

var num1 = 100;
var num2 = 0;
var result = (num2 = num1);
console.log(result); // 100

Let’s look at the following function assignment results:

  • The result of assignment (obj2.foo = obj1.foo) is the foo function;

  • If the foo function is called directly, it is bound by default;

function foo() {
  console.log(this);
}

var obj1 = {
  name: "obj1",
  foo: foo
};

var obj2 = {
  name: "obj2"
}

obj1.foo(); // obj1 object
(obj2.foo = obj1.foo)(); // window

3.3. ES6 Arrow Function

A very useful function type is added in ES6: arrow function

  • I won’t introduce the usage of arrow function in detail here, you can learn it by yourself.

The arrow function does not use the four standard rules of this (that is, it does not bind this), but determines this based on the outer scope.

Let’s look at a case of simulating network requests:

  • Here I use setTimeout to simulate network requests. How can I store the data in data after requesting it?

  • We need to get the obj object and set data;

  • But the this we get directly is window, we need to define it in the outer layer: var _this = this

  • Using _this in the callback function of setTimeout represents the obj object

var obj = {
  data: [],
  getData: function() {
    var _this = this;
    setTimeout(function() {
      // Simulate the obtained data
      var res = ["abc", "cba", "nba"];
      _this.data.push(...res);
    }, 1000);
  }
}

obj.getData();

The above code was our most commonly used method before ES6. Starting from ES6, we will use arrow functions:

  • Why can this be used directly in the callback function of setTimeout?

  • Because the arrow function does not bind the this object, the this reference will find the corresponding this from the upper scope.

var obj = {
  data: [],
  getData: function() {
    setTimeout(() => {
      // Simulate the obtained data
      var res = ["abc", "cba", "nba"];
      this.data.push(...res);
    }, 1000);
  }
}

obj.getData();

Thinking: If getData is also an arrow function, then who does this in the callback function in setTimeout point to?

  • The answer is window;

  • Still searching from the upper scope, we found the global scope;

  • In the global scope, this represents window

var obj = {
  data: [],
  getData: () => {
    setTimeout(() => {
      console.log(this); // window
    }, 1000);
  }
}

obj.getData();

4. This interview question

4.1. Interview Question 1:

var name = "window";
var person = {
  name: "person",
  sayName: function () {
    console.log(this.name);
  }
};
function sayName() {
  var sss = person.sayName;
  sss();
  person.sayName();
  (person.sayName)();
  (b = person.sayName)();
}
sayName();

This interview question is very simple. It’s nothing more than a detour, hoping to confuse the interviewer:

function sayName() {
  var sss = person.sayName;
  // Independent function call, not associated with any object
  sss(); // window
  // association
  person.sayName(); // person
  (person.sayName)(); // person
  (b = person.sayName)(); // window
}

4.2. Interview question 2:

var name = 'window'
var person1 = {
  name: 'person1',
  foo1: function () {
    console.log(this.name)
  },
  foo2: () => console.log(this.name),
  foo3: function () {
    return function () {
      console.log(this.name)
    }
  },
  foo4: function () {
    return () => {
      console.log(this.name)
    }
  }
}

var person2 = { name: 'person2' }

person1.foo1();
person1.foo1.call(person2);

person1.foo2();
person1.foo2.call(person2);

person1.foo3()();
person1.foo3.call(person2)();
person1.foo3().call(person2);

person1.foo4()();
person1.foo4.call(person2)();
person1.foo4().call(person2);

Here is the code analysis:

//Implicit binding, must be person1
person1.foo1(); // person1
// The combination of implicit binding and explicit binding, explicit binding takes effect, so it is person2
person1.foo1.call(person2); // person2

// foo2() is an arrow function, not all rules apply
person1.foo2() // window
// foo2 is still an arrow function and does not apply to explicit binding rules
person1.foo2.call(person2) // window

// Obtain foo3, but the calling position is global, so it is bound to window by default.
person1.foo3()() // window
// foo3 is displayed bound to person2
// But the returned function is still called globally, so it is still window
person1.foo3.call(person2)() // window
// Get the function returned by foo3 and bind it to person2 through display, so it is person2
person1.foo3().call(person2) // person2

// The function foo4() returns an arrow function
// The arrow function is executed in the upper scope, which is person1
person1.foo4()() // person1
// foo4() is explicitly bound to person2 and returns an arrow function
//The arrow function finds the upper scope, which is person2
person1.foo4.call(person2)() // person2
// foo4 returns an arrow function, which only looks at the upper scope
person1.foo4().call(person2) // person1

4.3. Interview question three:

var name = 'window'
function Person (name) {
  this.name = name
  this.foo1 = function () {
    console.log(this.name)
  },
  this.foo2 = () => console.log(this.name),
  this.foo3 = function () {
    return function () {
      console.log(this.name)
    }
  },
  this.foo4 = function () {
    return () => {
      console.log(this.name)
    }
  }
}
var person1 = new Person('person1')
var person2 = new Person('person2')

person1.foo1()
person1.foo1.call(person2)

person1.foo2()
person1.foo2.call(person2)

person1.foo3()()
person1.foo3.call(person2)()
person1.foo3().call(person2)

person1.foo4()()
person1.foo4.call(person2)()
person1.foo4().call(person2)

Here is the code analysis:

//Implicit binding
person1.foo1() // peron1
// Display binding priority is greater than implicit binding
person1.foo1.call(person2) // person2

// foo is an arrow function, it will find this in the upper scope, then it is person1
person1.foo2() // person1
// foo is an arrow function. Calling it using call will not affect the binding of this. Look up to the upper layer as above.
person1.foo2.call(person2) // person1

// The calling location is a global direct call, so it is still window (default binding)
person1.foo3()() // window
// Finally, we got the function returned by foo3 and called it directly globally (default binding)
person1.foo3.call(person2)() // window
// After getting the function returned by foo3, bind it to person2 through call and call it.
person1.foo3().call(person2) // person2

// foo4 returns the arrow function, which has nothing to do with its own binding. The upper layer finds person1
person1.foo4()() // person1
// When foo4 is called, person2 is bound, and the returned function is an arrow function. When calling, the person2 bound by the upper layer is found.
person1.foo4.call(person2)() // person2
//The arrow function returned by foo4 call has nothing to do with the call call. Find the upper person1
person1.foo4().call(person2) // person1

4.4. Interview question four:

var name = 'window'
function Person (name) {
  this.name = name
  this.obj = {
    name: 'obj',
    foo1: function () {
      return function () {
        console.log(this.name)
      }
    },
    foo2: function () {
      return () => {
        console.log(this.name)
      }
    }
  }
}
var person1 = new Person('person1')
var person2 = new Person('person2')

person1.obj.foo1()()
person1.obj.foo1.call(person2)()
person1.obj.foo1().call(person2)

person1.obj.foo2()()
person1.obj.foo2.call(person2)()
person1.obj.foo2().call(person2)

The following is the code analysis:

// obj.foo1() returns a function
// This function is executed directly under global action (default binding)
person1.obj.foo1()() // window
// Finally we get a returned function (although there is an extra step of call binding)
// This function is executed directly under global action (default binding)
person1.obj.foo1.call(person2)() // window
person1.obj.foo1().call(person2) // person2

// Get the return value of foo2(), which is an arrow function
// When the arrow function is executed, it looks for this in the upper scope, which is obj.
person1.obj.foo2()() // obj
// The return value of foo2() is still an arrow function, but person2 is bound to it when foo2 is executed.
// When the arrow function is executed, it looks for this in the upper scope and finds person2.
person1.obj.foo2.call(person2)() // person2
// The return value of foo2() is still an arrow function
// The arrow function will not bind this when called through call, so find this in the upper scope to be obj
person1.obj.foo2().call(person2) // obj