node.js中的命名空间导入

时间:2013-08-19 13:28:38

标签: javascript node.js namespaces

我有一些允许合并命名空间的函数,当模块包含很多函数时,它与import非常相似(我公开了一个带有几十个组合器的API) 它为导出

中的每个项目生成大量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

我的意思是我需要像

这样的东西
import('./paco'); 
// this should work like this
// var paco = require('./paco.js');       
// var between = paco.between;

但没有全局变异,也没有调用范围内的eval。

3 个答案:

答案 0 :(得分:2)

tl;博士:没有。

为了理解为什么这是不可能的,了解Node在幕后做什么很重要。

假设我们在test.js中定义了一个函数:

function foo() {
    var msg = 'Hello world';
    console.log(msg);
}

在传统的浏览器JavaScript中,只需将该函数声明放在一个文件中并使用<script>标记拉入该文件,就会导致foo在全局范围内声明。

当你require()一个文件时,

Node会以不同的方式做事。

  1. 首先,它根据a somewhat complex set of rules确切确定应加载哪个文件。

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

  3. 源文本在匿名函数中为wrapped。 test.js最终会看起来像这样:

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

    对于曾经在匿名函数表达式中包装自己的代码以防止变量在浏览器中泄漏到全局范围内的任何人来说,这应该是熟悉的。它也应该开始理解Node中的“神奇”变量是如何工作的。

  4. 模块加载器eval s 1 来自步骤3的源文本,然后调用生成的匿名函数,传入一个新的exports对象。 (见Module#_compile。)

    1 - 真的vm.runInThisContext,类似于eval,除了它无法访问来电者的范围

  5. 匿名包装函数返回后,module.exports的值在内部缓存,然后由require返回。 (对require()的后续调用将返回缓存的值。)

  6. 正如我们所看到的,Node通过简单地将文件的源代码包装在匿名函数中来实现“模块”。因此,不可能将函数“导入”模块,因为JavaScript不能直接访问函数的execution context - 即函数局部变量的集合。

    换句话说,我们无法循环遍历函数的局部变量,也没有办法让我们用任意名称创建局部变量,就像我们可以使用对象的属性一样。

    例如,对于对象,我们可以执行以下操作:

    var obj = { key: 'value' };
    for (var k in obj) ...
    obj[propertyNameDeterminedAtRuntime] = someValue;
    

    但是没有对象表示函数的局部变量,这对于我们将对象的属性(如模块的exports)复制到函数的局部范围中是必要的。 / p>

    您所做的是使用eval在当前范围内生成代码。生成的代码使用var关键字声明局部变量,然后将其注入调用eval的范围。

    无法将eval调用移出模块,因为这样做会导致注入的代码插入到不同的范围内。请记住,JavaScript具有静态范围,因此您只能访问包含函数的词法范围。

    其他解决方法是使用with,但应避免使用with

    with (require('./paco.js')) {
        between(start(), content(), end())
    }
    
    不应该使用

    with有两个原因:

    1. absolutely kills performance因为V8无法执行名称查找优化。
    2. 不推荐使用,在严格模式下禁止使用。

    3. 老实说,我建议不要对eval做一些棘手的事情,不要让你未来的维护者受宠,只需遵循将模块的导出分配给局部变量的标准做法。

      如果您经常输入它,请将其设为单字符名称(或使用更好的编辑器)。

答案 1 :(得分:0)

根据此回答Global variables for node.js standard modules?global对象与浏览器window中的对象相同。所以你可以为该对象添加密钥

function getNamespace(exports) {
    for(var item in exports){
        global[item] = exports[item];
    }
}

并将其用作:

paco.getNamespace(paco);

根本不需要评估。

答案 2 :(得分:0)

没有。无法从外部模块修改本地范围。原因是,当在外部模块中调用eval时,其上下文将是外部模块,而不是需要模块的范围。

此外, vm.runInThisContext does not have access到本地范围,因此也无法帮助您。