一 模块进化历史
1.1 全局 function 模式
module1.js
1
2
3
4
5
6
7
8
9
10//数据
let data = 'atguigu.com'
//操作数据的函数
function foo() {
console.log(`foo() ${data}`)
}
function bar() {
console.log(`bar() ${data}`)
}module2.js
1
2
3
4
5let data2 = 'other data'
function foo() { //与另一个模块中的函数冲突了
console.log(`foo() ${data2}`)
}test1.html
1
2
3
4
5
6
7
8<script type="text/javascript" src="module1.js"></script>
<script type="text/javascript" src="module2.js"></script>
<script type="text/javascript">
let data = "修改后的数据"
foo()
bar()
</script>说明:
- 全局函数模式: 将不同的功能封装成不同的全局函数
- 问题: Global 被污染了, 很容易引起命名冲突
1.2 namespace 模式
- module1.js
1
2
3
4
5
6
7
8
9let myModule = {
data: 'atguigu.com',
foo() {
console.log(`foo() ${this.data}`)
},
bar() {
console.log(`bar() ${this.data}`)
}
} - module2.js
1
2
3
4
5
6
7
8
9let myModule2 = {
data: 'atguigu.com2222',
foo() {
console.log(`foo() ${this.data}`)
},
bar() {
console.log(`bar() ${this.data}`)
}
} test2.html
1
2
3
4
5
6
7
8
9
10
11
12
13<script type="text/javascript" src="module2.js"></script>
<script type="text/javascript" src="module22.js"></script>
<script type="text/javascript">
myModule.foo()
myModule.bar()
myModule2.foo()
myModule2.bar()
myModule.data = 'other data' //能直接修改模块内部的数据
myModule.foo()
</script>说明
- namespace 模式: 简单对象封装
- 作用: 减少了全局变量
- 问题: 不安全
1.3 IIFE 模式
module3.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21(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)test3.html
1
2
3
4
5
6
7
8
9
10<script type="text/javascript" src="module3.js"></script>
<script type="text/javascript">
myModule.foo()
myModule.bar()
//myModule.otherFun() //myModule.otherFun is not a function
console.log(myModule.data) //undefined 不能访问模块内部数据
myModule.data = 'xxxx' //不是修改的模块内部的data
myModule.foo() //没有改变
</script>说明:
- IIFE 模式: 匿名函数自调用(闭包)
- IIFE : immediately-invoked function expression(立即调用函数表达式)
- 作用: 数据是私有的, 外部只能通过暴露的方法操作
- 问题: 如果当前这个模块依赖另一个模块怎么办?
1.4 IIFE 模式增强
- 引入 jquery 到项目中
module4.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22(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)test4.html
1
2
3
4
5<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()
</script>- 说明
- IIFE 模式增强 : 引入依赖
- 这就是现代模块实现的基石
- 页面加载多个 js 的问题
- 页面:
1
2
3
4<script type="text/javascript" src="module1.js"></script>
<script type="text/javascript" src="module2.js"></script>
<script type="text/javascript" src="module3.js"></script>
<script type="text/javascript" src="module4.js"></script> - 说明
- 一个页面需要引入多个 js 文件
- 问题:
- 请求过多
- 依赖模糊
- 难以维护
- 这些问题可以通过现代模块化编码和项目构建来解决
二 commonJS 规范
2.1 基于服务器端(node)应用
2.1.1 说明
- http://wiki.commonjs.org/wiki/Modules/1.1
- 每个文件都可当作一个模块
- 在服务器端: 模块的加载是运行时同步加载的
- 在浏览器端: 模块需要提前编译打包处理
2.1.2 基本语法
暴露模块
1 | module.exports = value |
具体参见 node.js 基础入门教程
引入模块
- require(xxx)
- 第三方模块:xxx 为模块名
- 自定义模块: xxx 为模块文件路径
2.1.3 实现
服务器端实现
Node.js
http://nodejs.cn/
浏览器端实现
Browserify
http://browserify.org/
也称为 CommonJS 的浏览器端的打包工具
区别 Node 与 Browserify
Node.js 运行时动态加载模块(同步)
Browserify 是在转译(编译)时就会加载打包(合并)require 的模块
2.1.4 Node.js 模块化教程
- 下载安装 node.js
- 创建项目结构
1 | |-modules- |
- 下载第三方模块
- npm install uniq —save
- 模块化编码
- module1.js
1
2
3
4
5module.exports = {
foo() {
console.log('moudle1 foo()')
}
} - module2.js
1
2
3module.exports = function () {
console.log('module2()')
} module3.js
1
2
3
4
5
6
7exports.foo = function () {
console.log('module3 foo()')
}
exports.bar = function () {
console.log('module3 bar()')
}app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27/**
1. 定义暴露模块:
module.exports = value;
exports.xxx = value;
2. 引入模块:
var module = require(模块名或模块路径);
*/
"use strict";
//引用模块
let module1 = require('./modules/module1')
let module2 = require('./modules/module2')
let module3 = require('./modules/module3')
let uniq = require('uniq')
let fs = require('fs')
//使用模块
module1.foo()
module2()
module3.foo()
module3.bar()
console.log(uniq([1, 3, 1, 4, 3]))
fs.readFile('app.js', function (error, data) {
console.log(data.toString())
})
- 通过 node 运行 app.js
- 命令: node app.js
2.2 Browserify 模块化使用教程
- 创建项目结构
1 | |-js |
- 下载 browserify
- 全局: npm install browserify -g
- 局部: npm install browserify —save-dev
- 定义模块代码
- module1.js
1
2
3
4
5module.exports = {
foo() {
console.log('moudle1 foo()')
}
} - module2.js
1
2
3module.exports = function () {
console.log('module2()')
} module3.js
1
2
3
4
5
6
7exports.foo = function () {
console.log('module3 foo()')
}
exports.bar = function () {
console.log('module3 bar()')
}app.js (应用的主 js)
1
2
3
4
5
6
7
8
9
10
11
12
13
14//引用模块
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
- 页面使用引入:
1
<script type="text/javascript" src="js/dist/bundle.js"></script>
三 AMD 规范
3.1 说明
- Asynchronous Module Definition(异步模块定义)
- https://github.com/amdjs/amdjs-api/wiki/AMD
- 专门用于浏览器端, 模块的加载是异步的
3.2 基本语法
定义暴露模块
1 | //定义没有依赖的模块 |
1 | //定义有依赖的模块 |
引入使用模块
1 | require(['module1', 'module2'], function(m1, m2){ |
3.3 require.js 使用教程
3.3.1 下载 require.js, 并引入
- 官网: http://www.requirejs.cn/
- github : https://github.com/requirejs/requirejs
- 将 require.js 导入项目: js/libs/require.js
3.3.2 创建项目结构
1 | |-js |
3.3.3 定义 require.js 的模块代码
dataService.js
1
2
3
4
5
6
7
8
9define(function () {
let msg = 'atguigu.com'
function getMsg() {
return msg.toUpperCase()
}
return {getMsg}
})alerter.js
1
2
3
4
5
6
7
8
9
10define(['dataService', 'jquery'], function (dataService, $) {
let name = 'Tom2'
function showMsg() {
$('body').css('background', 'gray')
alert(dataService.getMsg() + ', ' + name)
}
return {showMsg}
})
3.3.4 应用主(入口)js: main.js
1 | (function () { |
3.3.5 页面使用模块
3.3.6 使用第三方基于 require.js 的框架(jquery)
- 将 jquery 的库文件导入到项目:
- js/libs/jquery-1.10.1.js
- 在 main.js 中配置 jquery 路径
1
2
3paths: {
'jquery': 'libs/jquery-1.10.1'
} - 在 alerter.js 中使用 jquery
1
2
3
4
5
6
7
8define(['dataService', 'jquery'], function (dataService, $) {
var name = 'xfzhang'
function showMsg() {
$('body').css({background : 'red'})
alert(name + ' '+dataService.getMsg())
}
return {showMsg}
})
3.3.7 使用第三方不基于 require.js 的框架
(angular/angular-messages)
- 将 angular.js 和 angular-messages.js 导入项目
- js/libs/angular.js
- js/libs/angular-messages.js
- 在 main.js 中配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36(function () {
require.config({
//基本路径
baseUrl: "js/",
//模块标识名与模块路径映射
paths: {
//第三方库
'jquery' : 'libs/jquery-1.10.1',
'angular' : 'libs/angular',
'angular-messages' : 'libs/angular-messages',
//自定义模块
"alerter": "modules/alerter",
"dataService": "modules/dataService"
},
/*
配置不兼容AMD的模块
exports : 指定导出的模块名
deps : 指定所有依赖的模块的数组
*/
shim: {
'angular' : {
exports : 'angular'
},
'angular-messages' : {
exports : 'angular-messages',
deps : ['angular']
}
}
})
//引入使用模块
require( ['alerter', 'angular', 'angular-messages'], function(alerter, angular) {
alerter.showMsg()
angular.module('myApp', ['ngMessages'])
angular.bootstrap(document,["myApp"])
})
})() 页面:
1
2
3
4<form name="myForm">
用户名: <input type="text" name="username" ng-model="username" ng-required="true">
<div style="color: red;" ng-show="myForm.username.$dirty&&myForm.username.$invalid">用户名是必须的</div>
</form>
四 ES6 模块化
说明
依赖模块需要编译打包处理
4.1 语法
导出模块
- 分多次导出模块的多个部分
1 | export class Emp{ } |
- 一次导出模块的多个部分
1 | class Emp{ } |
- default 导出(只能有一个)
1 | export default {} |
导入模块
1 | import defaultModule from './myModule'; //导入默认的 |
4.2 使用示例
ES6-Babel-Browserify 使用教程
- 定义 package.json 文件
1 | { |
- 安装 babel-cli, babel-preset-es2015 和 browserify
1 | npm install babel-cli browserify -g |
- 定义.babelrc 文件
1 | { |
- 编码
- js/src/module1.js
1
2
3
4
5
6
7export function foo() {
console.log('module1 foo()');
}
export let bar = function () {
console.log('module1 bar()');
}
export const DATA_ARR = [1, 3, 5, 1] js/src/module2.js
1
2
3
4
5
6
7
8
9
10
11let data = 'module2 data'
function fun1() {
console.log('module2 fun1() ' + data);
}
function fun2() {
console.log('module2 fun2() ' + data);
}
export {fun1, fun2}js/src/module3.js
1
2
3
4
5
6export default {
name: 'Tom',
setName: function (name) {
this.name = name
}
}js/src/app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import {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/lib
- 使用 Browserify 编译 js : browserify js/lib/app.js -o js/lib/bundle.js
- 页面中引入测试
1 | <script type="text/javascript" src="js/lib/bundle.js"></script> |
- 引入第三方模块(jQuery)
1). 下载 jQuery 模块:- npm install jquery@1 —save
2). 在 app.js 中引入并使用1
2import $ from 'jquery'
$('body').css('background', 'red')
- npm install jquery@1 —save