——声明、定义与函数作用域组织)
教程【免费下载链接】jstipsThis is about useful JS tips!项目地址https://gitcode.com/gh_mirrors/js/jstips点击查看免费下载变量提升Hoisting是 JavaScript 中最容易让新手困惑、却也最能解释各种反直觉运行时行为的机制之一。本篇基于 JS Tips 项目第 11 条技巧_posts/en/javascript/2016-01-11-hoisting.md系统讲解声明declaration被提升、定义definition不被提升这一核心规律并通过逐行拆解原始示例帮助你在阅读完本文后能准确预判var、函数声明与函数表达式在作用域内的真实行为进而写出结构清晰、可预测的函数代码。一、核心规律声明提升定义不提升理解变量提升关键在于区分 JavaScript 中两个常被混用的概念声明Declaration让系统知道某个变量存在例如var definedLater;。声明会被提升hoisted到所在作用域的顶部。定义Definition给变量赋值例如definedLater I am defined!;。定义不会被提升——即使你把声明和定义写在同一行如var x 1;被提升的也只有其中的声明部分赋值动作仍停留在原位置执行。原技巧给出的总纲只有一句话变量声明和函数定义会被提升到顶部变量定义不会即使你在一行内同时声明并定义变量。这一规律适用于var声明的变量与函数声明function declaration是理解下述所有示例的钥匙。二、逐行拆解 doTheThing()五种场景的运行时真相原文档通过一个doTheThing()函数集中展示了五种不同写法的运行结果这里完整保留并逐一剖析function doTheThing() { // ReferenceError: notDeclared is not defined console.log(notDeclared); // Outputs: undefined console.log(definedLater); var definedLater; definedLater I am defined! // Outputs: I am defined! console.log(definedLater) // Outputs: undefined console.log(definedSimulateneously); var definedSimulateneously I am defined! // Outputs: I am defined! console.log(definedSimulateneously) // Outputs: I did it! doSomethingElse(); function doSomethingElse(){ console.log(I did it!); } // TypeError: undefined is not a function functionVar(); var functionVar function(){ console.log(I did it!); } }场景一访问从未声明的变量 → ReferenceErrorconsole.log(notDeclared); // ReferenceError: notDeclared is not definednotDeclared在整个作用域中从未出现过var声明因此 JavaScript 引擎无法为该标识符建立绑定访问它直接抛出ReferenceError。提升机制只对已声明的标识符生效这是它与场景二、三最本质的区别。场景二声明与定义分离 → 提升后输出 undefinedconsole.log(definedLater); // Outputs: undefined var definedLater; definedLater I am defined! console.log(definedLater); // Outputs: I am defined!var definedLater;的声明被提升到函数顶部相当于在console.log(definedLater)之前引擎已悄悄执行了变量已存在、值为undefined的初始化。因此第一次访问不会报错而是打印undefined当赋值语句真正执行后再次访问才得到字符串I am defined!。从引擎行为看上述代码等价于function doTheThing() { var definedLater; // 声明被提升到顶部初始值为 undefined console.log(definedLater); // undefined definedLater I am defined! // 赋值仍留在原位执行 console.log(definedLater); // I am defined! }场景三声明与定义同行 → 定义不提升依旧先打印 undefinedconsole.log(definedSimulateneously); // Outputs: undefined var definedSimulateneously I am defined! console.log(definedSimulateneously); // Outputs: I am defined!这是本技巧最反直觉、也最值得记住的一点即使var definedSimulateneously I am defined!写在同一行提升的也只是var definedSimulateneously;这半句赋值 I am defined!仍在原位。所以第一行输出依然是undefined赋值执行后才变为I am defined!。同一行声明并定义并不能让变量在提升后立刻拥有值。场景四函数声明 → 整体提升可提前调用doSomethingElse(); // Outputs: I did it! function doSomethingElse(){ console.log(I did it!); }函数声明function declaration与var变量声明不同它不仅把名字提升到作用域顶部连函数体定义一起整体提升。因此即使在定义语句之前调用doSomethingElse()也能正常执行并打印I did it!。场景五函数表达式 → 仅变量名被提升调用时报 TypeErrorfunctionVar(); // TypeError: undefined is not a function var functionVar function(){ console.log(I did it!); }var functionVar function(){...}的本质是变量声明 函数表达式赋值。按照声明提升、定义不提升的规律提升到顶部的只有var functionVar;此时functionVar的值是undefined。在赋值尚未执行的时刻调用functionVar()相当于对undefined做函数调用于是抛出TypeError: undefined is not a function。这正是场景三在函数形态下的复现也是函数声明 vs 函数表达式在提升行为上的关键分水岭。三、从仓库佐证看声明机制声明是作用域绑定的前提变量提升不是把代码物理移动到顶部而是 JavaScript 在进入一个作用域时先完成对该作用域内所有var声明与函数声明的绑定注册再开始逐条执行语句。这一理解与仓库第 47 条技巧_posts/en/javascript/2016-02-16-basics-declarations.md相互印证。该技巧演示了复合声明与赋值的展开规则var y, x y 1 // var x; var y; x y 1 console.log(-- 1:, x ${x}, y ${y}) //-- 1: x 1, y 1可以看到var x y 1会被拆解为两个var声明 两个赋值这与本技巧声明与定义分离的规律完全一致声明负责建立绑定赋值负责填充值。该文档还通过 IIFE 演示了未用var声明的变量如var x y 2中的y会泄漏到全局作用域——因为y根本没有被声明自然谈不上被提升到闭包内部这正是声明是作用域绑定前提的负面例证。四、var 与 let提升机制的边界在哪里仓库第 59 条技巧_posts/en/javascript/2016-08-25-keyword-var-vs-let.md从另一个角度划出了提升机制的边界var声明的变量作用域是函数作用域或全局声明会被提升访问未赋值变量得到undefinedlet声明的变量作用域是块级作用域不会像var那样提升到整个块——在声明语句之前访问会抛出ReferenceError即暂时性死区 TDZ。{ console.log(c); // undefined. Due to hoisting var c 2; } { console.log(b); // ReferenceError: b is not defined let b 3; }对比可见var c因提升而可被提前访问值为undefined而let b在声明前访问直接报错。该技巧还指出let不应被视为var的全面替代品——var仍然是变量贯穿整个函数这一意图的有效信号let只是在需要块级作用域时提供了更精确的选项。理解这一点有助于你在组织作用域时按需选择关键字。五、提升在实战中的经典陷阱循环中的闭包提升不仅影响单一函数内的访问顺序还会在循环与闭包组合时制造经典陷阱。仓库第 76 条技巧_posts/en/javascript/2017-06-14-closures-inside-loops.md展示了这一现象var funcs []; for (var i 0; i 3; i) { funcs[i] function() { console.log(i value is i); }; } for (var k 0; k 3; k) { funcs[k](); } // 实际输出 // i value is 3 // i value is 3 // i value is 3循环中var i的声明被提升到函数作用域顶部三个闭包共享同一个i绑定循环结束后i停在 3因此三个函数都打印3。该技巧给出的两种解法恰好对应两条提升边界用立即调用包裹按值传入i对应 IIFE 技巧见_posts/en/javascript/2016-01-25-Using-immediately-invoked-function-expression.md改用let i让i成为每次迭代的独立绑定for (let i 0; i 3; i) { funcs[i] function() { console.log(i value is i); } } // 输出0, 1, 2可见只要清楚var提升到函数作用域、let绑定到块/迭代这类陷阱的原因与解法都一目了然。六、最佳实践用提升规律组织函数作用域原技巧在结尾给出了三条可直接落地的编码建议这也是本篇文章的最终结论把所有var变量声明放在函数作用域顶部由于声明必然被提升与其让读者在函数中部意外发现变量不如主动在顶部集中声明让变量来自哪个作用域一目了然在使用变量之前完成定义赋值避免依赖提升后恰好是 undefined的隐式行为保证阅读顺序即执行顺序把函数定义放在作用域底部利用函数声明整体提升的特性让函数定义不挡路代码主体专注于业务逻辑需要时可随时调用底部定义的函数。按此组织doTheThing()可以重构为顶部集中var声明中部放业务逻辑与赋值底部放函数声明。这样的代码无论对读者还是对调试者作用域边界都清晰可辨。结语变量提升的本质是声明先注册、赋值后执行的两阶段模型var声明与函数声明被提升而赋值与函数表达式不被提升。掌握这一规律你不仅能准确解释本文示例中的undefined、ReferenceError与TypeError三种不同结果还能预判循环闭包、作用域泄漏等实战问题。将声明置顶、先定义后使用、函数定义置底作为日常编码习惯函数作用域的组织将变得井然有序。仓库中同主题的 Basics declarations、var vs let 与 Closures inside loops 提供了更多可继续深挖的实战示例。赞分享教程【免费下载链接】jstipsThis is about useful JS tips!项目地址https://gitcode.com/gh_mirrors/js/jstips点击查看免费下载相关推荐JavaScript 变量提升Hoisting完全指南声明与定义、函数提升与作用域组织实战JavaScript 变量提升Hoisting完全指南声明与定义、函数提升与作用域组织实战 导读 变量提升Hoisting是 JavaScript 中教程You Dont Know JS俄语版深入理解 JavaScript 变量提升Hoisting——声明、赋值与函数优先机制You Dont Know JS俄语版深入理解 JavaScript 变量提升Hoisting——声明、赋值与函数优先机制 导读 变量提升Hoi教程文档深入理解 JavaScript 提升Hoisting从 var 到 let、const 与函数声明的完整指南深入理解 JavaScript 提升Hoisting从 var 到 let、const 与函数声明的完整指南 Hoisting提升是 JavaScri教程文档创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考