Finish this and say Yes to the execution context

Posted by mezise on Tue, 01 Feb 2022 22:55:13 +0100

Who is this pointing to

  the specific details and rules of this point will be analyzed later. Here you can "memorize" the following rules:

  • In the function body, when a function is simply called non explicitly or implicitly, this in the function will be bound to undefined in the strict mode, and bound to the global object window in the non strict mode.
  • Generally, when the constructor is called with the new method, this in the constructor will be bound to the newly created object.
  • Generally, when the function is explicitly called through the call/apply/bind method, this in the function body will be bound to the object with the specified parameters.
  • Generally, when calling a function through the context, this in the function body will be bound to the object.
  • In the arrow function, the direction of this is determined by the outer (function or global) scope.

Analysis of practical examples

this in the global environment

  function f1() {
    console.log(this);
  }
  function f2 () {
    'use strict'
    console.log(this);
  }
  f1(); //Window
  f2(); //undefined

   the function is simply called in the browser global environment. In the non strict mode, this points to window. In the case of specifying the strict mode through 'use strict', this points to undefined

  const foo = {
    bar: 10,
    fn: function () {
      console.log(this);
      console.log(this.bar);
    }
  }
  var fn1 = foo.fn;
  fn1(); //Window  undefined

  this here still points to window. Although the fn function is used as the method of the object in the foo object, after being assigned to fn1, fn1 is still executed in the global environment of window.
  change the above code call to the following situation:

  const foo = {
    bar: 10,
    fn: function () {
      console.log(this);
      console.log(this.bar);
    }
  }

  foo.fn(); //{bar: 10, fn: ƒ} 10

At this point, this points to the object that finally calls it, in foo. In the FN () statement, this points to the foo object. Remember that explicit binding is not considered when executing the function. If this in the function is called by the object of the upper level, then this refers to the object of the upper level; Otherwise, point to the global environment.

this in context object call

   referring to the above conclusion, run the following code and finally return true

  const student = {
    name:'mgd',
    fn: function () {
      return this;
    }
  }
  console.log(student.fn() === student); //true

When there is more complicated calling relation, as the nesting relation in the following code, this will point to the object that finally calls it, and the code output is mgd.

  const person = {
    name: 'lpb',
    brother: {
      name: 'mgd',
      fn: function () {
        return this.name;
      }
    }
  }
  console.log(person.brother.fn()); //mgd

  so far, the context object call of this has been introduced more clearly. Look at another high-level topic:

  const o1 = {
    text: 'o1',
    fn: function () {
      return this.text;
    }
  } 
  const o2 = {
    text: 'o2',
    fn: function () {
      return o1.fn();
    }
  }

  const o3 = {
    text: 'o3',
    fn: function () { 
      var fn = o1.fn;
      return fn();
     }
  }

  console.log(o1.fn());
  console.log(o2.fn());
  console.log(o3.fn());

  the answer is o1, o1, undefined. Are you right? Let's analyze:

  • The output o1 of the first console is very simple. The difficulty lies in the second and third consoles. The key depends on the function that calls this
  • O2 in the second console FN () finally calls o1 FN (), so the result is o1
  • O3 in the third console FN () through VAR FN = O1 fn; The assignment of is called "streaking". Here this points to window, and the running result is undefined

  if you can already answer these questions during the interview, the interviewer may ask: what do you want the second console to do?
  if you answer bind, call and apply to intervene in this, the interviewer will then ask you if you don't use these methods?
  the answer is yes. This question examines the depth of basic knowledge and programming thinking. The methods are as follows:

  const o1 = {
    text: 'o1',
    fn: function () {
      return this.text;
    }
  } 
  const o2 = {
    text: 'o2',
    fn: o1.fn
  }
  console.log(o2.fn()) // o2

The above method also uses that important conclusion, and this points to the object that finally calls it. In the above code, we perform the assignment operation in advance, mount the function fn on the o2 object, and FN is finally called as the method of the o2 object.

Change the direction of this through bind, call and apply

  the common inspection points related to it are: the difference between call/bind/apply
   such questions are relatively basic and directly answered: they are used to change the direction of the related function this, but call and apply call the related functions directly; bind does not execute related functions, but returns a new function. This new function has been automatically bound with a new this point, which can be called manually. To be more specific, the difference between call and apply is mainly reflected in parameter setting. Please read the fourth edition of Hongbao book for details.
  let's take a look at an example and analyze it:

  const foo = {
    name: 'mgd',
    logName: function () {
      console.log(this.name);
    }
  }

  const bar = {
    name: 'lpb'
  }

  foo.logName.call(bar);

  the execution result of the above code is lpb, which is not difficult to understand. However, the advanced investigation of call/bind/apply often requires the interviewer to combine constructors and combinations to realize inheritance.

Constructor and this

   start with the code and think with questions:

  function Foo () {
    this.bar = 'mgd';
  }
  const instance = new Foo();
  console.log(instance.bar); //mgd

   such scenarios are often accompanied by a question: what does the new operator do when calling the constructor? The following simple answers are for reference only

  • Create an object
  • Point this of the constructor to the new object
  • Add properties, methods, and so on to this object.
  • Finally, a new object is returned.

The above process is expressed in Code:

  var obj = {};
  obj.__proto__ = Foo.prototype;
  Foo.call(obj);

   it should be pointed out that if there is an explicit return in the constructor, it should be noted that it can be divided into two scenarios:

  1. Execute the following code to output undefined. At this time, instance returns an empty object o
  function Foo () {
    this.user = 'mgd';
    const o = {};
    return o;
  }
  const instance = new Foo();
  console.log(instance.user); //undefined
  1. Execute the following code to output mgd, that is, inance returns the target object instance at this time:
  function Foo () {
    this.user = 'mgd';
    return 1;
  }
  const instance = new Foo();
  console.log(instance.user); //mgd

   therefore, if the constructor explicitly returns a value and returns an object (complex type), this points to the returned object; If an object (basic type) is not returned, this points to the instance.

this in arrow function

  in the arrow function, the direction of this is determined by the outer (function or global) scope.
   in the following code, this appears in the anonymous function in setTimeout, so this points to the window object:

  const foo = {
    fn: function () {
      setTimeout(function() {
        console.log(this); 
      });
    } 
  }
  foo.fn() //Window 

   if you need this to point to this object, you can use the arrow function. The code is as follows:

  const foo = {
    fn: function () {
      setTimeout(() => {
        console.log(this); 
      });
    } 
  }
  foo.fn() //{fn: ƒ}

  & emsp: the problem of this pointing in a simple arrow function is very simple, but if we integrate the left and right situations and investigate it in combination with the priority of this, the this pointing is not easy to determine at this time

this priority

   generally, the binding of call, bind, apply and new to this is called explicit binding, and the determination of this point according to the call relationship is called implicit binding.
  so who has the higher priority of explicit binding and implicit binding? Here's the announcement:
  execute the following code:

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

  const obj1 = {
    a: 1,
    foo: foo
  }

  const obj2 = {
    a: 2,
    foo: foo
  }

  obj1.foo.call(obj2);
  obj2.foo.call(obj1);

   the output is 2 and 1 respectively, that is, the explicit binding of call and apply generally has higher priority. Look at the following code:

  function foo (a) {
    this.a = a;
  }

  const obj1 = {};

  var bar = foo.bind(obj1);
  bar(2);
  console.log(obj1.a);

   the above code binds this in the bar function as obj1 object by binding bind. After executing bar(2), obj 1 A value is 1, that is, after bar(2) is executed, obj1 object is {a:2}
   when bar is used as the constructor again, for example, the following code will output 3:

  var baz = new bar(3);
  console.log(baz.a);

   the bind function itself is a function constructed through the bind method. It has internally bound this to obj1. When it is called again as a constructor through new, the returned instance has been unbound from obj1. In other words, the new binding modifies the this point in the bind binding, so the priority of the new binding is higher than that of the explicit bind binding.
  let's look at another code:

  function foo () {
    return a => {
      console.log(this.a);
    }
  }

  const obj1 = {
    a: 2
  }

  const obj2 = {
    a: 3
  }

  const bar = foo.call(obj1);
  bar.call(obj2);

  the output result is 2 Since this in foo is bound to obj1, this in bar (reference arrow function) will also be bound to obj1. The binding of arrow function cannot be modified.
   if foo is written in the form of the following arrow function, 123 will be output:

  var a = 123;
  const foo = () => a => {
    console.log(this.a);
  }
  const obj1 = {
    a: 2
  }
  const obj2 = {
    a: 3
  }
  var bar = foo.call(obj1);
  bar.call(obj2); //123

  modify the declaration of the first variable a in the above code, that is, change it into this one. Guess what the result is?

const a = 123;

  the answer is undefined because the variable declared by const will not be attached to the window global object. Therefore, when this points to window, the a variable cannot be found naturally.

Open example analysis

Implement a bind function:

  Function.prototype.bind = Function.prototype.bind || function (context) {
    let that = this;
    let args = Array.prototype.slice.call(arguments, 1);
    return function bound () {
      let innerArgs = Array.prototype.slice.call(arguments);
      let finalArgs = args.concat(innerArgs);
      return that.apply(context, finalArgs);
    }
  }

  this practice is already very good. However, as the rule shown in this priority analysis before: if the function returned by bind appears as a constructor with the new keyword, the bound this will be ignored.
  in order to implement such rules, developers need to consider how to distinguish the two invocation methods. Specifically, this instanceof should be judged in the bound function.
   another detail is that the function has a length attribute, which is used to represent the number of formal parameters. In the above implementation, the number of formal parameters will obviously be distorted. Therefore, the improved implementation needs to restore the length attribute. However, the difficulty is that the value of the length attribute of the function is not rewritable.

summary

  we can see that the usage of this is numerous and diverse, and the trend is not easy to grasp completely. We need to continue to digest and absorb it in addition to reading. Only by "memorizing" can we "use" it flexibly. An excellent front-end engineer lies not only in the accuracy of answering interview questions, but also in how to think and solve problems. If you don't understand this point, practice it; If you don't understand the principle, turn out the specification and have a look. I hope to master this completely in the near future.

Topics: Front-end Interview