一、什么是模块/模块化?
将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起,块(文件)的内部数据/实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信,这就是模块化。
二、模块化的进化史
1、全局模式
全局函数模式: 将不同的功能封装成不同的全局函数。
弊端:Global被污染了, 很容易引起命名冲突
最早我们用全局模式写代码:
module1.js:
function bar() {
console.log("bar()");
}
function foo() {
console.log("foo")
}
module2.js:
function foo() { //与另一个模块中的函数冲突了
console.log(`foo() ${data2}`)
}
<body>
<script type='text/javascript' src='module1.js'></script>
<script type='text/javascript' src='module2.js'></script>
<script type='text/javascript'>
window.onload=function () {
bar()//bar() other--
foo()//foo
}
</script>
</body>
2.namespace模式
namespace模式: 简单对象封装
作用: 减少了全局变量
问题: 不安全(数据不是私有的, 外部可以直接修改)
module1.js
let obj={
data:"我爱你中国",
bar(){
console.log(`obj()-${this.data}`)
}
}
module2.js
let obj2={
data:"我爱你中国!!!!!!obj2",
bar(){
console.log(`obj2()-${this.data}`)
}
}
<script type='text/javascript' src='module1.js'></script>
<script type='text/javascript' src='module2.js'></script>
<script type='text/javascript'>
window.onload=function () {
obj.bar()//obj()-我爱你中国
obj2.bar()//obj2()-我爱你中国!!!!!!obj2
obj.data="libufan" //外部可以直接修改
obj.bar()//obj()-libufan
}
</script>
3.IIFE模式
IFE模式: 匿名函数自调用(闭包) IIFE : immediately-invoked function expression(立即调用函数表达式) 作用: 数据是私有的, 外部只能通过暴露的方法操作
module3.js:
(function (window) {
//数据
let data = 'libufan'
//操作数据的函数
function foo() { //用于暴露有函数
console.log(`foo() ${data}`)
}
function bar() {//用于暴露有函数
console.log(`bar() ${data}`)
otherFun() //内部调用
}
function otherFun() { //内部私有的函数
console.log('otherFun()')
}
//暴露行为
window.myModule = {foo, bar}
})(window)
<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>
4.IIFE模式增强
IIFE模式增强 : 引入依赖
这就是现代模块实现的基石
(function (window,$) {
let data="wahaha"
function bar() {
console.log(`bar ${data}`)
$('body').css("background","red")
}
function foo() {
console.log("yaoyao")
privateMe()
}
function privateMe() {
console.log("私有")
}
window.myModule={bar,foo}
})(window,jQuery)
引入的js有顺序
<body>
<script type='text/javascript' src='jquery-1.10.1.js'></script>
<script type='text/javascript' src='module4.js'></script>
<script type='text/javascript'>
myModule.bar()//bar wahaha 页面红色
myModule.foo()//yaoyao 私有
</script>
</body>
如果一个页面需要引入多个js文件
那么会发生很多问题:
1). 请求过多
2). 依赖模糊
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()
</script>
首先我们要依赖多个模块,那样就会发送多个请求,导致请求过多
然后就是依赖关系模糊 我们不知道他们的具体依赖关系是什么 也就是说很容易因为依赖关系导致出错
以上的现象就导致了这样会很难维护。很可能出现牵一发而动全身的情况导致项目出现严重的问题
这些问题可以通过现代模块化编码和项目构建来解决
三、模块化规范
一.CommonJS
规范说明:
- 每个文件(js)都可当作一个模块,
- 在服务器端:模块的加载运行时是同步加载的;
- 在浏览器端: 模块需要提前编译打包处理, 因为浏览器不认识node,需要把node编译成浏览器认识的东西
规范基本语法:
1,暴露模块
- module.exports = value
- exports.xxx = value
- 问题: 暴露的模块到底是什么? 是exports对象,初始值是一个空对象 { }
2,引入模块
- require(xxx)
- 第三方模块:xxx为模块名
- 自定义模块: xxx为模块文件路径
CommonJS实现:
1.服务器端实现--CommonJS_Node模块化
步骤:
1. 下载安装node.js
2. 创建项目结构
```
|-modules
|-module1.js
|-module2.js
|-module3.js
|-app.js
|-package.json
{
"name": "commonJS-node",
"version": "1.0.0"
}
```
3. 下载第三方模块 /举例模块,可下载任意模块
* npm install uniq --save
4. 模块化编码
module1:
/**
* 使用module.exports = value向外暴露一个对象
*/
"use strict"
module.exports = {
bar(){
console.log("bar() module1")
}
}
module2:
/**
* 使用module.exports = value向外暴露一个函数。函数将exports初始值空对象覆盖
*/
"use strict"
module.exports=function foo() {
console.log("暴露方法foo() module2")
}
module3:
/* 暴露exports对象,给该对象添加不同的属性*/
exports.bar=function () {
console.log("module3 bar()")
}
exports.foo=function () {
console.log("module3 foo()")
}
app.js:主文件
/**
1. 定义暴露模块:
module.exports = value;
exports.xxx = value;
2. 引入模块:
let module = require(模块名或模块路径);
*/
"use strict"
//引用模块
//let uniq = require('uniq')
let module1=require('./module/module1')//module1是一个对象
let module2=require('./module/module2')//因为module2暴露的是一个函数,所以module2是一个函数
let module3=require('./module/module3')//module3是exports对象
//console.log(uniq([1, 3, 1, 4, 3]))
module1.bar()//bar() module1
module2()//暴露方法foo() module2
module3.bar()//module3 bar()
module3.foo()//module3 foo()
2.浏览器端实现 --CommonJS-Browserify模块化
Browserify模块化使用步骤 :
1. 创建项目结构
```
|-js
|-dist //打包生成文件的目录
|-src //源码所在的目录
|-module1.js
|-module2.js
|-module3.js
|-app.js //应用主源文件
|-index.html
|-package.json
{
"name": "browserify-test",
"version": "1.0.0"
}
```
2. 下载browserify--CommonJS的浏览器端的打包工具
* 全局: npm install browserify -g
* 局部: npm install browserify --save-dev //dev参数表示只在项目开发时依赖
3. 定义模块代码
module1:
/**
* 使用module.exports = value向外暴露一个对象
*/
module.exports = {
foo() {
console.log('moudle1 foo()')
}
}
module2.js
/**
* 使用module.exports = value向外暴露一个函数
*/
module.exports = function () {
console.log('module2()')
}
module3.js
/**
* 使用exports.xxx = value向外暴露一个对象
*/
exports.foo = function () {
console.log('module3 foo()')
}
exports.bar = function () {
console.log('module3 bar()')
}
app.js
/**
1. 定义暴露模块:
module.exports = value;
exports.xxx = value;
2. 引入模块:
var module = require(模块名或模块路径);
*/
//引用模块
let uniq = require('uniq')
let module1 = require('./module1')
let module2 = require('./module2')
let module3 = require('./module3')
//使用模块
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 // o 表示输出 ;o左边表示要打包的文件路径 右边表示打包后文件输出(存放)的路径
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--<script type="text/javascript" src="js/src/app.js"></script>-->
<!-- 引用打包后的文件-->
<script type="text/javascript" src="js/dist/bundle.js"></script>
</body>
</html>
区别Node与Browserify
Node.js运行时动态加载模块(同步)
Browserify是在转译(编译)时就会加载打包(合并)require的模块
二、AMD模块化
说明:
- Asynchronous Module Definition(异步模块定义)
- 专门用于浏览器端, 模块的加载是异步的
基本语法:
定义暴露模块:
//定义没有依赖的模块
define(function(){
return 模块
})//定义有依赖的模块
define(['module1', 'module2'], function(m1, m2){//暴露模块的通信方法
return 模块
})
引入使用模块:
require(['module1', 'module2'], function(m1, m2){
使用m1/m2
})
require.js使用步骤
1. 下载require.js, 并引入 官网: http://www.requirejs.cn/ github : https://github.com/requirejs/requirejs 将require.js导入项目: js/libs/require.js
2. 创建项目结构 ``` |-js |-libs |-require.js |-modules |-alerter.js |-dataService.js |-main.js |-index.html ```
3. 定义模块代码
dataService.js:
//定义没有依赖的模块
define(function () {
let msg= ' nihao '
function getMsg() {
return msg.toUpperCase()
}
function myMessage() {
console.log("mylibufan")
}
//暴露模块的通信方法
return {getMsg}
})
alerter.js:
/*
定义有依赖的模块
*/
define(['dataService', 'jquery'], function (dataService, $) {
let name = 'Tom2'
function showMsg() {
$('body').css('background', 'gray')
alert(dataService.getMsg() + ', ' + name)
}
return {showMsg}
})
4. 应用主(入口)js: main.js
(function () {
//因为引入了require.js模块,所以可以用require方法
//配置
require.config({
//基本路径
baseUrl: 'js/',
//映射: 模块标识名: 路径
paths: {
//自定义模块
'alerter': 'modules/alerter',
'dataService': 'modules/dataService',
//库模块
'jquery': 'libs/jquery-1.10.1',//jQuery支持require语法,内置jquery,所以 'jquery'小写
'angular': 'libs/angular'
},
//配置不兼容AMD的模块
//exports : 指定与相对应的模块名对应的模块对象
shim: {
angular: {
exports: 'angular'
}
}
})
//引入模块使用
require(['alerter', 'angular'], function (alerter, angular) {
alerter.showMsg()
console.log(angular);
})
})()
index.html:
<!DOCTYPE html>
<html>
<head>
<title>Modular Demo 2</title>
</head>
<body>
<!--引入require.js并指定js主文件的入口-->
<script type="text/javascript" src="js/libs/require.js" data-main="js/main.js"></script>
</body>
</html>