“变量、作用域、闭包”的版本间差异

来自Wikioe
跳到导航 跳到搜索
无编辑摘要
无编辑摘要
第1行: 第1行:
[[category:JavaScript]]
[[category:JavaScript]]


== 变量 ==
== 变量<ref group="MDN" name="Variables"/> ==
  ECMAScript 6 中引入了 let、const。
  ECMAScript 6 中引入了 let、const。


JavaScript 有三种声明方式:
JavaScript 有三种声明方式:
# '''var''':声明一个'''变量'''(<abbr title="在函数之外声明的变量">局部变量</abbr> / <abbr title="在函数内部声明的变量">全局变量</abbr>),初始化可选。
# '''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>:可被当前'''文档'''中的任何其他代码所访问;
# <span style="color: green">全局作用域</span>:可被当前'''文档'''中的任何其他代码所访问;
#: “全局变量”的作用域
# <span style="color: green">函数作用域</span>:可被当前'''函数'''内部的其他代码所访问;
# <span style="color: green">'''函数作用域'''</span>:可被当前'''函数'''内部的其他代码所访问;
#: “局部变量”的作用域
# <span style="color: green">'''块作用域'''</span>:可被当前'''块'''(由 '''<code>{ }</code>''' 包围)内部的其他代码所访问;
# <span style="color: green">'''块作用域'''</span>:可被当前'''块'''(由 '''<code>{ }</code>''' 包围)内部的其他代码所访问;
#: “局部变量”的作用域
#* 仅 let、const 支持


  <span style="color: blue">'''var 变量没有“块作用域”:将会使用(块所在的)“函数作用域”或“全局作用域”'''</span>
  <big>⭐</big> <span style="color: blue">'''var 变量没有“块作用域”''':将会使用(块所在的)“函数作用域”或“全局作用域”</span> —— 这是因为在早期的 JavaScript 中,块没有<span style="color: red">'''词法环境'''</span>
var 变量将透传 if、for 和其它代码块 —— 这是因为在早期的 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 有三种声明方式:

  1. var:声明一个变量,初始化可选。
  2. let:声明一个变量,初始化可选。
  3. const:声明一个常量,必须初始化。
“局部变量”/“全局变量”只与其定义位置有关:在“函数内部”/“函数之外”声明的变量

作用域[MDN 2][javascript.info 1]

ECMAScript 6 之前没有“块作用域”:语句块中声明的变量将成为语句块所在函数(或全局作用域)的局部变量。
  1. 全局作用域:可被当前文档中的任何其他代码所访问;
  2. 函数作用域:可被当前函数内部的其他代码所访问;
  3. 块作用域:可被当前(由 { } 包围)内部的其他代码所访问;
    • 仅 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):变量和函数的声明会在物理层面(在编译阶段被放入内存中)移动到代码的最前面 —— 变量/函数的“初始化”和“使用”可以在“声明”之前(先使用再声明)。

函数和变量相比,会被优先提升 —— 函数会被提升到更靠前的位置。
  1. 变量提升[MDN 4]
    bla = 2
    var bla;
    
    // 可以理解为:
    
    var bla;
    bla = 2;
    
  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):从“代码块的开始”直到“声明变量的行”,letconst 声明的变量都处于“暂时性死区”中 —— 这并不是“提升”!!!
  1. 在“暂时性死区”中访问变量将抛出 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)
    }
    
  2. “暂时性死区”取决于“执行顺序”(时间),而非“代码顺序”(位置):
    {
        // 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
\ var let
块作用域
变量提升
重新声明
var user = "Pete";
var user = "John";
console.log(user); // John
let user;
let user; // SyntaxError: 'user' has already been declared

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

其原因有两点:

  1. setTimeout 是一个任务,将在 for 所有循环完成之后执行;
  2. i 由 var 定义:所有的 setTimeout 实际上都引用了“相同作用域里的同一个 i” —— 而它在所有循环之后其值为 10;

要与预期输出一致,有两种方式:

  1. 通过使用“立即执行的函数表达式”来捕获 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)
      }
      
  2. 使用 let 替换 var
    for (let i = 0; i < 10; i++) {
        (function(i) {
            setTimeout(function() { console.log(i) }, 100 * i)
        })(i)
    }
    
var作用域或函数作用域

当用let声明一个变量,它使用的是词法作用域或块作用域。

参考

MDN

javascript.info

其他