preface

Think about moving bricks these years, step on the pit most is this, with an old saying to describe it is:

Poetry is like a dragon, whose head is not seen but its tail, or a claw and a scale are exposed in the clouds, and all are safe? — Talk about Dragon By Zhao Zhixin in Qing Dynasty

Every time I encounter this, I am in a fog and don’t know where it is pointing. So who does this point to? This reminds me of the popular saying, “This refers to whoever calls it.” There’s nothing wrong with that, but it’s not the whole story. To fully understand this, we need to return to one of the most basic concepts in Javascript: the execution context.

Execution context

define

When JavaScript code is running, explaining global code execution, calling functions, and so on creates and enters a new execution environment, which is called the execution context.

Context and scope

Context, environment is sometimes called scope, that is, the two concepts are sometimes used interchangeably; Context, however, refers to the overall environment, and scope is concerned with the accessibility (visibility) of identifiers (variables). The context is determined, and the scope is determined according to the scoping rules of the specific programming language. This is how context relates to scope.

The execution of JavaScript

The whole execution process of JavaScript code is divided into two stages, code compilation stage and code execution stage:

  1. Precompilation stage: The stage where the compiler compiles Javascript into executable code. Variable declarations are made and promoted, but with a value of undefined, as well as all non-expression function declarations
  2. Code execution phase: The main task of the execution phase is to execute the code logic, and the execution context is completely created in this phase.

foo(10)
function foo (num) {
  console.log(foo) // undefined
  foo = num
  console.log(foo) / / 10
  var foo
}
console.log(foo) // function foo() {... }
foo = 1
console.log(foo) / / 1
Copy the code

The execution process of the above code is analyzed as follows:

Global scope:

  1. Precompilation stage: Declare the function foo
  2. Execution phase 1: Call foo(10) and enter function fooLocal scope
    • Precompile phase: declare local variable foo (var foo)
    • Execution phase 1: The first console, foo, declares the variable promoted and outputs undefined
    • Execution stage 2: Assign num(10) to foo
    • Execution stage 3: Second console, output 10
  3. Execution stage 2: The third console outputs the function foo
  4. Phase 3: The global variable foo is assigned a value of 1, so the fourth console, outputs 1

From the process analysis above, it can be concluded that scopes are determined during the precompilation phase, but the scope chain is fully generated during the creation phase of the execution context, since the corresponding execution context is not created until the function is called.

structure

The execution context can be understood as an abstract object, including a variable object, a scope chain, and a this pointer, as described below:

parameter value describe
Variable object: Variable object {vars, function declartions, arguments… } Used to store variable and function declarations defined in the execution context
Scope chain: Scope chain [variable object + all parent scope] A list of objects to retrieve identifiers that appear in context code
ThisValue: context object context object A special object associated with the execution context, also known as executionContext object

Execution context and scope chain complement each other, but are not concepts. Intuitively, execution contexts contain scope chains, but they are also like the upstream and downstream of a river: the scope chain is part of the execution context.

The entire process of code execution is said to be like an assembly line. The first step is to create the variable object during the precompilation phase, where it is just created, not assigned. In the code execution stage of the next procedure, the Variable Object will be turned into the Active Object, that is, the transformation from Variable Object (VO for short) to Active Object (AO for short) will be completed. At this point, the scope chain will also be determined, which consists of the variable object of the current execution environment + all the outer objects that have been activated. This process ensures orderly access to variables and functions, meaning that if the variable is not found in the current scope, it continues to look up to the global scope.

As a whole, this process is the basis of JavaScript engine execution.

The call stack

The function of the code, call the function or functions (such as recursion), involve in an execution context to create another new execution context, complete, and wait for the new context will return before the execution context and then continue to execute, and it has formed the call stack call way.

The code is as follows:

function foo1 () {
  foo2()
}
function foo2 () {
  foo3()
}
function foo3 () {
  foo4()
}
function foo4 () {
  console.log('foo4')
}
foo1()
Copy the code

The call relationship in the above code is foo1 → foo2 → foo3 → foo4. Foo1 is pushed first, then foo2 is called, then foo2 is pushed, and so on until foo4 is finished, then foo4 is pushed, then foo3 is pushed, then foo2 is pushed, and finally foo1 is pushed. This process satisfies the first in, last out (last in, first out) rule, thus forming the call stack.

Note that normally, when a function completes and exits the stack, the local variables in the function are collected at the next garbage collection (GC) node, and the corresponding execution context of the function is destroyed, which is why variables defined in the function cannot be accessed from the outside world. That is, the variable is only accessible to related functions when the function executes, it is created during precompilation, it is activated during execution, and its context is destroyed after the function completes execution.

this

Before analyzing this, the definition of this (MDN) is scripted: the value of this is an attribute of the current execution context (global, function, or eval). In non-strict mode, this always refers to an object. In strict mode, it can be any value.

So, what is the value of this? The value of this varies depending on how the function is used. In short, this is the context object in which the function is run.

Knowing these “natural features” can help you avoid writing bad code and make your code more readable. Now let’s understand the true meaning of this from some real scenes.

1. This in the global environment

  • Scenario 1: The function is in global scope
function f1 () {
  console.log(this)}function f2 () {
  'use strict'
  console.log(this)
}

f1() // window
f2() // undefined
Copy the code
  • Scenario 2: Functions as methods of objects.
const foo = {
  bar: 10.fn: function () {
    console.log(this)
    console.log(this.bar)
  }
}
Copy the code

If you assign a function in an object to a global variable, this still refers to the window. In the following code, the fn function is used as the object’s method in foo, but after being assigned to fn1, fn1 is still executed in the Window global.

var fn1 = foo.fn
fn1()

// window => console.log(window)
// undefined => console.log(window.bar)
Copy the code

If this of a function is called by a higher-level object, then this executes the higher-level object. Otherwise point to the global environment.

foo.fn()

// {bar: 10, fn: ƒ}
/ / 10
Copy the code

2. The context object calls this

When there are complex invocation relationships, such as the nested relationships in the following code, this executes the object it was last called on

const foo = {
  bar: 10.self: function () {
    return this
  },
  obj: {
    name: 'sunshine'.fn: function() {
			console.log(this.name)
		}
	}
}

foo.obj.fn()
console.log(foo.self() === foo)

// sunshine
// true
Copy the code

Here is a set of complex scenarios:

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()) // o1
console.log(o2.fn()) // o1
console.log(o3.fn()) // undefined
Copy the code
  1. Because o1 calls fn, so this in o1.fn equals o1, the output is o1
  2. Because fn in O2 returns the result of the o1.fn() call, the output is also o1
  3. O1. fn in O3 is assigned to var. Var is in the global environment when o3 is called. So this refers to the window, and the result is undefined

If you want the output of O2.fn () to be O2, you can make the following changes without using methods like bind:

const o2 = {
  text: 'o2'.fn: o1.fn
}

console.log(o2.fn()) // o2
Copy the code

If o1.fn itself is assigned to o2.fn, then when O2 is called, this points to O2 and the output is o2

3. This in the constructor

  • Scenario 1: Execute the following code to output undefined, in which case instance returns the empty object obj
function Foo () {
  this.user = 'sunshine'
  const obj = {}
  return obj
}

const instance = new Foo()
console.log(instance.user) // undefined
Copy the code

Conclusion: If a constructor explicitly returns a value that is an object (complex data type), then this refers to the returned object.

  • Scenario 2: Run the following code to output sunshine, in which case instance returns the target object instance this
function Foo () {
  this.user = 'sunshine'
  return 1
}

const instance = new Foo()
console.log(instance.user) // sunshine
Copy the code

Conclusion: If the constructor explicitly returns a value that is not an object (primitive data type), then this still points to the instance.

So what exactly does the new operator do when it calls the constructor? The following is a simple simulation, more complex implementation can refer to the prototype, prototype chain related knowledge:

function newFoo () {
  // 1. Create an empty object
  var obj = {}
  // 2. Point the constructor's this to the new object
  obj.__proto__ = Foo.prototype
  // 3. Add attributes, methods, etc for this new object
  Foo.call(obj)
  // 4. Return a new object
  return obj
}
Copy the code

4. This in the arrow function

In arrow functions, the this pointer is determined by its function or global scope. this: the enclosing(function or global) scope

The setTimeout method is used to compare the difference between this and arrow functions:

  • This, in an anonymous function, refers to the window object
const foo = {
  fn: function () {
    setTimeout(function () {
      console.log(this)},200)
  }
}
foo.fn() // window
Copy the code
  • This is in the arrow function, pointing to fn itself
const foo = {
  fn: function () {
    setTimeout((params) = > {
      console.log(this)},200)
  }
}
foo.fn() / / {fn: ƒ}
Copy the code

5. Change the this pointer

If you want to forcibly redirect this, you can use the call, apply, and bind methods provided by function. prototype

Call and apply are used to change the direction of the function this, while call and apply execute the function directly. Bind does not execute the related function, but returns a new function that is automatically bound to the new this pointer and can be called manually by the developer; The main difference between Call and apply is in parameter Settings.

The following three pieces of code are equivalent:

const foo = {
  name: 'sunshine'.logName: function (age, weight) {
    console.log(this.name, age, weight)
  }
}

const bar = {
  name: 'colorful'
}

foo.logName.call(bar, 32.70)
foo.logName.apply(bar, [32.70])
foo.logName.bind(bar, 32.70) ()// colorful 32 70
Copy the code

In this example, we use the call and apply methods to make foo.logName’s this point to a bar object, so the output is colorful 32 70. Advanced uses of call,apply, and bind require a combination of constructors and combinations to implement inheritance.

6. This priority

We usually refer to the case where this is bound by call/apply/bind/new as explicit binding, and the case where this is referred to based on the call relationship as implicit binding. Here are a few things about their priorities:

Explicit/implicit binding

  • Scenario 1: This in a normal function, priority: explicit binding > Implicit binding > Default binding
function foo () {
  console.log(this.a)
}

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

const obj2 = {
  a: 2.foo: foo
}
// Default binding
obj2.foo() / / 2

// Show the binding
foo.call(obj2) / / 2

// Implicit binding
obj2.foo.call(obj1) // 1 => obj2.foo === foo
Copy the code

From the code above, this always refers to the object bound by call, regardless of who called foo

The new binding

  • Scenario 2: The new binding has a higher priority than the explicit binding
function foo (a) {
  this.a = a
}

const obj1 = {}
var bar = foo.bind(obj1) // The bar function this points to obj1
bar(2) // obj = { a: 2 }
console.log(obj1.a) / / 2

var baz = new bar(3) // bar and obj1 are unbound
console.log(baz.a)
Copy the code

In the above code, bind binds this in foo to obj1 and assigns it to bar. After bar executes, obj1 is {a:2}.

The bar function itself is a function constructed through the bind method. Internally, this is already bound to obj1, and when it is called again as a constructor through new, the returned instance is already unbound from obj1. That is, the new binding modifies the this pointer in the bind binding, so that the new binding takes precedence over the explicit bind binding.

Arrow function binding

In short, the arrow function abandons all the rules of the normal this binding and instead overrides the original value of this with the current lexical (static) scope. The binding of arrow functions cannot be modified (nor can new), and arrow functions are most commonly used in callback functions such as event handlers or timers.

  • Scenario 3: The arrow function’s this pointing binding cannot be modified, the first call generated the closure, and the memory was not garbage collected, so the pointing remains the same
function foo () {
  return a= > {
    console.log(this.a)
  }
}

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

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

foo.call(obj1)() / / 1
foo.call(obj2)() / / 1
Copy the code
  • Scenario 4: If the arrow function returns the arrow function, this points to the window. Note: If we change var to const, we print undefined, because const objects are not hung on the window object
var a = 123 // const a = 123
const foo = () = > a= >{
  console.log(this.a)
}

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

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

foo.call(obj1)() / / 123
foo.call(obj2)() / / 123
Copy the code

In summary, the priority of this binding is as follows: arrow function > New binding > Display binding > Implicit binding > Default binding

The arrow function has the highest precedence and ignores other binding rules. The default binding has the lowest priority. The default binding is used only when no other binding is used.

summary

About the use of this, summarize the following rules, I hope you remember!

  1. When a function is simply called in the function body, not explicitly or implicitly, in strict mode, the this inside the function is bound toundefinedIs bound to global objects in non-strict modewindow/globalOn.
  2. When a function is called from a context object, the this inside the function is bound to that object
  3. General usenewWhen the method calls the constructor, the this inside the constructor is bound to the newly created object
  4. In the arrow function,thisThe pointing of is determined by the outer (function or global) scope
  5. Usually bycall/apply/bindOf the constructor when the method explicitly calls the constructorthisWill be bound to the newly created object

The interview questions

Q1: The function this in the object points to

var fullName = 'language'
var obj = {
  fullName: 'javascript'.prop: {
    getFullName: function () {
      return this.fullName
    }
  }
}
console.log(obj.prop.getFullName())
var test = obj.prop.getFullName
console.log(test())
Copy the code

Answer: javascript language, getFullName() is called by obj.prop when the method executes, so this is obj.prop, and outputs javascript because this executes the object that last called it; Test () is called by window in the global scope and outputs window.fullname (language);

Q2: Comprehensive case

var num = 10
var obj = { num: 20 }
obj.fn = (function (num) {
  this.num = num * 3
  num++
  return function (n) {
    this.num += n
    num++
    console.log(num)
  }
})(obj.num)
var fn = obj.fn
fn(5)
obj.fn(10)
console.log(num, obj.num)
Copy the code

Answer: 22, 23, 65, 30

Global scope:

  1. Create num and obj with initial values of 10 and {num: 20}, respectively.

  2. Execution phase 1: obj.fn assigns a self-executing function and enters the self-executing function

    Self-executing function scope

    • Create parameter num with initial value obj.num (20)

    • Execution stage 1: Global variable num = 20*3 = 60

    • Execution stage 2: The parameter num increases by 1 to 21.

      The value of obj.num is 20, and the global variable num is 60

  3. Execution phase 2: Assign obj.fn to fn

  4. Execution phase 3: Fn (5) is executed, which triggers the execution of the callback function inside the self-executing function scope. Note: Since fn is called globally, this inside the callback still points to window

    The callback function scope

    • Precompile stage: create parameter n with an initial value of 5
    • Execution stage 1: Global variable num = 60 + 5 = 65.
    • Execution phase 2: The parameter num increases by 1 again, resulting in 22
    • Stage 3: Console output parameter num, 22
  5. Execution phase 4: The call to obj.fn(10) again triggers the execution of the callback function inside the self-executing function scope. Note: Since obj calls fn, this inside the callback refers to obj.

    The callback function scope

    • Precompilation stage: create parameter n with an initial value of 10
    • Run stage 1: obj.num = 20 + 10 = 30.
    • Execution stage 2: The parameter num increases by 1 again, resulting in 23
    • Stage 3: Console output parameter num, 23
  6. Phase 5: Console outputs global variables num 65 and obj.num 30

reference

  • Dig deeper into JS: Execute this in context

  • What are scopes and execution contexts

  • This in JavaScript refers to and error-prone front end questions

  • Book: Advanced Core Knowledge of Front-end Development, “Catch all this, Say Yes to Execution Context”

The last

If there are any mistakes in this article, please correct them in the comments section. If this article has helped you, please like it and follow it.

Want to read more quality articles, but my wechat public number [Yang Jie speak front end], push high-quality articles every day, we grow together.