JS模組化—CJS&AMD&CMD&ES6-前端面試知識點查漏補缺

腹黑的可樂發表於2023-02-06
本文從以時間為軸從以下幾個方面進行總結JS模組化。從無模組化 => IIFE => CJS => AMD => CMD => ES6 => webpack這幾個階段進行分析。

歷史

幼年期:無模組化

方式

  1. 需要在頁面中載入不同的js,用於動畫,元件,格式化
  2. 多種js檔案被分在了不同的檔案中
  3. 不同的檔案被同一個模板所引用
<script src="jquery.js"></script>
<script src="main.js"></script>
<script src="dep1.js"></script>

此處寫法檔案拆分是最基礎的模組化(第一步)

* 面試中的追問

script標籤的引數:async & defer

<script src="jquery.js" async></script>

總結

  • 三種載入
  • 普通載入:解析到立即阻塞,立刻下載執行當前script
  • defer載入:解析到標籤開始非同步載入,在後臺下載載入js,解析完成之後才會去載入執行js中的內容,不阻塞渲染
  • async載入:(立即執行)解析到標籤開始非同步載入,下載完成後開始執行並阻塞渲染,執行完成後繼續渲染

image.png

  • 相容性:> IE9
  • 問題可能被引導到 => 1. 瀏覽器的渲染原理 2.同步非同步原理 3.模組化載入原理
  • 出現的問題
  • 汙染全域性作用域

成長期(模組化前夜) - IIFE(語法測的最佳化)

image.png

作用域的把控

let count = 0;
const increase = () => ++count;
const reset = () => {
    count = 0;
}

利用函式的塊級作用域

(() => {
    let count = 0;
    ...
})
//最基礎的部分

實現一個最簡單的模組

const iifeModule = (() => {
    let count = 0;
    const increase = () => ++count;
    const reset = () => {
        count = 0;
    }
    console.log(count);
    increase();
})();
  • 追問:獨立模組本身的額外依賴如何最佳化
最佳化1:依賴其他模組的傳參型
const iifeModule = ((dependencyModule1,dependencyModule2) => {
    let count = 0;
    const increase = () => ++count;
    const reset = () => {
        count = 0;
    }
    console.log(count);
    increase();
    ...//可以處理依賴中的方法
})(dependencyModule1,dependencyModule2)

面試1:瞭解jquery或者其他很多開源框架的模組載入方案

將本身的方法暴露出去

const iifeModule = ((dependencyModule1,dependencyModule2) => {
    let count = 0;
    const increase = () => ++count;
    const reset = () => {
        count = 0;
    }
    console.log(count);
    increase();
    ...//可以處理依賴中的方法
    return 
        increase,reset
    }
})(dependencyModule1,dependencyModule2)
iifeModule.increase()

=> 揭示模式 revealing => 上層無需瞭解底層實現,僅關注抽象 => 框架

  • 追問:
  • 繼續模組化橫向展開
  • 轉向框架:jquery|vue|react模組細節
  • 轉向設計模式

成熟期

image.png

CJS (Commonjs)

node.js指定

特徵:

  1. 透過module + exports對外暴露介面
  2. 透過require去引入外部模組,

參考 前端進階面試題詳細解答

main.js

const dependencyModule1 = require('./dependencyModule1')
const dependencyModule2 = require('./dependencyModule2')

let count = 0;
const increase = () => ++count;
const reset = () => {
    count = 0;
}
console.log(count);
increase();

exports.increase = increase;
exports.reset = reset;

module.exports = {
    increase, reset
}

exe

const {increase, reset} = require(./main.js)

複合使用

(function(this.value,exports,require,module){
    const dependencyModule1 = require('./dependencyModule1')
    const dependencyModule2 = require('./dependencyModule2')
}).call(this.value,exports,require,module)

追問:一些開源專案為何要把全域性、指標以及框架本身作為引數

(function(window,$,undefined){
    const _show = function(){
        $("#app").val("hi zhuawa")
    }
    window.webShow = _show;
})(window,jQuery)
阻斷思路
  • 一. window

    1. 全域性作用域轉換為區域性作用域,window是全域性作用域,如果不轉成區域性作用域會有一個向上查詢知道全域性的過程,提升執行效率
    2. 編譯時最佳化:編譯後會變成(最佳化壓縮成本,銷燬)
    (function(c){})(window) // window會被最佳化成c
    //window在裡面所有別的執行所有的變化都會隨著執行完畢都會跟著c一起被銷燬
  • 二. jquery

    1. 獨立定製複寫和掛載
    2. 防止全域性串擾
  • 三. undefined
    防止改寫:在執行內部這段程式碼的時候保證undefined是正確的,不會被改寫,如在外部定義一個undefined =1
    undefined對jquery本身是一個很重要的一個存在

優缺點

  • 優點:CommonJS率先在服務端實現了,從框架層面解決了依賴,全域性變數未然的問題
  • 缺點: 針對服務端的解決方案,非同步拉取,依賴處理不是很友好

=> 非同步依賴的處理

AMD

透過非同步執行 + 允許指定回撥函式
經典實現框架:require.js

新增定義方式:

//define來定義模組
define(id, [depends], callback)
//require來進行載入
reuqire([module],callback)

模組定義的地方

define('amdModule',[dependencyModule1,dependencyModule2],(dependencyModule1,dependencyModule2) => {
    //業務邏輯
    let count = 0;
    const increase = () => ++count;
    module.exports = {
        increase
    }
})

引入的地方

require(['amdModule'],amdModule => {
    amdModule.increase()
})

面試題:如果在AMDModule中想相容已有程式碼,怎麼辦?

define('amdModule',[],require => {
    const dependencyModule1 = require('./dependencyModule1')
    const dependencyModule2 = require('./dependencyModule2')
    //業務邏輯
    let count = 0;
    const increase = () => ++count;
    module.exports = {
        increase
    }
})

面試題:手寫相容CJS&AMD

//判斷的關鍵:
    1. object還是function
    2. exports ?
    3. define

(define('AMDModule'),[],(require,export,module) => {
    const dependencyModule1 = require('./dependencyModule1')
    const dependencyModule2 = require('./dependencyModule2')

    let count = 0;
    const increase = () => ++count;
    const reset = () => {
        count = 0;
    }
    console.log(count);
    export.increase = increase();
})(
    //目標:一次性區分CJS還是AMD
    typeof module === 'object' && module.exports && typeof define !== function ? //CJS
    factory => module.exports = factory(require,exports,module)
    : //AMD
    define
)

優缺點

  • 優點:適合在瀏覽器中載入非同步模組的方案
  • 缺點:引入成本

CMD

按需載入
主要應用框架:sea.js
define('module',(require,exports,module) => {
    let $ = require('jquery')
    let dependencyModule1 = require('./dependencyModule1')
})

優缺點

  • 優點:按需載入,依賴就近
  • 缺點:依賴打包,載入邏輯存在於每個模組中,擴大了模組體積,同時功能上依賴編譯

ES6模組化

新增定義:

  • 引入:import
  • 引出:export

面試:

  1. 效能 - 按需載入
// ES11原生解決方案
import('./esMModule.js').then(dynamicModule => {
    dynamicModule.increase();
})

優點:
透過一種統一各端的形態,整合了js模組化的方案
缺點:本質上還是執行時分析

解決模組化新思路 - 前端工程化

遺留

根本問題:執行時進行依賴分析
解決方案:線下執行

編譯時依賴處理思路

<script src="main.js"></script>
<script>
  // 給構建工具一個標識位
  require.config(__FRAME_CONFIG__);
</script>
<script>
  require(['a', 'e'], () => {    // 業務邏輯
  })
</script>

define('a', () => {
    let b = require('b')
    let c = require('c')
})

完全體:webpack為核心的前端工程化 + mvvm框架的元件化 + 設計模式

相關文章