看完我的笔记不懂也会懂----javascript模块化
JavaScript模块化
模块化引子
-
什么是模块,什么是模块化
- 模块其实就是一个个有特定功能的js文件
- 模块化就是将一个复杂的js程序依据一定的规范分割成数个模块(js文件)(数个模块组合在一起就是一整个功能齐全程序)
-
为什么要模块化
数据更安全,只向外暴露接口(黑盒) -
模块化的好处
- 当项目代码量大时,便于管理
- 有效的减轻全局NameSpace的污染(命名冲突)
- 模块功能明确,可以按需部署、加载
- 更高的复用性与可维护性
-
模块化缺点
- 页面引入加载script,这就导致了相比原来需要发的网络请求增加
- 模块彼此之间相互依赖,容易导致依赖模糊
- 模块彼此依赖导致耦合度增加, 变得更加难以维护
模块化的历史进化
-
全局Function模式
//全局函数模式: 将不同功能封装成不同的全局函数 /* 1. 数据直接暴露 2. 容易造成全局的命名空间污染 */ let str = '全局函数模式' function foo(){ console.log('foo()' + str) } function bar(){ console.log('bar()' + str) } foo() bar()
-
nameSpace模式
//namespace模式: 使用对象对函数(方法、API)进行简单的封装 /*作用是: 保障了数据的安全性(相对全局中不容易被修改) 降低了全局命名空间的污染程度 */ let manufacture = { str: '飞机制造成功', madePlane(){ console.log(this.str) } } manufacture.madePlane()
-
IIFE(匿名函数自调用)
//IIFE: immeiately-invoked function expression //匿名函数自调用(闭包) (function foo(){ let str = 'closure !!' function foo(){ console.log(`str==>${str}`) } //window.foo = foo //向外暴露但是又容易造成命名空间的污染 //改进 window.module = { foo: foo } //进一步改进 es6对象属性简写 })()
-
IIFE模式增强 (这就是JS模块化的原理)
/* IIFE模式增强: 引入依赖 这就是现在模块实现的基石 */ (function(w,$){ let str = 'IIFE模式增强' function foo(){ console.log(str) //用于测试将网页变背景色 $('body').css( {'background-color': 'red'} ) } //向外暴露 w.module = foo })(window,jQuery)
-
Load Script模式
<script src="./script1.js"></script> <script src="./script2.js"></script> <script src="./script3.js"></script>
一个页面中需要引入多个js文件造成的问题:
- 请求过多
- 依赖模糊
- 难以维护
模块化规范----commonJS
commonJS是Node.js服务器端使用的JS模块化规范
commonJS的特性:
- 其中的每一个js文件都可当做一个模块
- 在服务器端,模块的加载是在运行时同步加载的
- 在浏览器端,由于浏览器不认识
require()
的语法,所以模块需要提前编译打包处理
commonJS语法之暴露模块
exports.key = value
module.exports = value
/*
注意:
module.exports的方式暴露模块,只能够暴露最后一次module.exports出的对象,因为后面暴露的对象会覆盖前面的
*/
commonJS向外暴露的都是exports
这个对象,为什么这么说呢?
学过node.js的同学应该都知道exports
默认指向的是一个object对象
module.exports = value
相当于改变exports
的指向,暴露的仍然是exports
而exports.key
就更不用说了,只是单纯的向exports
中添加属性然后暴露出去
commonJS语法之引入模块
// 当引入的是第三方模块, 直接写模块名
const $ = require('Jquery')
// 当引入的是自定义模块时, 需要书写完整的路径名
const HeaderComponent = require('./desktop/HeaderComponent.vue')
node端使用commonJS示例
分别定义三个module
// 暴露模块
module.exports = {
name: '我是module1',
printInfo () {
console.log(this.name)
}
}
// 暴露模块
module.exports = function () {
console.log('我是模块module2')
}
// 引入uniq模块用于测试
const uniq = require('uniq');
// 暴露模块
exports.sayHello = function () {
console.log('hello')
}
exports.sayName = function () {
console.log('Fitz')
}
exports.testUniq = function () {
arr = [1,1,3,4,4,5,3,5,6,4,5,4,4,5,8]
console.log(uniq(arr))
}
然后在主文件中引入所有需要用的模块
// 引入所有模块
const module1 = require('./modules/module1')
const module2 = require('./modules/module2')
const module3 = require('./modules/module3')
console.log(module1.name)
module1.printInfo()
module2()
module3.sayName()
module3.sayHello()
module3.testUniq()
browser端使用commonJS示例
需要用到的编译工具: brorserify
npm i -D browserify@14.5.0
分别定义三个module
// 暴露模块
module.exports = {
name: '我是module1',
printInfo () {
console.log(this.name)
}
}
// 暴露模块
module.exports = function () {
console.log('我是模块module2')
}
// 暴露模块
exports.sayHello = function () {
console.log('hello')
}
exports.sayName = function () {
console.log('Fitz')
}
然后在主文件中引入所有需要用的模块
// 引入所有模块
const module1 = require('./modules/module1')
const module2 = require('./modules/module2')
const module3 = require('./modules/module3')
console.log(module1.name)
module1.printInfo()
module2()
module3.sayName()
module3.sayHello()
在html页面引用主文件,也就是在浏览器端使用commonJS,需要先使用browserify来进行编译
browserify 主模块路径 -o 编译输出路径
模块化规范----AMD
AMD(Asynchronous Module Definition)规范是专门用于浏览器端的模块加载,它的加载过程是异步的,这样就有效避免了阻塞浏览器进行其他工作
AMD模块化规范依赖的是RequireJS这个工具
npm i -D require@2.3.6
AMD语法之暴露模块
// 暴露没有依赖的模块
define(function () {
// do something
return 模块
})
// 暴露有依赖的模块
define(
['依赖1','这里名字任意去但是要与主模块配置相同'],
function (m1, m2) {
// do something
return 模块
}
)
AMD语法之引入模块
// 配置模块依赖 这是必须的
requirejs.config({
baseUrl: '基础路径',
paths: {
'依赖1': '路径', // 注意路径最后不需要加文件后缀名
'这里名字任意去但是要与主模块配置相同': '路径'
}
})
requirejs(
['依赖'],
function (m1) {
// do something
}
)
对于像Jquery这些支持requireJS的库的模块引入
requirejs.config({
baseUrl: '基础路径',
paths: {
jquery: '路径' // key必须是小写的jquery
}
/*
原因是,jquery在源码最后显示指定了requirejs模块名
define('jquery', [], function(){return JQuery})
define的第一个参数用于显示指定requireJS模块名
指定后在config中必须使用这个名字,不能自定义
*/
})
requirejs(
['jquery'],
function ($) {
// do something
}
)
对于不支持requireJS规范的库,如angular
requirejs.config({
baseUrl: '基础路径',
paths: {
angular: '库的路径'
},
shim: {
angular: {
exports: 'angular' // 这里才是依赖的名字
}
}
})
requirejs(
['angular'],
function (angular) {
// do something
}
)
requireJS在浏览器端使用
<script data-main="主模块路径" src="requireJS库所在路径"></script>
模块化规范----CMD
CMD规范是阿里巴巴公司内部的一种JS模块化规范,现已经出售转让(官网已无法访问)。而且在日常开发中使用极少,在这仅作为介绍使用
CMD规范同样用于浏览器端,模块的加载也是异步的,在模块使用的时候才会加载执行
CMD语法之暴露模块
// 定义没有依赖的模块
define(function (require, exports, module) {
exports.xxx = yyy
module.exports = {}
})
// 定义有依赖的模块
define(function (require, exports, module) {
// 同步引入依赖
const necessary1 = require('模块')
// 异步引入依赖
require.async('模块', function (m) {
// do something
})
exports.xxx = yyy
module.exports = {}
})
CMD语法之引入模块
define(function (require, exports, module) {
const m1 = require('模块1')
const m2 = require('模块2')
// do something
})
CMD规范使用示例
定义两个模块
// 使用CMD规范, 创建没有依赖的模块
define(function(require, exports, module){
exports.sayName = function () {
console.log('Fitz')
}
})
// 这个模块依赖于module1.js
// 使用CMD规范, 创建有依赖的模块
define(function(require, exports, module) {
const m1 = require('./module1.js')
m1.sayName()
function sayHello () {
console.log('hello')
}
module.exports = {
sayHello
}
})
在主模块中引入其他模块
// IIFE是非必须的,使用只是能确保内部数据的安全
(function () {
define(function (require) {
const m2 = require('./modules/module2.js')
m2.sayHello()
})
})()
浏览器端引入sea.js并使用定义好的模块
<body>
<script src="./libs/sea.js"></script>
<script>
seajs.use('./index.js') // 使用模块
</script>
</body>
模块化规范----ES6规范
ES6规范是现在模块化中运用最多、最广泛的一种规范,其语法较为简单,需要我们重点掌握
ES6模块化同样需要对代码进行编译打包处理,运用到的工具有Babel和Browserify
npm i -D babel-preset-es2015@6.24.1
npm i -D browserify@14.5.0 babel
需要为babel添加转换语法相关的配置文件.babelrc
{
"presets": ["es2015"]
}
ES6语法之暴露模块
// 常规暴露之 分别暴露
export var a = 123
export const b = {age: 21}
export function () {
// do something
}
// 常规暴露之 统一暴露
function func1 () {}
function func2 () {}
function func3 () {}
export {
func1,
func2,
func3
}
// 默认暴露: 本质上就是常规暴露, 只不过暴露的是一个变量名叫default的接口对象
export default a = 123
export default function () {}
export default {}
ES6语法之引入模块
// 引入第三方模块
import identitify from '包名'
// 引入自定义模块
import moduleName from '模块路径'
ES6引入模块还有一种通用的方法,能够适用默认暴露、统一暴露、分别暴露
// 通用方法引入模块
import * as 自定义模块名 from '模块路径' // 一定要取别名
console.log(自定义模块名)
/*
是一个对象,包含模块内所有向外暴露的接口
其中默认暴露default会作为这个对象的一个属性
[module] {
接口1: 接口内容,
接口2: 接口内容,
default: 默认暴露的内容
}
*/
ES6规范使用示例
创建三个模块分别运用三种暴露方法
// 暴露模块之 分别暴露
export function foo () {
console.log('foo()')
}
export function bar () {
console.log('bar()')
}
export function baz () {
console.log('baz()')
}
export const name = 'module1.js'
// 暴露模块之 统一暴露
function func1 () {
console.log('func1()')
}
function func2 () {
console.log('func2()')
}
function func3 () {
console.log('func3()')
}
export {
func1,
func2,
func3
}
// 暴露模块之 默认暴露
// export default只能使用一次, 因为其本质就是向外暴露一个变量名为default的接口
export default function () {
console.log('使用默认暴露')
}
// 后续可以进行使用常规暴露
export function foo () {
console.log('module3 foo()')
}
在主模块中引入所有的模块
// 使用import语法引入模块
import {foo} from './modules/module1'
import {func1} from './modules/module2'
// 可以为模块内的对象改名
import {baz as baby} from './modules/module1'
// 引入默认暴露的模块
import md3 from './modules/module3'
// 如果不同模块中有同名对象,必须为对象改名,否则后面引入的同名对象会覆盖前面引入的
import {foo as md3Foo} from './modules/module3'
foo() // 'foo()'
func1() // 'func1()'
baby() // 'baz()'
md3() // '使用默认暴露'
md3Foo() // 'module3 foo()'
编译相关语法:
# 使用Babel将ES6编译转换成ES5的代码
babel 源文件 -d 目标路径
# 由于转换后会包含CommonJS的语法,所以需要用browserify进一步转换才能被浏览器使用
browserify 源文件 -o 目标路径