模塊是將一個複雜的程序依據一定的規則(規範)封裝成幾個塊(文件), 並進行組合在一起。塊的內部數據與實現是私有的, 只是向外部暴露一些接口(方法)與外部其它模塊通信
模塊化有兩個重要的概念:模塊的導出和模塊的導入
-
模塊的導出:暴露接口的過程即模塊的導出
-
模塊的導入:當通過某種語法或API去使用一個模塊時,這個過程叫做模塊的導入
1、CommonJS
因爲CommonJs是node服務提出的模塊化規範,所以我們首先需要安裝node環境
安裝nodeJs
官網地址:https://nodejs.org/zh-cn/
安裝完成後在dos
窗口中輸入node
命令驗證
使用nodeJs
在node環境中運行index.js文件
node index.js
nodejs直接運行某個js文件,該文件被稱之爲入口文件
爲什麼要有CommonJS?
在nodejs中,由於有且僅有一個入口文件(啓動文件),而開發一個應用肯定會涉及到多個文件配合,因此,nodejs對模塊化的需求比瀏覽器端要大的多。由於nodejs剛剛發佈的時候,前端沒有統一的、官方的模塊化規範,因此,它選擇使用社區提供的CommonJS作爲模塊化規範。
CommonJS使用
CommonJS使用exports
導出模塊,導出的是一個對象,require
導入模塊
具體規範如下:
- 如果一個JS文件中存在
exports
或require
,該JS文件是一個模塊 - 模塊內的所有代碼均爲隱藏代碼,包括全局變量、全局函數,這些全局的內容均不應該對全局變量造成任何污染
- 如果一個模塊需要暴露一些API提供給外部使用,需要通過
exports
導出,exports
是一個空的對象,你可以爲該對象添加任何需要導出的內容
//utils.js
function getNumber(){
return 1
}
exports = {
getNumber:getNumber //導出getNumber方法
}
//等同於
exports.getNumber = function(){
return 1; //導出一個方法叫getNumber
}
//等同於
function getNumber(){
return 1
}
module.exports = {getNUmber}
- 如果一個模塊需要導入其他模塊,通過
require
實現,require
是一個函數,傳入模塊的路徑即可返回該模塊導出的整個內容
//index.js
const util = require("./utils.js")
//等同於
const {getNumber} = require("./utils.js")
//等同於
console.log(require("./utils.js").getNumber())
注意:必須加路徑./
或../
不可省略,否則會報錯!
nodejs對CommonJS的實現
爲了實現CommonJS規範,nodejs對模塊做出了以下處理
-
爲了保證高效的執行,僅加載必要的模塊。nodejs只有執行到
require
函數時纔會加載並執行模塊 -
爲了隱藏模塊中的代碼,nodejs執行模塊時,會將模塊中的所有代碼放置到一個函數中執行,以保證不污染全局變量。(node筆記中解釋)
(function(){ //模塊中的代碼 })()
-
爲了保證順利的導出模塊內容,nodejs做了以下處理
- 在模塊開始執行前,初始化一個值
module.exports = {}
module.exports
即模塊的導出值- 爲了方便開發者便捷的導出,nodejs在初始化完
module.exports
後,又聲明瞭一個變量exports = module.exports
(function(module){ module.exports = {}; var exports = module.exports; //模塊中的代碼 return module.exports; })()
- 在模塊開始執行前,初始化一個值
-
爲了避免反覆加載同一個模塊,nodejs默認開啓了模塊緩存,如果加載的模塊已經被加載過了,則會自動使用之前的導出結果
CommonJS的工作原理
當使用require(模塊路徑)
導入一個模塊時,node會做以下兩件事情(不考慮模塊緩存):
- 通過模塊路徑找到本機文件,並讀取文件內容
- 將文件中的代碼放入到一個函數環境中執行,並將執行後module.exports的值作爲require函數的返回結果
正是這兩個步驟,使得CommonJS在node端可以良好的被支持
可以認爲,CommonJS是同步的,必須要等到加載完文件並執行完代碼後才能繼續向後執行
2、AMD
當瀏覽器遇到CommonJS
當想要把CommonJS放到瀏覽器端時,就遇到了一些挑戰
- 瀏覽器要加載JS文件,需要遠程從服務器讀取,而網絡傳輸的效率遠遠低於node環境中讀取本地文件的效率。由於CommonJS是同步的,這會極大的降低運行性能
- 如果需要讀取JS文件內容並把它放入到一個環境中執行,需要瀏覽器廠商的支持,可是瀏覽器廠商不願意提供支持,最大的原因是CommonJS屬於社區標準,並非官方標準
因爲瀏覽器無法支持模塊化。出現了AMD和CMD規範,有效的解決了瀏覽器模塊化的問題。
AMD(Asynchronous Module Definition):異步模塊加載機制
require.js實現了AMD規範
在AMD中,導入和導出模塊的代碼,都必須放置在define函數中
define([依賴的模塊列表], function(模塊名稱列表){
//模塊內部的代碼
return 導出的內容
})
在AMD中,入口文件需要特殊指明,用data-amin
屬性標記
<body>
<script data-main="./index.js" src="./require"></script>
</body>
AMD使用
//utils.js
define(function(){
let data = []
//...模塊內部代碼 進行一系列
return {
name:"導出的內容",
data:data
}
})
//index.js
define(['utils'], function(utils) {
//等utils模塊加載完成後執行自己的模塊,參數utils是utils模塊導出的內容
return {
name:"index模塊的內容"
}
});
如果多個文件都用到了同一個模塊,模塊只會讀取一次,執行一次,會加入緩存中
後來,require也實現了CMD規範
//utils2.js
define(function(require, exprots,module) {
const utils = require("./utils.js")
console.log("utils模塊代碼",utils)
module.exprots = "導出utils2模塊的內容"
});
注意:可以省略./
,但最好不要省略
3、CMD
CMD(Common Module Definition):公共模塊定義規範
sea.js實現了CMD規範
在CMD中,導入和導出模塊的代碼,都必須放置在define函數中,內部實現類似CommonJs
define(function(require, exports, module){
//模塊內部的代碼
})
CMD簡單使用
define(function(require, exprots,module) {
const utils1 = require("./utils1.js")
const utils2 = require("./utils2.js")
module.exprots = "導出該模塊的內容"
});
通過異步方式導入模塊
define(function(require, exprots,module) {
require.async("./utils1.js",function(utils1){
console.log(utils1)
})
require.async("./utils2.js",function(utils2){
console.log(utils2)
})
module.exprots = "導出a模塊的內容"
});
4、ES6模塊化
ECMA組織參考了衆多社區模塊化標準,終於在2015年,隨着ES6發佈了官方的模塊化標準,後成爲ES6模塊化
模塊化聲明方式有兩種
- 依賴延遲聲明 ,如:commonjs
- 優點:某些時候可以提高效率
- 缺點:無法在一開始確定模塊依賴關係(比較模糊)
- 依賴預聲明 ,如: cmd,amd,es6模塊
- 優點:在一開始可以確定模塊依賴關係
- 缺點:某些時候效率較低
ES6模塊化具有以下的特點
- 使用依賴預聲明的方式導入模塊
- 靈活的多種導入導出方式
- 規範的路徑表示法:所有路徑必須以./或…/開頭
模塊的引入
注意:這一部分非模塊化標準
目前,瀏覽器使用以下方式引入一個ES6模塊文件
<script src="入口文件" type="module">
ES6導入和導出
1、基本導入和導出
基本導出可以有多個,每個必須有名稱。export 聲明表達式
// 基本導出,聲明語句
export const name = "lkx" //導出name = "llx"
export function test(){
console.log("導出函數test")
}
其他寫法export {具名符號}
const age = 10
const sex = "男"
export {
age,
sex
}
//將age變量的名稱作爲導出的名稱,將age的值,作爲導出的值
// 等同於
export const age = 10
export const sex = "男"
也可以給導出變量起別名
const name = 10
export {
name as name1
}
由於基本導出必須具有名稱,所以要求導出內容必須跟上聲明表達式或具名符號
由於使用的是依賴預加載,因此,導入任何其他模塊,導入代碼必須放置到所有代碼之前
(不在開頭也可以,瀏覽器預編譯時進行import導入提升)不是解構賦值
對於基本導出,如果要進行導入,使用下面的代碼
import {導入的符號列表} from "模塊路徑"
import {name,test} from "./until.js"
console.log(name)
console.log(test())
注意以下細節:
-
導入時使用的符號是常量,不可修改
-
導入時,可以通過關鍵字
as
對導入的符號進行重命名
import {name,test as t} from "./until.js"
console.log(t())
- 可以使用*號導入所有的基本導出,形成一個對象
import * as obj from "./b.js"
//會把所有導出的內容形成一個對象,必須使用as重命名
- 多個文件導入同一個模塊,會加入緩存,不會重複運行
import "./init.js"
//這個導入語句,僅運行文件,不會導入模塊,如果有緩存,不運行
2、默認導入和導出
每個模塊,除了允許有多個基本導出之外,還允許有一個默認導出
默認導出類似於CommonJS中的module.exports
,由於只有一個,因此無需具名
具體的語法是
export default 默認導出的數據
//或
export {默認導出的數據 as default}
基本使用
export default {
name:name,
age:18
}
需要想要導入一個模塊的默認導出,需要使用下面的語法
import 接收變量名 from "模塊路徑"
由於默認導入時變量名是自行定義的,因此沒有別名一說
import data from "./utils.js"
console.log(data)
基本導入和默認導入混合使用:
如果希望同時導入某個模塊的默認導出和基本導出,可以使用下面的語法:
import 接收默認導出的變量, {接收基本導出的變量} from "模塊路徑"
export const sex = "fale"
export default {
name:name,
age:18
}
import data,{sex} from "./utils.js"
console.log(data,sex)
注:如果使用*號,會將所有基本導出和默認導出聚合到一個對象中,默認導出會作爲屬性default存在
//b.js
export const sex = "fale"
export default {
name:name,
age:18
}
import * as data from "./b.js"
console.log(data)
注意:
當導出一個內容時,儘量保證該內容是不可變的(大部分情況都是如此)。因爲,雖然導入後,無法更改導入內容,但是在導入的模塊內部卻有可能發生更改,這將導致一些無法預料的事情發生
導入的模塊是常量
import { name } from "./b.js"
name = 10 //報錯
如果我們只是想執行模塊中的一些代碼,而不需要導入它的任何內容,可以使用無綁定的導入:
import "模塊路徑"
有的時候,我們可能需要用一個模塊封裝多個模塊,然後有選擇的將多個模塊的內容分別導出,可以使用下面的語法輕鬆完成
export {綁定的標識符} from "模塊路徑"