多次引入同一JavaScript模块是否共享同一个词法作用域?
关于多次引入同一JS模块是否共享词法作用域的解答
核心结论
你观察到的修改共享现象和词法作用域共享没有直接关系,本质是CommonJS模块规范的默认缓存机制导致所有引入方拿到的是同一个模块导出实例。
原理说明
- Node.js的CommonJS模块加载机制规定:同一文件路径的模块只会在首次被require时执行一次,执行后得到的
module.exports对象会被存入内存缓存,后续所有对该模块的require请求都会直接返回缓存里的同一个对象,不会重新执行模块代码。 - 你的代码中存在笔误:你定义的模块文件名为
fruits.js,但client1、client2里写的引入路径是./closure.js,忽略这个笔误的前提下,模块最终导出的是set()函数执行后的返回值:这个返回的对象包含的四个get/set方法都是闭包,分别绑定了模块顶层的fruit变量、以及set函数执行时创建的flower局部变量。 - 由于缓存机制,client1.js和client2.js拿到的
fruitsnflowers是完全同一个对象,调用set方法修改的是同一个词法环境里的变量,自然client2读取到的就是client1修改后的结果。
代码验证
你提供的测试代码逻辑如下:
// fruits.js let fruit = "apples"; const set = () => { let flower = "lily"; const setfruit = (newfruit) => { fruit = newfruit; } const getfruit = () => { return fruit; } const setflower = (newflower) => { flower = newflower; } const getflower = () => { return flower; } return { setfruit, getfruit, setflower, getflower } } module.exports = set(); // client1.js const fruitsnflowers = require("./fruits.js") const execute = () => { console.log(fruitsnflowers.getfruit()) console.log(fruitsnflowers.getflower()) fruitsnflowers.setfruit("coconut") fruitsnflowers.setflower("hibiscus") } module.exports = execute; // client2.js const fruitsnflowers = require("./fruits.js") const execute = () => { console.log(fruitsnflowers.getfruit()) console.log(fruitsnflowers.getflower()) fruitsnflowers.setfruit("papaya") fruitsnflowers.setflower("lotus") } module.exports = execute; // driver.js const client1 = require("./client1.js") const client2 = require("./client2.js") client1() client2()
执行node driver.js的输出完全符合缓存机制的预期:
apples lily coconut hibiscus
扩展:如何避免实例共享
如果需要每次引入模块都拿到全新的独立实例,只需要把模块导出内容从执行后的实例改成工厂函数即可:
// 调整fruits.js的导出逻辑 module.exports = set; // 导出函数本身,不执行 // client1.js中使用时先调用生成独立实例 const createFruitsModule = require("./fruits.js") const fruitsnflowers = createFruitsModule();
这样client1和client2的实例就完全隔离,不会互相影响。
内容的提问来源于stack exchange,提问作者akash khamkar
相关产品推荐
相关产品推荐

