Chen Chen, a programmer of “Life is at rest”, is the front group of The First Profit Center of Wedoctor.

When you write JavaScript,

How often do you see this error * is not defined?

Does undefined always appear?

This is because the access to the variable is invalid or unavailable, and it is the scope of the variable that defines the code’s availability. So what is scope?

scope

A basic programming language is the ability to store a value in a variable and then access or modify that value. A scope is the accessible scope of a variable or function.

There are two main working models of scope, lexical (static) scope and dynamic scope:

  • Lexical scope: the scope is defined when it is defined, that is, when writing code, it is determined where the variable and block scopes are written.JavaScriptThe lexical scope is used.
  • Dynamic scope: Scope is determined at run time, for examplebash,Perl.

JavaScript has three types of scope:

  • Global scope: outermost layer of code.
  • Function scope: Creating a function creates a scope, whether you call it or not, as long as the function is created, it has its own scope.
  • Block-level scope for ES6:ES6The introduction of theletconstKey words and{}Combine so as to makeJavaScriptBlock-level scopes are available, as described below.

Global scope

The outermost layer is the global scope, which can be accessed anywhere in the script. Variables with global scope are also called “global variables.”

Let’s look at which variables have global scope:

  • In the browser, there is a global object called Window in the global scope that you can use directly.
// Get the height of the document display area of the window
window.innerHeight
Copy the code
  • Outermost functions and variables defined
var a = 1
console.log(window.a) // 1 -- the a declared by var becomes the global variable of the window

function func1 () {
    console.log('hello')
}
func1() // hello
window.func1() // hello
Copy the code
  • Variables assigned directly without keywords are automatically declared to have global scope, mounted onwindowOn the object.
b = 2 // Global variables
function func1 () {
    c = 2
}
func1()
console.log(window.b) / / 2
console.log(window.c) / / 2
Copy the code

Variables that omit keywords, no matter b outside the function or C inside the function, are global variables and are mounted on the window. However, such omitted keywords are not recommended because they are not standard and not good for maintenance.

Variable ascension

Reverse the a code above as follows:

console.log(window.a) / / output is undefined
var a = 1
Copy the code

In this case, a is accessible before the declaration, but the output is undefined, which is often referred to as “variable promotion”.

Variable promotion: Variables declared by the var keyword are considered to be declared at the top of the current scope (both in function and global scope), regardless of where they are actually declared.

Because the way the JS engine works is divided into two stages: compilation and execution:

  1. The code is parsed to get all declared variables;
  2. And then run it again.

So the following two pieces of code are equivalent:

console.log(a); / / output is undefined
var a =1;

/ / equivalent to the
var a;
console.log(a); / / output is undefined
a =1;
Copy the code
  • forvar a = 1, encountered by the compilervar aNew variables are declared in scopea.
  • The compiler then generates the code for the engine to run, processingconsole.log(a)anda = 1
  • Gets variables from the current collection of scopes when the engine runsa(at this point isundefined) and toa Assignment 1

Function scope

Function scope variables or internal functions, scope is function scope, external are closed, from the outer scope can not directly access the function scope, otherwise, the reference error exception will be reported. As follows:

function func1 () {
    var a = 1;
    return a
}
func1() // The inside of the 1 function is accessible
console.log(a) // Uncaught ReferenceError: a is not defined
Copy the code

Function declaration

In function declarations, the JS engine takes the function declaration before the code executes and generates the function definition in the execution context.

console.log(add(10.10)) // Return 20 normally
function add (a, b) {
    return a + b
}
Copy the code

The code works, and function declarations can be read and contextually added before any code is executed, that is, function declarations are promoted (just as variable declarations were promoted earlier).

Functional expression

The function expression must wait for the line of code to execute before generating the function definition in the execution context.

console.log(add(10.10)) // Uncaught TypeError: add is not a function
var add = function (a, b) {
    return a + b
}
Copy the code

Var add = function(){} In this case, add is a variable, so the declaration of the variable is also promoted to the top, while the assignment of the variable remains where it was, so the error is that the variable add is of the wrong type.

Function declarations and variable declarations

Function declaration promotion and variable declaration promotion have been mentioned before, and the phenomenon of using them. Here are some examples of both being used together:

test() // Execute function declaration
var test = function () {
    console.log('Execute function expression')}function test (a, b) {
    console.log('Execute function declaration')
}
test() // "Execute function expression"
Copy the code

The first test() prints “execute function declaration” and the second test() prints “execute function expression”, because it undergoes both function declaration promotion and variable declaration promotion (function promotion takes precedence over variable promotion). The code is equivalent to:

// Function declarations are promoted to the top
function test (a, b) {
    console.log('Execute function declaration')}// Variable promotion, variable promotion does not override function promotion, only if the variable is reassigned
var test

// Still in the same place
test() // Execute function declaration

test = function () {
    console.log('Execute function expression')
}
test() // "Execute function expression"
Copy the code

Block-level scope

ES6’s new let and const scopes are block-level scopes defined by the nearest pair of curly braces {}. Let is the following example:

{
  var a = 1
  let b = 2
}
console.log(a) / / 1
console.log(b) // Uncaught ReferenceError: b is not defined
Copy the code

Variables declared by let inside curly braces are not externally accessible, namely block-level scopes.

When a variable declared with the let keyword is pre-accessed:

{
    console.log(a) Uncaught ReferenceError: A is not defined
    let a = 1
}

Copy the code

The above error is caused by the “temporary dead zone” in let.

Temporary dead zone: a variable is not available until it is declared. Once it enters the current scope, the variable to be used already exists but cannot be retrieved until the line of code that declared the variable appears.

The above code can be equivalent to:

{
  //let a, where the temporary dead zone begins
  console.log(a) // Error because a = 2 is in temporary dead zone
  a = 1 // Where the temporary dead zone ends
}
Copy the code

const

Const and let are the same in that they have “temporary dead zones” with the following restrictions:

  • const When declaring a variable, it must be initialized to a value and cannot be reassigned.
<! --UncaughtSyntaxError: Missing initializer in const declaration -->
const a 
Copy the code
  • Assigned to an objectconstVariables cannot be assigned to any other reference value, but the key of the object is not restricted (Object.freeze() Objects can be frozen completely, and key-value pairs cannot be modified.
const a = 1
a = 1 // Uncaught TypeError: Assignment to constant variable.

/ / object
const obj = {
    a: 1.b: 2
}
obj.b = 3
console.log / / 3
Copy the code

Var, let, const

The difference var let const
scope Function scope Block scope Block scope
The statement The same scope can be declared more than once The same scope cannot be declared more than once The same scope cannot be declared more than once and must be assigned at the same time
features Variable promotion (global variables without var) Temporary dead zone Temporary dead zone

Common example: the for loop

for (var i = 0; i< 5; i++) {
  setTimeout(function() {
    console.log(i)
  })
}
// 5
 
for (let i = 0; i< 5; i++) {
  setTimeout(function() {
    console.log(i)
  })
}
// 0 1 2 3 4
Copy the code

Var: global variable. The iterated variable holds the value at the time the loop exits. All I’s are the same variable at the time the timeout callback is executed.

Let: Block scope, the JS engine declares a new variable for each iteration loop, and each timeout callback calls a different variable instance.

Const cannot change the value, so we cannot use the for loop i++.

// Iterate over the object key
for (const key in {a: 1.b: 1{})console.log(key) // a b
}

// Iterate over the numbers
for (const val of [1.2.3]) {
  console.log(val) / / 1 2 3
}
Copy the code

eval

Eval is generally not supported for use in code due to its poor performance, unsafe code, and confusing logic. However, it is important to understand eval.

This method is a complete ES interpreter that takes an argument, an ES (JavaScript) string to execute, parses the corresponding string into JavaScript code and runs it (parsing json strings into JSON objects). Simple uses of eval:

  • If the argument is a string expression, the expression is evaluated
  • If the argument is a string and represents one or moreJavaScriptStatement, then the statements will be executed
  • If the argument is not a string, the argument is returned unchanged
eval("2 + 2") / / output 4
eval("console.log('hi')") Hi / / output
eval(new String("2 + 2")) // String {'2 + 2'}
Copy the code
Eval’s impact on scope

Eval is called in JavaScript in two ways: directly and indirectly.

  • When called directly:evalThe scope of the inner code block is bound to the current scope and is used directlyeval().
function testEval () {
    eval('var a = 111')
    console.log(a) / / 111
}
testEval()
console.log(a)  / / an error
Copy the code

We can get a inside testEval, so eval modifies the testEval scope.

  • When invoked indirectly:evalThe scope of the inner code block is bound to the global scope, usingwindow.eval()(IE8 compatibility issues),window.execScript(IE8 and below supported), to resolve compatibility issues, you can also assign variables globally and then use them inside functions.
// There is an IE compatibility problem
function testEval () {
    window.eval('var a = 111')
    console.log(a) / / 111
}
testEval()
console.log(a)  // eval defines a variable bound to the global scope

// Resolve compatibility issues
var evalExp = eval
function testEval () {
    evalExp('var a = 111')
    console.log(a) / / 111
}
testEval()
console.log(a) // eval defines a variable bound to the global scope
Copy the code
Eval’s variable promotion problem

Any variables and functions defined by eval() are not promoted because they are contained in a string when parsing code. They are created only when eval() is executed.

Here are the different effects of let, var, and function, as follows:

/ / function
sayHi() // error: sayHi is not defined, no function is declared to promote

eval("function sayHi() { console.log('hi'); }"); 

sayHi() // hi


// var
msg // error: MSG is not defined

eval("var msg = 'hello world'") 

console.log(msg) // hello world


// let
eval("let msg = 'hello world'; console.log(msg)")  // // hello world

console.log(msg) // The error let scope can only be inside eval
Copy the code

The scope chain

Nesting of scopes occurs when a block or function is nested within another block or function. The query rules for nested scopes are as follows:

  • First, the JS engine looks for variables from the current execution scope.
  • Then, if it doesn’t find it, the engine continues the search in the outer nested scope.
  • Finally, until the variable is found or the outermost global scope is reached.

A list made up of multiple scopes is called a scope chain.

Such as:

var c = 1

function func () {
  var b = 2
  function add (a) {
      return a + b + c
  }
  return add
}
const addTest = func()
addTest(3) / / 6
Copy the code

The scope chain is:

When funcTest() is executed:

  1. To find theaddFunction scope to see if there area, gets the value 3 passed into the scope
  2. At this time to getaContinues the searchbFind the value ofaddFunction scope to see if there areb, there is no
  3. Find the upper scopefuncTo check whether there areb, gets the value 2 of the current scope
  4. At this point getbAfter the value ofcThe value of the inaddC is not found in the scope of the function
  5. Find the upper scopefunc, still can not query c
  6. Search for the global scope at the next level, query a, query to get the value of scope 1
  7. Return to 6

closure

If you’ve noticed, JavaScript scopes are defined at the time of definition, so you can’t access variables outside of the function you’re defining. In the nested scope example above, addTest can access variables inside func because of the closure.

A closure is a function defined inside a function, and its scope can be remembered and accessed, even if the function is executed outside the current lexical scope.

var c = 1

function func () {
  var b = 2
  function add (a) {
      return a + b + c
  }
  return add
}
const addTest = func()
addTest(3) / / 6
Copy the code
  • First of all, the functionadd()The scope of thefunc()The internal scope of
  • performfunc, the inner functionaddAssigns a reference to an external variableaddTestAt this time,addTestThe pointer is pointing to oradd
  • addStill holding on tofuncA reference to the scope, and this reference is called a closure
  • Execute externallyaddTestExternal executionaddThe closure allows access to the defined scope.

The original function func is not recycled when using closures and is included in the scope of add, so it takes up more memory than other functions and is prone to memory leaks

Use of closures

As you can see above, closures are everywhere in your code. Here’s how they work:

The callback

As in the let loop example above:

for (let i = 0; i< 5; i++) {
  setTimeout(function() {
    console.log(i)
  })
}
Copy the code

The setTimeout callback remembers the current lexical scope, and when the loop ends and the function executes, it can access the I of the current scope

modular

// Get positive and reverse permutations in the array
function arrOperate () {
  let errorMsg = 'Please pass in an array'
  / / positive sequence
  function getPositiveArr(arr) {
    if (Array.isArray(arr)) {
      return arr.sort((a, b) = > {
        return a - b
      })
    } else {
      throw errorMsg
    }
  }
  / / reverse
  function getBackArr(arr) {
    if (Array.isArray(arr)) {
      return arr.sort((a, b) = > {
          return b - a
      })
    } else {
      throw errorMsg
    }
  }
  return {
      getPositiveArr,
      getBackArr
  }
}
const arrObj = arrOperate()
arrObj.getPositiveArr([1.10.5.89.46]) // [1, 5, 10, 46, 89]
arrObj.getBackArr([1.10.5.89.46]) // [89, 46, 10, 5, 1]
arrObj.getPositiveArr(123) // Uncaught Please pass an array
Copy the code

This pattern is called a module in JavaScript, and arrOperate() returns an object containing a reference to the inner function, while the getPositiveArr() and getBackArr() functions have closures covering the inner scope of the module instance, Accessible errorMsg

conclusion

Scope determines the accessible scope of variables. Code can be seen everywhere. Understand scope, avoid using variables that cannot be accessed, and reduce errors at the beginning of the article.

The resources

  • JavaScript You Don’t Know
  • JaveScript Advanced Programming