JS模块化——模块暴露与模块引入
JS模块化四种规范:CommonJS、AMD、CMD、ES6
1. 引言
最近在研究前端框架,但发现好多JavaScript
知识不是很了解,很是苦恼,下面就来研究一下JavaScript
的模块化,先理解几个概念和模块化的进化过程。
2. 模块化
2.1 什么是模块?
模块就是将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起,块的内部数据和实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信。
一个模块的组成由两部分组成: 数据(内部的属性)、操作数据的行为(内部的函数)
2.2 模块化的进化过程
2.2.1 全局function模式
模块一:module1.js
// 数据
let data = 'module1'
// 操作数据的函数
function foo() {
console.log(`foo() ${data}`)
}
function bar() {
console.log(`bar() ${data}`)
}
模块二:module2.js
let data2 = 'module2'
function foo() { //与另一个模块中的函数冲突了
console.log(`foo() ${data2}`)
}
页面引入:test.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>
说明:全局函数模式是将不同的功能函数封装成不同的全局函数,这样会有一些问题, 比如上面两个模块中都有foo函数,如果同时引入,将不知道调用的是哪一个,很容易引起命名冲突,污染全局命名空间,导致数据不安全。(上面的两个模块中的函数会成为window对象中的函数)
2.2.2 namespace模式
模块一:module1.js
let myModule1 = {
data: 'module1',
foo() {
console.log(`foo() ${this.data}`)
},
bar() {
console.log(`bar() ${this.data}`)
}
}
模块二:module2.js
let myModule2 = {
data: 'module2',
foo() {
console.log(`foo() ${this.data}`)
},
bar() {
console.log(`bar() ${this.data}`)
}
}
页面引入:test.html
<script type="text/javascript" src="module1.js"></script>
<script type="text/javascript" src="module2.js"></script>
<script type="text/javascript">
// module.js模块
myModule1.foo()
myModule1.bar()
// module2.js模块
myModule2.foo()
myModule2.bar()
myModule1.data = 'other data' //能直接修改模块内部的数据
myModule1.foo()
</script>
说明:namespace模式就是简单对象封装,它虽然解决了命名冲突的问题,减少了全局变量,但依然存在问题,我们可以在外部可以直接修改模块内部的数据,导致数据不安全。
2.2.3 IIFE模式/增强
模块:module1.js
(function (window) {
//数据
let data = 'module1'
//操作数据的函数
function foo() { //用于暴露有函数
console.log(`foo() ${data}`)
}
function bar() {//用于暴露有函数
console.log(`bar() ${data}`)
otherFun() //内部调用
}
function otherFun() { //内部私有的函数
console.log('otherFun()')
}
// 暴露foo,bar,并没有暴露otherFun,所以外界无法访问
window.myModule = {foo, bar}
})(window)
页面引入:test.html
<script type="text/javascript" src="module1.js"></script>
<script type="text/javascript">
myModule.foo()
myModule.bar()
//myModule.otherFun() //myModule.otherFun is not a function,没有暴露
console.log(myModule.data) //undefined 不能访问模块内部数据
myModule.data = 'xxxx' //不是修改的模块内部的data
myModule.foo() //没有改变
</script>
说明:IIFE : immediately-invoked function expression(立即调用函数表达式),就是匿名函数自调用(闭包),它通过将数据和行为封装到一个函数内部, 通过给window添加属性来向外暴露接口,它的作用是,数据是私有的, 外部只能通过暴露的方法操作。但此时有个问题: 如果当前这个模块依赖另一个模块怎么办?
下面拿jQuery举例,把jQuery引入到项目中
模块:module1.js
(function (window, $) {
//数据
let data = 'module1'
//操作数据的函数
function foo() { //用于暴露有函数
console.log(`foo() ${data}`)
$('body').css('background', 'red')
}
function bar() {//用于暴露有函数
console.log(`bar() ${data}`)
otherFun() //内部调用
}
function otherFun() { //内部私有的函数
console.log('otherFun()')
}
//暴露行为
window.myModule = {foo, bar}
})(window, jQuery)
页面引入:test.html
<script type="text/javascript" src="jquery-1.10.1.js"></script>
<script type="text/javascript" src="module1.js"></script>
<script type="text/javascript">
myModule.foo()
</script>
引入依赖,是IIFE模式的增强,也是现代模块实现的基石。
3. 模块化规范
模块分的规范有如下四种:CommonJS
、AMD
、CMD
、ES6
,下面逐一介绍一下。
3.1 CommonJS
遵循CommonJS
规范的有:Node.js
(服务器端)、 Browserify
(浏览器端),Browserify
也称为js
的打包工具 。 暴露的本质就是暴露exports
对象
基本语法:定义暴露模块 : exports
exports.xxx = value
module.exports = value
引入模块 : require
var module = require('模块名') // 引入第三方模块
var module = require('模块相对路径')
说明
- 一个文件就是一个模块,
require
方法用来加载模块,该方法读取一个文件并执行,最后返回文件内部的module.exports
对象 require
是默认读取.js
文件的,所以require
(模块名)可以不写后缀module.exports
属性表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取module.exports
变量;为了方便也可以用exports
,exports
指向module.exports
;即exports = module.exports = {}
,exports.xxx
相当于在导出的对象上添加属性,该属性对调用模块可见- exports = 相当于给exports重新赋值,这样就切断了和module.exports的关联,调用模块就不能访问exports的对象及其属性;
3.2 AMD (浏览器端)
AMD是requireJS倡导的一种模块化规范,推崇依赖前置;在requireJS中模块是通过define来进行定义的,如果模块之间相互依赖,需要先将依赖模块导入进来,待导入完毕之后,在通过回掉函数的方式执行后面的代码,有效的解决了模块依赖的问题。
定义暴露模块
define([依赖模块名], function(){return 模块对象})
引入模块
require(['模块1', '模块2', '模块3'], function(m1, m2){//使用模块对象})
配置:
require.config({
// 基本路径
baseUrl: "js/lib",
// 标识名称与路径的映射
paths: {
"jquery": "jquery.min",
"underscore": "underscore.min",
"backbone": "backbone.min"
},
//非AMD的模块
shim: {
'underscore':{
exports: '_'
},
'backbone': {
deps: ['underscore', 'jquery'],
exports: 'Backbone'
}
}
});
3.3 CMD (浏览器端)
CMD
则是seaJS
倡导的一种解决模块之间相互依赖规范,推崇依赖就近,在seaJS
中一个脚本文件就是一个模块,所有的模块代码写在define的回调函数中,传递三个参数require
、exports
、module
,通过使用 module.exports(exports)
对象向外暴露。
require
函数加载模块的时候,会自动拿到模块内部的 module.exports
对象。定义暴露模块:
define(function (require, exports, module) {
// 通过require引入依赖模块
var moduleA = require('add.js') // 等待add.js下载、执行完
console.log(moduleA.add(10,20))
})
// add.js
define(function (require, exports, module) {
function add(a, b){
return a+b;
}
// 通过module/exports来暴露模块
module.exports.add = add;
})
AMD
和CMD
最大的区别是对依赖模块的执行时机处理不同,而不是加载的时机或者方式不同,二者皆为异步加载模块AMD
依赖前置,js
可以方便知道依赖模块是谁,立即加载;而CMD
就近依赖,需要使用把模块变为字符串解析一遍才知道依赖了那些模块
3. 4 ES6
ES6
在语言标准上面实现了模块功能。设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入输出变量,CommonJS
以及AMD
都只能在运行时确定,ES6
的模块并不是对象,而是通过export
显示指定输出的代码,再通过import
命令导入。
可以使用ES6
的模块暴露和引入来实现模块化编程,ES6
暴露模块的方式有三种:分别暴露、统一暴露(前两者也称为常规暴露)和默认暴露。
3.4.1 分别暴露
模块一:module1.js
// 分别暴露:也叫多行暴露,每个方法逐一暴露,这种方式在引入时需要用对象来引入
export function foo() {
console.log('foo() moudle1');
}
export function bar() {
console.log('bar() moudle1')
}
3.4.2 统一暴露
模块二:module2.js
function fun1() {
console.log('fun1() module2')
}
function fun2() {
console.log('fun2() module2')
}
// 统一暴露: 暴露的是一个对象,引入时也必须是个对象
export {foo,bar}
需要注意的是:以上两种向外暴露方式在主文件引入时必须使用对象的解构赋值引用,不能使用变量接收的方式来映入(注意和默认暴露的区别)
主模块:main.js
import {foo,bar} from '.js/src/module1.js'
import {fun1,fun2} from './js/src/module2.js'
3.4.3 默认暴露
export default {
foo() {
console.log('默认暴露方式')
},
bar() {
console.log('默认暴露')
}
}
默认暴露的方式只允许有一个: export default {}且在主模块引入时可以使用定义变量来接收的方式!
// 引入模块3
import module3 from '.js/src/module3.js'
// 使用模块
module3.foo()
module3.bar()
注意:在使用`ES6`时,会出现一些问题,现在由于并不是所有浏览器都能直接识别`ES6`模块化的语法,所有在不能识别`ES6`语法的浏览器上,就不能执行`ES6`代码,这时就需要用到`Babel`,将`ES6`语法转化为`ES5`(使用了`CommonJS`) ,但此时浏览器还不能直接执行,还需要再使用`Browserify`对`ES5`语法的代码进行打包处理,最后得到的文件,浏览器可以运行。更多推荐
所有评论(0)