具有NodeJS / CommonJS风格模块系统的语言

我真的很喜欢NodeJS(和它的浏览器端对应)处理模块的方式:

var $ = require('jquery'); var config = require('./config.json'); module.exports = function(){}; module.exports = {...} 

实际上我对ES2015的“导入”规范感到非常失望,这与大多数语言非常相似。

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

也许我错过了一些东西,或者更有可能的是,我的Google Foo没有达到要求,但是看看其他语言以类似的方式工作真的很有趣。

有没有人遇到类似的系统? 或者,甚至有人甚至可以提供不经常使用的原因。

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

我观察到这些差异:

  • 您可以使用require ,而不仅仅是让其他模块可用于您的模块。 例如,你可以用它来parsing一个JSON文件。
  • 您可以在代码中使用require ,而import只能在文件的顶部使用。
  • require实际执行所需的模块(如果尚未执行),而import具有更多的声明性质。 对于所有的语言来说这可能不是这样,但这是一种趋势。
  • require可以从子目录加载私有的依赖关系,而import通常为所有的代码使用一个全局的命名空间。 总之,这也是不正确的,而只是一种倾向。

责任

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

在代码中的位置

现在,我们只require使用模块依赖关系,在模块的顶部使用它是不好的做法。 当你在你的代码中的任何地方使用它时,只会让你很难看到模块依赖关系。 这就是为什么您只能在代码上使用import语句的原因。

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

怎么运行的

加载模块时执行代码也是一个问题,特别是在大型程序中。 你可能会遇到一个循环,在这个循环中一个模块需要传递。 这可能真的很难解决。 据我所知,nodejs处理这种情况是这样的:当A需要B和B需要A并且你首先要求A时,那么:

  • 模块系统会记住它当前正在加载A.
  • 它执行A中的代码
  • 它记得当前是加载B.
  • 它执行B中的代码
  • 它试图加载A,但A已经加载
  • A还没有完成加载
  • 它将一半的A返回给B.
  • B不希望A被加载一半

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

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

依赖pipe理

支撑不同方法的依赖pipe理确实是一个有趣的观点。 例如Java在当前版本中仍然没有正确的模块系统。 再次,它是宣布的下一个版本,但谁知道这是否会成为现实。 目前,你只能使用OSGi来获取模块,这远非易于使用。

下层nodejs的依赖pipe理非常强大。 但是,这也不完美。 例如,通过模块API公开的依赖关系的非私有依赖始终是一个问题。 但是,这是依赖pipe理的一个常见问题,所以不限于nodejs。

结论

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