简介
模块化就是将一个复杂的程序安装一定的规则封装成若干个模块,并组合在一起
模块的内部数据相对而言是私有的,只是向外暴露一些方法与外部其他模块通信
没有模块化之前,常常一个JS
文件中会有很多功能的代码,不容易维护;使用其他第三方插件,也经常会有依赖的问题
现在常用的Javascript
模块化规范有四种:CommonJS
、AMD
、CMD
、ES6 module
。
AMD
、CMD
现在已经很少使用了
node.js
环境下一般使用CommonJS
规范,如要使用ES6 module
,需要配置babel
浏览器端一般都是使用ES6 module
,为了兼容低版本浏览器,一般会配置babel
模块化的优势:
- 代码分离成一个个小的模块,方便维护代码,按需加载
- 用到什么功能就引入对应的模块,提高代码的复用性
- 降低代码耦合度
全局Function
这种模式是将不同的功能封装成不同的全局函数
不同的功能模块写在不同的文件中,主模块或者html
文件引入
module1.js
文件
var data1 = 'module1 data'
function func1() {
console.log('module1 -- func1:' + data1)
}
function func2() {
console.log('module1 -- func2:' + data1)
}
module2.js
文件
var data2 = 'module2 data'
function func1() {
console.log('module2 -- func1:' + data2)
}
index.html
文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>module test</title>
</head>
<body>
<script src="./module1.js"></script>
<script src="./module2.js"></script>
<script>
func1()
func2()
data1 = 'new data'
</script>
</body>
</html>
以上代码可以看出,有以下缺点
- 顶级变量(浏览器中是
window
,nodejs
中是global
)被污染了。- 不同模块没有隔离,变量和方法很容易引起命名冲突
- 外部可以改变模块的变量、方法等
namespace
每个模块都输出一个对象。只要输出的对象名不同,可以解决命名冲突的问题。
但是顶级变量被污染和尾部可以改变模块的变量问题没有解决
module1.js
文件
const module1 = {
data: 'module1 data',
func1: function() {
console.log('module1 -- func1:' + this.data)
},
func2: function() {
console.log('module1 -- func2:' + this.data)
}
}
module2.js
文件
const module2 = {
data: 'module2 data',
func1: function() {
console.log('module2 -- func1:' + this.data)
}
}
index.html
文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>module test</title>
</head>
<body>
<script src="./module1.js"></script>
<script src="./module2.js"></script>
<script>
module2.func1()
module1.func2()
module1.data = 'new data'
module1.func1()
</script>
</body>
</html>
IIFE
IIFE
:immediately-invoked function expression
使用匿名函数自调用的方式隔离。
模块中的数据是私有的,不在暴露出来。外部只能通过暴露的方法操作
module1.js
文件
!(function(w, $) {
let data = 'module1 data'
const func1 = function() {
console.log($)
console.log('module1 -- func1:' + data)
}
const func2 = function() {
console.log('module1 -- func2:' + data)
}
// 修改data。这里就形成了闭包
function changeData(param) {
data = param
}
// 将用到的方法暴露出去
w.module1 = { func1, func2, changeData }
})(window, jQuery)
module2.js
文件
!(function(w) {
let data = 'module2 data'
function func1() {
console.log('module2 -- func1:' + data)
}
w.module2 = { func1 }
})(window)
index.html
文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>module test</title>
</head>
<body>
<script src="./jquery-3.5.1.min.js"></script>
<script src="./module1.js"></script>
<script src="./module2.js"></script>
<script>
module2.func1()
module1.func2()
// 不能直接修改
// module1.data = 'new data'
module1.changeData('new data')
module1.func1()
</script>
</body>
</html>
这种方式已经解决了上面提到的三个问题。
还存在一些问题
- 一个页面需要引入多个
js
文件,请求过多- 比如好几个模块都需要引入
jQuery
。为了避免引入多次,可以选择在入口文件中引入。内部依赖关系混乱,看不出来谁依赖谁。容易混乱,难以维护
CommonJS
CommonJS
是Node.js
采用的服务器端模块的规范。
也可以用于浏览器端,但需要先使用Browserify
进行编译打包
模块是同步加载的,在运行时加载,所以动态语句可写在判断中
在模块中this
代表的是当前模块
使用require
导入:require('path')
、require('./test.js')
使用exports
或者module.exports
导出:module.exports = value
、exports.xxx = value
单个值导出;导出的是一个值的拷贝
exports
&module.exports
:
exports
是对module.exports
的一个引用,两者都是指向同一个对象。- 模块的
require
能看到的只有module.exports
这个对象,它是看不到exports
对象的;- 而我们在编写模块时用到的
exports
对象实际上只是对module.exports
的引用。(exports = module.exports
)。- 可以使用
exports.a = 'xxx'
或exports.b = function () {}
添加方法或属性,本质上它也添加在module.exports
所指向的对象身上- 不能直接
exports = { a: 'xxx'}
这样子的意义就是将exports
重新指向新的对象!它和module.exports
就不是指向同一个对象- 一般都是直接使用
module.exports
module1.js
文件
let data = 'module1 data'
const func1 = function() {
console.log('module1 -- func1:' + data)
}
const func2 = function() {
console.log('module1 -- func2:' + data)
}
function changeData(param) {
data = param
}
// 将用到的方法暴露出去 - module.exports
module.exports = { func1, func2, changeData }
module2.js
文件
let data = 'module2 data'
function func1() {
console.log('module2 -- func1:' + data)
}
// 将用到的方法暴露出去 - exports
exports.func1 = func1
index.js
文件
const module1 = require('./module1')
const module2 = require('./module2')
module2.func1()
module1.func2()
// 不能直接修改
// module1.data = 'new data'
module1.changeData('new data')
module1.func1()
服务器端使用
- 执行
node ./index.js
浏览器端使用
-
npm init -y
,在项目中生成package.json
文件 -
安装
browserify
:npm i browserify
-
修改
package.json
文件的scripts
部分,添加命令
{
// ...
"scripts": {
// ...
"build": "browserify ./index.js -o dist/bundle.js"
},
// ...
}
-
执行命令:
npm run build
,在dist/
目录下生成bundle.js
文件 -
修改
index.html
文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>module test</title>
</head>
<body>
<script src="./dist/bundle.js"></script>
</body>
</html>
备注:
- 只有在
node
环境下才可以直接使用未打包的index.js
引入,因为在node
环境下有exports
、require
这些全局方法。- 浏览器环境下使用的话,需要先将代码编译成浏览器可识别的代码
AMD
AMD
(异步模块定义) 是 RequireJS
在推广过程中对模块定义的规范化产出。
代表框架:require.js
依赖前置 在定义的时候就要声明依赖模块。
AMD
规范是专门用于浏览器端的,模块加载是异步的
定义暴露模块的方法使用define
// 定义没有依赖的模块
define(function() {
// return 模块
})
// 定义有依赖的模块
define(['module1', 'module2'], function(m1, m2) {
// return 模块
})
引入模块使用require
require(['module1', 'module2'], function(m1, m2) {
// 使用m1、m2
})
- 修改
module1.js
文件
define(['jquery'], function($) {
let data = 'module1 data'
const func1 = function() {
console.log('module1 -- func1:' + data)
}
const func2 = function() {
console.log('module1 -- func2:' + data)
}
function changeData(param) {
data = param
}
// 将用到的方法暴露出去
return { func1, func2, changeData }
})
- 修改
module2.js
文件
define(function() {
let data = 'module2 data'
function func1() {
console.log('module2 -- func1:' + data)
}
return { func1 }
})
- 修改主模块
index.js
文件
// 应用入口,主模块
// 配置模块的路径
require.config({
// 配置所有引入模块的公共路径(基本路径)
baseUrl:'./',
// 模块标识名与模块路径映射
paths : {
// 模块名称(一定要与引入的模块名称一一对应): 模块的路径
// 一定不能写文件的后缀名,它会自动补全
module1: 'module1',
module2: 'module2',
// moduleDemo: 'modules/demo',
// 库/框架自己实现模块化的功能,定义了暴露模块的名称
jquery: 'jquery-3.5.1.min'
}
})
// 主模块
require(['module1','module2'], function (m1, m2) {
m2.func1()
m1.func2()
// 不能直接修改
// m1.data = 'new data'
m1.changeData('new data')
m1.func1()
})
- 修改
index.html
文件,引入require.js
。data-main="./index"
表示入口文件是./index.js
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>module test</title> </head> <body> <script data-main="./index" src="./require.js"></script> </body> </html>
CMD
CMD
(通用模块定义) 是 SeaJS
在推广过程中对模块定义的规范化产出。是根据CommonJS
和AMD
基础上提出的
代表框架:sea.js
就近依赖 在用到某个模式时再去require
定义模块使用define
define(function(require, exports, module) {
// ...
})
引入模块使用require
或者require.async
const xx = require('./xxx')
const yy = require.async(['模块名'], function (模块暴露内容) {
// ...
})
暴露模块使用exports
或者module.exports
html
文件引入script
标签
<script src='sea.js'></script>
<script>seajs.use('app.js')</script>
- 修改
module1.js
文件
define(function(require, exports, module) {
// require:引入依赖模块
// exports:暴露模块
// module:暴露模块
let data = 'module1 data'
const func1 = function() {
console.log('module1 -- func1:' + data)
}
const func2 = function() {
console.log('module1 -- func2:' + data)
}
// 修改data。这里就形成了闭包
function changeData(param) {
data = param
}
// 将用到的方法暴露出去
module.exports = { func1, func2, changeData }
})
- 修改
module2.js
文件
define(function(require, exports, module) {
let data = 'module2 data'
function func1() {
console.log('module2 -- func1:' + data)
}
exports.func1 = func1
})
- 修改
index.js
文件
define(function(require) {
const module1 = require('./module1')
const module2 = require('./module2')
module2.func1()
module1.func2()
// 不能直接修改
// module1.data = 'new data'
module1.changeData('new data')
module1.func1()
})
- 修改
index.html
文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>module test</title>
</head>
<body>
<script src="./sea.js"></script>
<script>
seajs.use('./index')
</script>
</body>
</html>
ES6 module
旨在成为浏览器和服务器通用的模块解决方案,但还是主要专门针对浏览器端。
兼容性不好,webpack
会经过Babel
转换为CommonJS
规范
编译时输出接口,输出的是值的引用,可以导出多个
静态语句只能写在顶层
this
是undefined
引入模块import
;导出模块export
import
// 只会执行import的js文件,但是不会引入任何值
import './demo.js'
// 等价于 import { default as a, b, c } from './demo.js'
import a, { b, c } from './demo.js'
// 作为一个整体加载,结果为 { default: xxx, b: xxx, c: xxx }
import * as demo from './demo.js'
export
let a = 123
function addA() {
a++
}
// 等价于 export default 123,执行 addA 不会影响 default
export default a
// 想执行 addA 影响到 default,只能用 as 把 default 作为 a 的别称
export { a as default }
// 等价于 export let a = 123; export functin addA(){...}
export { a, addA }
复合写法
export { a, b } from './demo.js'
// 等价于
// import { a, b } from './demo.js'
// export { a, b }
// 整体输出 值得注意的是 export * 会把 demo.js 中的 default 给丢掉
export * from './demo.js'
import函数
import
是静态编译的,没办法动态加载
ES6
后来新出了一个import()
用来实现动态加载
if (condition) {
import('./demo.js').then(res => {
console.log(res)
})
}
示例
- 修改
src/module1.js
文件
let data = 'module1 data'
const func1 = function() {
console.log('module1 -- func1:' + data)
}
const func2 = function() {
console.log('module1 -- func2:' + data)
}
// 修改data。这里就形成了闭包
function changeData(param) {
data = param
}
// 将用到的方法暴露出去
export default { func1, func2, changeData }
- 修改
src/module2.js
文件
let data = 'module2 data'
export function func1() {
console.log('module2 -- func1:' + data)
}
// function func1() {
// console.log('module2 -- func1:' + data)
// }
// export default { func1 }
- 修改
src/index.js
文件
import module1 from './module1'
// import module2 from './module2'
import { func1 as module2Func1 } from './module2'
module2Func1()
module1.func2()
// 不能直接修改
// module1.data = 'new data'
module1.changeData('new data')
module1.func1()
-
安装
babel-cli
,babel-preset-es2015
和browserify
:npm i babel-cli babel-preset-es2015 browserify
-
添加
.babelrc
文件
{
"presets": ["es2015"]
}
- 修改
package.json
文件
{
// ...
"scripts": {
// ...
"build": "babel src -d build/app.js && browserify build/app.js -o dist/bundle.js"
},
// ...
}
-
执行命令
npm run build
,会在dist/
目录下生成bundle.js
文件 -
修改
index.html
文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>module test</title>
</head>
<body>
<script src="./dist/bundle.js"></script>
</body>
</html>
发表评论