如何避免客户端页面构建器重载script时出现Identifier已声明错误
问题根本原因
script标签插入DOM执行后,顶层声明的变量、函数会直接写入全局作用域,删除script标签本身只会移除DOM节点,已经写入全局作用域的内容不会被自动回收,再次加载同一份脚本时就会触发重复声明的报错。
可行解决方案
- 方案一:改造脚本避免顶层全局声明
所有脚本逻辑包裹在IIFE(立即执行函数)或者块级作用域内,所有变量都为局部作用域变量,不会残留到全局环境:
如果确实需要对外暴露方法,统一挂载到自定义的命名空间下,每次加载脚本前先重置该命名空间即可:// 示例:IIFE包裹后无全局变量残留 ;(function() { // 此处声明的let/const/function都只在函数内部生效 let pageConfig = {id: 1} function initPage() {} initPage() })()// 加载脚本前先清空命名空间 window.CustomModule = window.CustomModule || {} Object.keys(window.CustomModule).forEach(key => delete window.CustomModule[key]) // 脚本内部写法 window.CustomModule.pageConfig = {id:1} window.CustomModule.initPage = () => {} - 方案二:主动追踪清理新增全局变量
无法修改脚本内容的场景下,可在脚本加载前后快照全局属性,追踪脚本新增的全局变量,移除脚本时同步删除:- 脚本插入DOM前,采集当前
window的所有可枚举属性名存入数组 - 脚本执行完成后,再次采集window属性,对比得到本次脚本新增的属性名,和当前script实例绑定存储
- 用户移除script时,遍历存储的新增属性名,执行
delete window[属性名]清除
注意:顶层用
let/const声明的变量不会挂载到window上,这类变量无法通过delete清除,该场景需要使用沙箱方案。 - 脚本插入DOM前,采集当前
- 方案三:沙箱隔离脚本执行
允许用户上传任意脚本的场景推荐使用沙箱完全隔离作用域,从根源避免全局污染:- 用隐藏iframe作为沙箱:脚本放到iframe内执行,需要和主页面通信用postMessage,移除脚本时直接销毁iframe,所有执行上下文会被完全清除,不会残留任何主页面全局变量。
- 用JS沙箱:基于Proxy实现轻量沙箱,拦截脚本所有全局变量的访问和修改,所有声明的变量都存在沙箱内部上下文,销毁沙箱时直接清空上下文对象即可,不会影响主页面全局。
- 方案四:临时兼容方案
紧急场景下可将脚本内顶层的let/const声明替换为var,var允许重复声明不会抛出语法错误,该方案仅作为临时救急使用,重复加载时变量值会被覆盖,可能导致逻辑异常。
内容的提问来源于stack exchange,提问作者ronaldtgi
相关产品推荐
相关产品推荐

