A scope is a set of rules for determining where and how to look for variables (identifiers). The use of scopes improves the locality of program logic, enhances program reliability, and reduces name conflicts.

Lexical scope

Consider the following code:

   function foo(a) {

var b = a * 2;

function bar(c) {

console.log( a, b, c );

}

bar( b * 3 );

}

foo( 2 ); / / 2, 4, 12

Copy the code










  1. Contains the entire global scope with only one identifier: foo.
  2. Contains the scope created by Foo with three identifiers: a, -bar, and b.
  3. Contains the scope created by bar with a single identifier: c.

Lexical scope means that the scope is determined by the position of the function declaration when the code is written.

There are two mechanisms in JavaScript that can “cheat” lexical scopes: eval(..) And with. However, it is generally not recommended to use it.

Function scope and block scope

JavaScript has function-based scoping, meaning that each function declared creates a bubble for itself, whereas no other structure creates a scoped bubble. But that’s not exactly true, so let’s see.

Consider the following code:

function foo(a) {

var b = 2;

// Some code

function bar() {

// ...

}

// More code

var c = 3;

}

Copy the code

In this snippet, foo(..) The scope bubble contains identifiers A, B, C, and bar. Regardless of where the identifier declaration appears in the scope, the variable or function represented by the identifier will be attached to the bubble in the scope. bar(..) Has its own scoped bubble. The global scope also has its own scope bubble, which contains only one identifier: foo. Since identifiers A, B, C, and bar are all attached to foo(..) Scoped bubble, therefore cannot be retrieved from foo(..) External access to them. That is, none of these identifiers are accessible from the global scope, so the following code causes ReferenceError: bar(); // fail console.log(a, b, c); However, these identifiers (a, b, c, foo, and bar) are in foo(..). The interior of the bar(..) is accessible. The interior can also be accessed (assuming bar(..) There is no internal identifier declaration of the same name).

Hide the internal implementation

The traditional view of a function is to declare a function and then add code to it. But the reverse can also be instructive: taking an arbitrary piece of written code and wrapping it around function declarations effectively “hides” the code. The actual result is that a scope bubble is created around the code snippet, meaning that any declarations (variables or functions) in the code will be bound to the scope of the newly created wrapper function, not the previous one. In other words, you can wrap variables and functions in the scope of a function, and then use that scope to “hide” them. Such as:

function doSomething(a) {

b = a + doSomethingElse( a * 2 );

console.log( b * 3 );

}

function doSomethingElse(a) {

return a - 1;

}

var b;

doSomething( 2 ); / / 15

Copy the code





function doSomething(a) {

function doSomethingElse(a) {

return a - 1;

}

var b;

b = a + doSomethingElse( a * 2 );

console.log( b * 3 );

}

doSomething( 2 ); / / 15

Copy the code


Function scope

We already know that adding a wrapper function outside any code snippet “hides” internal variables and function definitions, and that the outer scope has no access to anything inside the wrapper function.

var a = 2;

Function foo() {// <-- add this line

var a = 3;

console.log( a ); / / 3

} // <-- and this line

foo(); // <-- and this line

console.log( a ); / / 2

Copy the code








var a = 2;

(function foo(){// <-- add this line

var a = 3;

console.log( a ); / / 3

}) (); // <-- and this line

console.log( a ); / / 2

Copy the code


Anonymous and named

The most familiar scenarios for function expressions are probably callback arguments, such as:

setTimeout( function() {

console.log("I waited 1 second!" );

}, 1000);

Copy the code

This is called an anonymous function expression because function().. There is no name identifier. Function expressions can be anonymous, and function declarations cannot omit function names — this is illegal in JavaScript syntax.

Anonymous function expressions are quick and easy to write, and many libraries and tools tend to encourage this style of code. But it also has several disadvantages to consider.

  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.

Inline function expressions are powerful and useful — the difference between anonymous and named doesn’t affect this at all. Assigning a function name to a function expression solves this problem effectively. It is a best practice to always name function expressions:

SetTimeout (function timeoutHandler() {// <--

console.log( "I waited 1 second!" );

}, 1000);

Copy the code

Execute the function expression immediately

var a = 2;

(function foo() {

var a = 3;

console.log( a ); / / 3

}) ();

console.log( a ); / / 2

Copy the code

Because the function is enclosed within a pair of () parentheses, it becomes an expression that can be executed immediately by adding another () to the end, such as (function foo(){.. }) (). The first () turns the function into an expression, and the second () executes the function. This pattern is so common that a few years ago the community gave it a term: IIFE, which stands for Immediately Invoked 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. While IIFE using named functions is not common, it has all the advantages of anonymous function expressions described above and is therefore a practice worth popularizing. (function(){.. function(){.. function(){.. } ()). Look carefully for the difference. In the first form, the function expression is enclosed in () and then called with another () parenthesis. The () parentheses used for calls in the second form have been moved into the () parentheses used for wrapping. The two forms are functionally identical. Which one to choose is a matter of personal preference. Another very common advanced use of IIFE is to call them as functions and pass in arguments. Such as:

var a = 2;

(function IIFE( global ) {

var a = 3;

console.log( a ); / / 3

console.log( global.a ); / / 2

})( window );

console.log( a ); / / 2

Copy the code





Block scope

Many programming languages other than JavaScript support block scope, so the related thinking will be familiar to developers of other languages, but the concept will be unfamiliar to developers who primarily use JavaScript.

for (var i=0; i<10; i++) {

console.log( i );

}

Copy the code

We define the variable I directly in the header of the for loop, usually because we only want to use I in the context inside the for loop, ignoring the fact that I is bound to an external scope (function or global). That’s where block scopes come in. Variables should be declared as close to where they are used as possible and as localized as possible. Another example:

var foo = true;

if (foo) {

var bar = foo * 2;

bar = something( bar );

console.log( bar );

}

Copy the code


try/catch

Very few people will notice that the CATCH clause in JavaScript’s ES3 specification states that the try/catch clause creates a block scope in which declared variables are only valid inside the catch.

try {

undefined(); // Perform an illegal operation to force an exception

}

catch (err) {

console.log( err ); // Can be executed properly!

}

console.log( err ); // ReferenceError: err not found

Copy the code

let

So far, we know that JavaScript behaves strangely in the way it exposes block-scoped functions. If this were the case, JavaScript developers would not have used block scope as a very useful mechanism for many years. Fortunately, ES6 changes that with the introduction of the new let keyword, which provides an alternative to var for variable declarations. The let keyword binds variables to any scope they are in (usually {.. } inside). In other words, let implicitly applies the block scope to the variables it declares.

var foo = true;

if (foo) {

let bar = foo * 2;

bar = something( bar );

console.log( bar );

}

console.log( bar ); // ReferenceError

Copy the code



Let cycle:





for (let i=0; i<10; i++) {

console.log( i );

}

console.log( i ); // ReferenceError

Copy the code





{

let j;

for (j=0; j<10; j++) {

let i = j; // Rebind each iteration!

console.log( i );

}

}

Copy the code

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 scoped constants contained in if

a = 3; / / normal!

b = 4; / / error!

}

console.log( a ); / / 3

console.log( b ); // ReferenceError!

Copy the code

summary

Functions are the most common unit of scope in JavaScript. Essentially, variables or functions declared inside a function are “hidden” in the scope at which they are located, which is an intentional design principle of good software. But functions are not the only unit of scope. Block scoping means that variables and functions can belong not only to the scope they are in, but also to a code block (usually {.. } inside). As of ES3, try/catch structures have block scope in catch clauses. The let keyword (a cousin of the var keyword) was introduced in ES6 to declare variables in arbitrary blocks of code. if(..) { let a = 2; } would declare a {.. } block, and add variables to the block. Some people think that block scope should not be used entirely as an alternative to function scope. Both functions should exist at the same time, and developers can and should choose which scope to use as needed to create good code that is readable and maintainable.

ascension

By now, you are familiar with the concept of scopes and how variables are assigned to scopes based on where and how they are declared. The behavior of a function scope is the same as that of a block scope, which can be summed up as: any variable declared in a scope will be attached to that scope. But scopes are subtly related to where variable declarations occur.

Which came first, the chicken or the egg

It is intuitive to think that JavaScript code is executed line by line from top to bottom. But that’s not entirely true, and there’s a special case where this assumption is wrong. Consider the following code:

a = 2;

var a;

console.log( a );

Copy the code






undefined
2

The compiler

When you see var a = 2; “, may think this is a statement. But JavaScript actually sees it as two declarations: var a; And a = 2; . The first definition declaration is made at compile time. The second assignment declaration is left in place for the execution phase. Our first snippet will be handled as follows:

var a;

a = 2;

console.log( a );

Copy the code








foo();

function foo() {

console.log( a ); // undefined

var a = 2;

}

Copy the code








function foo() {

var a;

console.log( a ); // undefined

a = 2;

}

foo();

Copy the code





foo(); // Not ReferenceError, but TypeError!

var foo = function bar() {

// ...

};

Copy the code

Function is preferred

Both function declarations and variable declarations are enhanced. But one notable detail (which can occur in code with multiple “duplicate” declarations) is that functions are promoted first, before variables.

foo(); / / 1

var foo;

function foo() {

console.log( 1 );

}

foo = function() {

console.log( 2 );

};

Copy the code





function foo() {

console.log( 1 );

}

foo(); / / 1

foo = function() {

console.log( 2 );

};

Copy the code



ignore





foo(); / / 3

function foo() {

console.log( 1 );

}

var foo = function() {

console.log( 2 );

};

function foo() {

console.log( 3 );

}

Copy the code

summary

We’re used to var a = 2; As a statement, which the JavaScript engine does not. It treats var a and a = 2 as two separate declarations, the first for the compile phase and the second for the execution phase. This means that no matter where the declaration in scope appears, it will be processed first before the code itself is executed. This process can be visualized as all declarations (variables and functions) are “moved” to the top of their scope. This process is called promotion.

The code snippet for javascript you Don’t Know

A scope is a set of rules for determining where and how to look for variables (identifiers). The use of scopes improves the locality of program logic, enhances program reliability, and reduces name conflicts.

Lexical scope

Consider the following code:

   function foo(a) {

var b = a * 2;

function bar(c) {

console.log( a, b, c );

}

bar( b * 3 );

}

foo( 2 ); / / 2, 4, 12

Copy the code










  1. Contains the entire global scope with only one identifier: foo.
  2. Contains the scope created by Foo with three identifiers: a, -bar, and b.
  3. Contains the scope created by bar with a single identifier: c.

Lexical scope means that the scope is determined by the position of the function declaration when the code is written.

There are two mechanisms in JavaScript that can “cheat” lexical scopes: eval(..) And with. However, it is generally not recommended to use it.

Function scope and block scope

JavaScript has function-based scoping, meaning that each function declared creates a bubble for itself, whereas no other structure creates a scoped bubble. But that’s not exactly true, so let’s see.

Consider the following code:

function foo(a) {

var b = 2;

// Some code

function bar() {

// ...

}

// More code

var c = 3;

}

Copy the code

In this snippet, foo(..) The scope bubble contains identifiers A, B, C, and bar. Regardless of where the identifier declaration appears in the scope, the variable or function represented by the identifier will be attached to the bubble in the scope. bar(..) Has its own scoped bubble. The global scope also has its own scope bubble, which contains only one identifier: foo. Since identifiers A, B, C, and bar are all attached to foo(..) Scoped bubble, therefore cannot be retrieved from foo(..) External access to them. That is, none of these identifiers are accessible from the global scope, so the following code causes ReferenceError: bar(); // fail console.log(a, b, c); However, these identifiers (a, b, c, foo, and bar) are in foo(..). The interior of the bar(..) is accessible. The interior can also be accessed (assuming bar(..) There is no internal identifier declaration of the same name).

Hide the internal implementation

The traditional view of a function is to declare a function and then add code to it. But the reverse can also be instructive: taking an arbitrary piece of written code and wrapping it around function declarations effectively “hides” the code. The actual result is that a scope bubble is created around the code snippet, meaning that any declarations (variables or functions) in the code will be bound to the scope of the newly created wrapper function, not the previous one. In other words, you can wrap variables and functions in the scope of a function, and then use that scope to “hide” them. Such as:

function doSomething(a) {

b = a + doSomethingElse( a * 2 );

console.log( b * 3 );

}

function doSomethingElse(a) {

return a - 1;

}

var b;

doSomething( 2 ); / / 15

Copy the code





function doSomething(a) {

function doSomethingElse(a) {

return a - 1;

}

var b;

b = a + doSomethingElse( a * 2 );

console.log( b * 3 );

}

doSomething( 2 ); / / 15

Copy the code


Function scope

We already know that adding a wrapper function outside any code snippet “hides” internal variables and function definitions, and that the outer scope has no access to anything inside the wrapper function.

var a = 2;

Function foo() {// <-- add this line

var a = 3;

console.log( a ); / / 3

} // <-- and this line

foo(); // <-- and this line

console.log( a ); / / 2

Copy the code








var a = 2;

(function foo(){// <-- add this line

var a = 3;

console.log( a ); / / 3

}) (); // <-- and this line

console.log( a ); / / 2

Copy the code


Anonymous and named

The most familiar scenarios for function expressions are probably callback arguments, such as:

setTimeout( function() {

console.log("I waited 1 second!" );

}, 1000);

Copy the code

This is called an anonymous function expression because function().. There is no name identifier. Function expressions can be anonymous, and function declarations cannot omit function names — this is illegal in JavaScript syntax.

Anonymous function expressions are quick and easy to write, and many libraries and tools tend to encourage this style of code. But it also has several disadvantages to consider.

  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.

Inline function expressions are powerful and useful — the difference between anonymous and named doesn’t affect this at all. Assigning a function name to a function expression solves this problem effectively. It is a best practice to always name function expressions:

SetTimeout (function timeoutHandler() {// <--

console.log( "I waited 1 second!" );

}, 1000);

Copy the code

Execute the function expression immediately

var a = 2;

(function foo() {

var a = 3;

console.log( a ); / / 3

}) ();

console.log( a ); / / 2

Copy the code

Because the function is enclosed within a pair of () parentheses, it becomes an expression that can be executed immediately by adding another () to the end, such as (function foo(){.. }) (). The first () turns the function into an expression, and the second () executes the function. This pattern is so common that a few years ago the community gave it a term: IIFE, which stands for Immediately Invoked 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. While IIFE using named functions is not common, it has all the advantages of anonymous function expressions described above and is therefore a practice worth popularizing. (function(){.. function(){.. function(){.. } ()). Look carefully for the difference. In the first form, the function expression is enclosed in () and then called with another () parenthesis. The () parentheses used for calls in the second form have been moved into the () parentheses used for wrapping. The two forms are functionally identical. Which one to choose is a matter of personal preference. Another very common advanced use of IIFE is to call them as functions and pass in arguments. Such as:

var a = 2;

(function IIFE( global ) {

var a = 3;

console.log( a ); / / 3

console.log( global.a ); / / 2

})( window );

console.log( a ); / / 2

Copy the code





Block scope

Many programming languages other than JavaScript support block scope, so the related thinking will be familiar to developers of other languages, but the concept will be unfamiliar to developers who primarily use JavaScript.

for (var i=0; i<10; i++) {

console.log( i );

}

Copy the code

We define the variable I directly in the header of the for loop, usually because we only want to use I in the context inside the for loop, ignoring the fact that I is bound to an external scope (function or global). That’s where block scopes come in. Variables should be declared as close to where they are used as possible and as localized as possible. Another example:

var foo = true;

if (foo) {

var bar = foo * 2;

bar = something( bar );

console.log( bar );

}

Copy the code


try/catch

Very few people will notice that the CATCH clause in JavaScript’s ES3 specification states that the try/catch clause creates a block scope in which declared variables are only valid inside the catch.

try {

undefined(); // Perform an illegal operation to force an exception

}

catch (err) {

console.log( err ); // Can be executed properly!

}

console.log( err ); // ReferenceError: err not found

Copy the code

let

So far, we know that JavaScript behaves strangely in the way it exposes block-scoped functions. If this were the case, JavaScript developers would not have used block scope as a very useful mechanism for many years. Fortunately, ES6 changes that with the introduction of the new let keyword, which provides an alternative to var for variable declarations. The let keyword binds variables to any scope they are in (usually {.. } inside). In other words, let implicitly applies the block scope to the variables it declares.

var foo = true;

if (foo) {

let bar = foo * 2;

bar = something( bar );

console.log( bar );

}

console.log( bar ); // ReferenceError

Copy the code



Let cycle:





for (let i=0; i<10; i++) {

console.log( i );

}

console.log( i ); // ReferenceError

Copy the code





{

let j;

for (j=0; j<10; j++) {

let i = j; // Rebind each iteration!

console.log( i );

}

}

Copy the code

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 scoped constants contained in if

a = 3; / / normal!

b = 4; / / error!

}

console.log( a ); / / 3

console.log( b ); // ReferenceError!

Copy the code

summary

Functions are the most common unit of scope in JavaScript. Essentially, variables or functions declared inside a function are “hidden” in the scope at which they are located, which is an intentional design principle of good software. But functions are not the only unit of scope. Block scoping means that variables and functions can belong not only to the scope they are in, but also to a code block (usually {.. } inside). As of ES3, try/catch structures have block scope in catch clauses. The let keyword (a cousin of the var keyword) was introduced in ES6 to declare variables in arbitrary blocks of code. if(..) { let a = 2; } would declare a {.. } block, and add variables to the block. Some people think that block scope should not be used entirely as an alternative to function scope. Both functions should exist at the same time, and developers can and should choose which scope to use as needed to create good code that is readable and maintainable.

ascension

By now, you are familiar with the concept of scopes and how variables are assigned to scopes based on where and how they are declared. The behavior of a function scope is the same as that of a block scope, which can be summed up as: any variable declared in a scope will be attached to that scope. But scopes are subtly related to where variable declarations occur.

Which came first, the chicken or the egg

It is intuitive to think that JavaScript code is executed line by line from top to bottom. But that’s not entirely true, and there’s a special case where this assumption is wrong. Consider the following code:

a = 2;

var a;

console.log( a );

Copy the code






undefined
2

The compiler

When you see var a = 2; “, may think this is a statement. But JavaScript actually sees it as two declarations: var a; And a = 2; . The first definition declaration is made at compile time. The second assignment declaration is left in place for the execution phase. Our first snippet will be handled as follows:

var a;

a = 2;

console.log( a );

Copy the code








foo();

function foo() {

console.log( a ); // undefined

var a = 2;

}

Copy the code








function foo() {

var a;

console.log( a ); // undefined

a = 2;

}

foo();

Copy the code





foo(); // Not ReferenceError, but TypeError!

var foo = function bar() {

// ...

};

Copy the code

Function is preferred

Both function declarations and variable declarations are enhanced. But one notable detail (which can occur in code with multiple “duplicate” declarations) is that functions are promoted first, before variables.

foo(); / / 1

var foo;

function foo() {

console.log( 1 );

}

foo = function() {

console.log( 2 );

};

Copy the code





function foo() {

console.log( 1 );

}

foo(); / / 1

foo = function() {

console.log( 2 );

};

Copy the code



ignore





foo(); / / 3

function foo() {

console.log( 1 );

}

var foo = function() {

console.log( 2 );

};

function foo() {

console.log( 3 );

}

Copy the code

summary

We’re used to var a = 2; As a statement, which the JavaScript engine does not. It treats var a and a = 2 as two separate declarations, the first for the compile phase and the second for the execution phase. This means that no matter where the declaration in scope appears, it will be processed first before the code itself is executed. This process can be visualized as all declarations (variables and functions) are “moved” to the top of their scope. This process is called promotion.

The code snippet for javascript you Don’t Know