What is the result of the following program?
Copy the code code as follows:
var foo = 1;
function bar() {
if (!foo) {
var foo = 10;
}
alert(foo);
}
bar();
The result is 10;
What about this one?
Copy the code code as follows:
var a = 1;
function b() {
a = 10;
return;
function a() {}
}
b();
alert(a);
The result is 1.
Does it scare you? What happened? This may be strange, dangerous, and confusing, but it's also actually a very useful and impressive JavaScript language feature. I don't know if there is a standard name for this behavior, but I like this term: "Hoisting". This article will give an introductory explanation of this mechanism, but first let us have some necessary understanding of the scope of JavaScript.
Scope of Javascript
For Javascript beginners, one of the most confusing areas is scope; in fact, it’s not just beginners. I have met some experienced JavaScript programmers, but they do not understand scope deeply. The reason why JavaScript scope is confusing is because the program syntax itself looks like a C family language, like the following C program:
Copy the code code as follows:
#include <stdio.h>
int main() {
int x = 1;
printf("%d, ", x); // 1
if (1) {
int x = 2;
printf("%d, ", x); // 2
}
printf("%d/n", x); // 1
}
The output result is 1 2 1. This is because C family languages have block scope. When program control enters a block, such as an if block, variables that only affect the block can be declared without affecting the effects outside the block. domain. But in Javascript, this doesn't work. Take a look at the code below:
Copy the code code as follows:
var x = 1;
console.log(x); // 1
if (true) {
var x = 2;
console.log(x); // 2
}
console.log(x); // 2
The result will be 1 2 2. Because javascript is function scope. This is the biggest difference from the C family of languages. The if in this program does not create a new scope.
For many C, C++, and Java programmers, this is not what they expect and welcome. Fortunately, due to the flexibility of JavaScript functions, there are ways to work around this. If you must create a temporary scope, do something like this:
Copy the code code as follows:
function foo() {
var x = 1;
if (x) {
(function () {
var x = 2;
// some other code
}());
}
// x is still 1.
}
This method is flexible and can be used anywhere you want to create a temporary scope. Not just within the block. However, I strongly recommend that you take the time to understand JavaScript scoping. It's very useful and one of my favorite features of JavaScript. If you understand scope, then variable hoisting will make more sense to you.
Variable declaration, naming, and promotion
In JavaScript, there are 4 basic ways for variables to enter scope:
•1 Built-in language: all scopes have this and arguments; (Translator's Note: After testing, arguments are not visible in the global scope)
•2 Formal parameters: The formal parameters of a function will be part of the scope of the function body;
•3 Function declaration: like this form: function foo(){};
•4 Variable declaration: like this: var foo;
Function declarations and variable declarations are always quietly "raised" to the top of the method body by the interpreter. This means, code like the following:
Copy the code code as follows:
function foo() {
bar();
var x = 1;
}
will actually be interpreted as:
Copy the code code as follows:
function foo() {
var x;
bar();
x = 1;
}
Regardless of whether the block in which the variable is defined can be executed. The following two functions are actually the same thing:
Copy the code code as follows:
function foo() {
if (false) {
var x = 1;
}
return;
var y = 1;
}
function foo() {
var x, y;
if (false) {
x = 1;
}
return;
y = 1;
}
Note that variable assignments are not hoisted, just declarations. However, the function declaration is a little different, and the function body is also promoted. But please note that there are two ways to declare a function:
Copy the code code as follows:
function test() {
foo(); // TypeError "foo is not a function"
bar(); // "this will run!"
var foo = function () { // variable points to function expression
alert("this won't run!");
}
function bar() { // Function declaration function named bar
alert("this will run!");
}
}
test();
In this example, only functional declarations are hoisted along with the function body. The declaration of foo will be hoisted, but the function body it points to will only be assigned during execution.
The above covers some of the basics of boosting, and they don't seem that confusing. However, in some special scenarios, there is still a certain degree of complexity.
Variable parsing order
The most important thing to keep in mind is variable resolution order. Remember the 4 ways that naming enters the scope I gave earlier? The order in which variables are parsed is the order I listed them.
Copy the code code as follows:
<script>
function a(){
}
var a;
alert(a);//Print out the function body of a
</script>
<script>
var a;
function a(){
}
alert(a);//Print out the function body of a
</script>
//But pay attention to the difference between the following two writing methods:
<script>
var a=1;
function a(){
}
alert(a);//Print out 1
</script>
<script>
function a(){
}
var a=1;
alert(a);//Print out 1
</script>
There are 3 exceptions here:
1 The built-in name arguments behave strangely. It seems that it should be declared after the function formal parameters, but before the function declaration. This means that if there are arguments in the formal parameter, it will have priority over the built-in one. This is a very bad feature, so avoid using arguments in formal parameters;
2 Defining this variable anywhere will cause a syntax error, which is a good feature;
3. If multiple formal parameters have the same name, the last one has priority, even if its value is undefined during actual operation;
named function
You can give a function a name. If so, it is not a function declaration, and the specified function name (if any, such as spam below, translator's note) in the function body definition will not be promoted, but ignored. Here is some code to help you understand:
Copy the code code as follows:
foo(); // TypeError "foo is not a function"
bar(); // valid
baz(); // TypeError "baz is not a function"
spam(); // ReferenceError "spam is not defined"
var foo = function () {}; // foo points to an anonymous function
function bar() {}; // function declaration
var baz = function spam() {}; // Named function, only baz is promoted, spam will not be promoted.
foo(); // valid
bar(); // valid
baz(); // valid
spam(); // ReferenceError "spam is not defined"
How to write code
Now that you understand scoping and variable hoisting, what does this mean for JavaScript coding? The most important thing is to always define your variables with var. And I strongly recommend that for a name, there should always be only one var declaration in a scope. If you do this, you won't have scope and variable hoisting issues.
What do you mean by language specification?
I find the ECMAScript reference documentation always useful. Here's what I found about scope and variable hoisting:
If a variable is declared in a function body class, it is function scope. Otherwise, it is globally scoped (as a property of global). Variables will be created when execution enters scope. Blocks will not define new scopes, only function declarations and procedures (the translator thinks it is global code execution) will create new scopes. Variables are initialized to undefined when they are created. If there is an assignment operation in the variable declaration statement, the assignment operation will only occur when it is executed, not when it is created.
I hope this article will bring a ray of light to programmers who are confused about JavaScript. I also try my best to avoid causing more confusion. If I said something wrong or overlooked something, please let me know.
Translator's supplement
A friend reminded me about the promotion problem of named functions in the global scope under IE:
This is how I tested it when I translated the article:
Copy the code code as follows:
<script>
functiont(){
spam();
var baz = function spam() {alert('this is spam')};
}
t();
</script>
This way of writing, that is, the promotion of named functions in non-global scope, has the same performance under ie and ff. I changed it to:
Copy the code code as follows:
<script>
spam();
var baz = function spam() {alert('this is spam')};
</script>
Then spam can be executed under ie, but not under ff. This shows that different browsers handle this detail differently.
This question also led me to think about two other questions, 1: For variables that have global scope, there is a difference between var and non-var. Without var, the variable will not be promoted. For example, of the following two programs, the second one will report an error:
Copy the code code as follows:
<script>
alert(a);
var a=1;
</script>
Copy the code code as follows:
<script>
alert(a);
a=1;
</script>
2: Local variables created in eval will not be promoted (it has no way to do it).
Copy the code code as follows:
<script>
var a = 1;
function t(){
alert(a);
eval('var a = 2');
alert(a);
}
t();
alert(a);
</script>