具有NodeJS / CommonJS样式模块系统的语言

时间:2016-02-25 20:25:21

标签: node.js import module export programming-languages

我非常喜欢NodeJS(以及它的浏览器端对应物)处理模块的方式:

var $ = require('jquery');

var config = require('./config.json');

module.exports = function(){};

module.exports = {...}

我实际上对ES2015 'import' spec感到非常失望,这与大多数语言非常相似。

出于好奇,我决定寻找实现甚至支持类似出口/进口风格的其他语言,但无济于事。

也许我错过了一些东西,或者更有可能的是,我的Google Foo还没有达到标准,但看看哪些其他语言以类似的方式工作会非常有趣。

有没有人遇到类似的系统? 或者也许有人甚至可以提供不经常使用的理由。

1 个答案:

答案 0 :(得分:3)

几乎不可能正确地比较这些功能。人们只能比较他们在特定语言中的实现。我主要使用Java和nodejs语言收集了我的经验。

我观察到了这些差异:

  • 您可以使用require,而不仅仅是为您的模块提供其他模块。例如,您可以使用它来解析JSON文件。
  • 您可以在代码中的任何位置使用require,而import只能在文件顶部使用。
  • require实际执行所需的模块(如果尚未执行),而import具有更具说明性的性质。对于所有语言而言可能并非如此,但这是一种趋势。
  • require可以从子目录加载私有依赖项,而import通常为所有代码使用一个全局命名空间。同样,这一般也不是真的,而只是一种倾向。

职责

如您所见,require方法有多个职责:声明模块依赖关系和读取数据。使用导入方法可以更好地分离,因为import应该只处理模块依赖性。我想,你喜欢使用require方法来阅读JSON,它为程序员提供了一个非常简单的界面。我同意拥有这种简单的JSON读取接口很好,但是不需要将它与模块依赖性机制混合在一起。可以有另一种方法,例如readJson()。这将分离关注点,因此只需要require方法来声明模块依赖关系。

代码中的位置

现在,我们只使用require作为模块依赖项,在模块顶部的其他地方使用它是一种不好的做法。当您在代码中的任何地方使用它时,很难看到模块依赖性。这就是为什么您只能在代码之上使用import语句。

我没有看到导入创建全局变量的点。它仅为每个依赖项创建一致的标识符,该标识符仅限于当前文件。如上所述,我建议只使用require方法,只在文件顶部使用它。它确实有助于提高代码的可读性。

工作原理

加载模块时执行代码也可能是一个问题,尤其是在大型程序中。您可能会遇到一个模块传递上需要自己的循环。这真的很难解决。据我所知,nodejs处理这种情况是这样的:当A需要B而B需要A而你需要A开始时,那么:

  • 模块系统记住它当前加载A
  • 它执行A
  • 中的代码
  • 它记得当前正在加载B
  • 它执行B
  • 中的代码
  • 尝试加载A,但A已加载
  • A尚未完成加载
  • 它将半载A返回给B
  • B预计A不会半载

这可能是个问题。现在,人们可以争辩说应该真正避免循环依赖,我同意这一点。但是,应该仅在程序的不同组件之间避免循环依赖性。组件中的类通常具有循环依赖性。现在,模块系统可以用于两个抽象层:类和组件。这可能是一个问题。

接下来,require方法通常会导致单例模块,这些模块不能在同一程序中多次使用,因为它们存储全局状态。然而,这并不是系统的错误,而是程序员错误地以错误的方式使用系统。不过,我的观察是require方法误导了特别是新程序员这样做。

依赖关系管理

支持不同方法的依赖关系管理确实是一个有趣的观点。例如,Java仍然错过了当前版本中的正确模块系统。它再次宣布下一个版本,但谁知道这是否会成为现实。目前,您只能使用OSGi获取模块,这非常不容易使用。

底层nodejs的依赖管理非常强大。但是,它也不完美。例如,非私有依赖项(通过模块API公开的依赖项)始终是一个问题。但是,这是依赖关系管理的常见问题,因此不限于nodejs。

结论

我猜两者都不是那么糟糕,因为每个都成功使用。但是,在我看来,importrequire有一些客观优势,比如职责分离。由此可以将import限制在代码的顶部,这意味着只有一个地方可以搜索模块依赖性。此外,import可能更适合编译语言,因为它们不需要执行代码来加载代码。