必须区分三类全局污染:Program级var声明、隐式赋值(a=1)、显式window.xxx挂载;ESM/CJS模块需结合导入关系分析;替换var为let/const会破坏typeof检测、for循环闭包及UMD环境判断,且Babel默认不转顶层var。

直接改变量名不解决全局泄露,反而可能让问题更隐蔽;必须先识别作用域污染路径,再按声明层级分批处理——全局挂载的 var、未声明直接赋值、window.xxx 显式挂载这三类必须区别对待。
怎么识别真正污染全局的变量声明
不能只看是不是 var,得结合作用域链和执行上下文判断:
-
var在函数外(即Program级作用域)声明 → 会挂到window或globalThis,属于高危泄露 - 没用
var/let/const直接赋值(如a = 1)→ 无论在哪都隐式挂全局,AST 中是AssignmentExpression且左值为Identifier,且父节点不是VariableDeclarator -
window.xxx = ...或globalThis.xxx = ...→ 属于显式挂载,需单独匹配MemberExpression节点,且object.name是'window'或'globalThis' - ESM 模块顶层的
var不会挂全局,但 CJS 的module.exports.xxx = ...可能被其他模块当全局用,这类要查导入关系,不能单靠 AST 判断
为什么不能统一把 var 替换为 let/const
替换后行为变化比想象中大,尤其在非严格模式下:
- 全局
var a = 1→let a = 1后,typeof a从'number'变成'undefined',很多老代码依赖typeof xxx === 'undefined'做兜底,会崩 -
for (var i = 0; i console.log(i)) }→ 改成let i后输出 0–9,原逻辑是输出 10 个 10,语义已变 - 某些 UMD 包靠
var全局声明做环境检测(如if (typeof module !== 'undefined')),改成const后该分支永远不进 - Babel 默认不转换顶层
var为let,因为目标环境(如 IE11)根本不支持let声明提升规则,生成代码会直接报错
重构时如何安全隔离命名冲突
冲突往往来自同名变量跨作用域“穿透”,比如 A 文件 var utils = {...},B 文件又 var utils = [],实际运行时后者覆盖前者。解法不是重命名,而是加作用域封装:
- 对无模块系统的脚本,用 IIFE 包一层:
(function() { /* 原内容 */ }).call(this),确保this指向window且内部变量不出去 - 遇到多个文件共用同一变量名(如都叫
config),用@babel/plugin-transform-modules-commonjs强制转成 CJS,再通过require显式导入导出,切断隐式共享 - TS 项目里若存在
declare const xxx和实际var xxx同名,必须先删掉declare,否则 Babel 生成代码时会忽略真实声明,导致运行时报xxx is not defined - 不要用
path.replaceWith(t.variableDeclaration('const', [...]))粗暴替换整条语句——如果原var a, b = 1中a未赋值、b有赋值,必须拆成两条声明,否则a会被误标为let
最麻烦的不是识别,是验证:改完之后,得跑一遍所有含 eval、with、Function 构造函数的代码路径,这些地方的变量查找是动态的,AST 静态分析根本覆盖不到。










