在node.js中导入名称空间
我有一些允许合并命名空间的函数,当模块包含很多的函数(我暴露了一个API与几十个combinators)非常类似于import
它会产生大量的var f = target.f;
为每个项目从出口
function getNamespace(name, exports){ var output=''; for(var item in exports){ output += 'var ' + item + ' = '+name+ '.'+item + ';'; } return output; }
和用法:
var paco = require('./paco.js'); eval(paco.getNamespace('paco', paco)); // instead of paco.between(paco.start(),paco.content(),paco.end()) between(start(), content(), end())
问题 :
我有一种方法可以将eval“隐藏”到某个函数中吗? 我不希望既不改变全局命名空间,也不要调用vm.runInThisContext
,只需要在调用函数类似于require
之后,将一些局部variables添加到调用上下文中。
我的意思是我需要类似的东西
import('./paco'); // this should work like this // var paco = require('./paco.js'); // var between = paco.between;
但在调用范围内没有全局变化而没有eval。
tl; dr:不。
为了理解为什么这是不可能的,了解Node在幕后做了什么很重要。
假设我们在test.js中定义一个函数:
function foo() { var msg = 'Hello world'; console.log(msg); }
在传统的浏览器JavaScript中,只要将该函数声明放在一个文件中,并用<script>
标签将文件拉入,将导致foo
在全局范围内声明。
当你require()
一个文件时,节点会做不同的事情。
-
首先,它根据一组有些复杂的规则确定应该加载哪个文件。
-
假设文件是JS文本(不是编译的C ++插件),Node的模块加载器调用
fs.readFileSync
来获取文件的内容。 -
源文本被封装在一个匿名函数中。 test.js最终会看起来像这样:
(function (exports, require, module, __filename, __dirname) { function foo() { var msg = 'Hello world'; console.log(msg); } });
对于曾经用匿名函数expression式来包装自己的代码的人来说,这应该很熟悉,以防止variables在浏览器中泄漏到全局范围中。 它也应该开始有意义的Node中的“魔术”variables的工作。
-
模块加载器将步骤3中的源文本
eval
1 ,然后调用生成的匿名函数,传入新的exports
对象。 (请参阅Module#_compile
。)1 – 真的
vm.runInThisContext
,这是像eval
除了它没有访问调用者的范围 -
匿名包装函数返回后,
module.exports
的值被内部caching,然后由require
返回。 (随后调用require()
返回caching的值。)
正如我们所看到的,Node通过简单地将文件的源代码包装在一个匿名函数中来实现“模块”。 因此,将函数“导入”模块是不可能的,因为JavaScript不能直接访问函数的执行上下文 – 也就是收集函数的局部variables。
换句话说,我们没有办法循环一个函数的局部variables,也没有办法让我们创build任意名称的局部variables,就像我们可以用一个对象的属性一样。
例如,用对象,我们可以做这样的事情:
var obj = { key: 'value' }; for (var k in obj) ... obj[propertyNameDeterminedAtRuntime] = someValue;
但是没有对象表示函数的局部variables,这对于我们将对象的属性(如模块的输出)复制到函数的局部范围中是必要的。
你所做的是使用eval
在当前范围内生成代码。 生成的代码使用var
关键字声明局部variables,然后将其注入到调用eval
的作用域中。
无法将eval
调用移出模块,因为这样做会导致将注入的代码插入到不同的作用域中。 请记住,JavaScript具有静态作用域,因此只能以词法forms访问包含函数的作用域。
另一个解决方法是使用,但你应该避免with
。
with (require('./paco.js')) { between(start(), content(), end()) }
不应该用于两个原因:
- 它绝对会杀死性能,因为V8无法执行名称查找优化。
- 它被弃用,并严格禁止模式。
说实话,我build议,而不是做一些棘手的eval
,做你的未来维护者一个忙,只是按照标准的做法,分配一个模块的出口本地variables。
如果你经常input它,把它作为一个单一的字符名称(或使用更好的编辑器)。
不可以。从外部模块修改本地范围是不可能的。 原因是,当在外部模块中调用eval时,其上下文将是外部模块,而不是需要模块的作用域。
另外, vm.runInThisContext 不能访问本地范围,所以也不会帮助你。
根据这个答案node.js标准模块的全局variables? 在浏览器中有与global
对象相同的window
。 所以你可以添加键到那个对象
function getNamespace(exports) { for(var item in exports){ global[item] = exports[item]; } }
并将其用作:
paco.getNamespace(paco);
根本不需要评估。