我们先来了解一下,什么是模块化,以及模块化的进化史。
将一个复杂的程序按照一定的规范,封装成几个块(文件),并进行组合在一起。 这些模块,最好都做到可复用性,比如可以在多个文件中使用处理时间的模块。
还有,块的内部数据/实现是私有的,只向外部暴露一些接口(方法)与外部其它模块进行通信。
最早,我们的js是写到一个文件中,想怎么写怎么写。这种情况,也就是我们最原始的html的方式。会造成变量的全局污染。
var gl = 'gl'
function a(){
gl = 'glb'
}
console.log(gl) // 'glb'
function b(){
}
简单的封装,把一部分属性放到对象中。通过对象.属性调用。 减少Global全局上的变量数目,但本质是对象,完全可以被改掉,不安全。
var wrap = {
foo:function(){console.log(1)},
sec:function(){}
}
wrap.foo = function(){alert(11)}
wrap.foo() // 就会弹出11
再看一个例子
如下:对象包括一个变量msg和一个方法foo,foo方法中使用了变量msg。
let obj = {
msg:'module2',
// es6简写
foo(){
// 想使用msg 就要使用this,否则是向全局要的msg变量
console.log(this.msg)
}
}
在另一个文件中使用
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script type="text/javascript" src="module.js"></script>
</head>
<body>
<script type="text/javascript">
obj.foo()
// 去改obj的msg同样也是可以修改的
obj.msg = 'NBA'
obj.foo() //NBA
</script>
</body>
</html>
立即执行函数。进来就执行,全局是看不到这个函数里面的数据。因此也修改(操作)不了它。
var Moudle = (fucntion(){
var _private = 'safe';
var foo = function(){
console.log(_private)
}
return {
foo:foo
}
})()
Module.foo()
Module._private; // undefined
那我们怎么使用呢?类似于JQuery把它暴露到window上。
如JQuery的最外层,其实就是一个立即执行函数。这就是模块模式,也是现代模块实现的基石。JQuery把$
和 jQ
添加给了window。所以我们才可以使用 $
var Moudle = (function(){
var _$body = $('body');
var foo = function(){
console.log(_$body)
}
return {
foo:foo
}
})(jQuery)
Moudle.foo()
当我们需要引入多个js文件。需要写多个标签。如下,如果1.js中用到jquery.js中的内容,这个加载顺序是不可以换的。并且,引入多少个<script>
标签,我们就需要发送多少次请求
<script src='jquery.js'></script>
<script src='1.js'></script>
<script src='2.js'></script>
<script src='3.js'></script>
<script src='4.js'></script>
复制代码
因此也就需要模块化规范
module.exports = value
exports.xxx = value
那么暴露的模块到底是什么? 肯定是个对象,是exports对象。
module.exports = value
,module.exports
本就有值,是空对象 {}
。然后value把空对象覆盖。exports.xxx = value
就相当于向对象添加属性。引入模块
require(xxx)
服务器端
node.js
浏览器端
Browserify,打包工具。
新建一个COMMONJS文件夹,然后 npm init
初始化packe.json(只需要回车就可以)。 modules用于存放所有子模块。modules的同级创建一个app.js它去使用其他子模块。
package.json
中存放着一些包管理信息,和配置信息
{
"name": "commonjs",
"version": "1.0.0",
"description": "",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
module.exports = value 暴露一个对象。
这就相当于定义一个对象变量。 let obj = {msg:''}
module.exports ={
msg:'module1',
foo(){
console.log(this.msg)
}
}
暴露一个函数 module.exports = function(){}
,当我们在他下面新加 module.export =function(){}
会将之前的覆盖掉。
这就相当于 定义一个 let a = function(){console.log(1)}
之后 再 a = function(){console.log(2)}
module.exports = function(){
console.log('module2')
}
// 在写会覆盖之前的
// module.exports = function(){
// console.log('覆盖了')
// }
exports.xxx = value 就可以随意向exports对象中添加内容,都可以导出。
// exports.xxx = value
exports.foo = function(){
console.log('foo() module3')
}
exports.bar = function(){
console.log('bar() module3')
}
调用,其他模块内容
使用require引入,因为是自定义模块所以是路径。
let module1 = require('./modules/module1')
let module2 = require('./modules/module2')
let module3 = require('./modules/module3')
// 现在module1是一个对象
module1.foo()
// module2等价于函数
module2()
module3.bar()
module3.foo()
然后运行看一下效果, 在app.js的层级运行node app.js
uniq 为什么需要加 --save ?
因为npm5.0之后都是默认加上了--save。所以不加上--save也会下载
npm install uniq --save
在module3中添加一个数组
// exports.xxx = value
exports.foo = function(){
console.log('foo() module3')
}
exports.bar = function(){
console.log('bar() module3')
}
exports.arr = [6,8,4,2,3,3,11]
在app中引入数组和uniq。注意第三方模块和自定义模块引入方式的区别。
let module1 = require('./modules/module1')
let module2 = require('./modules/module2')
let module3 = require('./modules/module3')
let uniq = require('uniq')
// 现在module1是一个对象
module1.foo()
// module2等价于函数
module2()
module3.bar()
module3.foo()
let data = uniq(module3.arr)
console.log(data) // [ 11, 2, 3, 4, 6, 8 ]
uniq可以去重,除了去重还可以排序。但是也存在一些问题,比如11排序到了2前面,因为它是按数字第一位编码排序的。
我们上面的服务端是基于node环境去运行的。而我们浏览器端,则是需要运行在浏览器页面的。
{
"name": "commonjs_browserify",
"version": "1.0.0"
}
browserify的作用就是打包,那项目放到线上环境,就与他无瓜了。
如下的安装命令,全局和局部都需要安装它。
## 全局
npm install browserify -g
## 局部
npm install browserify --save-dev
问题 这里局部安装时为什么会有 dev ?
那就需要知道如下的两个概念
言归正传,我们进入到commonJS_Browserify项目中运行 局部安装命令和全局安装命令。安装成功后,我们看一下package.json的内容。
{
"name": "commonjs_browserify",
"version": "1.0.0",
"devDependencies": {
"browserify": "^17.0.0"
}
}
不知道,你发没发现,这个依赖是加入到了devDependencies中的?我们在试一下安装uniq
npm install uniq // 这里其实相当于默认加了 --save
{
"name": "commonjs_browserify",
"version": "1.0.0",
"devDependencies": {
"browserify": "^17.0.0"
},
"dependencies": {
"uniq": "^1.0.1"
}
}
可以看到uniq加入到了dependencies下而不是devDependencies。
我们还是使用服务端的module中的内容
module1
// module.exports = value 暴露一个对象
module.exports ={
msg:'module1',
foo(){
console.log(this.msg)
}
}
module2
// 暴露一个函数 module.exports = function(){}
module.exports = function(){
console.log('module2')
}
module3
// exports.xxx = value
exports.foo = function(){
console.log('foo() module3')
}
exports.bar = function(){
console.log('bar() module3')
}
exports.arr = [6,8,4,2,3,3,11]
app
因为现在app.js和其他三个module.js文件在一个文件夹下。所以引入方式如下
let module1 = require('./module1');
let module2 = require('./module2');
let module3 = require('./module3');
module1.foo();
// 本质就是函数
module2();
module3.foo()
module3.bar()
我们试一下在index.html中直接引入app.js这个js的主文件
<html>
<head></head>
<body>
<script src="./js/src/app.js"></script>
</body>
</html>
如下报错:require is not defined。正如我们前面提到的,浏览器不认识require。因此需要browserify进行打包。
browserify js/src/app.js -o js/dist/bundle.js
browserify 要打包的文件 -output缩写 打包到的文件
可以看到bundle.js
文件已经生成了
如下就是bundle.js
文件的内容,也就是browserify进行打包后的文件。
引用打包后生成的文件。
<html>
<head></head>
<body>
<script src="./js/dist/bundle.js"></script>
</body>
</html>
AMD(Asyncchronous Module Definition) 异步模块定义。其主要用于浏览器端,模块的加载是异步的。他的出现是比commonJs规范的浏览器端实现,要早的。
AMD是要依赖于一个库的 Require.js
对于没有依赖的模块,define是一个方法,方法中只需要一个函数。而你想暴露的模块只需要return出去就可以使用了。
define(funtion(){
return 模块
})
此时,define方法的参数,就需要传两个了。
define(['module1','module2'],function(){
return 模块
})
require(['module1','module2'],function(m1,m2){
使用 m1 / m2
})
我们先看一下,不遵循模块化规范,也就是不使用Require.js依赖的方式。
新建文件
js/dataService.js 这个js模块,是没有引用外部依赖的。现在这个模块暴露到外面的名字是dataService。
// 立即执行函数
(function (window) {
let name = 'dataService.js';
function getName() {
return name
}
// 暴露给window 这个模块现在的名字:dataService
window.dataService = { getName } // 这里相当于 {getName:getName}
})(window)
js/alerter.js 我们的alert.js,依赖了上面的dataService模块
// 定义一个有依赖的模块
(function (window, dataService) {
let msg = 'alerter.js'
function showMsg() {
console.log(msg, dataService.getName())
}
window.alerter = { showMsg }
})(window, dataService)
App.js 在主模块中使用了alert模块
(function (alerter) {
alerter.showMsg()
})(alerter)
index.html
(O_O)? 直接引入主文件可以吗 ?
<html>
<head></head>
<body>
<script src="./App.js"></script>
</body>
</html>
当然是不太行。
📢 此处需要像我们最早的html一样,通过script标签,并且需要按顺序的引入文件。
<html>
<head></head>
<body>
<!-- 这么引入可以吗? -->
<script src="./js/dataService.js"></script>
<script src="./js/alerter.js"></script>
<script src="./App.js"></script>
</body>
</html>
require.js 官网 https://requirejs.org/
创建文件 与上面类似
libs/require.js
其内容复制于 requreJs官网 https://requirejs.org/docs/release/2.3.6/comments/require.js
其内容大概如下,不需要看懂
dataService.js
同样,是没有依赖其它模块的。我们上面没有引入reuireJs规范时,是通过window暴露的。而requireJs则只需要return 模块。
// 定义没有依赖的模块
define(function () {
let name = 'dataService.js'
function getName() {
return name;
}
// 暴露模块 之前都是window去暴露
return { getName }
})
alerter
是有依赖模块的。记得数组中的参数,与回调函数中的参数对应上。当然可以名字不对应上,但是为了更好的维护性,还是建议同名。
// 定义有依赖的模块
define(['dataService'], function (dataService) {
let msg = 'alerter.js'
function showMsg() {
console.log(msg, dataService.getName())
}
return { showMsg }
})
main.js
我么看一下requireJs官网是怎么使用的,如下的app.js
代表着主文件,我们这里用的是main.js
paths中的模块名要与模块所在位置对应,因为requirejs在 js/libs下。所以,我们dataService模块的相对路径为 ./modules/dataService
,alerter同理。
./modules/dataService
前面的。./modules/dataService
为什么不加.js
呢?因为它会默认加.js
,如果我们加了就会 .js.js
(function () {
requirejs.config({
// baseUrl:'js/libs'
paths: {
// 因为alerter中使用了
dataService: './modules/dataService',
alerter : './modules/alerter'
}
})
// 这里就不需要再向外暴露了 因为已经是主文件了
requirejs(['alerter'], function (alerter) {
alerter.showMsg()
})
})()
index.html
最后需要在html文件中引入主文件,官网的内容如下
<!DOCTYPE html>
<html>
<head></head>
<body>
<!-- src进来会先加载require.js 加载完成后去加载data-main中的内容也就是主文件 -->
<script data-main="js/main.js" src="js/libs/require.js"></script>
</body>
</html>
调用模块成功