“变量、作用域、闭包”的版本间差异
		
		
		
		
		
		跳到导航
		跳到搜索
		
				
		
		
	
无编辑摘要  | 
				无编辑摘要  | 
				||
| 第1行: | 第1行: | ||
[[category:JavaScript]]  | [[category:JavaScript]]  | ||
== 变量 ==  | == 变量<ref group="MDN" name="Variables"/> ==  | ||
  ECMAScript 6 中引入了 let、const。  |   ECMAScript 6 中引入了 let、const。  | ||
JavaScript 有三种声明方式:  | JavaScript 有三种声明方式:  | ||
# '''var''':声明一个'''变量'''  | # '''var''':声明一个'''变量''',初始化可选。  | ||
# '''let''':声明一个'''  | # '''let''':声明一个'''变量''',初始化可选。  | ||
# '''const''':声明一个'''  | # '''const''':声明一个'''常量''',必须初始化。  | ||
  “局部变量”/“全局变量”只与其定义位置有关:在“函数内部”/“函数之外”声明的变量  | |||
=== 作用域 ===  | === 作用域<ref group="MDN" name="Scope"/><ref group="javascript.info" name="var"/> ===  | ||
  ECMAScript 6 之前没有“块作用域”:语句块中声明的变量将成为语句块所在函数(或全局作用域)的局部变量。  |   ECMAScript 6 之前没有“块作用域”:语句块中声明的变量将成为语句块所在函数(或全局作用域)的局部变量。  | ||
# <span style="color: green">  | # <span style="color: green">全局作用域</span>:可被当前'''文档'''中的任何其他代码所访问;  | ||
# <span style="color: green">函数作用域</span>:可被当前'''函数'''内部的其他代码所访问;  | |||
# <span style="color: green">  | |||
# <span style="color: green">'''块作用域'''</span>:可被当前'''块'''(由 '''<code>{ }</code>''' 包围)内部的其他代码所访问;  | # <span style="color: green">'''块作用域'''</span>:可被当前'''块'''(由 '''<code>{ }</code>''' 包围)内部的其他代码所访问;  | ||
#  | #* 仅 let、const 支持  | ||
  <span style="color: blue">'''var   |   <big>⭐</big> <span style="color: blue">'''var 变量没有“块作用域”''':将会使用(块所在的)“函数作用域”或“全局作用域”</span> —— 这是因为在早期的 JavaScript 中,块没有<span style="color: red">'''词法环境'''</span>  | ||
* var 将透传 if、for 和其它代码块:  | |||
: <syntaxhighlight lang="JavaScript" highlight="">  | *: <syntaxhighlight lang="JavaScript" highlight="">  | ||
if (true) {  | if (true) {  | ||
     var x = 5;  |      var x = 5;  | ||
| 第38行: | 第34行: | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
=== 变量提升 ===  | === var:变量提升<ref group="MDN" name="Hoisting"/> ===  | ||
 <big>'''提升(Hoisting)'''</big>:变量和函数的'''声明'''会在物理层面(在编译阶段被放入内存中)移动到代码的最前面 —— 变量/函数的“初始化”和“使用”可以在“声明”之前(先使用再声明)。  | |||
 函数和变量相比,会被优先提升 —— 函数会被提升到更靠前的位置。  | |||
# '''变量提升'''<ref group="MDN" name="var"/>:  | |||
#: <syntaxhighlight lang="JavaScript" highlight="">  | |||
bla = 2  | |||
var bla;  | |||
// 可以理解为:  | |||
var bla;  | |||
bla = 2;  | |||
</syntaxhighlight>  | |||
# '''函数提升'''<ref group="MDN" name="function"/>:  | |||
#: <syntaxhighlight lang="JavaScript" highlight="">  | |||
hoisted(); // logs "foo"  | |||
function hoisted() {  | |||
    console.log('foo');  | |||
}  | |||
// 可以理解为:  | |||
function hoisted() {  | |||
    console.log('foo');  | |||
}  | |||
hoisted(); // logs "foo"  | |||
</syntaxhighlight>  | |||
 <big>⭐</big> <span style="color: blue">'''JavaScript 只会提升声明,不会提升其初始化'''</span> —— 如果在“声明”和“初始化”之前“使用”,将使用 <span style="color: red">'''undefined'''</span>  | |||
: 示例:  | |||
: <syntaxhighlight lang="JavaScript" highlight="">  | |||
// “使用”先于“初始化”  | |||
console.log(num); // Returns undefined  | |||
var num;  | |||
num = 6;  | |||
// “初始化”先于“使用”  | |||
num = 6;  | |||
console.log(num); // returns 6  | |||
var num;  | |||
</syntaxhighlight>  | |||
 <big>⭐</big> <span style="color: blue">'''函数表达式(function expressions)不会被提升'''</span>   | |||
: 示例:  | |||
: <syntaxhighlight lang="JavaScript" highlight="">  | |||
notHoisted(); // TypeError: notHoisted is not a function  | |||
var notHoisted = function() {  | |||
    console.log('bar');  | |||
};  | |||
</syntaxhighlight>  | |||
=== let:暂时性死区<ref group="MDN" name="let"/> ===  | |||
 <big>'''暂时性死区(Temporal dead zone,TDZ)'''</big>:从“代码块的开始”直到“声明变量的行”,'''let''' 或 '''const''' 声明的变量都处于“暂时性死区”中 —— 这并不是“提升”!!!  | |||
# 在“暂时性死区”中访问变量将抛出 '''ReferenceError''':  | |||
#: <syntaxhighlight lang="JavaScript" highlight="">  | |||
{   | |||
    // TDZ starts at beginning of scope  | |||
    console.log(bar); // undefined  | |||
    console.log(foo); // ReferenceError  | |||
    var bar = 1;  | |||
    let foo = 2;   | |||
    // End of TDZ (for foo)  | |||
}  | |||
</syntaxhighlight>  | |||
# “暂时性死区”取决于“执行顺序”(时间),而非“代码顺序”(位置):  | |||
#: <syntaxhighlight lang="JavaScript" highlight="">  | |||
{  | |||
    // TDZ starts at beginning of scope  | |||
    const func = () => console.log(letVar); // OK  | |||
    let letVar = 3;   | |||
    // End of TDZ (for letVar)  | |||
    func(); // Called outside TDZ!  | |||
}  | |||
</syntaxhighlight>  | |||
== 词法作用域 ==  | == 词法作用域 ==  | ||
| 第52行: | 第121行: | ||
== 闭包 ==  | |||
==   | == 补充内容 ==  | ||
=== var 与 let 的区别<ref group="MDN" name="var"/><ref group="MDN" name="let"/> ===  | |||
{| class="wikitable"  | |||
|+ var 与 let  | |||
|-  | |||
! \ !! var !! let  | |||
|-  | |||
| '''块作用域''' || <span style="color: red">✘</span> || <span style="color: green">✔</span>  | |||
|-  | |||
| '''变量提升''' || <span style="color: green">✔</span> || <span style="color: red">✘</span>  | |||
|-  | |||
| '''重新声明'''   | |||
| <span style="color: green">✔</span>  | |||
: <syntaxhighlight lang="JavaScript" highlight="">  | |||
var user = "Pete";  | |||
var user = "John";  | |||
console.log(user); // John  | |||
</syntaxhighlight>  | |||
| <span style="color: red">✘</span>  | |||
: <syntaxhighlight lang="JavaScript" highlight="">  | |||
let user;  | |||
let user; // SyntaxError: 'user' has already been declared  | |||
</syntaxhighlight>  | |||
|}  | |||
=== let 有没有“提升”? ===  | |||
 关于这一点,MDN 都描述得前后不一致,也是很操蛋了……  | |||
 1、MDN:《语法和数据类型》<ref group="MDN" name="Variables"/>:  | |||
     在 ECMAScript 6 中,let和const同样会被提升变量到代码块的顶部但是不会被赋予初始值。在变量声明之前引用这个变量,将抛出引用错误(ReferenceError)。这个变量将从代码块一开始的时候就处在一个“暂时性死区”,直到这个变量被声明为止。  | |||
 2、MDN:《语法和数据类型》<ref group="MDN" name="let"/>:  | |||
     var 和 let 的另一个重要区别,let 声明的变量不会在作用域中被提升,它是在编译时才初始化(参考下面的暂时性死区)。  | |||
各种说法大都以一个类似示例来说明:  | |||
: <syntaxhighlight lang="JavaScript" highlight="">  | |||
var foo = 33;  | |||
if (foo) {  | |||
    console.log(foo) // ReferenceError: Cannot access 'foo' before initialization  | |||
    let foo = 44;  | |||
}  | |||
</syntaxhighlight>  | |||
对于 ReferenceError 该如何解释呢 ❓   | |||
: 网上有说法,认为是由于“let 变量提升导致”  | |||
| 第129行: | 第241行: | ||
== 参考 ==  | == 参考 ==  | ||
=== MDN ===  | |||
<references group="MDN">  | |||
<ref group="MDN" name="Variables">参考:[https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Grammar_and_Types MDN:《语法和数据类型》]</ref>  | |||
<ref group="MDN" name="function">参考:[https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/function MDN:《function》]</ref>  | |||
<ref group="MDN" name="var">参考:[https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/var MDN:《var》]</ref>  | |||
<ref group="MDN" name="let">参考:[https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/let MDN:《let》]</ref>  | |||
<ref group="MDN" name="Scope">参考:[https://developer.mozilla.org/zh-CN/docs/Glossary/Scope MDN:《Scope(作用域)》]</ref>  | |||
<ref group="MDN" name="Hoisting">参考:[https://developer.mozilla.org/zh-CN/docs/Glossary/Hoisting MDN:《Hoisting(变量提升)》]</ref>  | |||
</references>  | |||
=== javascript.info ===  | |||
<references group="javascript.info">  | |||
<ref group="javascript.info" name="var">参考:[https://zh.javascript.info/var javascript.info:《老旧的 "var"》]</ref>  | |||
</references>  | |||
=== 其他 ===  | |||
<references/>  | <references/>  | ||
2023年4月16日 (日) 05:03的版本
变量[MDN 1]
ECMAScript 6 中引入了 let、const。
JavaScript 有三种声明方式:
- var:声明一个变量,初始化可选。
 - let:声明一个变量,初始化可选。
 - const:声明一个常量,必须初始化。
 
“局部变量”/“全局变量”只与其定义位置有关:在“函数内部”/“函数之外”声明的变量
作用域[MDN 2][javascript.info 1]
ECMAScript 6 之前没有“块作用域”:语句块中声明的变量将成为语句块所在函数(或全局作用域)的局部变量。
- 全局作用域:可被当前文档中的任何其他代码所访问;
 - 函数作用域:可被当前函数内部的其他代码所访问;
 - 块作用域:可被当前块(由 
{ }包围)内部的其他代码所访问;- 仅 let、const 支持
 
 
⭐ var 变量没有“块作用域”:将会使用(块所在的)“函数作用域”或“全局作用域” —— 这是因为在早期的 JavaScript 中,块没有词法环境
- var 将透传 if、for 和其它代码块:
if (true) { var x = 5; } console.log(x); // 5 if (true) { let y = 5; } console.log(y); // ReferenceError: y is not defined
 
var:变量提升[MDN 3]
提升(Hoisting):变量和函数的声明会在物理层面(在编译阶段被放入内存中)移动到代码的最前面 —— 变量/函数的“初始化”和“使用”可以在“声明”之前(先使用再声明)。 函数和变量相比,会被优先提升 —— 函数会被提升到更靠前的位置。
- 变量提升[MDN 4]:
bla = 2 var bla; // 可以理解为: var bla; bla = 2;
 - 函数提升[MDN 5]:
hoisted(); // logs "foo" function hoisted() { console.log('foo'); } // 可以理解为: function hoisted() { console.log('foo'); } hoisted(); // logs "foo"
 
⭐ JavaScript 只会提升声明,不会提升其初始化 —— 如果在“声明”和“初始化”之前“使用”,将使用 undefined
- 示例:
 // “使用”先于“初始化” console.log(num); // Returns undefined var num; num = 6; // “初始化”先于“使用” num = 6; console.log(num); // returns 6 var num;
⭐ 函数表达式(function expressions)不会被提升 
- 示例:
 notHoisted(); // TypeError: notHoisted is not a function var notHoisted = function() { console.log('bar'); };
let:暂时性死区[MDN 6]
暂时性死区(Temporal dead zone,TDZ):从“代码块的开始”直到“声明变量的行”,let 或 const 声明的变量都处于“暂时性死区”中 —— 这并不是“提升”!!!
- 在“暂时性死区”中访问变量将抛出 ReferenceError:
{ // TDZ starts at beginning of scope console.log(bar); // undefined console.log(foo); // ReferenceError var bar = 1; let foo = 2; // End of TDZ (for foo) }
 - “暂时性死区”取决于“执行顺序”(时间),而非“代码顺序”(位置):
{ // TDZ starts at beginning of scope const func = () => console.log(letVar); // OK let letVar = 3; // End of TDZ (for letVar) func(); // Called outside TDZ! }
 
词法作用域
闭包
补充内容
var 与 let 的区别[MDN 4][MDN 6]
| \ | var | let | 
|---|---|---|
| 块作用域 | ✘ | ✔ | 
| 变量提升 | ✔ | ✘ | 
| 重新声明 | ✔
  | 
✘
  | 
let 有没有“提升”?
关于这一点,MDN 都描述得前后不一致,也是很操蛋了…… 1、MDN:《语法和数据类型》[MDN 1]: 在 ECMAScript 6 中,let和const同样会被提升变量到代码块的顶部但是不会被赋予初始值。在变量声明之前引用这个变量,将抛出引用错误(ReferenceError)。这个变量将从代码块一开始的时候就处在一个“暂时性死区”,直到这个变量被声明为止。 2、MDN:《语法和数据类型》[MDN 6]: var 和 let 的另一个重要区别,let 声明的变量不会在作用域中被提升,它是在编译时才初始化(参考下面的暂时性死区)。
各种说法大都以一个类似示例来说明:
var foo = 33; if (foo) { console.log(foo) // ReferenceError: Cannot access 'foo' before initialization let foo = 44; }
对于 ReferenceError 该如何解释呢 ❓
- 网上有说法,认为是由于“let 变量提升导致”
 
一个有意思的例子
一个涉及到“Event Loop”和“var 作用域”的例子。
对于以下代码:
for (var i = 0; i < 10; i++) { setTimeout(function() { console.log(i) }, 100 * i) }
- 是否预期输出:
 0 1 2 3 4 5 6 7 8 9
- 但,实际输出:
 10 10 10 10 10 10 10 10 10 10
其原因有两点:
- setTimeout 是一个任务,将在 for 所有循环完成之后执行;
 - i 由 var 定义:所有的 setTimeout 实际上都引用了“相同作用域里的同一个 i” —— 而它在所有循环之后其值为 10;
 
要与预期输出一致,有两种方式:
- 通过使用“立即执行的函数表达式”来捕获 i:
for (var i = 0; i < 10; i++) { (function(i) { setTimeout(function() { console.log(i) }, 100 * i) })(i) }
- 不能妄想通过 let、const 来捕获:
for (var i = 0; i < 10; i++) { setTimeout(function() { const v = i; console.log(i); }, 100 * i) }
 
 - 使用 let 替换 var:
for (let i = 0; i < 10; i++) { (function(i) { setTimeout(function() { console.log(i) }, 100 * i) })(i) }
 
var作用域或函数作用域 当用let声明一个变量,它使用的是词法作用域或块作用域。
参考
MDN
- ↑ 1.0 1.1 参考:MDN:《语法和数据类型》
 - ↑ 参考:MDN:《Scope(作用域)》
 - ↑ 参考:MDN:《Hoisting(变量提升)》
 - ↑ 4.0 4.1 参考:MDN:《var》
 - ↑ 参考:MDN:《function》
 - ↑ 6.0 6.1 6.2 参考:MDN:《let》