模块化
模块化用来分割,组织和打包软件。每个模块完成一个特定的子功能,所有的模块按某种方法组装起来,成为一个整体,完成整个系统所要求的功能。
以前开发没用模块化可能导致的问题
- 命名空间冲突,多个库可能会使用同一名称
- 无法合理的管理项目的依赖和版本
- 无法方便的控制依赖的加载顺序
## 如今前端模块化的主流方式
利用自动化构建工具 Gulp/Webpack 把源代码转换成发布线上的可执行 JavaScrip、CSS、HTML 代码
通常自动化构建工具做的内容有:
- 代码转换:ECMASCRIPT6 编译成 ECMASCRIPT5、LESS 编译成 CSS 等。
- 文件优化:压缩 JavaScript、CSS、HTML 代码,压缩合并图片等
- 代码分割:提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载。
- 模块合并:在采用模块化的项目里会有很多个模块和文件,需要构建功能把模块分类合并成一个文件。
- 自动刷新:监听本地源代码的变化,自动重新构建、刷新浏览器。
- 代码校验:在代码被提交到仓库前需要校验代码是否符合规范,以及单元测试是否通过。
- 自动发布:更新完代码后,自动构建出线上发布代码并传输给发布系统。
## 模块化的几种规范
- CommonJS
- AMD
- ES6 模块化
## CommonJS
CommonJS 定义的模块分为:{模块引用 (require)} {模块定义(exports)} {模块标识(module)}
require() 用来引入外部模块;exports 对象用于导出当前模块的方法或变量,唯一的导出口;module 对象就代表模块本身。
- 使用方式
// foo.js
module.exports = function(x) { // 导出
console.log(x);
};
// main.js
var foo = require("./foo"); // 导入
foo("Hi");
- 原理
浏览器不兼容 CommonJS 的根本原因,在于缺少四个 Node.js 环境的变量。
- module
- exports
- require
- global
换言之,只要我们提供了这几个,浏览器就能加载 CommonJS 模块。下面是一个简单的示例
var module = {exports: {}
};
(function(module, exports) {exports.multiply = function (n) {return n * 1000};
}(module, module.exports))
var f = module.exports.multiply;
f(5) // 5000
- 模拟实现 require
function require(p){var path = require.resolve(p); // 返回路径
var mod = require.modules[path]; // 是否已注册
if (!mod) throw new Error('failed to require"' + p + '"'); // 未注册抛出
if (!mod.exports) { // 如果注册了 就执行模块
mod.exports = {};
mod.call(mod.exports, mod, mod.exports, require.relative(path)); // 执行模块
}
return mod.exports;
}
require.modules = {};
require.resolve = function (path){
var orig = path;
var reg = path + '.js';
var index = path + '/index.js';
return require.modules[reg] && reg || require.modules[index] && index || orig;
};
require.register = function (path, fn){ // 注册模块
require.modules[path] = fn;
};
require.relative = function (parent) {return function(p){if ('.' != p.charAt(0)) return require(p);
var path = parent.split('/');
var segs = p.split('/');
path.pop();
for (var i = 0; i < segs.length; i++) {var seg = segs[i];
if ('..' == seg) path.pop();
else if ('.' != seg) path.push(seg);
}
return require(path.join('/'));
};
};
require.register("moduleId", function(module, exports, require){// 代码写在这里});
var result = require("moduleId");
AMD
优点
- 可在不转换代码的情况下直接在浏览器中运行
- 可加载多个依赖
- 代码可运行在浏览器环境和 Node.js 环境下
缺点
- JavaScript 运行环境没有原生支持 AMD,需要先导入实现了 AMD 的库后才能正常使用
- 语法
define([module-name?], // 模块名
[array-of-dependencies?], // 依赖模块
[module-factory-or-object]); // 模块的实现,或者一个 JavaScript 对象
- 使用方法
define('a', [], function () {return 'a';});
define('b', ['a'], function (a) {return a + 'b';});
// 导入和使用
require(['b'], function (b) {console.log(b);
});
define 函数具有异步性。当 define 函数执行时,首先会异步的去调用第二个参数中列出的依赖模块,当所有的模块被载入完成之后,
如果第三个参数是一个回调函数则执行;然后告诉系统模块可用,也就通知了依赖于自己的模块自己已经可用
- 模拟实现
let factories = {};
function define(modName, dependencies, factory) {
factory.dependencies = dependencies; // 依赖
factories[modName] = factory; // 储存模块
}
function require(modNames, callback) {let loadedModNames = modNames.map(function (modName) {let factory = factories[modName];
let dependencies = factory.dependencies;
let exports;
require(dependencies, function (...dependencyMods) {exports = factory.apply(null, dependencyMods);
});
return exports;
})
callback.apply(null, loadedModNames);
}
目前 AMD 使用比较少了
ES6 模块化
ES6 模块化是 ECMA 提出的 JavaScript 模块化规范,它在语言的层面上实现了模块化。浏览器厂商和 Node.js
都宣布要原生支持该规范。它将逐渐取代 CommonJS 和 AMD` 规范,成为浏览器和服务器通用的模块解决方案。
-
优点
- 主流方案,各种浏览器厂商和 node 都逐渐支持
-
缺点
- 目前无法直接运行在大部分 JavaScript 运行环境下,必须通过工具转换成标准的 ES5 后才能正常运行
-
特点
- 如果你通过本地加载 Html 文件 (比如一个 file:// 路径的文件), 你将会遇到 CORS 错误,因为 Javascript 模块安全性需要。你需要通过一个服务器来测试。
- 自动使用严格模式。
- 加载一个模块脚本时不需要使用 defer 属性, 模块会自动延迟加载。
- 无法在全局获得. 因此,你只能在导入这些功能的脚本文件中使用他们,你也无法通过 Javascript console 中获取到他们
- 使用方法
- 基本使用
// 导出
export const name = 'xiaoming';
// 导入
import {name} from './person.js';
- 默认导出
// 导出
const name = 'xiaoming';
export default name
// 导入
import name from './person.js';
// 或者
import {default as name} from './person.js';
- 重命名导出与导入
export {
function1 as newFunctionName,
function2 as anotherNewFunctionName
};
// inside main.mjs
import {newFunctionName, anotherNewFunctionName} from '/modules';
- 全部导入
import * as Module from '/modules/module';
Module.function1()
Module.function2()
- 动态导入
import('/modules/myModule.mjs')
.then((module) => {// Do something with the module.});
// 需要 babel 解析才能使用
参考资料
珠峰培训
深入理解 commonjs
AMD 与 requireJS
JavaScript modules 模块