目录
一、模块化概述
1.概念
随着前端应用日趋复杂,项目代码也大量膨胀,模块化就是一种最主流的代码组织方式,一个模块就是一个实现特定功能的文件,它通过把我们的复杂代码按照功能的不同,划分为不同的模块单独维护的这种方式,去提高我们的开发效率,降低维护成本。要用什么功能就加载什么模块模块化开发是当下最重要的前端开发范式之一,其只是思想,不包含具体实现。
2.模块化开发的好处
- 避免变量污染、命名冲突等
- 提高代码复用率
- 提高维护性
- 依赖关系的管理
3.模块化演变过程
(1)文件划分方式
- 将每个功能和相关的一些状态数据单独存放在不同的文件当中,此时一个文件就是一个独立的模块。然后将这个模块引入页面当中,直接调用模块中的成员(变量/函数),一个script标签就对应一个模块,所有模块都在全局范围内工作。
html文件:
<script type='text/javascript' src='module1.js'></script>
<script type='text/javascript'>
foo();
bar();
msg='NBA'; //会污染全局变量
foo();
</script>
js文件:
/**
* 全局函数模式: 将不同的功能封装成不同的全局函数
* 问题: Global被污染了, 很容易引起命名冲突
*/
let msg = 'modulel'
function foo() {
console.log('foo()', msg);
}
function bar() {
console.log('bar()', msg);
}
输出结果:
- 缺点:
- 污染全局作用域
- 命名冲突问题
- 无法管理模块依赖关系
(2)对象封装
- 将所有模块成员封装在一个对象中,当要使用的时候就调用这个对象的属性。
html文件:
<script type='text/javascript' src='module1.js'></script>
<script type='text/javascript'>
obj.foo();
obj.msg = 'NBA';
obj.foo();
</script>
js文件:
/**
* namespace模式: 简单对象封装
* 作用: 减少了全局变量
* 问题: 不安全(数据不是私有的, 外部可以直接修改)
*/
let obj = {
msg: 'atguigu',
foo() {
console.log('foo()', this.msg);
}
};
结果:
- 缺点:
- 没有私有空间,模块成员仍然可以在外部被访问或修改
- 无法管理模块依赖关系
(3)IIFE模式(立即执行函数)
- 采用该方式为模块提供私有空间,将模块中每个成员都放在一个函数提供对的私有作用域中,可确保私有成员的安全。私有成员只能在模块成员内通过闭包的形式访问。
html文件:
<script type='text/javascript' src='module3.js'></script>
<script type='text/javascript'>
msg='NBA';
console.log(msg);//此时不会修改函数内部msg,相当于给window添加了msg属性
obj.foo(); //此时obj是window的一个对象,可以调用foo属性
</script>
js文件:
/**
* IIFE模式: 匿名函数自调用(闭包)
* IIFE : immediately-invoked function expression(立即调用函数表达式)
* 作用: 数据是私有的, 外部只能通过暴露的方法操作
* 问题: 如果当前这个模块依赖另一个模块怎么办?
*/
//IIFE模式
(function (window) {
let msg = 'atguigu';
function foo() {
console.log('foo()', msg);
}
window.obj = { foo } //向window暴露一个对象
})(window)
结果:
(4)IIFE模式增强
- 引入jQuery依赖包
html文件:
<script type='text/javascript' src='jquery-1.10.1.js'></script>
<script type='text/javascript' src='module4.js'></script>
<script type='text/javascript'>
fn();
</script>
js文件:
/**
* IIFE模式增强 : 引入依赖
* 这就是现代模块实现的基石
*/
(function(window,$){
let msg='atguigu';
function foo() {
console.log('foo()',msg);
}
window.fn=foo; //向window添加方法
$('body').css('background','red'); //改变body的颜色
})(window,jQuery);
结果:
二、模块化规范
1.commonJS规范
(1)概述:
- node应用由模块组成,采用common.js模块规范。每一个文件就是一个模块,拥有自己独立的作用域、变量、方法等,读其他的模块不可见。在服务器端: 模块的加载是运行时同步加载的;在浏览器端: 模块需要提前编译打包处理。
- CommonJS规范规定,module变量代表当前模块,其是一个对象,其属性exports,即module.exports,是对外的接口。
module.exports = value或exports.xxx = value
- require方法用于加载模块,即加载模块的module.exports属性。
require(xxx)
第三方模块:xxx为模块名
自定义模块: xxx为模块文件路径
(2)commonJS特点:
- 所有代码都运行在模块作用域,不会污染全局作用域
- 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了;以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
- 模块加载的顺序,按照其在代码中出现的顺序。
(3)基于node.js模块化教程:
- 1.下载安装node.js
- 2.创建项目结构
|-modules
|-module1.js
|-module2.js
|-module3.js
|-app.js
|-package.json
{
"name": "commonJS-node",
"version": "1.0.0"
}
注意:在创建package.json的时候可以手动创建,也可以npm init进行创建,但是根目录中不能出现中文字符
- 3.下载第三方模块
npm install uniq --save
- 4.模块化编码
## module1.js
// module.exports=value 暴露一个对象
module.exports={
msg:'module',
foo(){
console.log(this.msg);
}
}
## module2.js
/* 暴露一个函数 module.exports=function(){} */
module.exports=function(){
console.log('module2');
}
// module.exports=function(){} 会覆盖上一个函数
## module3.js
// exports.xxx = value
exports.foo = function () {
console.log('foo() module3');
}
exports.bar = function () {
console.log('bar() module3');
}
exports.arr=[2,4,5,2,3,5,1,11];
## app.js
// 将其他模块汇集到主模块
const uniq = require('uniq'); //第三方模块:xxx为模块名
let module1=require('./modules/module1'); //自定义模块: xxx为模块文件路径
let module2=require('./modules/module2');
let module3=require('./modules/module3');
//使用模块
module1.foo();
module2();
module3.foo();
module3.bar();
let result=uniq(module3.arr);
console.log(result);
输出结果:
- 5.通过node运行app.js
- 命令: node app.js
- 工具: 右键-->运行
(4)commonjs基于浏览器端(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
* 全局: npm install browserify -g
* 局部: npm install browserify --save-dev
注意:在下载browserify时,我只局部安装报错了,我全局安装以后就好使了。
- 3.定义模块代码
## module1.js
module.exports = {
foo() {
console.log('moudle1 foo()')
}
}
## module2.js
module.exports = function () {
console.log('module2()')
}
## module3.js
exports.foo = function () {
console.log('module3 foo()')
}
exports.bar = function () {
console.log('module3 bar()')
}
## app.js
//引用模块
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]))
- 结果:
- 4.打包处理js
browserify js/src/app.js -o js/dist/bundle.js
其中:
-o:output
-js/src/app.js 是需要打包的文件路径
-js/dist/bundle.js 打包之后的输出路径
- 4.页面引入(在html文件中引入)
<script type="text/javascript" src="js/dist/bundle.js"></script>
引入的是打包好的文件,不要引错
2.AMD规范
(1)概述:
- AMD是Asynchronous Module Definition的缩写,意思就是"异步模块定义"。
- 异步模块模式AMD是当请求发出后,继续其他业务逻辑,直到模块加载完成执行后续逻辑,实现模块开发中的对模块加载完成后的引用。由于Node.js主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不需要异步加载,所以CommonJS规范比较适用。但是,如果是浏览器环境,要从服务器端加载模块,这时就必须采用异步模式,因此浏览器端一般采用AMD规范。
- AMD对应的就是很有名的RequireJS
- RequireJS是一个工具库,主要用于客户端的模块管理。它可以让客户端的代码分成一个个模块,实现异步或动态加载,从而提高代码的性能和可维护性。它的模块管理遵守AMD规范。
- 专门用于浏览器端,模块的加载是异步的
**
(2)定义引入模块:
定义没有依赖的模块:
define(function(){
.....
return 模块
})
定义有依赖的模块:
define(['module1','module2'],function(m1,m2){
.....
return 模块
})
引入模块:
require(['module1','module2'],function(m1,m2){
使用m1/m2
})
(3)实现:
-----a:非AMD实现
- 创建目录结构:
|-js
|-src //源码所在的目录
|-alerter.js
|-dataService.js
|-index.html
|-app.js
- 代码实现:
## dataService.js
// 定义一个没有依赖的模块
(function(window){
let name='dataService.js';
function getName(){
return name
}
window.dataService={getName};
})(window)
## alerter.js
// 定义一个有依赖的模块
(function(window,dataService){
let msg='alerter.js'
function showMsg(){
console.log(msg,dataService.getName());
}
window.alerter={showMsg};
})(window,dataService)
## app.js
(function (alerter) {
alerter.showMsg();
})(alerter)
## test.html
<script src="./js/dataService.js"></script> //由于alerter.js依赖于dataService,所以也要在引入alerter之前引入dataService.js文件
<script src="./js/alerter.js"></script> //引入alerter.js文件
<script src="./app.js"></script> //单独引入app.js找不到alerter文件
-----b:AMD实现
- 1.下载require.js, 并引入
-
- 创建项目结构
|-js
|-libs
|-require.js
|-modules
|-alerter.js
|-dataService.js
|-main.js
|-index.html
- 3.定义require.js的模块代码
##dataService.js
// 定义没有依赖的模块
define(function () {
let msg = 'atguigu.com'
function getMsg() {
return msg
}
// 暴露模块
return { getMsg }
})
##alerter.js
// 定义有依赖模块
define(['dataService'], function (dataService) {
let name = 'Tom2'
function showMsg() {
console.log(name, dataService.getMsg());
}
// 暴露模块
return { showMsg }
})
##main.js
// 引入模块
(function () {
// 配置
require.config({
//映射: 模块标识名: 路径
paths: {
//自定义模块
alerter: './modules/alerter',
dataService: './modules/dataService',
},
})
// 引入模块使用
requirejs(['alerter'], function (alerter) {
alerter.showMsg();
})
})()
##index.html
<script data-main="js/main.js" src="js/libs/require.js"></script>
##先加载require.js文件,然后加载main.js
- 4.使用第三方基于require.js的框架(jquery)
- 将jquery的库文件导入到项目:
* js/libs/jquery-1.10.1.js
- 在main.js中配置jquery路径
```
paths: {
'jquery': 'libs/jquery-1.10.1'
}
```
- 在alerter.js中使用jquery
```
define(['dataService', 'jquery'], function (dataService, $) {
var name = 'xfzhang'
function showMsg() {
$('body').css({background : 'red'})
alert(name + ' '+dataService.getMsg())
}
return {showMsg}
})
3.CMD规范
(1)概述:
专门用于浏览器端, 模块的加载是异步的 ;模块使用时才会加载执行。
(2)基本语法:
- 定义没有依赖的模块:
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
})
- 引入模块:
define(function (require) {
var m1 = require('./module1')
var m4 = require('./module4')
m1.show()
m4.show()
})
(3)模块化编程:
- 下载sea.js, 并引入,* 将sea.js导入项目: js/libs/sea.js
- 创建文件目录
|-js
|-libs
|-sea.js
|-modules
|-module1.js
|-module2.js
|-module3.js
|-module4.js
|-main.js
|-index.html
- 定义sea.js的模块代码
##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
<script type='text/javascript' src='js/libs/sea.js'></script>
<script type='text/javascript'>
seajs.use('./js/modules/main.js');
</script>
4.ES6规范
(1)概述:
- 依赖模块需要编译打包处理;
- 导出模块: export;
- 引入模块: import;
- 使用Babel将ES6编译为ES5代码;
- 使用Browserify编译打包js;
(2)实现:
-1. 定义package.json文件
{
"name" : "es6-babel-browserify",
"version" : "1.0.0"
}
- 2.安装babel-cli, babel-preset-es2015和browserify
* npm install babel-cli browserify -g
* npm install babel-preset-es2015 --save-dev
-3. 定义.babelrc文件
{
"presets": ["es2015"]
}
-4. 编码
##js/src/module1.js
// 暴露模块 分别暴露
export function foo(){
console.log('foo() module1');
}
export function bar(){
console.log('bar() module1');
}
export let arr=[1,2,3,4,5]
## js/src/module2.js
// 统一暴露
function fun() {
console.log('fun() module2');
}
function fun2() {
console.log('fun2() module2');
}
export { fun, fun2 };
## js/src/module3.js
//默认暴露 可以暴露任意数据类型,暴露什么数据类型,暴露什么数据接收到的就是什么数据
// export default value
// 方式一:
/* export default ()=>{
console.log('我是默认暴露的箭头函数');
} */
// 方式二:
export default {
msg: '默认暴露',
foo() {
console.log(this.msg);
}
}
## js/src/app.js
//引入其他的模块 语法:import xxx from '路径'
/* import module1 from './module1'
import module2 from './module2'
console.log(module1,module2); //undefined undefined
*/
// 引入第三方库
import $ from 'jquery'
// 需要使用解构赋值
import { foo, bar } from './module1'
import { fun, fun2 } from './module2'
import module3 from './module3'
$('body').css('background','green');
foo();
bar();
fun();
fun2();
// module3(); //默认暴露方式一的调用
module3.foo(); //默认暴露方式二的调用
- 5.编译(以上文件需要经过编译打包处理)
* 使用Babel将ES6编译为ES5代码(但包含CommonJS语法) : babel js/src -d js/lib
* 使用Browserify编译js : browserify js/lib/app.js -o js/lib/bundle.js
- 6.页面中引入测试
<script type="text/javascript" src="js/lib/bundle.js"></script>
注意:引入的是经过打包处理之后的文件,并且如果有改动还需要重新编译打包。
- 7.引入第三方模块(jQuery)
1)下载jQuery模块:
npm install jquery@1 --save
2). 在app.js中引入并使用
import $ from 'jquery'
$('body').css('background', 'red')