🐬 🐬 and Star 🐳 🐳

We often say that everything has a value, and that is true, but think about it for a moment. Is it necessary to assume that everything has some value in some field?

For example, cars are absolutely valuable stuff, which brings great convenience to our daily travel and cargo transportation. Chopsticks, is also a very valuable stuff, it brings us a great convenience to eat. But can cars help us put food in our mouths? Can chopsticks carry us around?

So, some of the domains I mentioned above, can we call them scopes? I think so.

Having said that, I would like to ask: is scope a similar concept in JS?

First of all, I can say for sure that this is a very important concept in JavaScript, related to many of the core mechanics in JS, understand it, a lot of problems solved.

So, ask yourself, in JS, what is the scope?

I probably know what it is, but when I think about it, I can’t say it clearly.

It doesn’t matter, we will savor this interesting east east.

Throw the concept first:

A scope is responsible for collecting and maintaining a series of queries made up of all declared identifiers (variables) and enforcing a very strict set of rules that determine access to these identifiers by currently executing code.

In layman’s terms, a scope is equivalent to an administrator (with its own set of rules) who manages the ordered query of all declared identifiers.

Let’s tell a story about what the scope does.

The three brothers came together

Long long ago, I have 3 gay friends who are very close. The oldest one is called engine, the second one is called editor, and the third one is called scope. The three brothers looked old, but they still had little silver. Everyone was worried, so the three brothers plotted to do something together.

Job search process: This is roughly tens of thousands of words…

Ultimately, they are responsible for compiling and running JS.

Here’s what they do:

Their boss gives them the task of compiling and executing the following code:

var a = 1;
console.log( a );
Copy the code

Start work:

  • Compiler: scope, help me see if you have stored variable A.
  • Scope: brother, not yet.
  • Compiler: Ok, save one for me.
  • Engine: Third, do you have a variable called A?
  • Compiler: Big brother, really have, just two elder brother let me store one.
  • Engine: That’s great, get it out for me, what is its value, I need to copy it.
  • Compiler: Big brother, its value is 2.
  • Engine: Thank you, brother, so I can print its value.

That’s an unfortunate little story, but that’s about the relationship between the three.

Lexical scope VS dynamic scope

  • Lexical scope

As discussed in getting to the bottom of JavaScript scope, the first stage of work for most standard language compilers is called lexing (also known as lexicalization). Recall that the lexicalization process checks for characters in source code and, in the case of stateful parsing, assigns semantics to words.

In JS, the scope used is lexical scope.

Simply put, lexical scope is the scope of a definition at the lexical stage. In other words, the lexical scope is determined by where you write the variable and block scopes when you write the code, so the lexical analyzer keeps the scope the same when it processes the code (most of the time).

  • Dynamic scope

In JS, dynamic scope is closely related to the This mechanism. Its scope poem is determined during operation

var a = 1; function foo() { var a = 2; console.log( this.a ); } foo(); / / 1Copy the code

From the code above, we can see that the value of a printed in Foo is not determined by where the code is written, but by where foo is executed.

  • The difference between

    • Lexical scope is determined at code writing or definition time, whereas dynamic scope is determined at run time. (this is!
    • Lexical scopes focus on where functions are declared, while dynamic scopes focus on where functions are called from.

Function scope

In JS, the way to generate scope is:

  • function
  • With, eval (not recommended, performance affected)

From this, we know that the vast majority of scope in JS is based on function generation.

Each function generates a scoped bubble for itself. All identifiers in this bubble can be used in this bubble.

function bar() {
    var a = 1;

    function foo() {
        var b = 2;
        console.log(b);
    }

    foo();

    console.log(a);
}

bar();
Copy the code

In the above code, the bar bubble has identifiers A and foo, so a and foo can be accessed in the bar bubble. The foo bubble has an identifier b, so b is accessible in the bar bubble; There is also, of course, a global bubble that has the BAR identifier in it so that the bar can be accessed in the global bubble.

Minimum authorization principle

The principle of minimum authorization states that in software design, the necessary content should be exposed as little as possible and everything else should be “hidden”, such as the API design of a module or object.

This principle extends to how scopes are selected to contain variables and functions. If all variables and functions are in the global functional domain, of course they can be accessed in all internal nested scopes. But this breaks the principle of least privilege mentioned earlier, because too many variables or functions can be exposed that should be private and that proper code should be able to prevent access to.

Such as:

function doSomething(a) { b = a + doSomethingElse( a * 2 ); console.log( b * 3 ); } function doSomethingElse(a) { return a - 1; } var b; doSomething( 2 ); / / 15Copy the code

In this snippet, the variable B and the function doSomethingElse(..) Should be to doSomething (..) Internal concrete implementation of “private” content. Give an external scope to b and doSomethingElse(..) Not only are “access rights” unnecessary, but they can be “dangerous” because they can be used intentionally or unintentionally in unexpected ways, resulting in exceeding doSomething(..) Is applicable to. A more “sensible” design would hide these private details in doSomething(..) Inside,

Such as:

function doSomething(a) { function doSomethingElse(a) { return a - 1; } var b; b = a + doSomethingElse( a * 2 ); console.log( b * 3 ); } doSomething( 2 ); / / 15Copy the code

Now, b and doSomethingElse(..) Cannot be accessed from the outside, but can only be accessed by doSomething(..) The control of. Functionality and final results are not compromised, but the design privates the specifics that all well-designed software implements.

To avoid conflict

As we add more code to our programs, it is inevitable that variable conflicts will occur. Then how to avoid conflict becomes extra important.

Functions can be used to “hide” identifiers so that they cannot be accessed from the outside. This feature can be used to avoid conflicts.

function foo() {
    var a = 1;
}

function bar() {
    var a = 2;
}
Copy the code

The same variable a is defined in foo and bar, but does not affect each other. Because functions do a good job of “hiding” identifiers.

  • Global namespace

A typical example of variable conflict exists in a global scope. When a program loads multiple third-party libraries, it can easily cause conflicts if they don’t properly hide internally private functions or variables. These libraries usually declare a variable with a unique enough name in the global scope, usually an object. This object is used as the library’s namespace, and all functionality that needs to be exposed to the outside world becomes the property of the object (namespace), rather than exposing its identifier to the top-level lexical scope.

Such as:

var myLibrary = { name: 'echo', getName: function() { console.log( this.name ); }}Copy the code

Function declarations VS function expressions

Function declarations and function expressions are judged by whether the life of a function begins with the function keyword. Declarations that start with the keyword function are function declarations, and the rest are all function expressions.

Function foo() {} var foo = function () {}; (function() { })();Copy the code

Named functions VS anonymous functions

  • Named function A function that has a name

    function foo() {
    
    }
    
    var foo = function bar() {
    
    }
    
    setTimeout( function foo() {
    
    } )
    
    +function foo() {
    
    }();
    Copy the code

Note: Function declarations must be named functions.

  • Anonymous function A function without a name

    var foo = function () {
    
    }
    
    setTimeout( function foo() {
    
    } )
    
    -function foo() {
    
    }();
    Copy the code

Execute functions now (IIFE)

vara=2; (function foo() { var a=3; console.log( a ); / / 3}) (); console.log( a ); / / 2Copy the code

The function starts with (), not with the keyword function, so IIFE is a function expression

Function names are of course not required for IIFE, and the most common use of IIFE is to use an anonymous function expression. Although IIFE with appliance name functions is not common, it has the following advantages:

  1. Anonymous functions do not show meaningful function names in the stack trace, making debugging difficult.
  2. Without a function name, the expired arguments.callee reference can only be used when the function needs to reference itself, such as in recursion. Another example of a function needing to reference itself is when the event listener needs to unbind itself after the event is fired.
  3. Anonymous functions omit function names that are important to code readability/understandability. A descriptive name lets the code speak for itself.

So IIFE for named functions is also a practice worth popularizing.

  • Another way of expressing it
(function() {

}())
Copy the code

This is also an expression of IIFE, which is functionally consistent with the above. Which one to choose depends on personal preference.

  • Parameter passing

IIFE can also implement parameter passing just like any other form of function (again: parameter passing by value).

(function foo(a) { console.log(a); }) (3);Copy the code

Another application of this pattern is to resolve exceptions (although not common) caused by error overwriting the default value of undefined identifier. Call a parameter undefined, but pass no value at the corresponding position, thus ensuring that the value of the undefined identifier in the code block really is undefined:

undefined = true; // Create a big hole for other code! Don't ever do that! (function IIFE( undefined ) { var a; if (a === undefined) { console.log( "Undefined is safe here!" ); }}) ();Copy the code
  • UMD (Universal Module Definition)

Another variation of IIFE is to invert the running order of the code, placing the function that needs to be run in the second place and passing it in as an argument after IIFE execution. Although this pattern is a bit verbose, some people think it’s easier to understand.

var a=2; (function IIFE(def) {def(window); })(function def(global) {var a= 1; console.log( a ); // 3 console.log( global.a ); / / 2});Copy the code

Block scope

Although function scopes are the most common unit of scope, and certainly the most common design approach in most JavaScript today, other types of scope units exist, and even better and cleaner code can be maintained by implementing them.

  • try… Catch Very few people will notice that the ES3 specification of JavaScript states that the catch clause of try/catch creates a block scope, and that the parameter variable of a catch is valid only inside the catch.
try{
    throw undefined;
}catch(a){
    a = 2;
    console.log(a); // 2
}
console.log(a);  // ReferenceError
Copy the code
  • let

The ES6 standard makes it easy to create block scopes, one of which is defined by the let keyword definition.

Variables defined by let have the following characteristics:

  1. Let invisible create block scope ({… })
  2. Variables declared by lets cannot be promoted, so they must be defined first and used later
{
    let a = 1;
    console.log(a); // 1
}
console.log(a);  // ReferenceError
Copy the code

A typical use of let is in a for loop

Let’s look at the following two examples:

For (var I = 0; i < 5 ; i++ ) { setTimeout(() => { console.log( i ); }, I *1000)} for(let I = 0; i < 5 ; i++ ) { setTimeout(() => { console.log( i ); }, i *1000) }Copy the code

The reason for this is that the LET forms five block scopes so that each output variable is fetched from the block scope of the loop.

Of course there are other ways we can achieve the second effect, which we’ll talk about in closures that are really beautiful.

  • const

In addition to lets, ES6 introduces const, which can also be used to create block-scoped variables, but whose value is fixed (constant). Any subsequent attempts to modify the value will cause an error.

var foo = true; if (foo) { var a=2; const b = 3; // block scope constant a=3 contained in if; / / normal! b=4; / / error! } console.log( a ); // 3 console.log( b ); // ReferenceError!Copy the code

The scope chain

The scope chain is composed of the current scope and a series of parent scopes. The head of the scope is always the current scope and the tail is always the global scope. Scope chains guarantee ordered access by the current context to variables to which it is entitled.

var a = 2; function bar() { function foo() { console.log(a); } } bar(); / / 2Copy the code

The above code is made up of three scoped bubbles. The foo bubble attempts to print variable A. The engine cannot find variable A in foo, so it looks for its parent bubble bar… And so on until you find the global scope bubble, find the variable A, and print out its value. If not found, ReferenceError is reported.