模块化基础知识
1 简介
1.1 模块
将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起。
块的内部数据/实现是私有的, 只是向外暴露一些接口(方法)与外部其它模块通信
一个模块的组成:
- 私有的数据:内部的变量
- 私有的行为(操作数据):内部的函数
- 向外暴露n个行为
1.2 模块化
描述一种特别的编码项目JS
的方式:以模块为单元一个一个编写的
模块化的项目: JS
编码时是按照模块一个一个编码的
模块化,就是遵守固定的规则,把一个大文件拆成独立并互相依赖的多个小模块,可以提高了代码的复用性
、提高了代码的可维护性、可以实现按需加载
2 模块化的进化过程
2.1 全局function模式
编码: 全局变量/函数
问题: 污染全局命名空间, 容易引起命名冲突/数据不安全
案例:module1.js
// 数据
let data = 'module1.js'
// 操作数据的函数
function foo() {
console.log(`foo() ${data}`)
}
function bar() {
console.log(`bar() ${data}`)
}
module2.js
let data2 = 'module2.js';
function foo() { // 与另一个模块中的函数冲突了
console.log(`foo() ${data2}`)
}
test1.html
<script type="text/javascript" src="module1.js"></script>
<script type="text/javascript" src="module2.js"></script>
<script type="text/javascript">
let data = "修改后的数据"
foo()
bar()
</script>
2.2 namespace模式: 简单对象封装
编码: 将数据/行为封装到对象中
解决: 命名冲突(减少了全局变量)
问题: 数据不安全(外部可以直接修改模块内部的数据)
案例:module1.js
let myModule = {
data: 'module1.js',
foo() {
console.log(`foo() ${this.data}`)
},
bar() {
console.log(`bar() ${this.data}`)
}
}
module2.js
let myModule2 = {
data: 'module2.js',
foo() {
console.log(`foo() ${this.data}`)
},
bar() {
console.log(`bar() ${this.data}`)
}
}
test2.html
<script type="text/javascript" src="module1.js"></script>
<script type="text/javascript" src="module2.js"></script>
<script type="text/javascript">
myModule.foo()
myModule.bar()
myModule2.foo()
myModule2.bar()
myModule.data = 'other data' // 能直接修改模块内部的数据
myModule.foo()
</script>
2.3 IIFE
模式/ IIFE
增强模式
IIFE
: 立即调用函数表达式--->匿名函数自调用
编码: 将数据和行为封装到一个函数内部, 通过给window
添加属性来向外暴露接口
引入依赖: 通过函数形参来引入依赖模块
(function(window, module2){
var data = 'atguigu'
function foo() {
module2.xxx()
console.log('foo()'+data)
}
function bar() {
console.log('bar()'+data)
}
window.module = {foo}
})(window, module2)
3 CommonJS
Commonjs
可以在服务器端(通过Nodejs
)和浏览器端(通过Browserify
)实现
nodejs
是在运行时,动态同步引入到模块中的,而Browserify
则是在运行前就对模块进行编译打包的处理(已经将依赖的模块包含进来了),运行的是打包生成的js, 运行时不存在需要再从远程引入依赖模块
3.1 基本语法
3.1.1 向外暴露成员:exports或module.exports
// 1. 分别向外暴露多个成员
exports.xxx = xxx
exports.yyy = yyy
// 2. 统一向外暴露多个成员
module.exports = {
xxx,
yyy
}
// 3. 默认只向外暴露一个成员
module.exports=XXX
只向外暴露一个成员,必须使用
module.exports=XXX
这种方式
3.1.2 引用使用模块 require
var module = require('模块名/模块相对路径')
3.2 commonjs
模块原理
在 Node 中,每个模块内部默认都有一个自己的 module 对象
console.log(module)
// 返回结果:
id: '.',
exports: {}, // 默认也是一个对象
parent: null,
filename: 'E:\\nodejs\\资料\\03\\code\\1.js',
loaded: false,
children: [],
paths:
[ 'E:\\nodejs\\资料\\03\\code\\node_modules',
'E:\\nodejs\\资料\\03\\node_modules',
'E:\\nodejs\\资料\\node_modules',
'E:\\nodejs\\node_modules',
'E:\\node_modules' ] }
每个模块最后都会默认 return module.exports
,所以如果你想要导出一个成员,直接给 exports 赋值是不起作用的
// 导出一个成员,直接给 exports 赋值是不管用的
exports='hello'
// 必须是直接给 module.exports 直接赋值 才可以
module.exports='hello'
该 module 对象中,有一个成员叫:exports 也是一个对象,默认是空对象
console.log(module.exports) // 返回结果:{}
也就是说如果你需要对外导出成员,只需要把导出的成员挂载到 module.exports 中即可
因为每次导出接口成员的时候都通过 module.exports.xxx = xxx 的方式,Node 为了简化你的操作,专门提供了一个变量:exports
等于 module.exports
console.log(exports === module.exports) // true
exports.foo='bar'
// 等价于
module.exports.foo='bar'
案例:
// {foo: bar}
exports.foo = 'bar'
// {foo: bar, a: 123}
module.exports.a = 123
// exports !== module.exports
// 最终 return 的是 module.exports
// 所以无论你 exports 中的成员是什么都没用
exports = {
a: 456
}
// {foo: 'haha', a: 123}
module.exports.foo = 'haha'
// 没关系,混淆你的
exports.c = 456
// 重新建立了和 module.exports 之间的引用关系了
exports = module.exports
// 由于在上面建立了引用关系,所以这里是生效的
// {foo: 'haha', a: 789}
exports.a = 789
// 前面再牛逼,在这里都全部推翻了,重新赋值
// 最终得到的是 Function
module.exports = function() {
console.log('hello')
}
3.3 服务器端实现模块化
3.3.1 初始化项目
会自动生成一个 package.json
配置文件
npm init -y
案例的目录结构如下:
|-modules
|-module1.js
|-module2.js
|-module3.js
|-app.js
|-package.json
{
"name": "Commonjs-Nodejs",
"version": "1.0.0"
}
3.3.2 安装项目依赖
# 用于数组去重
npm install uniq --save
# 或者是下面的简写
npm i -S uniq
3.3.3 模块化编码案例代码
module1.js
向外 暴露一个 对象:module.exports=XXX
// 向外暴露一个 对象
module.exports = {
foo() {
console.log('向外暴露一个对象:module1 foo()')
}
}
module2.js
向外 暴露一个 函数:module.exports=XXX
// 向外暴露一个 函数
module.exports = function () {
console.log('向外暴露一个函数:module2()')
}
module3.js
// 分别向外暴露 成员
exports.foo = function () {
console.log('分别向外暴露对象:module3 foo()')
}
exports.bar = function () {
console.log('分别向外暴露对象:module3 bar()')
}
exports.arrData=[1,2,3,4,2,5,6,8,31,22]
// 或者一次性向外暴露多个成员
function foo(){
console.log('分别向外暴露对象:module3 foo()')
}
function bar(){
console.log('分别向外暴露对象:module3 bar()')
}
module.exports={
foo:foo,
bar:bar,
arrData:arrData
}
// 还可以简写为:
module.exports={
foo,
bar,
arrData
}
app.js:主模块文件
// 引入核心模块
const fs = require('fs')
// 引入第三方模块
const uniq = require('uniq')
// 引入自定义模块
const module1 = require('./modules/module1')
const module2 = require('./modules/module2')
const module3 = require('./modules/module3')
// 使用模块
module1.foo(); //module1是暴露的一个对象,对象是一个函数
module2(); //module2是暴露的一个函数
module3.foo() //module3是暴露的一个对象
module3.bar() //module3是暴露的一个对象
//使用第三方包(uniq数组去重)
console.log(uniq(module3.arrData))
3.3.4 通过node运行编译 app.js
node app.js
3.4 浏览器端实现模块化
3.4.1 初始化项目
会自动生成一个 package.json
配置文件
npm init -y
案例的目录结构如下:
|-js
|-dist // 打包生成文件的目录
|-src // 源码所在的目录
|-module1.js
|-module2.js
|-module3.js
|-app.js / /应用主源文件
|-index.html
|-package.json
{
"name": "browserify-test",
"version": "1.0.0"
}
3.4.2 安装项目依赖
# 全局安装
npm install browserify -g
# 项目局部安装 --save-dev 开发依赖
npm install browserify --save-dev
3.4.3 定义模块代码
module1.js
// 向外暴露一个对象
module.exports = {
foo() {
console.log('向外暴露一个对象:module1 foo()')
}
}
module2.js
//向外暴露一个函数
module.exports = function () {
console.log('向外暴露一个函数:module2()')
}
module3.js
// 分别向外暴露成员 foo bar arrData
exports.foo = function () {
console.log('分别向外暴露对象:module3 foo()')
}
exports.bar = function () {
console.log('分别向外暴露对象:module3 bar()')
}
exports.arrData=[1, 3, 1, 4, 3]
// 或者一次性向外暴露多个成员
module.exports={
foo,
bar,
arrData
}
app.js (应用的主js):主模块js文件
//引用模块
const module1 = require('./module1')
const module2 = require('./module2')
const module3 = require('./module3')
//使用模块
module1.foo()
module2()
module3.foo()
module3.bar()
3.4.4 打包编译处理js
browserify ./src/app.js -o ./dist/bundle.js
4 ES6模块化
ES6本身就内置了模块化的实现,但是目前并不是所有浏览器都能直接识别ES6模块化的语法 ,一个好的解决方案是使用 webpack
编译打包
4.1 基本语法
4.1.1 向外暴露成员:export
// 1. 默认暴露一个成员
export default 成员
// 2. 分别暴露多个成员
export const a = value1 // 分别暴露一个成员
export let b = value2 // 分别暴露一个成员
// 3. 统一暴露多个成员
const c = value1
let d = value2
export { c, d }
4.1.2 引用使用模块 import
// 1. 引入default默认暴露的模块:
import xxx from '模块路径/模块名'
// 2. 引入一般模块
import { a, b } from '模块路径/模块名'
import * as module1 from '模块路径/模块名'
4.2 模块化实现
4.2.1 向外暴露成员
module1.js :分别向外暴露多个成员 export XXX
export function foo() {
console.log('module1 foo()')
}
export function bar() {
console.log('module1 bar()')
}
export const DATA_ARR = [1, 3, 5, 1]
module2.js :统一向外暴露多个成员 export {XXX,YYY}
let data = 'module2 data'
function fun1() {
console.log('module2 fun1() ' + data);
}
function fun2() {
console.log('module2 fun2() ' + data);
}
// 统一向外暴露多个成员
export {fun1, fun2}
module3.js: 默认向外暴露 export default XXX
默认暴露:可以暴露任意数据类型,暴露什么数据,就可以接收什么数据
默认暴露只能向外暴露一次
// 1. 默认暴露一个函数
export default () => {
console.log('我是默认暴露的箭头函数')
}
// 2. 默认暴露一个对象
export default {
msg: 'hello world',
foo() {
console.log('我是默认暴露里面的回调函数:'+this.msg.toUpperCase())
}
}
4.2.2 引入使用模块 import
1、 如果模块文件采用的是: 分别向外暴露多个成员或统一向外暴露多个对象 的方式,那么引入模块的时候,可以采用 对象解构的方式
import {foo, bar, DATA_ARR} from './module1';
foo();
bar();
console.log(DATA_ARR);
import {fun1, fun2} from './module2';
fun1();
fun2();
2、 如果模块文件采用的是:默认暴露的方式,可以直接获取模块
import module3 from './module3'
module3();
3、 引入第三方模块
//引入第三方模块
import $ from 'jquery';
$('body').css('background', 'red')
4.3 浏览器端实现模块化
4.3.1 项目说明
1、 使用 Babel 将 ES6 编译为 ES5 代码
2、 使用 Browserify 编译打包 js
4.3.2 实现步骤
1 初始化项目
npm init -y
2 安装项目依赖
# 安装babel依赖:
npm i @babel/core @babel/cli @babel/preset-env -D
# 安装browserify依赖:
npm install browserify -D
3 配置文件
新建 .babelrc
文件,配置babel预设(或者文件名为: babel.config.json
)
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"ie":"10", //这个是自己添加的,最好是加上
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1"
},
"useBuiltIns": "usage" (这个选项可以不写)
}
]
]
}
4 编码
module1.js:分别暴露多个成员
export function foo() {
console.log('module1 foo()')
}
export function bar() {
console.log('module1 bar()')
}
export const DATA_ARR = [1, 3, 5, 1]
module2.js :统一暴露多个成员
let data = 'module2 data'
function fun1() {
console.log('module2 fun1() ' + data);
}
function fun2() {
console.log('module2 fun2() ' + data);
}
//统一暴露对象
export {fun1, fun2}
module3.js : 默认暴露一个成员
//默认暴露一个箭头函数
export default () => {
console.log('我是默认暴露的箭头函数')
}
module4.js:默认暴露一个对象
//默认暴露一个对象
export default {
msg: 'hello world',
foo() {
console.log('我是默认暴露里面的回调函数:'+this.msg.toUpperCase())
}
}
main.js:主模块文件
import {foo, bar, DATA_ARR} from './module1';
foo();
bar();
console.log(DATA_ARR);
import {fun1, fun2} from './module2';
fun1();
fun2();
//采用默认暴露的方式:引入模块的方法
import module3 from './module3'
module3();
import module4 from './module4'
module4.foo();
//引入第三方模块
import $ from 'jquery';
$('body').css('background', 'red')
5 编译
1、 使用Babel将ES6编译为ES5代码
babel js/src -d js/build
2、 使用Browserify编译js :
browserify js/build/main.js -o js/build/bundle.js
5 AMD require.js
基本语法
// 定义要向外暴露的模块:
define([依赖模块名], function() {
return 模块
})
// 引入模块:
require(['模块1', '模块2'], function(m1, m2) {
//使用m1与m2
})
//配置
require.config({
// 基本路径
baseUrl: 'src/',
//映射: 模块标识名: 路径
paths: {
//自定义模块
'a': 'modules/a', // 可以不加后缀名
'b': 'modules/b', //第三方库
'jquery': 'libs/jquery-1.10.1',
}
})
5.1 如何在浏览器端实现模块化
主要是通过 require.js
在浏览器端实现模块化
5.1.1 下载require.js 并引入
- 官网: http://www.requirejs.cn/
- github : https://github.com/requirejs/requirejs
- 将require.js导入项目: js/libs/require.js
5.1.2 创建项目结构
|-js
|-libs
|-require.js
|-modules
|-alerter.js
|-dataService.js
|-main.js
|-index.html
5.1.3 定义require.js的模块代码
dataService.js:没有依赖的模块
//定义没有 依赖 的模块
define(function () {
const msg = 'dataService.js'
// 将字符串转化为大写
function getMsg() {
return msg.toUpperCase()
}
// 返回大写的 HELLO WORLD
// 向外暴露模块 return
return { getMsg }
})
alert.js:定义有 依赖 的模块
// 定义有依赖的模块:依赖项dataService jquery
define(['dataService', 'jquery'], function (dataService, $) {
const name = 'alert.js'
function showMsg() {
$('body').css('background', 'red')
alert(dataService.getMsg() + ', ' + name)
}
return {showMsg}
})
5.1.4 主程序文件: main.js
(function () {
//配置
requirejs.config({
//基本路径(相对于根目录)
baseUrl: "js/",
//模块标识名与模块路径映射
paths: {
"dataService": "modules/dataService",
"alert": "modules/alert",
//引入 第三方模块 (前提是jquery是支持AMD的,如果第三方库不支持AMD,则不可以引入)
"jquery": "libs/jquery-3.3.1.min",
}
})
//引入使用模块
requirejs(['alert'], function (alert) {
alert.showMsg()
})
})()
5.1.4 页面使用模块
配置data-main属性和src属性
<script data-main="js/main" src="js/libs/require.js"></script>
5.2 使用基于require.js的第三方框架(jQuery)
1、 将jquery的库文件导入到项目:
js/libs/jquery-1.10.1.js
2、 在main.js中配置jquery路径,设置映射
paths: {
'jquery': 'libs/jquery-1.10.1'
}
3、 在alerter.js中使用jquery
define(['dataService', 'jquery'], function (dataService, $) {
var name = 'xfzhang'
function showMsg() {
$('body').css({background : 'red'})
alert(name + ' '+dataService.getMsg())
}
return {showMsg}
})
5.3 使用第三方不基于require.js的框架(angular)
1、 将angular.js导入项目 js/libs/angular.js
2、 在main.js中配置
(function () {
require.config({
//基本路径
baseUrl: "js/",
//模块标识名与模块路径映射
paths: {
//第三方库
'jquery' : './libs/jquery-1.10.1',
'angular' : './libs/angular',
//自定义模块
"alerter": "./modules/alerter",
"dataService": "./modules/dataService"
},
/*
配置不兼容AMD的模块
exports : 指定与相对应的模块名对应的模块对象
*/
shim: {
'angular' : {
exports : 'angular'
}
}
})
//引入使用模块
require( ['alerter', 'angular'], function(alerter, angular) {
alerter.showMsg()
console.log(angular);
})
})()
6 CMD sea.js
基本语法:
// 定义暴露模块:
define(function(require, module, exports) {
// 通过require()引入依赖模块
// 通过module/exports来暴露模块
exports.xxx = value
})
// 使用模块:
seajs.use(['模块1', '模块2'])
6.1 如何在浏览器端实现模块化
CMD主要是通过 sea.js
在浏览器端实现模块化
6.1.1 下载Sea.js第三方包
6.1.2 创建项目结构
|-js
|-libs
|-sea.js
|-modules
|-module1.js
|-module2.js
|-module3.js
|-module4.js
|-main.js
|-index.html
6.1.3 定义 sea.js
的模块代码
module1.js:向外暴露一个成员
define(function (require, exports, module) {
// 内部变量数据
var data = 'atguigu.com'
// 内部函数
function show() {
console.log('module1 show() ' + data)
}
// 向外暴露一个成员
exports.show = show
})
module2.js:默认向外暴露一个成员
define(function (require, exports, module) {
module.exports = {
msg: 'I Will Back'
}
})
module3.js:
define(function (require, exports, module) {
const API_KEY = 'abc123'
exports.API_KEY = API_KEY
})
module4.js
define(function (require, exports, module) {
// 同步引入依赖模块
var module2 = require('./module2')
function show() {
console.log('module4 show() ' + module2.msg)
}
exports.show = show
// 异步引入依赖模块
require.async('./module3', function (m3) {
console.log('异步引入依赖模块3 ' + m3.API_KEY)
})
})
main.js : 主(入口)模块
define(function (require) {
var m1 = require('./module1')
var m4 = require('./module4')
m1.show()
m4.show()
})
- index.html:
<!--
使用seajs:
1. 引入sea.js库
2. 如何定义导出模块 :
define()
exports
module.exports
3. 如何依赖模块:
require()
4. 如何使用模块:
seajs.use()
-->
<script type="text/javascript" src="js/libs/sea.js"></script>
<script type="text/javascript">
seajs.use('./js/modules/main')
</script>