如果你是JavaScript的新手,诸如模块捆绑器与模块加载器,Webpack与浏览器,AMD与CommonJS之类的术语,可能会变得不知所措,但了解它对Web开发人员至关重要,下面我们就一起来看看。
目录
什么是模块?
好的作者将他们的书分为章节,优秀的程序员将其程序分为模块,好的模块是高度独立的,具有独特的功能,可以根据需要对它们进行改组,删除或添加,而不会破坏整个系统。为什么要使用模块?
可维护性模块是独立的。设计良好的模块旨在尽可能减少对代码库各部分的依赖,从而使其能够独立增长和改进。当模块与其他代码解耦时,更新单个模块要容易得多。命名空间在JavaScript中,顶级函数范围之外的变量是全局变量。因此,普遍存在命名空间污染,这是一大禁忌,模块允许我们通过为变量创建私有空间来避免名称空间污染。可重用性可以将以前编写的代码复制到一个新的项目中使用。模块模式
Module模式用于模仿类的概念(因为JavaScript本身不支持类),因此我们可以将公共方法和私有方法以及变量存储在单个对象中-类似于在Java等其他编程语言中使用类的方式或Python。这使我们可以为要公开的方法创建面向公众的API,同时仍将私有变量和方法封装在闭包范围内。下面介绍几种方法可以完成模块模式:
匿名闭包(function () { //在封闭范围内,我们将这些变量设为私有 var myGrades = [93, 95, 88, 0, 55, 91]; var failing = function(){ var failingGrades = myGrades.filter(function(item) { return item < 70;}); return 你失败了 + failingGrades.length + 次.; } console.log(failing()); }()); // 你失败了2次. 引入全局jQuery类库使用的另一种流行方法是全局导入。它类似于我们刚刚看到的匿名闭包,只不过现在我们将全局变量作为参数传递(function ($, HELLO) { // 当前域有权限访问全局jQuery($)HELLO }(jQuery, HELLO)); 模块出口有时你不只想用全局变量,但你需要先声明他们(模块的全局调用)。我们用匿名函数的返回值,很容易输出他们,这样做就完成了基本的模块模式。var Module = (function () { var my = {}, privateVariable = 1; function privateMethod() { // ... } my.moduleProperty = 1; my.moduleMethod = function () { // ... }; return my; }());CommonJS、AMD、UMD
CommonJS
CommonJS是一个志愿者工作组,负责设计和实现用于声明模块的JavaScript API。 CommonJS模块本质上是一段可重用的JavaScript,它可以导出特定的对象,使它们可用于其他模块在其程序中需要。如果你使用Node.js编程,你将非常熟悉这种格式。function myModule() { this.hello = function() { return hello!; } this.goodbye = function() { return goodbye!; } } module.exports = myModule;想要使用myModule时,他们可以在文件中要求它,如下所示:
var myModule = require(myModule); var myModuleInstance = new myModule(); myModuleInstance.hello(); // hello! myModuleInstance.goodbye(); // goodbye!AMD
CommonJS很好,但是如果我们要异步加载模块怎么办?答案称为异步模块定义,简称AMD。使用AMD加载模块如下所示
define([myModule, myOtherModule], function(myModule, myOtherModule) { console.log(myModule.hello()); }); 与CommonJS不同,AMD采用了浏览器优先的方法以及异步行为来完成工作,,AMD的另一个好处是你的模块可以是对象,函数,构造函数,字符串,JSON和许多其他类型,而CommonJS仅支持将对象作为模块UMD
对于要求同时支持AMD和CommonJS功能的项目,还有另一种格式:通用模块定义(UMD)。 UMD本质上创建了一种使用这两种方法之一的方式,同时还支持全局变量定义。所以,UMD模块能够在客户端和服务器上工作。(function (root, factory) { if (typeof define === function && define.amd) { // AMD define([myModule, myOtherModule], factory); } else if (typeof exports === object) { // CommonJS module.exports = factory(require(myModule), require(myOtherModule)); } else { // 浏览器全局变量(注意:root是window) root.returnExports = factory(root.myModule, root.myOtherModule); } }(this, function (myModule, myOtherModule) { // 方法 function notHelloOrGoodbye(){}; // 私有方法 function hello(){}; // 一个公共方法,因为它已返回 function goodbye(){}; //一个公共方法,因为它已返回 // 公开的公共方法 return { hello: hello, goodbye: goodbye } }));ES6
以上所有模块都不是JavaScript固有的。相反,我们通过使用模块模式CommonJS或AMD创建了模拟模块系统的方法,幸运的是,TC39(定义ECMAScript语法和语义的标准机构)引入了ECMAScript 6(ES6)内置模块。
ES6为导入和导出模块提供了各种各样的可能性,相对于 CommonJS 或 AMD,ES6模块的优势在于它提供了两全其美的功能: 紧凑声明语法和异步加载,以及更好的循环依赖支持。
下面是一个例子
CommonJS方式:
// lib/counter.js var counter = 1; function increment() { counter++; } function decrement() { counter--; } module.exports = { counter: counter, increment: increment, decrement: decrement }; // src/main.js var counter = require(../../lib/counter); counter.increment(); console.log(counter.counter); // 1在这个示例中,我们基本上复制了两个模块: 一个在导出时复制,另一个在需要时复制。
main.js 中的副本与原始模块断开了连接。这就是为什么即使我们增加我们的计数器,它仍然返回1ーー因为我们导入的计数器变量是从模块中断开的计数器变量的副本。
因此,递增计数器将在模块中递增它,但不会递增所复制的版本。修改计数器变量复制版本的唯一方法是手动修改:
counter.counter++; console.log(counter.counter); // 2我们使用ES6模块来重写:
// lib/counter.js export let counter = 1; export function increment() { counter++; } export function decrement() { counter--; } // src/main.js import * as counter from ../../counter; console.log(counter.counter); // 1 counter.increment(); console.log(counter.counter); // 2这就是CommonJS与ES6 modules的区别之一。
总结
我想看了这么多内容你心里一定对JavaScript modules有了大致了解。在大多数情况下(不管是web还是node)我都更愿意使用ES6 Modules来写我的JavaScript代码。我也希望这篇文章能让你更好的理解JavaScript的模块。
千锋HTML5学院:如何讲清楚函数防抖?122 赞同 · 7 评论文章千锋HTML5学院:如何讲清楚函数节流?78 赞同 · 6 评论文章千锋HTML5学院:如何讲清楚闭包?25 赞同 · 2 评论文章