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
- Contains the entire global scope with only one identifier: foo.
- Contains the scope created by Foo with three identifiers: a, -bar, and b.
- 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.
- Anonymous functions do not show meaningful function names in the stack trace, making debugging difficult.
- 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.
- 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
- Contains the entire global scope with only one identifier: foo.
- Contains the scope created by Foo with three identifiers: a, -bar, and b.
- 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.
- Anonymous functions do not show meaningful function names in the stack trace, making debugging difficult.
- 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.
- 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