ESLint no-shadow 规则完全指南:消除变量遮蔽,让作用域边界清晰可读

发布时间:2026/9/12 5:34:53

ESLint no-shadow 规则完全指南:消除变量遮蔽,让作用域边界清晰可读 ESLint no-shadow 规则完全指南消除变量遮蔽让作用域边界清晰可读【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslintESLint 的no-shadow规则suggestion类型用于禁止局部变量遮蔽外层作用域中已声明的变量帮助开发者避免因同名变量嵌套而引发的代码误读与难以追踪的赋值 bug。本文以 ESLint 仓库中 no-shadow 规则文档 为主体结合 规则实现源码 与 完整测试用例系统讲解遮蔽shadowing的判定逻辑、全部配置项builtinGlobals、hoist、allow、ignoreOnInitialization及两个 TypeScript 专用选项的语义与取舍读完即可在 JavaScript 与 TypeScript 项目中精确配置并运用该规则。什么是变量遮蔽Shadowing变量遮蔽是指一个局部变量与它所在外层作用域中的某个变量同名。例如const a 3; function b() { const a 10; }此时b()内部的变量a遮蔽了全局作用域中的变量a。这种写法会造成两方面问题阅读代码时产生困惑看到a时难以立刻判断它到底指向哪一层作用域的声明外层变量无法被访问在被遮蔽的范围内外层同名变量完全不可达一旦需要读取外层值就只能通过改名或绕行方式处理。no-shadow规则的目标正是消除这类被遮蔽的变量声明从源头阻止同名变量跨作用域嵌套。规则详情违规与合规示例启用方式规则默认关闭可在配置中设为error或warn/*eslint no-shadow: error*/以下代码都会被判定为违规/*eslint no-shadow: error*/ const a 3; function b() { const a 10; // 遮蔽外层 a } const c function () { const a 10; // 遮蔽外层 a } function d(a) { a 10; // 参数 a 遮蔽外层 a } d(a); if (true) { const a 5; // 块级作用域内遮蔽外层 a } const f wrap(function f() {}); // 函数表达式名 f 遮蔽外层 f const C wrap(class C {}); // 类表达式名 C 遮蔽外层 C注意最后两个例子虽然函数/类表达式与赋值给它的变量同名通常被豁免详见下文初始化器豁免但当函数/类表达式不是直接作为初始化器而是被包在wrap(...)调用中时仍然会报告遮蔽。以下代码是合规的/*eslint no-shadow: error*/ const a 3; function b(c) { const d 10; // 不与外层任何变量同名 if (c) { const e 20; // 同样不冲突 } } /* * 函数与类的名称允许遮蔽变量 * 前提是该函数/类表达式作为变量的初始化器或默认值被赋值。 */ const f function f() {}; const g foo ? (bar || function g() {}) : baz; const { h function h() {} } obj; function qux(i function i() {}) {} const C class C {};其中最后一段对应源码中的初始化器豁免逻辑lib/rules/no-shadow.js中的isFunctionNameInitializerException()会检查内层变量是否为FunctionName且节点是FunctionExpression或ClassName且节点是ClassExpression并且该函数/类表达式必须是外层变量初始化器的顶层可求值表达式通过unwrapExpression递归展开||、、??与?:等逻辑/条件表达式后判断才会放行。Options全部配置项详解规则接收一个对象作为唯一选项包含以下属性属性类型默认值适用语言builtinGlobalsbooleanfalseJavaScript / TypeScripthoiststringfunctionsJavaScript / TypeScriptallowstring[][]JavaScript / TypeScriptignoreOnInitializationbooleanfalseJavaScript / TypeScriptignoreTypeValueShadowbooleantrueTypeScript onlyignoreFunctionTypeParameterNameValueShadowbooleantrueTypeScript only上述默认值可以从 规则源码中的defaultOptions得到印证。完整的配置示例{ no-shadow: [error, { builtinGlobals: false, hoist: functions, allow: [], ignoreOnInitialization: false }] }builtinGlobals是否拦截内置全局变量builtinGlobals默认为false。设为true后规则会禁止遮蔽Object、Array、Number等内置全局变量。违规示例{ builtinGlobals: true }/*eslint no-shadow: [error, { builtinGlobals: true }]*/ function foo() { const Object 0; }从源码实现看遮蔽检测通过astUtils.getVariableByName(scope.upper, variable.name)在外层作用域查找同名变量而shadowed.identifiers.length 0 || (builtinGlobals writeable in shadowed)这一条件允许规则在外层变量没有标识符节点即内置全局对象时也能在builtinGlobals开启的情况下将其识别为被遮蔽对象并抛出noShadowGlobal消息{{name}} is already a global variable.。测试中还覆盖了.d.ts声明文件中declare const遮蔽全局变量的场景见 tests/lib/rules/no-shadow.js。hoist控制声明前置遮蔽的判定hoist选项共五个取值决定了内层变量出现在外层声明之前时是否算作遮蔽functions默认—— 报告在外层函数声明之前出现的遮蔽all—— 报告在外层变量/函数声明之前出现的所有遮蔽never—— 永不报告外层变量/函数声明之前出现的遮蔽typesTypeScript 专用—— 报告在外层类型定义之前出现的遮蔽functions-and-typesTypeScript 专用—— 报告在外层函数与类型定义之前出现的遮蔽。hoist: functions默认违规示例/*eslint no-shadow: [error, { hoist: functions }]*/ if (true) { const b 6; // 在外层 function b 声明之前出现 → 报告 } function b() {}尽管if语句中的const b出现在外层函数声明之前它依然是违规的——因为函数声明会被提升hoisting规则认为内层声明遮蔽了函数。合规示例/*eslint no-shadow: [error, { hoist: functions }]*/ if (true) { const a 3; // 在外层 const a 之前出现 → 不报告 } const a 5;因为外层是变量声明const不会被提升到可访问状态内层声明并未真正遮蔽它。这一语义对应源码中的isInTdz()函数默认hoist: functions时仅当外层定义节点类型是FunctionDeclaration且内层声明位置在其之前才判定为遮蔽即外层声明处于暂时性死区TDZ之中时放行。hoist: all违规示例/*eslint no-shadow: [error, { hoist: all }]*/ if (true) { const a 3; // 在外层 const a 之前 → 报告 const b 6; // 在外层 function b 之前 → 报告 } const a 5; function b() {}hoist: never合规示例/*eslint no-shadow: [error, { hoist: never }]*/ if (true) { const a 3; // 都在外层声明之前 → 不报告 const b 6; } const a 5; function b() {}因为if语句中的const a、const b都出现在外层声明之前规则不再报告。在源码中isInTdz()针对hoist: never之外的取值才会进行 TDZ 判定因此hoist never时这类先内后外的同名声明不会被计入遮蔽。hoist: typesTypeScript 专用违规示例/*eslint no-shadow: [error, { hoist: types }]*/ type BarFoo 1; type Foo 1; // 类型参数 Foo 遮蔽了外层类型 Foohoist: functions-and-typesTypeScript 专用违规示例/*eslint no-shadow: [error, { hoist: functions-and-types }]*/ // types type BarFoo 1; type Foo 1; // 类型遮蔽 // functions if (true) { const b 6; } function b() {} // 函数遮蔽这两个 TypeScript 模式在源码中由isInTdz()配合TYPES_HOISTED_NODESTSInterfaceDeclaration与TSTypeAliasDeclaration实现当hoist为types时外层定义若不是这两种会被提升的类型节点就认定内层声明处于 TDZ 中从而放行functions-and-types则额外把FunctionDeclaration也排除在外。测试用例覆盖了type FooA 1与interface FooA {}遮蔽外层类型/接口的各种组合见 tests/lib/rules/no-shadow.js 中hoist: types与hoist: functions-and-types分组。allow按标识符名豁免allow接受一个标识符名称数组这些名字的遮蔽行为将被放行。常见用法是豁免回调风格代码中反复出现的resolve、reject、done、cb等名称。合规示例{ allow: [done] }/*eslint no-shadow: [error, { allow: [done] }]*/ import async from async; function foo(done) { async.map([1, 2], function (e, done) { done(null, e * 2) }, done); } foo(function (err, result) { console.log({ err, result }); });实现上isAllowed()直接检查allow.includes(variable.name)见 lib/rules/no-shadow.js命中则跳过该变量的遮蔽检测。ignoreOnInitialization容忍初始化期回调中的遮蔽ignoreOnInitialization默认为false。设为true时规则不再报告在变量初始化器中发生的遮蔽前提是被遮蔽变量尚处于未初始化状态。判定条件较为严格被遮蔽变量必须位于左侧即正在被初始化遮蔽变量必须位于右侧且声明在回调函数或IIFE立即执行函数表达式中。违规示例{ ignoreOnInitialization: true }/*eslint no-shadow: [error, { ignoreOnInitialization: true }]*/ const x x x; // 遮蔽变量 x 遮蔽了已初始化的外层 x → 仍报告因为这里的箭头函数x x立即被赋给x遮蔽变量会遮蔽已经初始化的外层x。合规示例{ ignoreOnInitialization: true }/*eslint no-shadow: [error, { ignoreOnInitialization: true }]*/ const x foo(x x) // 回调中的 x 遮蔽了尚未初始化的外层 x → 放行 const y (y y)() // IIFE 参数同理 → 放行其背后的设计理由是回调函数通常会在初始化过程中被调用当遮蔽变量实际被使用时外层被遮蔽变量尚未完成初始化因此不存在真正的访问冲突。源码中isInitPatternNode()会向上回溯找到包裹回调的CallExpression并确认该调用的右值范围落在外层变量声明VariableDeclarator.init、AssignmentPattern.right、for...in/of右侧表达式等之内同时要求变量作用域是函数表达式/箭头函数且其外层作用域正是被遮蔽变量所在作用域。测试还覆盖了const a [].find(a a)、const [a [].find(a true)] dummy、function func(a [].find(a true)) {}、for (const a of [].find(a true)) {}及链式map/filter/find等多种形态。ignoreTypeValueShadow忽略类型与值的同名遮蔽TypeScript默认为true。开启后规则忽略类型名与变量名相同的遮蔽。这通常是安全的在类型位置上如果没有typeof运算符就无法引用变量因此类型与值同名造成混淆的风险很小。合规示例{ ignoreTypeValueShadow: true }/*eslint no-shadow: [error, { ignoreTypeValueShadow: true }]*/ type Foo number; interface Bar { prop: number; } function f() { const Foo 1; const Bar test; }源码中的isTypeValueShadow()会对比两个变量的isValueVariable属性当内层是值变量而外层是纯类型或类型导入时判定为类型/值遮蔽并忽略反之如果关闭该选项false这类情况会照常报告。测试中以ignoreTypeValueShadow: false配置验证了类型导入与值变量同名的违规场景。ignoreFunctionTypeParameterNameValueShadow忽略函数类型参数的遮蔽TypeScript默认为true。函数类型的每个参数都会在函数类型作用域内创建一个值变量以便之后用typeof引用该参数类型type Func (test: string) typeof test; declare const fn: Func; const result fn(str); // typeof result string这意味着函数类型参数会遮蔽父作用域中的同名值变量let test 1; type TestType typeof test; // number type Func (test: string) typeof test; // 这里的 test 引用的是参数而非外层变量 declare const fn: Func; const result fn(str); // typeof result string如果你不在函数类型返回类型位置使用typeof运算符就可以安全地开启此选项。开启后默认以下代码合规/*eslint no-shadow: [error, { ignoreFunctionTypeParameterNameValueShadow: true }]*/ const test 1; type Func (test: string) typeof test;实现上isFunctionTypeParameterNameValueShadow()检查变量定义节点是否属于ALLOWED_FUNCTION_VARIABLE_DEF_TYPES集合包含TSFunctionType、TSMethodSignature、TSDeclareFunction、TSConstructSignatureDeclaration、TSConstructorType等类型相关的函数签名节点。测试用例覆盖了开启/关闭两种配置下函数类型参数遮蔽外层变量的大量组合。常见疑问为什么枚举成员与外层变量同名会被报告这不是 bug规则在按预期工作。原因在于枚举的一个容易被忽略的特性枚举成员会在枚举自身作用域内引入一个变量从而允许成员无需限定符即可被引用。看这个例子const A 2; enum Test { A 1, B A, } console.log(Test.B); // 会打印什么直观上可能会认为是2因为外层变量A的值是2但实际打印的是1即Test.A的值。因为在枚举内部B A会被解释为B Test.A。正是由于这种行为枚举成员A遮蔽了外层变量声明A因此规则会如实报告。相应的测试用例也出现在 tests/lib/rules/no-shadow.js 中const A 2; enum Test { A 1, B A }被断言为违规并给出第 2 行的noShadow错误。此外源码中isDuplicatedEnumNameVariable()会跳过枚举自身名字在枚举作用域内产生的重复变量避免误报。与 no-redeclare 的区别需要特别注意遮蔽shadowing特指两个相同的标识符位于不同的、嵌套的作用域中而重复声明redeclaration指两个相同的标识符位于同一个作用域中后者由独立的 no-redeclare 规则 负责。例如{ let a; } let a;这种跨块声明属于遮蔽范畴受no-shadow管辖而同一作用域内的重复声明则由no-redeclare处理——两者的分工边界清晰配置时不应混淆。相关规则与实战配置建议no-shadow有一个强相关规则 no-shadow-restricted-names它专门禁止遮蔽undefined、NaN、Infinity、arguments、eval以及可选globalThis等受限全局名称且默认为recommended随推荐集启用。相比之下no-shadow覆盖面更广但默认关闭。实践中的常见搭配{ rules: { no-shadow: [error, { builtinGlobals: true, hoist: functions, allow: [resolve, reject, done, cb, err] }] } }配置建议新项目 / 追求强约束直接启用no-shadow并将builtinGlobals设为true以一并拦截Object、Array等内置全局的遮蔽存量代码渐进改造先用默认hoist: functionsallow白名单放行回调惯用名逐步消除报告后再收紧TypeScript 项目保持ignoreTypeValueShadow与ignoreFunctionTypeParameterNameValueShadow的默认值true通常无需改动只有当类型位置滥用typeof导致真实混淆时才考虑将其关闭。小结no-shadow是 ESLint 中约束作用域整洁度的核心规则之一它基于 eslint-scope 的作用域树在Program:exit时遍历所有子作用域见 lib/rules/no-shadow.js 中的checkForShadows与栈式遍历逻辑逐变量检测是否遮蔽外层同名声明并输出noShadow{{name}}is already declared in the upper scope on line {{shadowedLine}} column {{shadowedColumn}}.或noShadowGlobal消息。通过builtinGlobals、hoist、allow、ignoreOnInitialization以及两个 TypeScript 专用选项的组合团队可以精确平衡消除遮蔽带来的可读性收益与回调、初始化器等惯用法带来的噪音让作用域边界在代码审查中一目了然。【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/9/12 5:34:53

大模型幻觉治理:根因分析与全链路防控体系实践

这里写自定义目录标题欢迎使用Markdown编辑器一、幻觉问题为什么如此顽固二、幻觉的分类:不同类型,不同解法三、源头治理:把幻觉扼杀在训练和配置环节四、RAG 链路治理:让模型"有据可依"五、生成后校验:给答…

2026/9/12 6:19:58

坭兴陶水杯工艺解析与选购指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/12 6:19:58

阿里云OSS命令行工具使用指南与实战技巧

1. OSS命令行工具入门指南阿里云OSS(Object Storage Service)作为国内主流云存储服务,其命令行工具是开发者日常管理文件的利器。相比图形界面,命令行工具在批量操作、自动化脚本和服务器环境中展现出明显优势。我最初接触OSS命令…

2026/9/12 6:19:58

HTML零基础入门与蓝桥杯Web开发备赛指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/12 6:19:58

过滤器思维:从代码filter到布隆过滤器与Wireshark的跨领域应用

今天想认真聊一聊 Filter(过滤器)。这个词在编程、网络调试、音视频处理、工业设备选型里都会出现,但绝大多数人对它的理解都停留在"过滤一下"这种直觉层面。写代码的人提到 filter 就想到数组挑数据,网络工程师想到 Wi…

2026/9/12 6:19:57

语音指令分类模型训练与优化实践

1. 语音指令分类模型训练概述语音指令分类是当前人机交互领域的关键技术,它能将用户的语音输入转化为机器可理解的指令类别。我在智能家居和车载系统项目中多次应用这项技术,发现一个高效的分类模型能显著提升用户体验。基于机器学习的语音指令分类&…

2026/9/12 6:14:57

微信文件过期恢复与预防全攻略

1. 微信文件过期问题的痛点解析作为每天都要处理大量微信文件的打工人,最崩溃的瞬间莫过于看到那个灰色提示框:"文件已过期或已被清理"。上周我就差点因为这个丢了重要合同——客户发来的报价单突然打不开,而deadline就在两小时后。…

2026/9/12 2:05:33

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/12 3:55:12

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/9 16:31:09

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/12 0:04:17

MATLAB仿生优化框架:长鼻浣熊算法多策略融合实现

简介:本资源是一份面向智能优化算法研究者与MATLAB初学者的仿生智能算法实践代码包,聚焦于长鼻浣熊优化算法(COA)的多策略改进与性能验证。针对传统COA易陷局部最优、收敛精度不足等问题,作者融合Circle映射初始化提升…

2026/9/12 0:04:17

【JAVA毕设源码分享】基于 JavaWeb 的校园一卡通管理系统的设计与实现 基于 JavaWeb 的校园卡业务管理系统(程序+文档+代码讲解+一条龙定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/9/12 0:04:17

【JAVA毕设源码分享】基于 Java 的图书馆借阅管理平台的搭建与实现 基于 Java 的图书馆综合管理系统(程序+文档+代码讲解+一条龙定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/9/10 12:32:02

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/10 15:19:50

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/10 15:49:53

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码