本文正在参加「金石计划 . 瓜分6万现金大奖」
模块化
什么是模块?
- 将JS按照一定的要求比如说功能拆分成多个JS,除了向外部暴露的数据以外,外部无法访问内部没有暴露的数据,我们这里称为私有数据。
全局function模式
最开始没有模块化标注的时候我们使用的是全局的function模式,例子如下
<script type="text/javascript" src="module1.js"></script>
<script type="text/javascript" src="module2.js"></script>
<script type="text/javascript">
foo()
bar()
//foo() change data
//module1.js:10 bar()
</script>
//module1.js
function foo() {
console.log('foo()')
}
function bar() {
console.log('bar()')
}
//module2.js
let data2 = 'change data'
function foo() { //与另一个模块中的函数命名冲突
console.log(`foo() ${data2}`)
}
缺点 :不同功能的代码在不同的js文件中,多人开发中容易出现与其他人的函数命名冲突
Namespace模式
为了防止命名冲突 提出了命名空间的模块化方法 具体使用方法如下
//module1.js
let myModule = {
data: 'data1',
foo() {
console.log(`foo() ${this.data}`)
},
bar() {
console.log(`bar() ${this.data}`)
}
}
//module2.js
let myModule2 = {
data: 'data2',
foo() {
console.log(`foo() ${this.data}`)
},
bar() {
console.log(`bar() ${this.data}`)
}
}
<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() //other data
</script>
通过保存函数到对象中形成的namespace可以高效率的减少命名冲突的问题,有可能此处几十个函数只会封装成俩个命名空间,但是
namespace的缺点是内部的数据可能会被外部修改
IIFE
为了解决外部修改问题 又出现了可以引入依赖的IIFE的模块化方式,该方式为现代模块化的基石,既能引入依赖,又能解决外部修改内部值的问题
(function (window, $) {
let data = 'data1'
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)//引入依赖
<script type="text/javascript" src="module.js"></script>
<script type="text/javascript" src="jquery-1.10.1.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>
但是多个script引入模块的方法会引起一些问题:多个scrpit发送多个请求、各个script之间的相互依赖会造成依赖模糊的现象,修改其中一个script可能会引起牵一发而动全身的效果,导致问题出现
为了对以上模块化进行改善,制定了模块化标准
模块化规范
CommonJs规范
//使用方法
1. 定义暴露模块:
module.exports = value;
exports.xxx = value;
2. 引入模块:
var module = require(模块名或模块路径);
//modules1.js
module.exports = {
foo() {
console.log('moudle1 foo()')
}
}
//modules2.js
module.exports = function () {
console.log('module2()')
}
//modules3.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()
//html
<!--<script type="text/javascript" src="js/src/app.js"></script>>
//ReferenceError: require is not defined 浏览器无法识别语法
如何打包
npm install browserify -g //下载打包工具
browserify js/src/app.js -o js/dist/bundle.js 将我们的app.js路径打包到dist下面的js中
//html
//需要使用工具进行打包
<script type="text/javascript" src="js/dist/bundle.js"></script>
AMD规范
AMD需要依赖require.js,声明两个模块,模块之间存在依赖,alerter.js返回一个showMsg是依赖了jq的函数,dataService.js抛出getMsg函数模块
//alerter.js
define(['dataService', 'jquery'], function (dataService, $) {
let name = 'Tom2'
function showMsg() {
$('body').css('background', 'gray')
alert(dataService.getMsg() + ', ' + name)
}
return {showMsg}
})
//dataService.js
define(function () {
let msg = 'atguigu.com'
function getMsg() {
return msg.toUpperCase()
}
return {getMsg}
})
在main.js中通过require.js这个基础的conifg中来配置引入我们已经写好的模块,配置好在后面我们可以看出alerter函数可以使用了
//main.js
(function () {
//配置
require.config({
//基本路径
baseUrl: 'js/',
//映射: 模块标识名: 路径
paths: {
//自定义模块
'alerter': 'modules/alerter',
'dataService': 'modules/dataService',
//库模块
'jquery': 'libs/jquery-1.10.1',
'angular': 'libs/angular'
},
//配置不兼容AMD的模块
shim: {
angular: {
exports: 'angular'
}
}
})
//引入模块使用
require(['alerter', 'angular'], function (alerter, angular) {
alerter.showMsg()
console.log(angular);
})
})()
<script type="text/javascript" src="js/libs/require.js" data-main="js/main.js"></script>
CMD
CMD也是模块化规范的一部分,写法比较类似于CommonJs和AMD写法的结合,这里暂时不做具体使用
ES6
ES6模块语法
//modules1.js
export function foo() {
console.log('module1 foo()');
}
export let bar = function () {
console.log('module1 bar()');
}
export const DATA_ARR = [1, 3, 5, 1]
//modules2.js
let data = 'module2 data'
function fun1() {
console.log('module2 fun1() ' + data);
}
function fun2() {
console.log('module2 fun2() ' + data);
}
export {fun1, fun2}
//modules3.js
export default {
name: 'Tom',
setName: function (name) {
this.name = name
}
}
//app.js
import {foo, bar} from './module1'
import {DATA_ARR} from './module1'
import {fun1, fun2} from './module2'
import person from './module3'
import $ from 'jquery'
$('body').css('background', 'red')
foo()
bar()
console.log(DATA_ARR);
fun1()
fun2()
person.setName('JACK')
console.log(person.name);
在此处我们的app.js仍然无法直接在浏览器中进行使用,因为浏览器识别不了ES6的写法,我们需要通过babel转换工具对ES6转化为ES5
babel js/src -d js/lib 将src下的文件转换语法 打包到lib文件夹下`将ES6转换成ES5`
但转换完仍然包含require语法浏览器还是无法识别require
babel转换后的app.js仍然包含require语法 那么参考上面的模块化使用browserify来解决
browserify js/lib/app.js -o js/lib/bundle.js
//html
<script type="text/javascript" src="js/lib/bundle.js"></script>
至此通过最终转换出来的bundle.js 我们就可以在浏览器编译我们ES6模块化的代码