查看“变量、作用域、闭包”的源代码
←
变量、作用域、闭包
跳到导航
跳到搜索
因为以下原因,您没有权限编辑本页:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
[[category:JavaScript]] == 变量<ref group="MDN" name="Variables"/> == ECMAScript 6 中引入了 let、const。 JavaScript 有三种声明方式: # '''var''':声明一个'''变量''',初始化可选。 # '''let''':声明一个'''变量''',初始化可选。 # '''const''':声明一个'''常量''',必须初始化。 “局部变量”/“全局变量”只与其定义位置有关:在“函数内部”/“函数之外”声明的变量 === 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> === 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> |} == 作用域<ref group="MDN" name="Scope"/><ref>参考:[https://juejin.cn/post/7069578126979760158 《【JavaScript】深入理解JS中的词法作用域与作用域链》]</ref> == <big>'''作用域'''</big>是当前的执行上下文,值和表达式在其中“可见”或可被访问。 # <span style="color: green">全局作用域</span>:可被当前'''文档'''中的任何其他代码所访问; # <span style="color: green">函数作用域</span>:可被当前'''函数'''内部的其他代码所访问; # <span style="color: green">'''块作用域'''</span>:可被当前'''块'''(由 '''<code>{ }</code>''' 包围)内部的其他代码所访问; #* 仅 let、const 支持 ECMAScript 6 之前没有“块作用域”:语句块中声明的变量将成为语句块所在函数(或全局作用域)的局部变量。 === 作用域链 === <big>'''作用域链'''</big>:由于作用域的层层嵌套而形成的关系。 作用域链用于变量的查找过程: : <syntaxhighlight lang="JavaScript" highlight=""> var x = 10 function outer() { var x = 20 function inner() { console.log(x) } inner() } outer() // 20 </syntaxhighlight> : 以上代码,包括三个作用域: :# 全局作用域:包含变量 x(=10) :# 函数作用域(outer):包含变量 x(=20) :# 函数作用域(inner):不包含变量 : 查找过程: :# console.log(x) 将在“函数作用域(inner)”中查找变量“x”:失败 :# 在“函数作用域(outer)”查找变量“x”:成功(x = 20) :# 使用该变量:输出 20 === 作用域模型:词法作用域<ref>参考:[https://www.freecodecamp.org/chinese/news/javascript-lexical-scope-tutorial/ 《JavaScript 中的词法作用域——JS 中的作用域究竟是什么?》]</ref> === <div class="mw-code" style="white-space: normal; line-height: 1.6;"> 从语言的层面来说,作用域模型分两种:(根据作用域的生成时机) # '''静态作用域''':在'''代码编写时'''完成划分,作用域沿以'''定义位置'''向外延伸 #* 也称“<span style="color: green">'''词法作用域'''</span>”,是最为普遍的一种作用域模型 # '''动态作用域''':在'''代码运行时'''完成划分,作用域链以'''调用栈'''向外延伸 #* 由 bash、Perl 等语言所使用 </div> <span style="color: green">'''词法作用域(Lexical Scope)'''</span>:即“词法分析时生成的作用域”,根据源代码中“声明变量的位置”来确定该变量在何处可用 : 示例: : <syntaxhighlight lang="JavaScript" highlight=""> let x = "Eijux"; function getX() { return x; } console.log(getX()) // Eijux </syntaxhighlight> : 变量 x 的“词法作用域”为“全局作用域”(定义时的作用域)而非“getX 函数作用域” <big>⭐</big> 一个声明(定义变量、函数等)的词法作用域就是其“'''定义时的作用域'''” —— 注意:并不是“调用时的作用域” ==== 欺骗“词法作用域” ==== 虽然“词法作用域”在代码编写时就确定,但仍然可以通过 '''eval()''' 和 '''with''' 语句来修改。 —— eval():有性能和安全隐患,不建议使用 —— with:有语义不明和兼容问题,已弃用 # : <syntaxhighlight lang="JavaScript" highlight=""> var num = 10 var str = "var num = 20" function fn(str) { eval(str) console.log(num) } fn(str) </syntaxhighlight> == 闭包 == == 补充内容 == === var 没有“块作用域”<ref group="javascript.info" name="var"/> === <big>⭐</big> <span style="color: blue">'''var 变量没有“块作用域”''':将会使用(块所在的)“函数作用域”或“全局作用域”</span> —— 这是因为在早期的 JavaScript 中,块没有<span style="color: red">'''词法环境'''</span> var 将透传 if、for 和其它代码块: : <syntaxhighlight lang="JavaScript" highlight=""> if (true) { var x = 5; } console.log(x); // 5 if (true) { let y = 5; } console.log(y); // ReferenceError: y is not defined </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 变量提升,导致此处使用“块作用域内定义变量”,但该变量处于“暂时性死区”,所以出现此错误。 #: 网上有内容更是强行将 let 变量创建分为三个过程:创建、初始化、赋值 …… 有点扯。 # 说法二:由于'''词法作用域''',导致此处使用“块作用域内定义变量”,但该变量处于“暂时性死区”,所以出现此错误。 <big>💡</big> 个人理解:let 并不会提升,以上示例是“词法作用域”影响。—— 或许看看 JS 引擎执行过程更清楚 == <span style="color: green">'''一个有意思的例子'''</span> == 一个涉及到“Event Loop”和“var 作用域”的例子。 对于以下代码: : <syntaxhighlight lang="JavaScript" highlight=""> for (var i = 0; i < 10; i++) { setTimeout(function() { console.log(i) }, 100 * i) } </syntaxhighlight> : 是否预期输出: : <syntaxhighlight lang="bash" highlight=""> 0 1 2 3 4 5 6 7 8 9 </syntaxhighlight> : 但,实际输出: : <syntaxhighlight lang="JavaScript" highlight=""> 10 10 10 10 10 10 10 10 10 10 </syntaxhighlight> 其原因有两点: # setTimeout 是一个任务,将在 for 所有循环完成之后执行; # i 由 var 定义:所有的 setTimeout 实际上都引用了“相同作用域里的同一个 i” —— 而它在所有循环之后其值为 10; 要与预期输出一致,有两种方式: # 通过使用“'''立即执行的函数表达式'''”来捕获 i: #: <syntaxhighlight lang="JavaScript" highlight=""> for (var i = 0; i < 10; i++) { (function(i) { setTimeout(function() { console.log(i) }, 100 * i) })(i) } </syntaxhighlight> #* 不能妄想通过 let、const 来捕获: #*: <syntaxhighlight lang="JavaScript" highlight=""> for (var i = 0; i < 10; i++) { setTimeout(function() { const v = i; console.log(i); }, 100 * i) } </syntaxhighlight> # '''使用 let 替换 var''': #: <syntaxhighlight lang="JavaScript" highlight=""> for (let i = 0; i < 10; i++) { (function(i) { setTimeout(function() { console.log(i) }, 100 * i) })(i) } </syntaxhighlight> #* var作用域或函数作用域 当用let声明一个变量,它使用的是词法作用域或块作用域。 == 参考 == === 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/>
返回至“
变量、作用域、闭包
”。
导航菜单
个人工具
登录
命名空间
页面
讨论
大陆简体
已展开
已折叠
查看
阅读
查看源代码
查看历史
更多
已展开
已折叠
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
笔记
服务器
数据库
后端
前端
工具
《To do list》
日常
阅读
电影
摄影
其他
Software
Windows
WIKIOE
所有分类
所有页面
侧边栏
站点日志
工具
链入页面
相关更改
特殊页面
页面信息