本文主体部分 翻译+搬运 自外网著名技术博客网站 medium.com 的一篇点赞数 2.7k 的文章 (文章链接在结尾处)


什么是 JavaScript 模块

JavaScript 模块指的是一段可复用的,独立的代码。他们通常都有特定的功能,能在整个代码系统里被引进或删除 —— 这个概念多少类似于 Java 或者 Python 中的类。

模块通常是独立的——与其他代码解耦,因此方便修改。这也提高了代码的可读性和可维护性。模块化在使部分代码保持私有,仅暴露公共部分的的同时,还解决了命名空间模糊性的问题。

史前时代

在标准的 JavaScript 模块化方案还没被提出之前,显示模块化模式(Revealing Module Pattern)被用来模拟模块化。

var revealingModule = (function () {var privateVar = "Ben Thomas"; // 私有变量function setNameFn( strName ) {privateVar = strName;}
return {setName: setNameFn, // 暴露的公用方法};
})(); // 自执行函数 + 闭包
revealingModule.setName( "Paul Adams" );

利用自执行函数和闭包的特性将部分代码封装私有化,仅暴露部分公有的方法。

使用这种模式的优点是可以在一个js文件中定义多个模块,但缺点是无法异步导入模块,也无法动态地导入。

注:

我认为这种模式也一定程度地损害了代码的可读性和可维护性:

  1. 想象有多个js文件被同时使用,后面的 js 代码里要保证模块在前面的 js 里被声明了才能安心使用该模块;
  2. 当后面的 js 文件中使用到某个模块时,IDE也无法帮你快速找到这个模块的定义是什么、在哪个文件;
  3. 一旦模块的命名重复了,后面声明的模块就会覆盖掉前面的,造成bug (命名空间问题)
  4. 代码规模到达一定程度后,这种模块化就会失控,无法维护

CommonJS

后来,CommonJS 规范被提出来了。

采用 CommonJS 规范需要使用两个关键字 requireexports ,其中 require 用来声明导入某个 模块,而 exports 声明当前 js 文件要导出的内容

//------ store/customer.js 文件------
exports = function(){return customers.get('store);
}

//------ payments.js 文件------
var customerStore = require('store/customer'); // 导入模块

Nodejs 模块化的实现基本遵循了 CommonJS 规范,稍有不同的是 Nodejs 使用了 module.exports 而不是 exports

//store/customer.js 文件
function customerStore(){return customers.get('store);
}
modules.exports = customerStore;

特点:

Nodejs 模块化的实现是同步的,因为 Nodejs 通常在服务端使用,所有js文件都在文件系统上,基本没有异步的问题。

require 即可以传入具体的模块路径也可以传入模块名。当 require 某个模块名时,Nodejs就会在 node_modules 文件夹中查找对应的模块。

缺点:

一个文件一个模块的形式有点僵硬。

只有对象能被导出(函数也是特殊的对象),即无法导出变量/常量。

CommonJS 规范不能直接在浏览器的js环境下使用 ( 必须使用 Webpack 等工具转译处理)

Asynchronous Module Definition (AMD)

AMD 规范的出现就是因为 CommonJS 无法直接在浏览器 js 环境下使用,并且正如它的名字所表达的意思 —— AMD 天然支持异步的模块加载

AMD 使用 define 函数。define 函数有多种重载版本
define 函数最多接收3个参数:模块id, 依赖的模块构成的数组,回调函数

   define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {exports.verb = function() {return beta.verb();//Or:return require("beta").verb();}});

一个匿名的模块

   define(["alpha"], function (alpha) {return {verb: function(){return alpha.verb() + 2;}};});

直接导出对象字面量

   define({add: function(x, y){return x + y;}});

还能兼容 CommonJS 的 require 和 exports

   define(function (require, exports, module) {var a = require('a'),b = require('b');exports.action = function () {};});

特点:

AMD 的设计初衷就是给浏览器环境使用的——可以加快页面启动时间,而且这些模块导出内容可以是对象、函数、构造器、字符串、JSON等等。支持多模块多文件。

实现:

RequireJS 是 AMD API 完整的实现者,有兴趣地可以去官方文档看看 RequireJS

ECMAScript 6 模块化 (Native JavaScript)

ECMAScript 6 又名 ES6 又名 ES2015 ,终于推出了 JavaScript 原生的模块化方案

主要使用了两个关键字 imprort 和 export (注意和 CommonJS 不同,没有 s 后缀 )

//  lib/math.js
export function sum (x, y) { return x + y }
export var pi = 3.141593//  someApp.js
import * as math from "lib/math"
console.log("2π = " + math.sum(math.pi, math.pi))//  otherApp.js
import { sum, pi } from "lib/math"
console.log("2π = " + sum(pi, pi))

还可以使用 通配符 和 default 关键字

//  lib/mathplusplus.js
export * from "lib/math"  // 把 lib/math 里的内容一起导出
export var e = 2.71828182846
export default (x) => Math.exp(x) // 默认导出幂函数//  someApp.js
import exp, { pi, e } from "lib/mathplusplus"  // exp 是默认导出的 幂函数; 而 pi 其实是来源于 lib/math
console.log("e^{π} = " + exp(pi))

特点:

不同于 require 和 define , 在 ES6 import 语句是静态的,只能放在 js文件的头部(前几行)—— 在之后的ES版本中,将支持动态/异步的import 参考官方文档

export 语句用来导出模块内容,支持导出对象,函数,变量等等。

由于 import 和 export 都是静态语句,方便代码依赖分析,比如 IDE 就能轻松地帮我们跳转、查询依赖模块。

原生JavaScript的方案,目前已被大多数现代框架使用 如 React,Vue, Angular 等等。

缺点:

目前浏览器环境还没有普遍支持es6的模块化语法,还是需要借助 Babel 等工具转译代码。

如果你现在从零开始写一个js项目的话, 强烈推荐使用 ES6 的模块化方案,因为这是原生JavaScript的实现而且必定是未来的趋势。


总结

本文回顾了 JavaScript 模块化的历史发展,从 显示模块化模式 到 CommonJS 到 AMD 最后到 ES6,JavaScript 模块化方案不断在发展,改良和进化。

Bonus

问:我想在 现有的 Nodejs 项目中使用 ES6 模块化方案怎么办?

答:由于Nodejs 直到 13.2 版本才支持 ES6 ,很多旧项目还是只能使用传统的 CommonJS 规范。但是有个神奇的库叫 esm ( Github 链接),只需要几行代码或者一个配置就能帮我们在 Nodejs 中愉快地使用 import / export 了,我在个人项目中已经用上了,真的香!

相关文章和链接:

https://medium.com/computed-comparisons/commonjs-vs-amd-vs-requirejs-vs-es6-modules-2e814b114a0b

https://requirejs.org/

https://github.com/amdjs/amdjs-api/blob/master/AMD.md

standard-things/esm

无法找到模块“mint-ui”的声明文件_[搬运] JavaScript 模块化:CommonJS vs AMD vs ES6...相关推荐

  1. TS无法找到模块“common.js”的声明文件

    按照百度的新建common.d.ts文件并输入declare module '*.js';  依然会报错common.d.tsis not a module. 正确解决办法是: src下的shims- ...

  2. 无法找到模块“@antv/l7-district”的声明文件。

    antv l7 更新这么久了也能遇到这问题也是够了. 根目录或者其他地方随便创建一个xx.d.ts文件,里面写declare module '@antv/l7-district' 忽视它. /** C ...

  3. ts自动编译声明文件_拥抱 TS:细数选择 TS 的 N 种理由

    作者 | 马靖 day day up, bye bye bug 最近在做一个新项目,技术大佬告知前端要用 TS .前端小白的我内心疑惑"弱类型语言它不香嘛,为什么选择 TS ?" ...

  4. 单片机sleep函数的头文件_单片机代码模块化设计思想浅谈

    前言:前段时间分享的文章[单片机裸机代码框架设计思路],很多读者给我留言,觉得很不错,对于初学者而言,这是一个进阶的技巧,对于我而言,这是对自己总结和表达能力的一个提升. 本文章我们再谈谈单片机代码的 ...

  5. python web项目导出zip文件_通过javascript在网页端生成zip压缩包并下载

    zip.js是什么 通过zip.js封装一个能在网页端生成zip文件的插件, 直接在网页中创建包含文件夹和文件的压缩包,也可以自定义名字并下载: 如何使用: 1:引用zip.js 2:引用jQuery ...

  6. Typescript声明文件详解

    简介 声明文件是以.d.ts为后缀的文件,开发者在声明文件中编写类型声明,TypeScript根据声明文件的内容进行类型检查.(注意同目录下最好不要有同名的.ts文件和.d.ts,例如lib.ts和l ...

  7. Javascript模块规范(CommonJS规范AMD规范)

    Javascript模块化编程(AMD&CommonJS) 前端模块化开发的价值:https://github.com/seajs/seajs/issues/547 模块的写法 查看 AMD规 ...

  8. 无法找到模块“vue-awesome-swiper/dist/ssr”的声明文件

    报错信息: 文字:无法找到模块"vue-awesome-swiper/dist/ssr"的声明文件."e:/work/1010/vue-front-1010/node_m ...

  9. 关于vue3+ts import引入js出错无法找到模块“XXXXX”的声明文件。

    最近在写一个小项目在做一个提示封装时候出现了 无法找到模块"-/components/message.js"的声明文件."e:/Vue/客/cms_v3/src/comp ...

最新文章

  1. CSS grid 的用法
  2. 高性能 Java 应用层网关设计实践
  3. 爬虫网页搜索策略的选择
  4. 四、redis比mysql快的原因
  5. Foxmail安装以及使用
  6. 从 Go 语言一个文件描述符错误讲起
  7. WHATWG API——url.parse()的替代方案
  8. 几种分页方法效率比较
  9. Quasi-Dense Similarity Learning for Multiple Object Tracking
  10. 电机振动噪声(NVH)气隙磁场推导
  11. av_buffersrc_add_frame分析
  12. Unix网络编程学习笔记之第11章 名字与地址转换
  13. 身家过亿的帝都富豪来参加1024节专属盛典,小码农献上单链表一篇来庆祝盛典
  14. 在caffe 中添加Crowd counting 数据层
  15. tp管理界面找不到服务器,TP-LINK路由器无法登陆管理界面的解决办法
  16. matlab之isnumeric函数
  17. 最大化参数 火车头_火车头采集器教程:使用正则匹配模式采集数据
  18. 免费c语言程序设计题库app,2020知到APPC语言程序设计(青岛职业技术学院)答案搜题公众号...
  19. 网络安全专家,这5本入门秘籍人手一套
  20. 报文分片(16位分片标识、3位标志、13位片偏移字段详解)

热门文章

  1. C语言基础专题 - 数据类型与强制类型转换(编辑中)
  2. python @符号_用Python学数学之Sympy代数符号运算
  3. HBase伪分布式搭建
  4. 我的实例我做主--ECS运维必读
  5. 好消息:Dubbo Spring Boot要来了
  6. 误删除了Oracle的dbf文件后的解决方法
  7. IDC评述网:1月下旬国内域名注册商净增量Top10
  8. Apache Struts 1宣告退出舞台
  9. 关于.NET CF的底层资料(幻灯片)
  10. Java正则表达式入门概念与范例代码