不论是前端老司机仍是刚接触前端的"菜鸟"。模块化想必在天天工做中,或多或少都会接触到。尤为针对一些针对React
、Vue
开发的同窗来讲,那就是天天都会脱口而出的一个必备术语。而且在不少技术文档中,都经常看到AMD
、UMD
、COMMONJS
还有ES6中的module
。javascript
可是,模块化的本质是什么!前端是如何从"茹毛饮血"的<script>
到如今es6的module
的呢。html
今天咱们就来唠唠这段不为人知的故事。前端
何为模块化。其实就是功能的单一化或者说功能的切片化编程。更直白一点就是,每个独立的功能都有本身独立的做用域。java
让咱们看看针对模块的英文定义es6
Modules are an integral piece of any robust application's architecture and typically help in keeping the units of code for a project both cleanly separated and organized.编程
而JS在实现模块代码有以下方式:设计模式
让咱们针对每个方式来一一说明api
因为JS语法自己没有块级做用域的概念(es6以前),因此是无法直接利用{}
来将指定的代码进行封装。若是想将特定用于处理相似功能的代码合并到一块儿。对象字面量不失为一个很好的方式。(有人会说,用函数封装也能够啊,记住JS中一切皆对象)浏览器
var myModule = {
myProperty: "北宸",
// 对象字面量能够包含属性和方法
// 咱们还能够为该模块定义配置信息:
myConfig: {
useCaching: true,
language: "en"
},
//
saySomething: function () {
console.log( "你好啊,世界" );
},
// 基于配置信息输出一些信息
reportMyConfig: function () {
console.log( "缓存: " + ( this.myConfig.useCaching ? "可用" : "禁用") );
},
// 从新配置信息
updateMyConfig: function( newConfig ) {
if ( typeof newConfig === "object" ) {
this.myConfig = newConfig;
console.log( this.myConfig.language );
}
}
};
// 你好啊,世界
myModule.saySomething();
// 缓存可用
myModule.reportMyConfig();
// fr
myModule.updateMyConfig({
language: "fr",
useCaching: false
});
// 缓存禁用
myModule.reportMyConfig();
复制代码
从上述代码中,能够看到,将一些操做和数据进行了封装。实现了,功能切片化处理。可是若是细想,感受利用字面量来封装数据和方法,感受有点鸡肋。由于这个对象是一个单例。若是只是在一个地方使用和操做,那彻底没有问题,可是若是是多个地方都用到呢,同时多个地方都须要对指定的属性进行修改。这就是牵一发而动全身的操做。缓存
同时,咱们能够看到利用字面量进行数据和方法封装。这些属性都是对外友好的。都是能在外部访问到的。没有丝毫的隐私,用传统OOP编程术语来说。这些属性都是public
的。也就是说,没法实现属性私有化。
为了解决字面量没法进行属性私有化。模块模式应用而生。这也是JS语言在早起比较经常使用的模块化处理方式。
var MODULE = (function () {
var my = {},
privateVariable = 1;
function privateMethod() {
// ...
}
my.moduleProperty = 1;
my.moduleMethod = function () {
// ...
};
return my;
}());
复制代码
从代码上看到,一个IIFE赫然映入眼帘。偷偷的告诉你们,模块模式
就是利用IIFE
实现的。
为了避免占用很大篇幅来说解这个实现。特定为你们准备了饭后甜点。JS_Module模式深刻了解一下
其实AMD(Asynchronous Module Definition)是一种为浏览器环境书写模块的模式。 而可以实现异步加载的关键就在于RequireJS。
RequireJS
是在ES6module
没出现以前,经常使用的前端模块解决方案。RequireJS
将加载的每个独立模块做为<script>
,并利用head.appendChild()
追加到文档中。
RequireJS等待全部依赖模块加载,将该模块须要的额外模块进行排序,并在依赖模块加载完以后,调用本模块的定义函数。
在项目中存在以下结构,咱们用cart.js
、inventory.js
来构建一个shirt.js
define(["./cart", "./inventory"], function(cart, inventory) {
//返回一个对象用于定义"my/shirt"模块
return {
color: "blue",
size: "large",
addToCart: function() {
inventory.decrement(this);
cart.add(this);
}
}
}
);
复制代码
固然,上述中的本地模块cart.js
也能够换成JQuery
等现成的模块。
若是想对AMD
有一个更深的了解,或者想知道如何定义一个AMD
模块。能够先移步RequireJS官网。
JS有一条定律:Atwood's Law
any application that can be written in JavaScript, will eventually be written in JavaScript.
JS是能够在服务端存在,因此出现了CommonJS
(A Module Format Optimized For The Server),使得JS不只仅在浏览器端应用,并且在服务端开始发光发热。
CommonJS
是专一于
和AMD
不是一个服务层面。
a.js
var x = 5;
var addX = function (value) {
return value + x;
};
module.exports.x = x;
module.exports.addX = addX;
复制代码
上面代码经过module.exports输出变量x和函数addX。
b.js
var example = require('./a.js');
console.log(example.x); // 5
console.log(example.addX(1)); // 6
复制代码
在ES6中,从语法层面就提供了模块化的功能。然而受限于浏览器的实现程度,若是想要在浏览器中运行,仍是须要经过Babel等转译工具进行编译。
person-module.js
var firstName = '北宸';
var lastName = '范';
export { firstName, lastName };
复制代码
test-module.js
import {firstName,lastName} from './person-module.js';
console.log(`${lastName}${firstName}`)//范北宸
复制代码
具体细节请参考Module的用法
模块化方案 | 加载 | 同步/异步 | 浏览器 | 服务端 | 模块定义 | 模块引入 |
---|---|---|---|---|---|---|
Module Pattern | 取决于代码 | 取决于代码 | 支持 | 支持 | IIFE | 命名空间 |
AMD | 提早预加载 | 异步 | 支持 | 构建工具r.js | define | require |
Common | 值拷贝,运行时加载 | 同步 | 原生不支持 | 须要使用browserify提早打包编译 | module.exports | require |
ES Modules(ES6) | 实时绑定,动态绑定,编译时输出 | 同步 | 支持 | 需用babel转译 | export | import |
本文参考连接: