在使用 vue、react、node 的时候,经常会看到 module.exports,export default,require,import等字段,由于我对这些字段的概念很是模糊,因此致使我在写代码的时候,在node项目里混用了 export default,在 vue 的项目里写 module.exports。html
那么今天就来梳理一下有关模块化的知识。前端
ESM(ECMA Script Modules)模块主要由两个命令构成:export 和 import。vue
暴露模块:export default {} , export {} , export function(){} 引入模块:import {xxx} from 'path' 复制代码
import 的大括号里面指定要从其余模块导入的变量名,若是 export 命令没有写 default,那么 import 大括号里面的变量名,必须与 export 导出的名称相同。node
// test.js export {foo} // main.js import { foo } from './test.js' // 若是想为输入的变量从新取一个名字,要使用as关键字,将输入的变量重命名 import { foo as bar } from './test.js'; 复制代码
default 为模块指定默认输出,这样在引入时就没必要关心模块输出的名字。react
// test.js function foo() {}; export default {foo} // main.js import bar from './test.js' 复制代码
本质上,export default 就是输出一个叫作 default 的变量或方法,而后系统容许你为它取任意名字。webpack
有关 ESM 模块的语法,能够阅读 阮一峰 的文章,这里不详细写出全部写法。git
咱们在使用 import 和 export 的时候,经常看到的是在顶层做用域使用 export 和 import,不会在块级做用域内看到 import 和 export,这是为何呢?es6
由于 ESM 模块的设计思想是尽可能静态化,编译时就能肯定模块的依赖关系,以及输入和输出的变量,若是处于块级做用域内,就无法作静态优化了,违背了 ES6 模块的设计初衷。github
可是我明明在 vue router 的使用中看到了这样的写法:web
const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue') 复制代码
此时 import 的写法和常规写法不同,是import(), 而且确实出如今了块级做用域内。
ESM有一个提案,建议引入import()函数,完成动态加载。如今支持动态加载的好比:vue router、webpack。
vue router 的 路由懒加载
webpack 动态导入
暴露模块:module.exports = value 或 exports.xxx = value
引入模块:require(xxx),若是是第三方模块,xxx为模块名;若是是自定义模块,xxx为模块文件路径
复制代码
Node 的模块输出和引入的方式与ESM不一样,Node 采用的是 CommonJS 模块规范。
CommonJS 规范规定,在每一个模块内部,module 变量表明当前模块。这个变量是一个对象,它的 exports 属性(module.exports)是对外的接口。
Node.js 主要用于服务端项目,CommonJS 规范也主要用于服务端编程,因此 Node 的模块设计采用 CommonJS 规范很合适。
服务端模块的加载是同步的,可是浏览器资源是异步加载,同步意味着阻塞,在没有ESM模块以前,浏览器想作模块化怎么办呢?
AMD CMD解决方案。
实际上AMD (Asynchronous Module Definition) 是 RequireJS 在推广过程当中对模块定义规范化的产出。
CMD (Common Module Definition) 是 SeaJS 在推广过程当中对模块定义的规范化产出。
SeaJS 和 requireJS 解决的都是模块化问题,只不过在模块定义方式和模块加载(能够说运行、解析)时机上有所不一样。
如下例子引用自 WEB 前端模块化都有什么
AMD
// hello.js define(function() { console.log('hello init'); return { getMessage: function() { return 'hello'; } }; }); // world.js define(function() { console.log('world init'); }); // main define(['./hello.js', './world.js'], function(hello) { return { sayHello: function() { console.log(hello.getMessage()); } }; }); // 输出 // hello init // world init 复制代码
CMD
// hello.js define(function(require, exports) { console.log('hello init'); exports.getMessage = function() { return 'hello'; }; }); // world.js define(function(require, exports) { console.log('world init'); exports.getMessage = function() { return 'world'; }; }); // main define(function(require) { var message; if (true) { message = require('./hello').getMessage(); } else { message = require('./world').getMessage(); } }); // 输出 // hello init 复制代码
CMD 的输出结果中,没有打印"world init", 并是不 world.js 文件没有加载。
AMD 与 CMD 都是在页面初始化时加载完成全部模块,区别是 CMD 就近依赖, 是当模块被 require 时才会触发执行, AMD 推崇依赖前置,在定义模块的时候就要声明其依赖的模块。
一样都是异步加载模块,AMD 在加载模块完成后就会执行该模块,全部模块都加载执行完后会进入require的回调函数,执行主逻辑,这样的效果就是依赖模块的执行顺序和书写顺序不必定一致,看网络速度,哪一个先下载下来,哪一个先执行,可是主逻辑必定在全部依赖加载完成后才执行。
CMD加载完某个依赖模块后并不执行,只是下载而已,在全部依赖模块加载完成后进入主逻辑,遇到require语句的时候才执行对应的模块,这样模块的执行顺序和书写顺序是彻底一致的。
这也是不少人说AMD用户体验好,由于没有延迟,依赖模块提早执行了,CMD性能好,由于只有用户须要的时候才执行。
AMD & CMD,CommonJS 规范是两类规范,AMD & CMD 用于浏览器模块化,CommonJS 用于服务端模块化,可是你们的指望有一个统一的规范来支持这两种规范。因而,UMD规范诞生了。
UMD (Universal Module Definition),它能够经过运行时或者编译时让同一个代码模块在使用 CommonJs、CMD / AMD 的项目中运行,同一个 JavaScript 包在浏览器 / 服务端只须要遵照同一个写法就能够了。
UMD 没有本身专有的规范,是集结了 CommonJs、CMD、AMD 的规范于一身,UMD 先判断是否支持 Node 模块格式(exports是否存在),存在则使用 Node 模块格式,再判断是否支持 AMD(define是否存在),存在则使用 AMD 方式加载模块,若是前两个都不存在,则将模块公开到全局(window或global)。
当即执行函数实现模块化(IIFE,Immediately-Invoked Function Expression)
使用当即执行函数,表达式中的变量不能从外部访问。如今项目中已经看不到这样的写法了。
例如:
(function(){ var count = 0; return count; })(); 复制代码
ESM | CommonJS | AMD | CMD | UMD | |
---|---|---|---|---|---|
加载机制 | 编译时 | 运行时 | 提早预加载 | 编译时 & 运行时按需加载 | - |
同步/异步 | 异步 | 同步 | 异步 | 异步,有延迟执行的状况 | - |
适用场合 | 浏览器、服务端 | 服务端 | 浏览器 | 浏览器 | 浏览器、服务端 |
是否常见 | ☆☆☆ | ☆☆☆ | ☆ | ☆ | ☆ |
ESM 在语言标准的层面上,成为浏览器和服务端通用的模块解决方案。
webpack 在定义模块上,支持上面提到的全部模块声明方式,只须要在 webpack 的 output 中添加 libraryTarget: 'commonjs/amd/umd'便可。