目标:
1. 理解模块与模块化
2. 了解各种模块化规范及其实现
3. 区别各个模块化规范之间的区别
4. 掌握基于CommonJS和ES6模块化规范的编码
一. 模块化的理解
1). 什么是模块?
将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起
块的内部数据/实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信
2). 一个模块的组成
私有的数据—>内部的变量
私有的行为(操作数据)—>内部的函数
向外暴露n个行为
3). 模块化
描述一种特别的编码项目JS的方式: 以模块为单元一个一个编写的
模块化的项目: JS编码时是按照模块一个一个编码的
4). 模块化的进化过程
- 全局function模式:
编码: 全局变量/函数
问题: 污染全局命名空间, 容易引起命名冲突/数据不安全
/**
* 全局函数模式: 将不同的功能封装成不同的全局函数
* 问题: Global被污染了, 很容易引起命名冲突
*/
//数据
let data = 'atguigu.com'
function foo() {
console.log('foo()')
}
function bar() {
console.log('bar()')
}
- namespace模式:
编码: 将数据/行为封装到对象中
解决: 命名冲突(减少了全局变量)
问题: 数据不安全(外部可以直接修改模块内部的数据)
/**
* namespace模式: 简单对象封装
* 作用: 减少了全局变量
* 问题: 不安全(数据不是私有的, 外部可以直接修改)
*/
let myModule = {
data: 'atguigu.com',
foo() {
console.log(`foo() ${this.data}`)
},
bar() {
console.log(`bar() ${this.data}`)
}
}
- IIFE模式/增强
IIFE: 立即调用函数表达式—>匿名函数自调用
编码: 将数据和行为封装到一个函数内部, 通过给window添加属性来向外暴露接口
引入依赖: 通过函数形参来引入依赖模块
1) IIFE
js文件中:
/**
* IIFE模式: 匿名函数自调用(闭包)
* IIFE : immediately-invoked function expression(立即调用函数表达式)
* 作用: 数据是私有的, 外部只能通过暴露的方法操作
* 问题: 如果当前这个模块依赖另一个模块怎么办?
*/
(function (window) {
//数据
let data = 'atguigu.com'
//操作数据的函数
function foo() { //用于暴露有函数
console.log(`foo() ${data}`)
}
function bar() {//用于暴露有函数
console.log(`bar() ${data}`)
otherFun() //内部调用
}
function otherFun() { //内部私有的函数
console.log('otherFun()')
}
//暴露行为
window.myModule = {foo, bar}
})(window)
//html文件中
myModule.foo()
myModule.bar()
//myModule.otherFun() //myModule.otherFun is not a function
console.log(myModule.data) //undefined 不能访问模块内部数据
myModule.data = 'xxxx' //不是修改的模块内部的data
myModule.foo() //没有改变
2). IIFE增强版
js文件中:
/**
* IIFE模式增强 : 引入依赖
* 这就是现代模块实现的基石
*/
(function (window, $) {
//数据
let data = 'atguigu.com'
//操作数据的函数
function foo() { //用于暴露有函数
console.log('foo() ${data}')
$('body').css('background', 'red')
}
function bar() {//用于暴露有函数
console.log(`bar() ${data}`)
otherFun() //内部调用
}
function otherFun() { //内部私有的函数
console.log('otherFun()')
}
//暴露行为
window.myModule = {foo, bar}
})(window, jQuery)
html文件中
<!--引入的js必须有一定顺序-->
<script type="text/javascript" src="jquery-1.10.1.js"></script>
<script type="text/javascript" src="module4.js"></script>
<script type="text/javascript">
myModule.foo()
问题:
1. 一个页面需要引入多个js文件
2. 问题:
1). 请求过多
2). 依赖模糊
3). 难以维护
3. 这些问题可以通过现代模块化编码和项目构建来解决
-->
<script type="text/javascript" src="module1.js"></script>
<script type="text/javascript" src="module2.js"></script>
<script type="text/javascript" src="module4.js"></script>
<script type="text/javascript" src="module3.js"></script>
<script type="text/javascript" src="../04_IIFE模式增强/jquery-1.10.1.js"></script>
<script type="text/javascript" src="../04_IIFE模式增强/test4.js"></script>
<script type="text/javascript">
module.foo()
二、CommonJS
缺点:在服务器端可能会发生阻塞;在浏览器端可能耗时长,用户体验不好,并且require语法浏览器端不认识,要提前编译打包处理
暴露的本质是exports对象,module1和3种,都是将空对象赋给exports,module2是将一个函数赋给export
1.commonjs基于服务器端(node)应用
Node.js模块化教程
- 下载安装node.js
- 创建项目结构
|-modules
|-module1.js
|-module2.js
|-module3.js
|-app.js
|-package.json //可以不手写,用npm init
{
"name": "commonJS-node",
"version": "1.0.0"
}
- 下载第三方模块
- npm install uniq --save //去重排序的第三方模块
- 模块化编码
- module1.js
* 使用module.exports = value向外暴露一个对象
module.exports = {
foo() {
console.log('moudle1 foo()')
}
}
- module2.js
使用module.exports = value向外暴露一个函数
module.exports = function () {
console.log('module2()')
};
// module.exports = { };再写一个不可以,会把上面的覆盖,module3.js可以
- module3.js
* 使用module.exports = value向外暴露一个对象
* //使用这种方法可以无限添加属性,module2.js不可以
exports.foo = function () {
console.log('module3 foo()')
};
exports.bar = function () {
console.log('module3 bar()')
};
exports.arr = [1,5,2,3,4,4];
- app.js
将其他模块汇集到主模块
/**
1. 定义暴露模块:
module.exports = value;
exports.xxx = value;
2. 引入模块:
var module = require(模块名或模块路径);
*/
"use strict";
//引用模块
let uniq = require('uniq') ;
//默认引入第三方库的时候要放在自定义库的上面,右边的uniq表示库,左边的表示函数
let module1 = require('./modules/module1');
let module2 = require('./modules/module2');
let module3 = require('./modules/module3');
//使用模块
module1.foo();
module2();
module3.foo();
module3.bar();
console.log(uniq([module3.arr]));
- 通过node运行app.js
- 命令: node app.js
- 工具: 右键–>运行
2.commonjs基于浏览器端应用服务
Browserify模块化使用教程
- 创建项目结构
|-js
|-dist //打包生成文件的目录
|-src //源码所在的目录
|-module1.js
|-module2.js
|-module3.js
|-app.js //应用主源文件
|-index.html //在浏览器端就要在浏览器页面上
|-package.json
{
"name": "browserify-test",
"version": "1.0.0"
}
- 下载browserify
- 全局: npm install browserify -g
- 局部: npm install browserify --save-dev //–save表示局部安装把依赖写进去,加上–dev是开发依赖的包,运行依赖不用加dev,这个是在开发时帮助编译打包用的。
package.json中上面是开发依赖,下面是运行依赖。但是实际上这里只是个说明,写错地方也没关系,因为用包的时候会去node_module中去找
- 定义模块代码
- module1.js
module.exports = { foo() { console.log('moudle1 foo()') } }
- module2.js
module.exports = function () { console.log('module2()') }
- module3.js
exports.foo = function () { console.log('module3 foo()') } exports.bar = function () { console.log('module3 bar()') }
- app.js (应用的主js)
//引用模块 let module1 = require('./module1') let module2 = require('./module2') let module3 = require('./module3') let uniq = require('uniq') //使用模块 module1.foo() module2() module3.foo() module3.bar() console.log(uniq([1, 3, 1, 4, 3]))
- 打包处理js:
- browserify js/src/app.js -o js/dist/bundle.js
- 页面使用引入:
<script type="text/javascript" src="js/dist/bundle.js"></script> //引入app.js会出错,需要用browserify编译打包,即上一步的处理, //才能将commonjs规范应用到浏览器端
三、AMD
定义有依赖的模块是显式声名依赖注入
1.noAMD
不用AMD自己写
文件结构
- dataService.js
//定义一个没有依赖的模块
(function (window) {
let msg = 'atguigu.com'
function getMsg() {
return msg.toUpperCase()
}
window.dataService = {getMsg}
})(window)
- alert.js
//定义一个有依赖的模块
(function (window, dataService) {
let name = 'Tom'
function showMsg() {
alert(dataService.getMsg() + ', ' + name)
}
window.alerter = {showMsg}
})(window, dataService)
- main.js
(function (alerter) {
alerter.showMsg()
})(alerter)
- test1.html
<script type='text/javascript' src='js/modules/dataService.js'></script>
<script type='text/javascript' src='js/modules/alerter.js'></script>
<script type="text/javascript" src="js/main.js"></script>
缺点:发三次请求,且依赖关系顺序不能出错
2.AMD规范
require.js使用教程
- 下载require.js, 并引入
- 官网: http://www.requirejs.cn/
- github : https://github.com/requirejs/requirejs
- 将require.js导入项目: js/libs/require.js
A.自定义的模块
- 创建项目结构
|-js
|-libs
|-require.js
|-modules
|-alerter.js
|-dataService.js
|-main.js
|-index.html
- 定义require.js的模块代码
- dataService.js
//定义没有依赖的模块 define(function () { let name = 'dataService.js' function getName() { return name; } //暴露模块 return {getName} })
- alerter.js
//定义有依赖模块 //右边是形参 define(['dataService'], function (dataService) { let msg = 'alert.js' function showMsg() { alert(dataService.getMsg() + ', ' + name) } //暴露的模块,是个对象,因为对象能够暴露多个属性 return {showMsg} })
- 应用主(入口)js: main.js
(function () {
//配置
requirejs.config({
//基本路径
baseUrl: "js/", //出发点在根目录下
//模块标识名与模块路径映射
paths: {
"alerter": "modules/alerter", //后面不加.js
"dataService": "modules/dataService",
}
})
//引入使用模块
requirejs( ['alerter'], function(alerter) {
alerter.showMsg()
})
})()
- 页面使用模块:
B.引入第三方模块
- 使用第三方基于require.js的框架(jquery)
- 将jquery的库文件导入到项目:
- js/libs/jquery-1.10.1.js
- 在main.js中配置jquery路径
paths: { 'jquery': 'libs/jquery-1.10.1' //jquery不可以写成jQuery,如下图所示 }
- 在alerter.js中使用jquery
define(['dataService', 'jquery'], function (dataService, $) { //这里的jquery也用小写 let msg = 'alert.js' function showMsg() { $('body').css({background : 'red'}) alert(name + ' '+dataService.getMsg()) } return {showMsg} })
jQuery支持AMD规范,库中写:若是使用AMD规范,则模块名字用jquery
C.不支持AMD的第三方库
- 使用第三方不基于require.js的框架(angular)
- 将angular.js导入项目
- js/libs/angular.js
angular需要单独的配置
- 在main.js中配置
(function () { require.config({ //基本路径 baseUrl: "js/", //模块标识名与模块路径映射 paths: { //第三方库 'jquery' : './libs/jquery-1.10.1', 'angular' : './libs/angular', //自定义模块 "alerter": "./modules/alerter", //地址中最前面的点不能去,表示当前目录,去掉点是表示一个层级 "dataService": "./modules/dataService" }, /* 配置不兼容AMD的模块 exports : 指定与相对应的模块名对应的模块对象 */ shim: {//就是一个单独的配置 'angular' : { //指向path中的angular模块 exports : 'angular' //暴露出的angular对象 } } }) //引入使用模块 require( ['alerter', 'angular'], function(alerter, angular) { alerter.showMsg() console.log(angular); }) })()
三、CMD
CMD兼具了AMD和commonJS的一些特点
sea.js简单使用教程
- 下载sea.js, 并引入
- 官网: http://seajs.org/
- github : https://github.com/seajs/seajs
- 将sea.js导入项目: js/libs/sea.js
- 创建项目结构
|-js
|-libs
|-sea.js
|-modules
|-module1.js
|-module2.js
|-module3.js
|-module4.js
|-main.js
|-index.html
- 定义sea.js的模块代码
- module1.js
定义没有依赖的模块define(function (require, exports, module) { //内部变量数据 let msg = 'module1'; //内部函数 function show() { return msg; } //向外暴露 module.exports = {show}; })
- module2.js
define(function (require, exports, module) { module.exports = { msg: 'I Will Back' } })
- module3.js
define(function (require, exports, module) { const API_KEY = 'abc123' exports.API_KEY = API_KEY })
- module4.js
define(function (require, exports, module) { //引入依赖模块(同步) var module2 = require('./module2') console.log(module2.msg); //引入依赖模块(异步) require.async('./module3', function (m3) { console.log('异步引入依赖模块3 ' + m3.API_KEY) }) function show() { console.log('module4 show()); } exports.show = show })
- main.js : 主(入口)模块
define(function (require) { var m1 = require('./module1') var m4 = require('./module4') m1.show() m4.show() }) //输出的顺序为模块1.2.4.3,因为注意有异步引入依赖模块
- index.html:
<!--
使用seajs:
1. 引入sea.js库
2. 如何定义导出模块 :
define()
exports
module.exports
3. 如何依赖模块:
require()
4. 如何使用模块:
seajs.use()
-->
<script type="text/javascript" src="js/libs/sea.js"></script>
<script type="text/javascript">
seajs.use('./js/modules/main')
</script>
四、 ES6
**
ES6-Babel-Browserify使用教程
上图为目录结构
- 定义package.json文件
{
"name" : "es6-babel-browserify",
"version" : "1.0.0"
}
- 安装babel-cli, babel-preset-es2015和browserify
//cli:command line interface ,babel-cli用来使用babel的命令的库
//babel-preset-es2015,es6打包成es5的库,babel有很多库,而我们目前用到的是这个
- npm install babel-cli browserify -g
- npm install babel-preset-es2015 --save-dev
- preset 预设(将es6转换成es5的所有插件打包)
- 定义.babelrc文件 //在根目录下,运行babel之前先要读这个文件 run control运行时需要读的文件
{ "presets": ["es2015"] //读到这个知道了自己要做的事是转换es6到es5
}
```
4. 编码
-
js/src/module1.js 分别暴露
export function foo() { console.log('module1 foo()'); } export function bar() { console.log('module1 bar()'); } export const DATA_ARR = [1, 3, 5, 1]
-
js/src/module2.js 统一暴露
let data = 'module2 data' function fun1() { console.log('module2 fun1() ' + data); } function fun2() { console.log('module2 fun2() ' + data); } export {fun1, fun2}
-
js/src/module3.js 默认暴露,可以暴露任意数据类型,暴露什么数据接收到的就是什么数据
//export default()=>{ // console.log("我是默认暴露的箭头函数"); //} //此种写法调用:import module3 from(./module3.js); //左边module3为形参,可以写xxx //module3();//因为此时module3是一个函数 //默认暴露只能写一次,要暴露多个数据 用对象 export default { name: 'Tom', setName: function (name) { this.name = name } }
-
js/src/app.js
//引入其他的模块 //语法: import xxx from '路径' import {foo, bar} from './module1' import {DATA_ARR} from './module1' import {fun1, fun2} from './module2' //分别暴露和统一暴露在引入的时候,必须用对象结构赋值的形式。因为模块里的东西很多,只取自己需要的 //统一暴露和分别暴露被统一称为常规暴露 import person from './module3' import $ from 'jquery'//第七步中详细解释 $('body').css('background', 'red') foo() bar() console.log(DATA_ARR); fun1() fun2() person.setName('JACK') console.log(person.name);
- 编译
- 使用Babel将ES6编译为ES5代码(但包含CommonJS语法) : babel js/src -d js/build //会自动形成build文件夹
- 使用Browserify编译js : browserify js/build/app.js -o js/dist/bundle.js
//因为执行上步之后会有require,某些浏览器仍然需要编译,不能自动生成dist文件夹,需要自己新建
- 页面中引入测试
<script type="text/javascript" src="js/lib/bundle.js"></script>
- 引入第三方模块(jQuery)
1). 下载jQuery模块:- npm install jquery@1 --save
2). 在app.js中引入并使用
import $ from 'jquery' $('body').css('background', 'red')
- npm install jquery@1 --save
本文为尚硅谷模块化教学总结
几种模块化规范的区别可看https://blog.youkuaiyun.com/Real_Bird/article/details/54869066
commonjs和ES6及requirejs模块循环引用