在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()一个文件时,节点会做不同的事情。

  1. 首先,它根据一组有些复杂的规则确定应该加载哪个文件。

  2. 假设文件是​​JS文本(不是编译的C ++插件),Node的模块加载器调用 fs.readFileSync来获取文件的内容。

  3. 源文本被封装在一个匿名函数中。 test.js最终会看起来像这样:

     (function (exports, require, module, __filename, __dirname) { function foo() { var msg = 'Hello world'; console.log(msg); } }); 

    对于曾经用匿名函数expression式来包装自己的代码的人来说,这应该很熟悉,以防止variables在浏览器中泄漏到全局范围中。 它也应该开始有意义的Node中的“魔术”variables的工作。

  4. 模块加载器将步骤3中的源文本eval 1 ,然后调用生成的匿名函数,传入新的exports对象。 (请参阅Module#_compile 。)

    1 – 真的vm.runInThisContext ,这是像eval除了它没有访问调用者的范围

  5. 匿名包装函数返回后, 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()) } 

不应该用于两个原因:

  1. 它绝对会杀死性能,因为V8无法执行名称查找优化。
  2. 它被弃用,并严格禁止模式。

说实话,我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); 

根本不需要评估。