前言:大型项目的后期维护想要轻松就需要考虑使用模块化规范取管理
早期js文件的页面加载情况:
<body>
<script src=""></script>
<script src=""></script>
<script src=""></script>
<script src=""></script>
<script src=""></script>
</body>
问题:
请求过多
依赖模糊---需要考虑顺序
难以维护
💖💖所以一定是要做出改变的!!!
什么是模块/模块化?
- 将一个复杂的程序依据一定的规则(规范)封装成几个块(文件),并进行组合在一起。块的内部数据/实现是私有的,只是向外部暴露一些接口(方法)与外部其他模块通信
模块化的进化史
一:最早的JS函数封装逻辑--Global被污染,很容易命名冲突
/*一:最早的JS函数封装逻辑--Global被污染,很容易命名冲突
---直接写在全局中
function feature1() { }
function feature2() { }
*/
举例说明:
let globalExposure='opp';
function feature1(){
console.log(globalExposure)
}
feature1() //opp
function feature2(){
console.log(globalExposure)
}
//如果我意外修改了global
globalExposure='my bad'
feature1() // my bad
feature2() // my bad
二:简单封装的Namespace模式逻辑--减少Global上的变量数目--本质是对象,不安全会被修改
/*二:简单封装的Namespace模式逻辑--减少Global上的变量数目--本质是对象,不安全会被修改
---定义一个api对象接口
let api ={
feature1:function(){},
feature2:function(){}
}
api.feature1();
*/
举例说明:
let obj = {
localExposure:'module2',
feature1(){
console.log('我feature1能访问localExposure吗?',this.localExposure)
}
}
obj.feature1() //我feature1能访问localExposure吗? module2
obj.localExposure='my bad'
obj.feature1(); //我feature1能访问localExposure吗? my bad
三:匿名闭包:IIFE模式--函数是Javascript唯一的Local Scope
/*三:匿名闭包:IIFE模式--函数是Javascript唯一的Local Scope
var api =(
//匿名函数
function(){
var _private='safe variable'
var feature1=function(){
console.log(_private)
}
return { feature1: feature1}
}
)()
api.feature1(); //safe variable
console.log(api._private) //undefined ---闭包内的变量是私有的(作用域的问题)*/
四:再增强一点:在IIFE的基础上引入依赖--模块模式的基石
/* 四:再增强一点:在IIFE的基础上引入依赖--模块模式的基石*/
举例说明:
var api =(
function($){//成功引入并使用上了Jquery
var _$body=$('body')
var feature1=function(){
console.log(_$body)
}
return { feature1: feature1 }
})(Jquery)
api.feature1()
举例说明一:
举例说明一:
//nodejs下没有window属性----基于nodejs环境下测试
(
function(global){
//在该作用域下是私有的
let _private="module3";
function feature1(){
console.log(_private)
}
// console.log('IIFE',_private) //IIFE module3
//暴露在全局
global.module3={feature1:feature1}
}
)(global)
module3.feature1() //IIFE module3
feature1() //调用失败
let _private='my bad' //添加到了global上
console.log('global',_private) //global my bad
举例说明二:
举例说明二:
(
function (global) {
let _private="module4"
function feature2(){
console.log('module4暴露',_private)
}
global.module4=feature2;
})(global)
module4(); //module4暴露 module4 ---module4是一个函数
举例说明三:
举例说明三:
var api =(
function(){
let _private="module5"
var feature1=function(){
console.log( _private)
}
return { feature1: feature1 }
})()
console.log(api) //{ feature1: [Function: feature1] }
api.feature1() //module5
为什么要模块化
-
降低复杂度
Code complexity(复杂) grows as the site gets bigger -
降低耦合度
Code complexity(复杂) grows as the site gets bigger -
部署准确性
Deployment(部署) wants optimizedd code in few HTTP calls
模块化的好处
-
避免命名冲突(减少命名空间污染)
-
更好的分离(按需加载)
-
更高的复用性
-
高可维护性
模块化规范
- CommonJS---Nodejs是基于Commonjs模块化规范编写的
- AMD
- CMD
- ES6
1.模块化详解--CommonJs
-
1.1 规范说明:
说明: 每个文件(js文件)都可以当作一个模块
在服务器端: 模块的加载是运行时同步加载的 ---基于服务器端实现:这里以借助Nodejs环境为例说明
在浏览器端: 模块需要提前编译打包处理 ---基于浏览器端实现:这里以借助browserify浏览器打包工具为例说明
-
1.2 基本语法
💖暴露模块 第一种方式---->module.exports=value(任意的数据类型)
分析---exports本身就是一个对象, 分析---value这个任意数据类型覆盖了原本的默认空对象(exports)的数据类型
第二种方式---->exports.xxx=value(任意的数据类型)
问题:暴露的模块到底是什么? 暴露的本质是exports这个对象
💖引入模块 ---->require(xxx) ---引入第三方模块时:xxx为模块(包)名 ---引入自定义模块时:xxx为模块文件路径
-
1.3 Commonjs实现在服务器端的构建目录为:
1.3.1这里是module1.js的页面代码
//这里是module1.js的页面代码
//暴露模块 module.exports=value
//value这个任意数据类型覆盖了原本的默认空对象(exports)的数据类型//module.exports={}本来是一个空对象
//这里以暴露一个对象为例
module.exports={
msg:'是我module1',
feature1(){ console.log(this.msg) }
}
1.3.2.这里是module2.js的页面代码
//这里是module2.js的页面代码
//暴露模块 module.exports=value
//这里以暴露一个函数为例
module.exports=function(){
console.log('是我module2')
}
//如果继续这样写下面的exports又被一个空对象覆盖了
// module.exports={}
1.3.3.这里是module3.js的页面代码
//这里是module3.js的页面代码
//exports.xxx=value可以暴露多个不会相互覆盖
//exports.xxx=value
exports.feature2=function(){
console.log('是我feature2','module3')
}
exports.feature3=function(){
console.log('是我feature3','module3')
}
exports.arr=[2,4,5,3,1,5,6,5]
1.3.4.这里是app.js的页面代码
//这里是app.js的页面代码
/** * 终极目标--基于服务器端实现(nodejs环境下)
* ---将(其他的模块)modules下的所有模块汇集到这个主模块
* ---使用CommonJs规范 * */
//引入第三方模块--基于node_modules文件夹下查找
let uniq=require('uniq')
//引入自定义模块
let module1=require('./modules/module1')
let module2=require('./modules/module2')
let module3=require('./modules/module3')
// 通过module.exports={}语法暴露的---暴露出一个具有feature1属性的对象
module1.feature1(); //是我module1
//通过module.exports=function(){}语法暴露的--暴露出一个函数
module2(); //是我module2
//通过exports.xxx=value暴露的---现在的module3是一个对象(对象上一直可以增加属性和方法)
module3.feature3() //是我feature3 module3
module3.feature2() //是我feature2 module3
//利用引入的第三方模块uniq实现数组的去重
let res=uniq(module3.arr);
console.log(res) //[ 1, 2, 3, 4, 5, 6 ]
- 1.4 Commonjs实现在浏览器端的构建目录为:
1.4.1 这里是module1.js的页面代码
//这里是module1.js的页面代码
//暴露模块 module.exports=value
//这里以暴露一个对象为例
module.exports={
msg:'是我module1',
feature1(){
console.log(this.msg)
}
}
1.4.2. 这里是module3.js的页面代码
//这里是module3.js的页面代码
//这里用exports.xxx=value的语法暴露
exports.feature2=function(){
console.log('是我feature2','module3')
}
exports.feature3=function(){
console.log('是我feature3','module3')
}
exports.arr=[2,4,5,3,1,5,6,5]
1.4.3. 这里是module2.js的页面代码
//这里是module2.js的页面代码
//暴露模块 module.exports=value
//这里以暴露一个函数为例
module.exports=function(){
console.log('是我module2')
}
//如果继续这样写下面的exports又被空对象覆盖了
// module.exports={}
1.4.4. 这里是app.js的页面代码
//这里是app.js的页面代码
/**应用主模块文件
* 终极目标--基于浏览器端实现
* ---将(其他的模块)modules下的所有模块汇集到这个主模块
* ---使用CommonJs规范 *
**/
/*
如果直接在index.html引入<script type='text/javascript' src='./js/src/app.js'></script>
并在浏览器中打开的话会默认报错:app.js:8 Uncaught ReferenceError: require is not defined*/
let module1=require('./module1')
let module2=require('./module2')
let module3=require('./module3')
module1.feature1();
module2();
module3.feature2();
module3.feature3();
//重大改变:两步曲
//1.先通过browserify打包编译处理
//browserify js/src/app.js -o js/dist/bundle.js
//2.再改变index.html引入的路径就实现了CommonJs在浏览器端成功运行
/* <script type='text/javascript' src='./js/dist/bundle.js'></script> */
1.4.5. 注意事项
注意事项:
cnpm init 时给packagename取名时不能有中文和大写字母
--save 局部安装把依赖写入进去 运行依赖--线上(生产)时需要用的依赖
--save-dev 开发依赖--开发的时候需要用到依赖
所需下载第三方模块:
1.cnpm install uniq --save
2.cnpm install browserify -g
3.cnpm install browserify --save-dev
2.模块化详解--AMD规范
规范:
一:
说明:
Asynchronous Module Definition(异步模块定义)
专门用于浏览器端,模块的加载是异步的
二:
基本语法:
💖定义暴露模块
---->定义没有依赖的模块
define(function(){
return 模块(通过return去暴露模块,否则括号内的都是私有的)
})
---->定义有依赖的模块
define(['module1','module2'],function(m1,m2){
return 模块
})
💖引入使用模块---放在主模块汇集
require(['module1','module2'],function(m1,m2){
使用m1/m2
})
实现:利用require.js在浏览器端实现
----
情景重现一:在还没有AMD规范时是怎么模拟实现AMD规范的
情景重现一:noamd模拟实现的构建目录为:
这里是aleter.js的页面代码
//这里的代码中dataService依赖于dataservice.js
//所以在引用的时候要先去引用dataservice.js
//定义一个有依赖的模块
(function(window,dataService){
let msg='alerter.js';
function showMsg(){
console.log(msg,dataService.getName())
}
window.alerter={showMsg} //alerter被挂载在了window上
})(window,dataService) //dataServeice被挂载在window上
这里是dataservice.js的页面代码
//定义一个没有依赖的模块
(function(window){
let name='dataService.js';
function getName(){
return name;
}
//在window对象上挂载着dataServeice对象
window.dataService={getName}
})(window)
这里是app.js的页面代码
(function(){
alerter.showMsg()
})(alerter)
//因为在aleter.js中把alerter通过window暴露了(暴露在了window)
这里是test.html的页面代码
//特别注意script的引入顺序
<!DOCTYPE html>
<html lang="en">
<head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title></head>
<body>
<!-- 依赖关系顺序不能变 请求增加 -->
<script type="text/javascript" src="./js/dataservice.js"></script>
<script type="text/javascript" src="./js/aleter.js"></script>
<script type="text/javascript" src="./app.js"></script>
</body>
</html>
情景重现二:AMD模块化规范出现后的使用方式
情景重现二:AMD模块化规范的使用方式的构建目录为:
这里是dataService.js的页面代码
//定义没有依赖的模块
define(function(){
let name='dataService';
function getName(){
return name;
}
//暴露模块---暴露出去一个对象
return {getName}
})
这里是alerter.js的页面代码
//定义有依赖的模块
//jquery支持amd规范----jquery规定好了如果监测到requirejs默认暴露出名字叫'jquery'
//jquery-1.10.1.js有关于amd规范的设置在第9802行
define(['dataService','jquery'],function(dataService,$){
let msg='alerter.js';
function showMsg(){
console.log(msg,dataService.getName())
}
$('body').css('background','pink')
//暴露模块---暴露出去一个对象
return {showMsg};
})
这里是main.js的页面代码
(function(){
/**
* angular如果想要使用amd规范需改增加配置shim
* requirejs.config({
// baseUrl:'', //基本的路径 ---以它的角度去找路径 js/-->./js 出发点在根目录下
paths:{ //配置路径 ---这里是站在main.js的角度去找路径 ---映射文件路径
angular:'./libs/angular'
},
shim:{
angular:{
//暴露的是这个库的这个对象
exports:'angular'
}
}
//requirejs在拼接路径的时候会自动把.js后缀名拼接上去
})
*
*/
requirejs.config({
// baseUrl:'', //基本的路径 ---以它的角度去找路径 js/-->./js 出发点在根目录下
paths:{ //配置路径 ---这里是站在main.js的角度去找路径 ---映射文件路径
dataService:'./modules/dataService', //不用加.js文件后缀名
alerter:'./modules/alerter',
jquery:'./libs/jquery-1.10.1'
}
//requirejs在拼接路径的时候会自动把.js后缀名拼接上去
})
requirejs(['alerter'],function(alerter){//无需引入不需要的依赖
alerter.showMsg(); })
})()
这里是test2.html的页面代码
<!DOCTYPE html>
<html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title></head>
<body>
<!-- 引入require.js并指定js主文件的入口 -->
<script data-main="js/main.js" src="js/libs/require.js"></script>
</body>
</html>
3.模块化详解--CMD(了解即可)
规范:
一:
说明:
Common Module Definition(通用模块定义)
专门用于浏览器端,模块的加载是异步的
模块使用时才会加载执行
二:
基本语法:
💖定义暴露模块
---->定义没有依赖的模块
define(function(require,exports,module){
exports.xxx=value
module.exports=value
})
---->定义有依赖的模块
define(function(require,exports,module){
//引入依赖模块(同步)
var module2=require('./module2')
//引入依赖模块(异步)
//引入模块的相对路径 回调函数
require.async('./module3',function(m3){
})
//暴露模块
exports.xxx=value
})
💖引入使用模块---不再需要向外去暴露只需require一个参数
define(function(require){
var m1=require('./module1')
var m4=require('./module4')
m1.show()
m4.show()
})
可以基于浏览器端实现---依赖 Sea.js
CMD模块化规范的构建目录为:
这里是module1.js的页面代码
//定义没有依赖的模块
define(
function(require,exports,module){
let msg='module1';
function foo(){
return msg;
}
//暴露模块
module.exports={foo}
}
)
这里是module2.js的页面代码
define(function(require,exports,module){
let msg="module2";
function bar(){
console.log(msg)
}
module.exports=bar;
})
这里是module3.js的页面代码
define(function(require,exports,module){
let data='module3';
function fun(){
console.log(data)
}
exports.module3={fun}
})
这里是module4.js的页面代码
define(function(require,exports,module){
let msg='module4';
//同步引入
let module2=require('./module2')
module2();//同步会阻塞
//异步引入
require.async('./module3',function(module3){
module3.module3.fun()
}) //异步会等待
function fun2(){
console.log(msg)
}
exports.fun2=fun2;
})
这里是main.js的页面代码
define(function(require){
let module1=require('./module1')
console.log(module1.foo());
let module4=require('./module4')
module4.fun2();
})
这里是index.html的页面代码
<!DOCTYPE html>
<html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title></head>
<body>
<!--引入的是sea.js依赖-->
<script type="text/javascript" src="./js/libs/sea.js"></script>
<script type="text/javascript">
seajs.use('./js/modules/main.js')
</script>
</body>
</html>
4.模块化详解--ES6规范
规范:
一:
说明:
依赖模块需要编译打包处理
二:
基本语法:
💖定义暴露模块---导出模块
----> export
💖引入使用模块
----> import
可以基于浏览器端实现
---->先使用Babel将ES6编译为ES5代码
---->再使用Browserify编译打包js
实现ES6模块化规范开始的工作之一:
1.安装 babel-cli babel-preset-es2015 browserify 三个第三方包
//cli ---command line interface 命令行接口
cnpm install babel-cli browserify -g
cnpm install babel-preset-es2015 --save-dev
cnpm install babel-prest-env -D
preset 预设(将es6转换成es5的所有插件打包)
---------
2.定义 .babelrc文件
//run control 运行时控制文件
{
"presets":["es2015"]
}
可能遇到的问题一:
Uncaught SyntaxError: Cannot use import statement outside a module
问题一解决方法:使用Babel将ES6编译为ES5代码(但包含CommonJs语法)
终端输入指令 ---> babel js/src -d js/lib
可能遇到的问题二:
Uncaught ReferenceError: require is not defined
问题二解决方法:使用Browserify编译js
终端输入指令 ---> browserify js/lib/main.js -o js/lib/bundle.js
注意事项:
-------
注意事项:
在通过npm下在第三方包的时候可以通过指令指定版本下载
cnpm install jquery@1
cnpm install jquery@1.11
import $ from 'jquery' //在通过import语法就可以使用了
如果不借助nodejs环境可以使用<script type='module'></script>
ES6模块化规范的构建目录为:
这里是module1.js的页面代码
//这里是module1.js的页面代码
//暴露模块 分别暴露--不会被覆盖
export function foo(){
console.log('foo() module1')
}
export function bar(){
console.log('bar() module1')
}
export let arr=[1,2,3,4,5]
----------
这里是module2.js的页面代码
//这里是module2.js的页面代码
//统一暴露
function fun(){
console.log('fun() module2')
}
function fun2(){
console.log('fun2() module2')
}
export { fun, fun2}
-----------
这里是module3.js的页面代码
//这里是module3.js的页面代码
//默认暴露 可以暴露任意数据类型
//暴露出的是那么数据类型接收到的就是什么数据类型
// export default ()=>{// console.log('我是默认暴露的')// }
//暴露一个函数,可以这样不划算,还不如暴露一个对象
export default {
msg:"我又是默认暴露的",
foo(){
console.log('会覆盖掉之前用export default 暴露的',this.msg)
}
}
---------
这里是main.js的页面代码
//这里是main.js的页面代码
//引入其他模块//语法---->import xxx from '路径'
import {foo,bar} from './module1';
import {fun,fun2} from './module2';
console.log(foo,fun)
import xx from './module3'
// xx(); //我是默认暴露的 console.log(xx)
这里是index.html的页面代码----引用的script是编译构建后的bundle.js
//这里是index.html的页面代码----引用的script是编译构建后的bundle.js
<!DOCTYPE html>
<html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title></head>
<body>
<script src='./js/lib/bundle.js'></script>
</body>
</html>