模塊化之CommonJs、AMD、CMD和ES6模塊化

模塊是將一個複雜的程序依據一定的規則(規範)封裝成幾個塊(文件), 並進行組合在一起。塊的內部數據與實現是私有的, 只是向外部暴露一些接口(方法)與外部其它模塊通信

模塊化有兩個重要的概念:模塊的導出和模塊的導入

  • 模塊的導出:暴露接口的過程即模塊的導出

  • 模塊的導入:當通過某種語法或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導入模塊

具體規範如下:

  1. 如果一個JS文件中存在exportsrequire,該JS文件是一個模塊
  2. 模塊內的所有代碼均爲隱藏代碼,包括全局變量、全局函數,這些全局的內容均不應該對全局變量造成任何污染
  3. 如果一個模塊需要暴露一些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}
  1. 如果一個模塊需要導入其他模塊,通過require實現,require是一個函數,傳入模塊的路徑即可返回該模塊導出的整個內容
//index.js
const util = require("./utils.js")
//等同於
const {getNumber} = require("./utils.js")
//等同於
console.log(require("./utils.js").getNumber())

注意:必須加路徑./../不可省略,否則會報錯!

nodejs對CommonJS的實現

爲了實現CommonJS規範,nodejs對模塊做出了以下處理

  1. 爲了保證高效的執行,僅加載必要的模塊。nodejs只有執行到require函數時纔會加載並執行模塊

  2. 爲了隱藏模塊中的代碼,nodejs執行模塊時,會將模塊中的所有代碼放置到一個函數中執行,以保證不污染全局變量。(node筆記中解釋)

     (function(){
         //模塊中的代碼
     })()
    
  3. 爲了保證順利的導出模塊內容,nodejs做了以下處理

    1. 在模塊開始執行前,初始化一個值module.exports = {}
    2. module.exports即模塊的導出值
    3. 爲了方便開發者便捷的導出,nodejs在初始化完module.exports後,又聲明瞭一個變量exports = module.exports
     (function(module){
         module.exports = {};
         var exports = module.exports;
         //模塊中的代碼
         return module.exports;
     })()
    
  4. 爲了避免反覆加載同一個模塊,nodejs默認開啓了模塊緩存,如果加載的模塊已經被加載過了,則會自動使用之前的導出結果

CommonJS的工作原理

當使用require(模塊路徑)導入一個模塊時,node會做以下兩件事情(不考慮模塊緩存):

  1. 通過模塊路徑找到本機文件,並讀取文件內容
  2. 將文件中的代碼放入到一個函數環境中執行,並將執行後module.exports的值作爲require函數的返回結果

正是這兩個步驟,使得CommonJS在node端可以良好的被支持

可以認爲,CommonJS是同步的,必須要等到加載完文件並執行完代碼後才能繼續向後執行

2、AMD

當瀏覽器遇到CommonJS

當想要把CommonJS放到瀏覽器端時,就遇到了一些挑戰

  1. 瀏覽器要加載JS文件,需要遠程從服務器讀取,而網絡傳輸的效率遠遠低於node環境中讀取本地文件的效率。由於CommonJS是同步的,這會極大的降低運行性能
  2. 如果需要讀取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模塊化

模塊化聲明方式有兩種

  1. 依賴延遲聲明 ,如:commonjs
    1. 優點:某些時候可以提高效率
    2. 缺點:無法在一開始確定模塊依賴關係(比較模糊)
  2. 依賴預聲明 ,如: cmd,amd,es6模塊
    1. 優點:在一開始可以確定模塊依賴關係
    2. 缺點:某些時候效率較低

ES6模塊化具有以下的特點

  1. 使用依賴預聲明的方式導入模塊
  2. 靈活的多種導入導出方式
  3. 規範的路徑表示法:所有路徑必須以./或…/開頭

模塊的引入

注意:這一部分非模塊化標準

目前,瀏覽器使用以下方式引入一個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())

注意以下細節:

  1. 導入時使用的符號是常量,不可修改

  2. 導入時,可以通過關鍵字as對導入的符號進行重命名

import {name,test as t} from "./until.js"
console.log(t())
  1. 可以使用*號導入所有的基本導出,形成一個對象
import * as obj from "./b.js"
//會把所有導出的內容形成一個對象,必須使用as重命名
  1. 多個文件導入同一個模塊,會加入緩存,不會重複運行
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)

注意:

  1. 儘量導出不可變值

當導出一個內容時,儘量保證該內容是不可變的(大部分情況都是如此)。因爲,雖然導入後,無法更改導入內容,但是在導入的模塊內部卻有可能發生更改,這將導致一些無法預料的事情發生

導入的模塊是常量

import { name } from "./b.js"
name = 10 //報錯
  1. 可以使用無綁定的導入用於執行一些初始化代碼

如果我們只是想執行模塊中的一些代碼,而不需要導入它的任何內容,可以使用無綁定的導入:

import "模塊路徑"
  1. 可以使用綁定再導出,來重新導出來自另一個模塊的內容

有的時候,我們可能需要用一個模塊封裝多個模塊,然後有選擇的將多個模塊的內容分別導出,可以使用下面的語法輕鬆完成

export {綁定的標識符} from "模塊路徑"
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章