随着 JavaScript 开发变得越来越普遍, 命名空间和依赖性变得越来越难以处理. 前端开发者都以模块化的方式处理该问题. 在这篇文章中, 我们将探讨前端开发人员目前使用的模块化方案以及试图解决的问题.
为什么需要 JavaScript 模块?
模块化可以使你的代码低耦合, 功能模块直接不相互影响.
可维护性: 每个模块都是单独定义的, 之间相互独立. 模块尽可能的需要和外部撇清关系, 方便我们独立的对其进行维护与改造. 维护一个模块比在全局中修改逻辑判断要好的多.
命名空间: 为了避免在 JavaScript 中的全局污染, 我们通过模块化的方式利用函数作用域来构建命名空间.
可复用性: 虽然粘贴复制很简单, 但是考虑到我们之后的维护以及迭代, 你会相当崩溃.
模块化的解决方案有哪些?
讲完了 JavaScript 模块化的好处, 我们来看下有哪些解决方案来实现 JavaScript 的模块化.
揭示模块模式(Revealing Module)
- var myRevealingModule = (function () {
- var privateVar = "Ben Cherry",
- publicVar = "Hey there!";
- function privateFunction() {
- console.log( "Name:" + privateVar );
- }
- function publicSetName( strName ) {
- privateVar = strName;
- }
- function publicGetName() {
- privateFunction();
- }
- // Reveal public pointers to
- // private functions and properties
- return {
- setName: publicSetName,
- greeting: publicVar,
- getName: publicGetName
- };
- })();
- myRevealingModule.setName( "Paul Kinlan" );
通过这种构造, 我们通过使用函数有了自己的作用域或 "闭包".
这种方法的好处在于, 你可以在函数内部使用局部变量, 而不会意外覆盖同名全局变量, 但仍然能够访问到全局变量.
优点:
可以在任何地方实现(没有库, 不需要语言支持).
可以在单个文件中定义多个模块.
缺点:
无法以编程方式导入模块(除非使用 eval).
需要手动处理依赖关系.
无法异步加载模块.
循环依赖可能很麻烦.
很难通过静态代码分析器进行分析.
CommonJS
CommonJS 是一个旨在定义一系列规范的项目, 以帮助开发服务器端 JavaScript 应用程序. CommonJS 团队试图解决的一个领域就是模块. Node.JS 开发人员最初打算遵循 CommonJS 规范, 但后来决定反对它.
在 CommonJS 的规范中, 每个 JavaScript 文件就是一个独立的模块上下文(module context), 在这个上下文中默认创建的属性都是私有的. 也就是说, 在一个文件定义的变量(还包括函数和类), 都是私有的, 对其他文件是不可见的.
需要注意的一点是, CommonJS 以服务器优先的方式来同步载入模块, 假使我们引入三个模块的话, 他们会一个个地被载入.
- // In circle.JS
- const PI = Math.PI;
- exports.area = (r) => PI * r * r;
- exports.circumference = (r) => 2 * PI * r;
- // In some file
- const circle = require('./circle.js');
- console.log( `The area of a circle of radius 4 is ${circle.area(4)}`);
Node.JS 的模块系统通过 library 的方式对 CommonJS 的基础上进行了模块化实现.
在 Node 和 CommonJS 的模块中, 基本上有两个与模块系统交互的关键字: require 和 exports.
require 是一个函数, 可用于将接口从另一个模块导入当前范围. 传递给的参数 require 是模块的 id. 在 Node 的实现中, 它是 node_modules 目录中模块的名称(或者, 如果它不在该目录中, 则是它的路径).
exports 是一个特殊的对象: 放入它的任何东西都将作为公共元素导出.
Node 和 CommonJS 之间的一个独特区别在于 module.exports 对象的形式.
在 Node 中, module.exports 是导出的真正特殊对象, 而 exports 它只是默认绑定到的变量 module.exports.
另一方面, CommonJS 没有任何 module.exports 对象. 实际意义是, 在 Node 中, 无法通过以下方式导出完全预构造的对象 module.exports:
- // This won't work, replacing exports entirely breaks the binding to
- // modules.exports.
- exports = (width) => {
- return {
- area: () => width * width
- };
- }
- // This works as expected.
- module.exports = (width) => {
- return {
- area: () => width * width
- };
- }
优点
简单: 开发人员可以在不查看文档的情况下掌握概念.
集成了依赖管理: 模块需要其他模块并按所需顺序加载.
require 可以在任何地方调用: 模块可以通过编程方式加载.
支持循环依赖.
缺点
同步 API 使其不适合某些用途(客户端).
每个模块一个文件.
浏览器需要加载程序库或转换.
模块没有构造函数(Node 支持).
很难进行静态代码分析.
AMD
AMD 诞生于一群对 CommonJS 的研究方向不满的开发人员. 事实上, AMD 在开发早期就与 CommonJS 分道扬镳, AMD 和 CommonJS 之间的主要区别在于它支持异步模块加载.
- //Calling define with a dependency array and a factory function
- define(['dep1', 'dep2'], function (dep1, dep2) {
- //Define the module value by returning a value.
- return function () {};
- });
- // Or:
- define(function (require) {
- var dep1 = require('dep1'),
- dep2 = require('dep2');
- return function () {};
- });
通过使用 JavaScript 的传统闭包来实现异步加载:
在请求的模块加载完成时调用函数. 模块定义和导入模块由同一个函数承载: 定义模块时, 其依赖关系是明确的. 因此, AMD 加载器可以在运行时具有项目的模块依赖图. 因此可以同时加载彼此不依赖的库. 这对于浏览器尤其重要, 因为启动时间对于良好的用户体验至关重要.
优点
异步加载(更好的启动时间).
支持循环依赖.
require 和的兼容性 exports.
完全整合了依赖管理.
如有必要, 可以将模块拆分为多个文件.
支持构造函数.
插件支持(自定义加载步骤).
缺点
语法稍微复杂一些.
除非编译, 否则需要加载程序库.
很难分析静态代码.
除了异步加载以外, AMD 的另一个优点是你可以在模块里使用对象, 函数, 构造函数, 字符串, JSON 或者别的数据类型, 而 CommonJS 只支持对象.
UMD
统一模块定义 (UMD:Universal Module Definition ) 就是将 AMD 和 CommonJS 合在一起的一种尝试, 常见的做法是将 CommonJS 语法包裹在兼容 AMD 的代码中.
- (function(define) {
- define(function () {
- return {
- sayHello: function () {
- console.log('hello');
- }
- };
- });
- }(
- typeof module === 'object' && module.exports && typeof define !== 'function' ?
- function (factory) { module.exports = factory(); } :
- define
- ));
该模式的核心思想在于所谓的 IIFE(Immediately Invoked Function Expression), 该函数会根据环境来判断需要的参数类别
ES6 模块
支持 JavaScript 标准化的 ECMA 团队决定解决模块问题,
兼容同步和异步操作模式.
- //------ lib.JS ------
- export const sqrt = Math.sqrt;
- export function square(x) {
- return x * x;
- }
- export function diag(x, y) {
- return sqrt(square(x) + square(y));
- }
- //------ main.JS ------
- import { square, diag } from 'lib';
- console.log(square(11)); // 121
- console.log(diag(4, 3)); // 5
ES6 模块的设计思想是尽量的静态化, 使得编译时就能确定模块的依赖关系, 以及输入和输出的变量. CommonJS 和 AMD 模块, 都只能在运行时确定这些东西.
由于 ES6 模块是编译时加载, 使得静态分析成为可能. 有了它, 就能进一步拓宽 JavaScript 的语法, 比如引入宏 (macro) 和类型检验 (type system) 这些只能靠静态分析实现的功能. 除了静态加载带来的各种好处, ES6 模块还有以下好处.
不再需要 UMD 模块格式了, 将来服务器和浏览器都会支持 ES6 模块格式. 目前, 通过各种工具库, 其实已经做到了这一点.
将来浏览器的新 API 就能用模块格式提供, 不再必须做成全局变量或者 navigator 对象的属性.
不再需要对象作为命名空间(比如 Math 对象), 未来这些功能可以通过模块提供.
ES6 的模块自动采用严格模式, 不管有没有在模块头部加上 "use strict";.
严格模式主要有以下限制.
变量必须声明后再使用
函数的参数不能有同名属性, 否则报错
不能使用 with 语句
不能对只读属性赋值, 否则报错
不能使用前缀 0 表示八进制数, 否则报错
不能删除不可删除的属性, 否则报错
不能删除变量 delete prop, 会报错, 只能删除属性 delete global[prop]
eval 不会在它的外层作用域引入变量
eval 和 arguments 不能被重新赋值
arguments 不会自动反映函数参数的变化
不能使用 arguments.callee
不能使用 arguments.caller
禁止 this 指向全局对象
不能使用 fn.caller 和 fn.arguments 获取函数调用的堆栈
增加了保留字(比如 protected,static 和 interface)
其中, 尤其需要注意 this 的限制. ES6 模块之中, 顶层的 this 指向 undefined, 即不应该在顶层代码使用 this.
export
export 语法被用来创建 JavaScript 模块. 你可以用它来导出对象 (包括函数) 和原始值(primitive values). 导出有两种类型: named 和 default.
- // named
- // lib.JS
- export function sum(a, b) {
- return a + b;
- }
- export function substract(a, b) {
- return a - b;
- }
- function divide(a, b) {
- return a / b;
- }
- export { divide };
- // default
- // dog.JS
- export default class Dog {
- bark() {
- console.log('bark!');
- }
- }
- import
import 语句用来导入其他模块.
整个导入
- // index.JS
- import * as lib from './lib.js'; console.log(lib.sum(1,2)); console.log(lib.substract(3,1)); console.log(lib.divide(6,3));
导入一个或多个 named 导出
- // index.JS
- import {
- sum, substract, divide
- } from './lib';
- console.log(sum(1,2));
- console.log(substract(3,1));
- console.log(divide(6,3));
需要注意, 相应的导入导出名字必须匹配.
导入一个 default 导出
- // index.JS
- import Dog from './dog.js';
- const dog = new Dog();
- dog.bark(); // 'bark!'
注意, defualt 导出在导入时, 可以用任意的名字. 所以我们可以这样做:
- import Cat from './dog.js';
- const dog = new Cat();
- dog.bark(); // 'bark!'
参考
很全很全的 JavaScript 的模块讲解
JavaScript Module Systems Showdown: CommonJS vs AMD vs ES2015
ECMAScript 6 入门 http://es6.ruanyifeng.com/#docs/module
来源: https://www.cnblogs.com/jingh/p/10915466.html