要正确比较这些功能几乎是不可能的。只能比较它们在特定语言中的实现。我主要收集 Java 和 nodejs 语言的经验。
我观察到了这些差异:
- 您可以使用
require,而不仅仅是让其他模块对您的模块可用。例如,您可以使用它来解析 JSON 文件。
- 您可以在代码中的任何位置使用
require,而import 只能在文件顶部使用。
-
require 实际执行所需的模块(如果尚未执行),而 import 具有更多的声明性。这可能不适用于所有语言,但这是一种趋势。
-
require 可以从子目录加载私有依赖,而import 通常为所有代码使用一个全局命名空间。同样,这也不是一般情况,而只是一种趋势。
职责
如你所见,require 方法有多重职责:声明模块依赖和读取数据。这与导入方法更好分开,因为import 应该只处理模块依赖关系。我想,您喜欢使用require 方法读取 JSON 的原因在于,它为程序员提供了一个非常简单的接口。我同意拥有这种简单的 JSON 读取接口很好,但是没有必要将它与模块依赖机制混合。可能只是另一种方法,例如readJson()。这将分离关注点,因此仅在声明模块依赖项时才需要 require 方法。
代码中的位置
现在,我们只将require 用于模块依赖项,因此在模块顶部以外的任何其他地方使用它是一种不好的做法。当您在代码中的任何地方使用它时,它只会让您很难看到模块依赖关系。这就是为什么您只能在代码顶部使用 import 语句的原因。
我没有看到 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。
结论
我想两者都不是那么糟糕,因为每个都成功使用了。但是,在我看来,import 比require 有一些客观的优势,比如职责分离。由此可见,import 可以限制在代码的顶部,也就是说只有一个地方可以搜索模块依赖。此外,import 可能更适合编译语言,因为它们不需要执行代码来加载代码。