https://juejin.im/post/6844903744518389768
通常一個模塊會有各自的作用域,會向外界暴露特定的變量或者函數
將一個複雜的程序,依據規範封裝成一塊塊的文件,即是模塊
模塊就是一組特定功能的文件,以下foo 與bar組合成的module1.js 也可被稱為模塊
// module1.js
function foo() {
...
}
function bar() {
...
}
產生問題:
假設今天我們有好幾個模塊需要引入到同一個地方會發現,假設不同的模塊都有一個foo函數不就出事情(這個情況其實就是汙染了全局變量)
// module1.js
let module1 = new Object({
_count: 0,
foo: function () {
...
},
bar: function () {
...
}
})
這樣只要我們調用 foo函數 就可以利用 module1.foo ,如果有其他模塊也有foo就不會發生衝突。
產生問題:
可是到這裡我們又發現,_count可以被任意訪問,他明明就是計數器怎麼可以任意被外部改變。
有私人的變量,外界只能透過暴露的方法獲取變量
// module1.js
(function (window) {
let _count = 0
function foo() {
_count += 1
}
function getCount() {
foo()
console.log(_count);
}
// 暴露給全局
window.module1 = {
// ES6 增強語法
getCount,
}
})(window)
到目前為止都還不錯,不過我們還忘了引入依賴(這裡拿JQuery當例子)
// body部分
<body>
<script
src="https://code.jquery.com/jquery-3.5.1.js"
integrity="sha256-QWo7LDvxbWT2tbbQ97B53yJnYU3WhH/C8ycbRAkjPDc="
crossorigin="anonymous"
></script>
<script src="./module1.js"></script>
</body>
js部分
// module1.js
(function (window, $) {
let _count = 0
function foo() {
_count += 1
}
function getCount() {
foo()
console.log(_count);
}
function changeColor() {
console.log(++_count);
$('body').css('background', 'red')
}
window.module1 = {
// ES6 增強語法
getCount,
changeColor
}
})(window, jQuery)
原因有兩個,導致難以維護
請求過多
有一堆 ,而且依賴過多需要發發送過多請求
依賴模糊
從HTML來看根本看不出來誰依賴誰
Node.js 為主要實踐者,每個模塊有各自的作用域,變量或函數都是私有,外界不可視
服務器端:模塊的加載是運行時同步加載的
瀏覽器端:模塊需要提前編譯打包處理
特點:
- 模塊可以多次加載,但只會在第一次加載運行一次,會將運行結果緩存,下次從其他地方導入會從緩存讀取,如果想再一次運行模塊需要清除緩存
- 加載的順序,按照其在代碼中出現的順序。
module.exports 或是 exports
require(xxx)
第三方模塊(比方說npm install xxx): xxx即是模塊名子
自定義模塊: xxx是路徑
例子:
CommonJS模塊的加載機制是,輸入的是被輸出的值的淺拷貝。
注意跟ES6 差很多
// module1.js
let counter = 5;
let objCounter = {
value: 5
}
function addCounter() {
++counter;
}
function addObjCounter(params) {
++objCounter.value
}
module.exports = {
counter,
objCounter,
addCounter,
addObjCounter,
};
// module2.js
// 注意這裡要用node
// 所以要再控制台輸入 node module2.js (注意先cd到放module2.js的資料夾)
const module1 = require('./module1');
console.log(module1.counter); // 5
console.log(module1.objCounter.value); // 5
module1.addCounter()
module1.addObjCounter()
console.log(module1.counter); // 5
console.log(module1.objCounter.value); // 6(因為是淺拷貝,所以會增加)
建議使用module.exports進行導出,因為最終導出的絕對是module.exports 指向的內存地址的對象
epxorts比較像是node給你的語法糖
如果今天exports 指向新的對象
導出是module.exports 指向的內存地址的對象,所以當然還是
{name: 'Mike', age: 15}
AMD (Asynchronous Module Definition),如同他的名子,處理異步加載模塊
如果是瀏覽器環境,要從服務器端加載模塊,這時就必須採用非同步模式,因此瀏覽器端一般採用AMD規範
順帶一提Commonjs處理同步,因為Node.js主要用於服務端編成,模塊文件儲存在本地硬碟,加載快速。所以通常不會造成阻塞
導出模塊:
// 不依賴其他模塊
define(function(){
return 模块
})
// 依賴其他模塊
define(['module1', 'module2'], function(m1, m2){
return 模块
})
導入模塊:
require(['module1', 'module2'], function(m1, m2){
// 使用m1和m2
})
目錄結構
├─alert.js
├─index.html
├─main.js
└store.js
// store.js文件
(function (window) {
let msg = '我在store.js裡'
function getMsg() {
return msg
}
window.store = {
getMsg,
}
})(window)
// alerter.js文件
(function (window, store) {
let addMsg = '我被alert添加了'
function showMsg() {
alert(store.getMsg() + ', ' + addMsg)
}
window.alerter = {
showMsg
}
})(window, store)
// main.js文件
(function (alerter) {
alerter.showMsg()
})(alerter)
<!-- index.html -->
<body>
<script src="./store.js"></script>
<script src="./alert.js"></script>
<script src="./main.js"></script>
</body>
缺點:
- 會發送多個請求(一堆)
- 只看index.html根本看不出依賴誰
- 引入順序完全不能有錯
require載點
https://github.com/requirejs/requirejs/blob/master/require.js
目錄結構
├─index.html
├─main.js
├─lib
| └require.js
├─js
| ├─alerter.js
| └store.js
// store.js文件
// 定義無依賴模塊
define(function () {
let msg = '我在store.js裡'
function getMsg() {
return msg
}
// 暴露模块
return {
getMsg
}
})
// alerter.js文件
// 定義有依賴模塊
define([
'store',
], function(store) {
let addMsg = '我被alert添加了'
function showMsg() {
alert(store.getMsg() + ', ' + addMsg)
}
return {
showMsg
}
});
// main.js文件
(function () {
// 配置require
require.config({
baseUrl: 'js/',
paths: {
// 映射,標註模塊名子(依賴時的數組裡的名子就是這個)
alerter: './alerter', // 不能寫成alter.js會報錯誤
store: './store'
// 導入第三方庫
// jquery: './libs/jquery-1.10.1' //注意:寫成jQuery會報錯
}
})
require(['alerter'], function (alerter) {
alerter.showMsg()
})
})()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 引入require.js並指定js主文件的入口 -->
<script data-main="main" src="lib/require.js"></script>
</body>
</html>
注意事項
- 入口文件(main)定義在index.html
- 入口文件配置所有導出模塊的名稱(映射關係)
整合AMD以及CommonJS
導出模塊:
// 不依賴其他模塊
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()
})
sea載點
目錄結構
├─index.html
├─result.txt
├─lib
| └sea.js
├─js
| ├─main.js
| ├─moduleA.js
| ├─moduleB.js
| ├─moduleC.js
| └moduleD.js
// moduleA.js文件
define(function (require, exports, module) {
console.log('我(A)被加載了')
//内部數據
var data = ' 我在ModuleA裡面'
//内部函数
function show() {
console.log('moduleA show() ' + data)
}
//向外暴露
exports.show = show
// 上面那樣用跟module.exports.show依樣意思
})
// moduleB.js文件
define(function (require, exports, module) {
//内部數據
var data = ' 我在ModuleB裡面'
//向外暴露
console.log('我(B)被加載了')
exports.data = data
})
// moduleC.js文件
// 這個會被異步加載
define(function (require, exports, module) {
const TOKEN = 'abc123'
exports.TOKEN = TOKEN
})
// moduleD.js文件
define(function (require, exports, module) {
//引入依賴模塊(同步)
var moduleB = require('./moduleB')
function show() {
console.log('moduleD show() ' + moduleB.data)
}
exports.show = show
// 異步引入依賴模塊
require.async('./moduleC', function (moduleC) {
console.log('異步引入moduleC ' + moduleC.TOKEN)
})
})
// main.js文件
define(function (require) {
var moduleA = require('./moduleA')
var moduleD = require('./moduleD')
moduleA.show()
moduleD.show() // moduleD引入
})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script type="text/javascript" src="lib/sea.js"></script>
<script type="text/javascript">
seajs.use('./js/main')
</script>
</body>
</html>
注意事項
- 入口文件(main)也是定義在index.html
- 在各個模塊透過require導入,不像AMD集體定義在main.js
- 跟CommonJS一樣有exports跟module.exports
export var a = 125
export const _b = 'Mike'
export let c = 2222
var a = 125
const _b = 'Mike'
let c = 2222
export {a, _b, c};
var a = 125
const _b = 'Mike'
let c = 2222
export {
a as var1,
_b as var2,
c as var3 };
注意:
export命令規定要處於模塊頂層, 假如出現在塊級作用域( { } ) 就會報錯,import同理
// module2.js
export default function(){
console.log('foo')
}
// 相当于
function a(){
console.log('foo')
}
export {a as default};
import可以指定任意名字
import Foo from './module2'
// 相当于
import {default as Foo} from './module2'
import {a, _b ,c} from './profile'
import {stream1 as firstVal} from './profile'
import { foo } from './module1'
import { bar } from './module1'
// 相当于
import {foo,bar} from './module1'
import * as circle from './module1'
circle.foo();
circle.bar();
- CommonJS模塊輸出的是一個值的淺拷貝,ES6模塊輸出的是值的引用(賦值)
- CommonJS模塊是運行時加載,ES6模塊是編譯時加載
第一個差異:
// lib.js
export let counter = 3;
export function incCounter() {
counter++;
}
// main.js
import { counter, incCounter } from './lib';
console.log(counter); // 3
incCounter();
console.log(counter); // 4 (會指向lib模塊的counter(內存地址))
第二個差異:
運行時加載: CommonJS 模塊就是對象;即在輸入時是先加載整個模塊,生成一個對象,然後再從這個對像上面讀取方法,這種加載稱為“運行時加載”。
編譯時加載: ES6模塊不是對象,而是通過export
命令顯式指定輸出的代碼,import
時採用靜態命令的形式。即在import
時可以指定加載某個輸出值(可能會導致變量提升),而不是加載整個模塊,這種加載稱為“編譯時加載”。