一、模块化的理解
1.什么是模块和模块化
- 将一个复杂的程序依据一定的规则(规范)封装成几个块(文件),并进行组合在一起块的内部数据/实现是私有的,只是向外部暴露一些接口(方法)与外部其它模块通信
模块化进化史
- 全局Function模式(存在的问题:全局被污染,很容易存在命名冲突)
/*全局函数 将不同功能封装成不同的全局函数*/
let msg = 'test'
function foo() {
console.log("foo",msg);
}
function bar() {
console.log("bar",msg);
}
- 简单封装:Namespace模式(减少全局上的变量数目,本质是对象,不安全,对象的属性可以随意被拿到并且更改)
/*Namespace模式,简单对象封装*/
let obj = {
msg:"test",
foo(){
console.log("foo",this.msg);
}
}
- 匿名闭包:IIFE模式(函数是JavaScript唯一的Local Scope)
/*IIFE模式,匿名函数自调用(闭包)*/
(function () {
let msg ="test";
function foo(){
console.log("foo",this.msg);
}
//向window添加一个module属性,将foo函数暴露出去
window.module = {foo};
})()
- 引入依赖:(模块模式,现代模块实现的基石)
/*引入依赖*/
(function (window,JQurey) {
let msg ="test";
function foo(){
console.log("foo",this.msg);
}
//向window添加一个module属性,将foo函数暴露出去
window.module = {foo};
$('body').css('background','red');
})(window,JQurey)
2.为什么要模块化
- 降低复杂度
- 提高解耦性
- 部署方便
3.模块化的好处
- 避免命名冲突(减少命名空间污染)
- 更好的分离,按需加载
- 更高复用性
- 高可维护性
4.页面引入加载script带来的问题
- 请求过多
- 依赖模糊
- 难以维护
二、模块化规范
1.CommonJS
- 每个文件都可当作一个模块
- 在服务器端:模块的加载是运行时同步加载的
- 在浏览器端:模块需要提前编译打包处理
基本语法
- 暴露模块
- module.exports = value(value代表任何的数据类型)
- exports.xxx = value(value代表任何的数据类型)
- 暴露的模块其实是一个exports对象
- 引入模块
- require(xxx)
- 第三方模块:xxx为文件名
- 自定义模块:xxx为模块文件路径
- require(xxx)
实现
- 服务器端实现
- Node,js
- http://nodejs.cn/
- 先创建三个模块
- 初始化package.json文件,在终端中进入到相应的目录,输入npm init ,初始化,第一个命名时不要出现大写字母和中文,其他的一直按确认就行。如果初始文件夹不是大写的和没有中文的,可以输入npm init -yes,之间初始化完成,自动生成package.json,当然也可以选择手动创建(注意:需要提前安装node.js,教程自行搜索)
- 浏览器端实现
- Browserify
- http://browserify.org/
- CommonJS的浏览器端打包工具
- 创建下面的目录结构(src下的文件是上述例子的,这个文件夹存放的是源代码文件 ,package.json生成方法同上,package-lock.json是自动生成的)
- 先在index.html中引入app.js,在浏览器运行,浏览器并不认识require
- 这里需要安装一个包(全局和局部都要安装,注意前提是先安装了node.js)
- 全局安装:npm i browserify -g
- 局部安装:npm i browserify --save-dev
- 安装完成会生成上图的文件,同时package.json中会多了一个配置
- 打包处理js
- browserify js/src/app.js -o js/dist/bundle.js
- 以 -o 为界,前面是要打包的文件的路径,后面是打包完成后的文件路径
- 要在对应项目的根路径下,否则会找不到相应的路径
- 一旦改动了相应的js文件,就要重新打包
- 此时dist中会自动生成已打包好的文件
- 在index.html中引入该文件,在浏览器成功运行
2.AMD
- Asynchronous Module Definition(异步模块定义)
- AMD专门用于浏览器端,模块的加载是异步的
基本语法
- 定义暴露模块
//定义没有依赖的模块
define(function(){
return模块
})
//定义有依赖的模块,//m1与module1对应
define(['module1', 'module2'],function(m1, m2){
return模块
})
- 引入使用模块
require(['module1', 'module2'], function(m1, m2){
使用m1/m2
})
实现
- 需要依赖一个库:Require.js
- https://requirejs.org/
- 下载完成后需要在项目中引入
- 不使用AMD进行模块化(采用引入依赖)
- dataService.js
//定义没有依赖的模块
(function (window){
let name = 'dataService.js ';
function getName( ) {
return name;
}
window.dataService = {getName}
})(window);
- alerter.js
//定义有依赖的模块
(function (window,dataService) {
let msg = 'alerter.js ' ;
function showMsg() {
console.log(msg,dataService.getName());
}
window.alerter = {showMsg}
})(window,dataService);
- main.js
(function (alerter) {
alerter.showMsg();
})(alerter);
- index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script src="js/modules/dataService.js"></script>
<script src="js/modules/alerter.js"></script>
<script src="js/main.js"></script>
</body>
</html>
- 运行截图
- 使用AMD进行模块化(自定义模块)
- dataService.js
//定义没有依赖的模块
define( function (){
let name = 'dataService.js ';
function getName( ) {
return name;
}
//暴露模块
return {getName};
});
- alerter.js
//定义有依赖的模块
define( ['dataService'],function(dataService){
let msg = 'alerter.js ' ;
function showMsg() {
console.log(msg,dataService.getName());}
//暴露模块
return {showMsg};
});
- main.js
(function () {
requirejs.config({
baseUrl: 'js/',//基本的路径
paths: {//配置路径
dataService: './modules/dataService',
alerter: './modules/alerter',
}
});
requirejs( [ 'alerter' ], function (alerter) {
alerter.showMsg();
})
})();
- index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script data-main="js/main.js" src="js/libs/require.js"></script>
</body>
</html>
- 运行截图
3.CMD(了解)
- Common Module Definition(通用模块定义)
- 专门用于浏览器端,模块的加载是异步的模块使用时才会加载执行
- 官网已经打不开了,所以所需要的依赖库已经下载不了,就当作了解
基本语法
- 定义暴露模块
/定义没有依赖的模块
define(function(require, exports, module){
exports.XXX = value;
module.exports = value;
}
//定义有依赖的模块
define(function(require, exports,module)//引入依赖模块(同步)
var module2=require( ./module2')//引入依赖模块(异步)
require.async(./ module3' , function (m3){
})
//暴露模块
exports.XXX = value;
})
- 引入使用模块
define(function (require){
var m1 =require( './module1')
var m4 = require('./ module4')
m1.show()
m4.show()
})
4.ES6
- 依赖模块需要编译打包处理
- 基本语法
- 导出模块:export
- 引入模块:import zzz from xxx
- zzz,在分别暴露和统一暴露中,暴露的是什么,zzz就是什么,默认暴露中可以自己命名
- 第三方模块:xxx为文件名
- 自定义模块:xxx为模块文件路径
- 实现
- 使用Babel将ES6编译为ES5代码
- https://www.babeljs.cn/
- 使用Browserify编译打包js
- 创建以下目录结构
- 初始化package.json
- 安装babel-cli, babel-preset-es2015和browserify
- npm install babel-cli browserify -g
- npm install babel-preset-es2015 --save-dev
preset预设(将es6转换成es5的所有插件打包)
- 定义.babelrc文件,创建一个文件,命名为.babelrc
- module1.js
//分别暴露
export function foo() {
console.log( 'foo() module1' );
}
export function bar() {
console.log("bar() module1");
}
export let arr = [1,2,3,4]
- module2.js
//统一暴露
function fun(){
console.log( 'fun( ) module2 ' );
}
function fun2(){
console.log( " fun2() module2");
}
export {fun,fun2};
- module3.js
//默认暴露可以暴露任意数据类型,暴露什么数据接收到的就是什么数据.
// export default vaLue;
//默认暴露只能写一次
export default () => {
console.log("我是默认暴露的箭头函数");
}
- main.js
import {foo, bar} from './module1';
import {fun, fun2} from './module2';
import module3 from './module3 ';
foo();
bar();
fun();
fun2();
module3();
- index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script src="js/dist/bundle.js"></script>
</body>
</html>
- 直接引入main.js,浏览器不能识别import
- 对其进行编译,将es6语法转成es5
- 使用Babel将ES6编译为ES5代码(但包含CommonJS语法) : babel js/src -d js/build
- 以-d分界,左边是需要转换的路径,它会找到该路径下所有的js文件,右边是转换后文件的存放路径,具体放哪可以自己定义,这个命令可以创建文件夹
- 此时若引入build中的main.js,浏览器不能识别require
- 使用Browserify编译js : browserify js/build/main.js -o js/dist/bundle.js
- 这个命令不能创建文件夹,要自己手动创建
- 此时引入dist下的bundle.js,成功执行