1. Introduction

This is one of the most important questions to ask in an interview. But many people have a vague understanding of this. They have to print it out at work before they dare to write it down. They can only give a general answer in an interview. Today we’re going to focus on that pain point and get rid of it!

Now let’s look at a problem:

var number = 5;
var obj = {
    number: 3.fn: (function () {
        var number;
        this.number *= 2;
        number = number * 2;
        number = 3;
        return function () {
            var num = this.number;
            this.number *= 2;
            console.log(num);
            number *= 3;
            console.log(number); }}}) ()var myFun = obj.fn;
myFun.call(null);
obj.fn();
console.log(window.number);
Copy the code

The answers are, in order: 10, 9, 3, 27, 20. If you answered this correctly and easily, it means your understanding of this is very good. Otherwise, just read the passage with me

2. What is this?

When discussing this, it’s common to say “point to XXX.” This is a pointer, and before we get to the point, let’s introduce a few nouns:

  • The default binding
  • Implicit binding
  • According to the binding
  • The new binding

And their priority: New binding > Show binding > Implicit binding > Default binding.

2.1. Default binding

  • Used when no other binding rules can be applied, usually stand-alone function calls.
  • Independent function: a function in the global context whose this refers to:
    • Non-strict and in a Node environment:globalThis;
    • Non-strict and Windows environment:window;
    • In strict mode:undefined.

Unless otherwise stated, this article is the result of execution in the browser environment.

function getName(){
  console.log('Name: '.this.name);
}
var name = 'laohuang';
getName();
Copy the code

When getName() is called, it is in the global context, the default binding is applied, this refers to the global object window, so the console prints: Name: laohuang.

2.2. Implicit binding

  • A function is called by an object, or an object exists at the call location, i.eobj.fn();
  • This refers to the last level in the chain of object properties. Such asObj1.obj2.obj3.fn (), this points to obj3;
  • The implicit binding may be lost. Please remember:obj.fn()Is implicitly bound, but iffn()There’s nothing up front. It’s the default binding.

2.2.1. Typicalobj.fn

function getName(){
  console.log('Name: '.this.name);
}
var laohuang = {
  name: 'laohuang'.getName: getName
}
var name = 'feifei';
laohuang.getName();
Copy the code

}}}}}}}}}}}}}}}}}}}}}}} GetName in LaoHuang is equivalent to an assignment operation. When calling laohuang.getName(), the location is laohuang. The implicit binding binds this in getName to laoHuang, so the console prints: Name: laohuang.

2.2.2. This refers to the last level in the chain of object properties

function getName(){
  console.log('Name: '.this.name);
}
var feifei = {
  name: 'feifei'.getName: getName
}
var laohuang = {
  name: 'laohuang'.friend: feifei
}
var name = 'FEHuang';
laohuang.friend.getName();
Copy the code

Analytic: this point to the last layer in the chain of object properties, so the implicit binding to bind this to laohuang. The frend is feifei, so the console will print: Name: feifei.

2.2.3. The big trap of implicit binding – binding loss

1. Binding loss – When a reference to a function is to another variable:

function getName(){
  console.log('Name: '.this.name);
}
var laohuang = {
  name: 'laohuang'.getName: getName
}
var name = 'FEHuang';
var getNameCopy = laohuang.getName;
getNameCopy();
Copy the code

Var getNameCopy = laohuang. GetName = getName; getNameCopy = laohuang. GetName = getName; GetNameCopy () has nothing before it, so it’s the default binding, and this points to the global context. So the console will print: Name: FEHuang.

2. Binding loss – callback function:

function getName(){
  console.log('Name: '.this.name);
}
var feifei = {
  name: 'feifei'.getName: getName
}
var laohuang = {
  name: 'laohuang'.getName: function() {
    setTimeout(function() {
      console.log('Name: '.this.name)
    })
  }
}
var name = 'FEHuang';
laohuang.getName(); // Name: FEHuang
setTimeout(feifei.getName, 1000); // Name: FEHuang
setTimeout(function() {
  feifei.getName(); // Name: feifei
}, 1000)
Copy the code

Resolution:

  • laohuang.getName(): That’s easy to understand.setTimeoutIn the callback function ofthisThe default binding is used, and this time it is in non-strict mode, so printName: FEHuang;
  • setTimeout(feifei.getName, 1000): Here is equivalent tofeifei.getNameThe quote is given directly tosetTimeoutThe first variable, the last variable to execute. Here the binding is lost, the default binding is used, so point to the global context, print:Name: FEHuang;
  • setTimeout(function() { feifei.getName(); }, 1000).: Although also insetTimeoutCallback, but here it is executed directlyfeifei.getName(), using implicit binding,thisPoint to thefeifei. So print:Name: feifei.

2.3. Show bindings

  • Show binding is throughcall.apply.bindIs explicitly specifiedthisThe object to which the.
  • call.applyandbindThe first argument to phi is phi of the corresponding functionthiThe object to which s points.callandapplyThe function of the same, but different ways of passing reference.callandapplyWill execute the corresponding function, andbindMethod doesn’t.
  • Pay attention tocall.applyPass null/undefined –> global context; Raw value –> Object (non-strict mode)/ raw value (strict mode)

2.3.1. Typical display bindings

function getName(){
  console.log('Name: '.this.name);
}
var laohuang = {
  name: 'laohuang'.getName: getName
}
var name = 'FEHuang';
var getNameCopy = laohuang.getName;
getNameCopy.call(laohuang);
Copy the code

The console will print: Name: laohuang.

2.3.2. Special Cases – Binding loss may also occur when using call, apply, or bind

So, does the use of a display binding mean that the loss of the binding encountered by an implicit binding does not occur? Clearly not so, do not believe, continue to read.

function getName(){
  console.log('Name: '.this.name);
}
var laohuang = {
  name: 'laohuang'.getName: getName
}
var name = 'FEHuang';
var getNameCopy = function(fn) {
  fn();
};
getNameCopy.call(laohuang, laohuang.getName);
Copy the code

原 文 : GetNamecopy.call (laohuang, laohuang. GetName) does bind this to this. But the second argument to call passes a reference to getName, so when fn() is executed, it is equivalent to calling getName() directly. So the console will print: Name: FEHuang. Think: What if you want the binding not to be lost? (The answer is in the last thought 2)

2.3.3. Special cases – Special arguments for call, apply, bind

When using Call and Apply in non-strict mode, an attempt is made to convert to an object if the value used as this is not an object. Null and undefined are converted to global objects. Raw values such as 7 or ‘foo’ are converted to objects using the appropriate constructor.

1. Passing null/undefined: converts it to a global object, using the default binding.

var laohuang = {
  name: 'laohuang'
}
var name = 'FEHuang';
function getName() {
  console.log(this.name);
}
getName.call(null); //FeHuang 
Copy the code

The default binding is applied, so the console will print: FEHuang.

2. Pass the original value: it will be converted to the corresponding object

var doSth = function(name){
  console.log(this);
  console.log(name);
}
doSth.call(2.'laohuang'); // Number{2}, 'laohuang'
var doSth2 = function(name){
  'use strict';
  console.log(this);
  console.log(name);
}
doSth2.call(2.'laohuang'); // 2, 'laohuang'
Copy the code

2.4. The new binding

2.4.1. What did New do?

The introduction on MDN looks like this:

  1. Create an empty simple JavaScript object (that is, {});
  2. Link the object (set the object’sconstructor) to another object;
  3. Use the newly created object in Step 1 asthisThe context of;
  4. Returns if the function returns no objectthis.

2.4.2. For example

function getName(name) {
    this.name = name
}

var laohuang = new getName('laohuang');
console.log('Name: ', laohuang.name);
Copy the code

Var laohuang = new getName(‘laohuang’);} var laohuang = new getName(‘laohuang’);} So the console will print: Name: laohuang.

2.5. Arrow function

Let’s take a look at the characteristics of the arrow function:

  • Functional in vivothisObject that inherits from the outer code blockthis.Pay attention toThe “this” inside the: arrow function is not the object in which it was defined, but the “this” in the outer code block.
  • It cannot be used as a constructor, that is, it cannot use the new command, otherwise an error will be thrown.
  • Do not useargumentsObject that does not exist in the function body. You can use it if you wantrestParameter instead.
  • Do not useyieldCommand, so the arrow function cannot be usedGeneratorFunction.
  • Arrow functions don’t have their ownthis, so it can’t be usedCall (), apply(), bind()These are ways to changethisPointing to.
var names = {
    getName0: function() {
        console.log(this);
        return () = > {
            console.log(this); }},getName1: function() {
        return function() {
            console.log(this);
            return () = > {
                console.log(this); }}},getName2: () = > {
        console.log(this); }}/ / the first paragraph
var name0 = names.getName0(); / / object names
name0(); / / object names

/ / the second paragraph
var name1 = names.getName1();
var _name1 = name1(); / / the window object
_name1(); / / the window object

/ / the third paragraph
names.getName2(); / / the window object
Copy the code

Resolution:

The first paragraph:

  • names.getName0()Corresponding to implicit binding,thisBinding on thenamesSo the console will print the: names object;
  • name0()It’s executing the arrow function. Arrow functionthisInheriting from a previous code segmentthis(i.e.getName0()The runtimethis, i.e.,names). So the console will print the: names object;

The second paragraph:

  • name1isnames.getName1()A completely new function is returned after running, which corresponds to the case of implicit binding loss described above. The default binding is applied,thisPoints to theGlobal object Window. soname1()Print: window object;
  • _name1()It executes the arrow function.
    • If the arrow function this inherits from the custom object, then_name1()The name object should be printed, but here the Window object is printed, so obviously the understanding iserror.
    • In accordance with theThe this of the arrow function is inherited from the this of the outer code blockIt makes sense. The outer code block that we just analyzed,thisPoints to thewindow, so here the console prints the: window object.

The third paragraph:

  • Names.getname2 () executes the arrow function. Because this does not exist in the current code block names, you can only search at the upper level. So here the console prints the: window object.

2.5.1. Remember that this in the arrow function inherits from this in the outer code library block

It is also possible that this in the arrow function is dynamic.

var names = {
    getName1: function() {
        return function() {
            console.log(this);
            return () = > {
                console.log(this); }}}},var name0 = names.getName1();

var n1 = name0(); // window
n1(); // window

var n2 = name0.call(names); // names
n2(); // names  
Copy the code

3. Summary

3.1. How to accurately judge the direction of this

As a refresher, the priority for bindings is: New binding > Show binding > Implicit binding > Default binding. Then we can judge by the following steps:

  • Whether the function is called in new (new binding), if so, then this is bound to the newly created object;
  • Whether the function is called via call,apply, or bind. If so, then this binds to the specified object.
  • Whether the function is called in a context object (implicit binding), if so, this is bound to that context object. It’s usually obj.fun();
  • If none of the above, use the default binding. If in strict mode, it is bound to undefined, otherwise to a global object (globalThis for the Node environment, window for the browser environment);
  • If null or undefined is passed to call, apply, or bind as a binding object to this, these values are ignored. The default binding rules apply.
  • In the case of an arrow function, the this of the arrow function inherits from the this of the outer code block.

Finally, this point needs more practice, this article is just a list of the general, only continuous practice to master oh ~ error, welcome to correct oh ~

4. Think about

4.1. Thinking about 1

var number = 5;
var obj = {
    number: 3.fn: (function () {
        var number;
        this.number *= 2;
        number = number * 2;
        number = 3;
        return function () {
            var num = this.number;
            this.number *= 2;
            console.log(num);
            number *= 3;
            console.log(number); }}}) ()var myFun = obj.fn;
myFun.call(null);
obj.fn();
console.log(window.number);
Copy the code

Resolution:

  • Definition phase: Fn’s corresponding closure is executed when obj is defined. When you execute the code in the closure and return the function, it is clear that this is not a new binding (no new keyword), it is not a display binding (no call, aply, bind), it is not an implicit binding (no obj.fun()), so it is the default binding. This refers to the global variable window. The data change process is as follows:
  var number; // The newly declared number. Because it is a closure, the number will not be destroyed.
  this.number *= 2; Window. number: 5 * 2 --> 10
  number = number * 2; // There is no call through this, where number is the new one declared in the closure. Closure number: undefined --> NAN.
  number = 3; // closure number: NAM --> 3.
Copy the code
  • Var myFun = obj.fn: assign a reference to obj.fn to myFun. The implicit binding is clearly missing here, so it should be the default binding. MyFun’s this will point to the global variable window. It’s just assignment, there’s no method call, so the data doesn’t change.
  • Myfun.call (NULL) : Call is used, but the first argument is NULL, so the default binding is still applied. The data changes as follows:
// myFunc
function () {
    var num = this.number; // this points to window. So num === window.number, value 10.
    this.number *= 2; // this points to window. Window. number: 10 * 2 --> 20
    console.log(num); // print 10 if num is 10.
    number *= 3; // Not called through this, where number is the one in the closure. Closure number: 3 * 3 --> 9.
    console.log(number); // Print the closure number, print 9.
}
Copy the code
  • Obj.fn () : the typical default binding, where this refers to obj. Therefore, the data changes as follows:
// obj.fn
function () {
    var num = this.number; // this points to obj. So num === obj.number, value 3.
    this.number *= 2; // this points to obj. Obj. Number: 3 * 2 --> 6.
    console.log(num); // if num is 3, print 3
    number *= 3; // Not called through this, where number is the one in the closure. Closure number: 9 * 3 --> 27.
    console.log(number); // Print the closure number, print 27
}
Copy the code
  • Console. log(window.number) : In this case, window.number is 20.
  • So, the final print is: 10, 9, 3, 27, 20.

4.2. Thinking about 2

Problem: Show bindings – what do I do if I want bindings not to be lost? Answer: When calling fn, give it a display binding as well.

function getName(){
    console.log('Name: '.this.name);
}
var laohuang = {
    name: 'laohuang'.getName: getName
}
var name = 'FEHuang';
var getNameCopy = function(fn) {
  fn().bind(this);
};
getNameCopy.call(laohuang, laohuang.getName);
Copy the code

Reference 5.

  • Hey, do you really understand this?
  • MDN – this
  • Interviewer: JS this point