编辑:为了想出一个解决方案,我编辑了这篇文章,以便更清楚地解释我想要完成的任务。
我正在尝试重新发明轮子,用最少的代码创建跨平台异步模块加载系统。
理想情况下,这应该适用于任何ES5运行时引擎,但是主要的目标是node.js和浏览器。
我想要完成的是使用setter创建一个全局对象,从中设置的对象是模块内容。Node.js用module.exports = {}实现了这一点,我正在尝试复制这种行为。
我遇到的问题很有趣,因为全局设置程序没有为模块文件名和导出的对象创建1:1的映射。
第一次尝试:
到目前为止,我已经尝试将setter绑定为特定于特定函数调用。它总是求助于加载的最后一个module。我认为,通过将setter封装在一个闭包中,它会将module参数保存在调用堆栈中,但我错了--因为setter更改了。
是一种改进的解决方案,但还没有完全实现:
我还尝试使用导出对象中定义的name属性来创建此映射,但事实证明它是无效的,易于规避。也就是说,导出一个与其所做的不相符的名称,并且可以有意或无意地覆盖系统中的其他模块。
,下面是一些示例代码:
let exporter = {}
global.exporter = exporter
const imports = function(module, callback) {
return new (function(module, callback) {
Object.defineProperty(exporter, 'exports', {
enumerable: false,
configurable: true,
set: function(exportFile) {
console.log('Setting export file:', exportFile.name, ':', module)
callback(exportFile)
},
})
console.log('loading module: ', module)
require(module)
})(module, callback)
}在模块文件中使用setter的:
exporter.exports = {
name: 'File1',
}使用新导入.的示例代码
function load(name) {
imports(__dirname + '/modules/' + name, function(exportFile) {
console.log('Module loaded: ', exportFile.name)
})
}
load('1') // instant
load('2') // 2 second timeout
load('3') // 1 second timeout输出:
loading module: .../modules/1
Setting export file: File1 : .../modules/1
Module loaded: File1
loading module: .../modules/2
loading module: .../modules/3
Setting export file: File3 : .../modules/3
Module loaded: File3
Setting export file: File2 : .../modules/3
Module loaded: File2我很感激任何能解决这个上下文问题的帮助!
我也愿意接受任何其他建议来完成同样的任务,而不使用任何特定于节点的建议,因为我计划使这个跨平台兼容。
发布于 2019-03-08 16:10:33
我想要完成的是使用setter创建一个全局对象,从中设置的对象是模块内容。Node.js用
module.exports = {}实现了这一点,我正在尝试复制这种行为。
您的问题是,您确实是使用全局对象的。由于模块是异步加载的,所以当模块执行时,全局对象可能处于不正确的状态。可能有一种方法可以在require调用之后重置全局对象,从而使特定示例工作良好,但有些情况无法涵盖,而且您将在很长一段时间内使用bug。
虽然module看起来像一个全局对象,但实际上它是为每个模块重新创建的一个对象。文档非常明确地说明了这一点:
Node.js帮助提供一些实际特定于模块的全局变量,如:
module和exports对象。__filename和__dirname,包含模块的绝对文件名和目录路径。为模块提供可以修改的独立对象将使代码更简单。
在我上面引用的部分文档中,您会发现:
在执行模块的代码之前,Node.js将其包装为如下所示的函数包装: (函数(导出、要求、模块、__filename、__dirname) { //模块代码实际驻留在这里});
您可以从其中提取一页,并有一个包装器,如:
(function (exporter, ...) {
// Module code here...
});下面是一个例子:
const source = `
exporter.exports = {
SomeVar: "Some Value",
};
`;
function wrapInFunction(source) {
return `(function (exporter) { ${source} })`;
}
const exporter = {
exports: {},
};
eval(wrapInFunction(source))(exporter);
console.log(exporter);这里有一个关于eval使用的说明。你可能听说过“巫术是邪恶的”。就目前而言,这是真的。这句话是为了提醒人们,const x = /* value from some user input */; eval('table.' + x );既没有必要(因为您可以执行table[x]),也很危险,因为用户输入是原始计算的,并且您不信任用户输入运行任意代码。用户会将x设置为做坏事的东西。在某些情况下,仍然需要使用eval,比如这里的情况。在浏览器中,您可以通过将源代码插入script并侦听load事件来避免使用load,但在安全性方面您没有得到任何好处。再说一遍,这是平台特有的。如果您在Node.js中,您可以使用模块,但是它附带一个免责声明:“ vm模块不是一种安全机制。不要使用它来运行不受信任的代码.”,而且它也是特定于平台的。
顺便说一下,您当前的代码不是跨平台的。您的代码依赖于require调用,该调用仅在某些平台上可用。(在没有加载额外模块的情况下,浏览器中明显不存在这种情况。)我怀疑您把它作为稍后开发功能的占位符,但我想我还是要提到它,因为跨平台支持是您的目标之一。
https://stackoverflow.com/questions/54760810
复制相似问题