Learning JS has also had a period of time, but often mentioned execution context, scope, closure, variable promotion, this and other keywords always have a vague concept in mind, as if to know and as if not to know, so I want to discuss these concepts with you systematically. I hope I can help the students who are also distressed by these familiar and strange words like me!

1. The data type of js

I’m not going to start with those concepts here. Every language has built-in data types, and different ways of building them mean different ways of using them. But from the JS data type start step by step analysis, can let you know the above several concepts.

1.1 JS is a weakly typed, dynamic language

  • Static and dynamic languages
    • Static languages: Those that require validation of their variable data types before use are called static languages. Static languages like C, C++, Java, etc.
    • Dynamic languages: Languages that need to check data types during runtime, such as JS, PY, etc.
  • Weakly typed and strongly typed languages
    • Weakly typed languages: languages that support implicit type conversions, such as C and JS, are weakly typed
    • Strongly typed languages: Languages that do not support implicit type conversions, such as Python and Java, are strongly typed

If you pay close attention to a detail of JS language, you will find that JS is a weakly typed dynamic language.

The js code automatically evaluates an expression to a Boolean value in an if statement, and the definition of a variable declared in JS does not need to be a string, number, or Boolean, which means you can store different types of data in a single variable. It is worth mentioning that this type of dynamic language will bring great convenience at the same time will also bring some troubling problems, in vue this excellent framework using Flow to do static type language check on JS.

1.2 Basic types and reference types

Now that we know that JS is a weakly typed, dynamic language, let’s look at data types in JS

In JS, data types are divided into basic types and reference types:

(1) Basic types:

  • null
  • undefined
  • boolean
  • number
  • string
  • Symbol (ES6 introduced)

(2) Js reference types are subtypes of object, including the following:

  • Object
  • Function
  • Array
  • RegExp
  • Date
  • Wrapper classes: String, Number, Boolean
  • Math

2. Js memory model

Js does not operate on different types of data in the same way. To understand the differences, you need to understand the STORAGE model in JS. (From the Geek)

In the process of JS execution, there are three main types of memory space, respectively: code space, stack space, heap space

2.1 Stack space and heap space

Data types of primitive types are stored in stack space, and values of reference types are stored in the heap.

// Define four variables
var num1 = 5
var num2 = num1;
var obj1 = {
    name: 'Piggy Piggy'
}
var obj2 = obj1

// Modify num1 and obj1
num1 = 4
obj1.name = 'pig'

// Output four variables
console.log(num1) / / 4
console.log(num2) / / 5
console.log(obj1.name) / / the pig
console.log(obj2.name) / / the pig
Copy the code

The output of num1 and num2 above is well understood because in JS primitive data types are stored in stack space. If a variable assigns a value of the primitive type to another variable, a new value is created on the variable object and then copied to the location allocated for the new variable.

So why is the name output of obj1 and obj2 changed? This is because the values referenced to types in JS are stored in the heap. If a variable to another variable assignment the value of a reference type, also in the variable object to create a new value, and then copy the value to the new variable for the location of the distribution, but unlike the base type, the value is a pointer, the pointer points to the same object in the heap, so any one object is modified in is to modify the same object.

Now that you have an understanding of stacks and heaps, let’s take a look at the way arguments are passed in JS. In JS, all function parameters are passed by value, which means that values outside the function are copied to be used inside the function, just as values are copied from one variable to another.

This means that the passing of primitive types and reference type values is the same as the copying process described above.

// Basic type pass
function addTen(num){
      num += 10
      return num
}
var count = 20
var result = addTen(count)
alert(count) / / 20
alert(result) / / 30

// Passing of reference types
function setName(obj) {
    obj.name = "Piggy Piggy"
}
var person = {}
setName(person)
console.log(person.name) // Piggy Piggy
Copy the code

There are two ways to access variables: by value and by reference. Why do you pass parameters by value?

The passing of the underlying type is easy to understand, but local changes to the passing of the reference type are reflected globally, and some students may mistakenly think that the passing of the reference type is passed as a parameter. But what really happens is this:

  • An object is created and stored in the Person variable
  • The setName function is called, and the person variable is passed into setName
  • The value of person is copied to OBj, which copies a pointer to an object in the heap
  • Modified the obj
  • And that’s shown in person

As you can see from the above process, the person variable is passed by value. Let’s look at another example to illustrate this problem

function setName(obj){
    obj.name = "Piggy Piggy"
    obj = new Object()
    obj.name = The God of three.
}
var person = {}
setName(person)
alert(person.name) // Piggy Piggy
Copy the code

If passed by reference, the display value should be “triple god”, but the js reference type is also passed by value, so the print is “piggy piggy stupid”.

3. The execution process of JS code

This is an example of how the implementation context, scope, closure, variable promotion, and this are not mentioned at all. You’ve seen it all! Don’t worry! The idea of this article is to start from the top down, start from the outermost familiar place, slowly penetrate to the bottom of each concept, each knowledge point together, forming a knowledge system.

showName() / / the pig
console.log(myName) // undefiend
var myName = "Piggy Piggy"
function showName() {
    console.log("Pig")}Copy the code

The result of the above code is no surprise to everyone. This is the well-known variable promotion, but what is happening inside of it that causes this result?

The explanation given in many places is that during the execution of JS code, the JS engine will promote the declaration of variables and functions to the beginning of the code. The promoted variable is set to its default value, which is undefined. This is true, but we need to take a closer look at what happens inside this so-called variable promotion.

Next, we’ll get to the focus of this article. The js code execution process is divided into two parts: compilation and execution.

  • Compilation: In the above explanation, the JS engine pushes the declaration of variables and functions to the beginning of the code, which is not accurate. In the second part of JS memory model, we can see that there are three types of memory space in the process of JS execution, namely: code space, stack space and heap space. In fact, the position of variable and function declarations in the code does not change, depending on the code you wrote in the first place. Then, after the compile phase, there are two parts: the execution context and the executable code. Variable and function declarations are put into the execution context by the JS engine.
  • Execution: Once everything is in place, the JS engine executes the executable code line by line

3.1 Execution Context

This brings us to our first point: What is the execution context?

Execution context creation falls into three categories:

  • Execute global code, compile global code, create global context, and only one
  • When a function is called, the code inside the function is compiled, creating a function context that is destroyed after the function is executed
  • Use the eval function, which is rarely encountered and will not be discussed here.

In JS, context is managed by the call stack, one of the three memory Spaces in JS execution. Let’s see how it works:

  1. Js compiles the global code, creates the global context and pushes it to the bottom of the stack
  2. The global code executes console.log, printing undefined
  3. Assign the myName variable to piggy Piggy
  4. The setName function is called, and JS compiles it to create the execution context of setName function
  5. When the setName function completes execution, the execution context of the setName function pops up the stack and is destroyed
  6. Global code execution is completed, the stack is popped, and the code is finished

So this is where we can answer our previous question. The so-called variable promotion is that during the execution of JS code, the code will be compiled first. During the compilation process, the declaration of variables and functions will be put into the call stack to form the context call stack, and the remaining ones will generate the execution code. This leads to the phenomenon of variable escalation.

By the way, the size of the call stack is limited. If more than a certain number of execution contexts are pushed, the JS engine will report an error. This phenomenon is called stack overflow.

function stackOverFlow (a, b) {
    return stackOverFlow (a, b)
}
console.log(stackOverFlow(1.2))
Copy the code

Now that you’ve seen this, you understand what execution context is and what variable promotion is. Is it easy? I’m going to take you through the rest of the concepts, and with that foundation, the rest of the content will be easier to understand.

4. Scope and scope chain

We’ve already seen that variable promotion is a major design flaw of JavaScript, which leads to a lot of unintuitive code due to the variable promotion feature of JS.

var name = "Piggy Piggy"
function showName(){
    console.log(name);
    if (0) {
        var name = "Pig"
    }
    console.log(name)
}
showName()
// undefined
// undefiend
Copy the code

ShowName () will be given a context in which the name inside the function will be put into the variable environment and given the value of undefined. The second print is undefined because the statement inside the if statement was not executed.

(1) Scope

Js has three scopes. Before ES6 there were only two:

  • Global scope
  • Function scope
  • Block-level scopes (new in ES6)

(2) Scope chain

It’s easy to think that this code will print “Peepy Piggy”, which has to do with another concept we’ll cover next: scope chains

function bar() {
    console.log(name)
}

function foo() {
    var name = "Piggy Piggy"
    bar()
}

var name = "Pig"

foo() / / the pig
Copy the code

We will look at the scope chain in conjunction with the execution context:

  • The variable environment of each execution context contains an external reference to the external execution context, which we call outer.
  • When a piece of code uses a variable, the JS engine looks for it in the current execution context, and if it doesn’t find it, it continues to look for it in the execution context of outer’s execution. Such level by level lookups form a scope chain.

  • The generation of the scope chain is determined by the code, regardless of the call. So when bar is compiled, outer points to the global context, so instead of printing piggy Inside foo()

(3) block-level scope

As mentioned above, ES5 previously had only global and function scopes. ES6 introduced block-level scopes to solve the problem of variable promotion. We are all familiar with this, but how can JS support both variable promotion and block-level scope?

We continue to address this issue from the perspective of the execution context

function foo() {
    var a = 1
    let b = 2
    {
        let b = 3
        var c = 4
        let d = 5
        console.log(a)
        console.log(b)
    }
    console.log(b)
    console.log(c)
    console.log(d)
}
foo()
Copy the code
  • The first step is to compile and create the execution context
    • All variables declared by var inside the function are stored in the variable environment at compile time.
    • Variables declared by let are deposited in the Lexical Environment at compile time.
    • Inside the function’s scoped block, variables declared by let are not stored in the lexical environment.

  • Execute to code block
    • The let declarations inside the code block are stored in a new area

  • Perform the console. The log (a)

  • When the scoped block completes execution, its internally defined variables are popped from the top of the lexical environment’s stack

The new scope chain created above is the implementation of JS support for both variable promotion and block-level scope.

A common question: How do I solve the following loop output problem?

for(var i = 1; i <= 5; i ++){
  setTimeout(function timer(){
    console.log(i)
  }, 0)}Copy the code
  • Cause: setTimeout is a macro task. After the synchronization task is completed, I is 6, so five 6’s will be output
  • Workaround: Use let to form block-level scopes
for(let i = 1; i <= 5; i ++){
  setTimeout(function timer(){
    console.log(i)
  }, 0)}Copy the code

4.1 the closure

It’s easy to understand closures once you know the scope chain!

  • What is a closure?

ES5 exist in the two scopes: global scope, function, scope, function scope will be automatically destroyed at the end of the function scope chain: find a variable when starting from its own scope along the scope chain has been up for closure: use of scope, function can be within the scope of variables to access

(1) How the closure is generated:

  • Return function (common)
const a = 2
function out () {
  let a = 1
  return function b () {
    console.log(a)
  }
}
const b = out()
b() / / 1
Copy the code
  • Functions passed as arguments: Functions as arguments have access to the internal scope of the function body
var a = 1
function bar(fn) {
  var a = 2
  console.log(fn)
}

function baz() {
  console.log(a)
}

bar(baz) / / 1
Copy the code
  • In timers, event listeners, Ajax requests, cross-window communication, Web Workers, or any asynchrony, whenever you use a callback function, which in fact is the case above, you’re using a closure by treating the function as an argument.
/ / timer
setTimeout(function timeHandler(){
  console.log('111');
}, 100)

// Event listener
$('#app').click(function(){
  console.log('DOM Listener');
})
Copy the code
  • Execute the function immediately:
var a = 2;
(function IIFE(){
  2 / / output
  console.log(a); }) ();Copy the code

IIFE(execute function expressions immediately) creates closures that hold both the global scope window and the scope of the current function, so variables can be global.

for(var i = 1; i <= 5; i ++){
  (function(j){
      setTimeout(function timer(){
        console.log(j)
      }, 0)
  })(i)
}
Copy the code

(2) Application Scenarios:

  • Corey:

Add (1)(2)(3)(4) == 10

function add (. args) {
  return args.reduce((a, b) = > a + b)
}

function currying(fn) {
  let args = []
  return function _c (. newArgs) {
    if (newArgs.length) {
      args = [...args, ...newArgs]
      return _c
    } else {
      return fn.apply(this, args)
    }
  }
}

let addCurry = currying(add)
let total = addCurry(1) (2) (3.4) (5.6 ,7) ()console.log(total) / / 28
Copy the code

(3) Disadvantages: Global use of closures will cause memory leaks, so use them sparingly

5. this

In the previous section we saw that when bar is compiled outer points to the global context, so instead of printing piggy inside foo(), most people get this xor confused with the concept of scoped chains.

The truth is, the scope chain mechanism does not allow us to directly obtain the variables inside the object, but also set up a new mechanism, do not confuse the two!

var obj = {
    name: "Piggy Piggy".showName: function () {
        console.log(name)
    }
}

var name = "Pig"
obj.showName() / / the pig
Copy the code

The above is a classic interview question, the output is “piggy” rather than the internal “piggy Piggy”, with the previous understanding of context and scope chains, it can be easily explained, not repeated here.

Once again, there is no relationship between scope and this! This exists in the execution context alone, in parallel with the variable, lexical, and outer environments in the execution context.

So how do we use this? If you want the code above to print the internal name, you can use this to do so.

var obj = {
    name: "Piggy Piggy".showName: function () {
        console.log(this.name)
    }
}

var name = "Pig"
obj.showName() // Piggy Piggy
Copy the code

Next, make a summary of the direction of this:

  • Default binding: In the context of global execution, this refers to a global object. In browsers, this refers to the Window object.
  • Implicit binding: In the context of function execution, the value of this depends on how the function is called. If it is called by a reference object, this is set to that object, otherwise this is set to global or undefined (in strict mode).
  • Show bindings :apply, call, bind
  • The arrow function’s this is determined by the outer (function or global) scope
var person = {
    name: "Piggy Piggy".changeName: function () {
        setTimeout(function(){
            this.name = "Pig"
        }, 100)
    }
}

person.changeName()
Copy the code

The code above wants to change the name attribute inside person using the changeName method, but there are some problems with the code, so we’ll use the summary of this to solve the problem.

(1) Cache internal this

var person = {
    name: "Piggy Piggy".changeName: function () {
        var self = this
        setTimeout(function(){
            self.name = "Pig"
            console.log(person.name)
        }, 100)
    }
}

person.changeName() / / the pig
Copy the code

(2) Use call, apply, or bind to display bindings

var change = function () {
    this.name = "Pig"
}
var person = {
    name: "Piggy Piggy".changeName: function () {
        setTimeout(function(){
            change.call(person)
            console.log(person.name)
        }, 100)
    }
}

person.changeName() / / the pig
Copy the code

(3) Use the arrow function

var person = {
    name: "Piggy Piggy".changeName: function () {
        setTimeout(() = > {
            this.name = "Pig"
            console.log(person.name)
        }, 100)
    }
}

person.changeName() / / the pig
Copy the code

Ok, so far the article mentioned at the beginning of the words have been combed for everyone, if you feel good, to piggy skin stay a 👍!

reference

  • “Js advanced programming” the third edition
  • Geek’s column
  • Js you Don’t Know
  • Idol god Sanyuan’s blog