1. 程式人生 > 其它 >前端工程化1-模組

前端工程化1-模組

寫在前面

最近一兩個月在思考未來自己的職業規劃,團隊內小夥伴陸陸續續走的走,來的來。大家都處於職業的焦慮之中,我一直都很想進一家大廠,目前仍然沒有機會,也沒有能力能夠進去。畢業已經3年整了,不能繼續說打好基礎,把所有的業餘精力都放在js基礎學習上。應該著眼於自己的優勢和有競爭力的方面努力,這樣才不容易被取代。所以前端工程化是必不可少的深挖領域,以後部落格都會產出工程化相關的文章。

前端工程化是一個很大的議題,基本上工程化體系是離不開webpack和js模組。首先先從模組說起,面試的時候免不了會被問到幾個模組相關的問題。例如

  1. 說一說模組化的好處?前端模組化解決了哪些問題?
  2. 說一說es module和commonjs module的區別?commonjs require函式的工作原理?
  3. module.export、export 與 export defalut 有什麼區別?
    ...
    ...
    ...

js模組介紹

模組化就是將一個大檔案拆分成相互依賴的小檔案,再通過打包工具進行統一的拼裝和載入。有人會問了模組化的好處是什麼,或者說為什麼要做模組化?其實這樣做的好處可以從兩個角度分析

  1. 前端開發人員可以遵循統一標準規範,多人協作同時進行系統開發,加快開發速度
  2. 程式碼穩定,模組間相互隔離,相互獨立,互不影響。解決命名衝突等。
  3. 程式碼可讀性:程式碼被分割成細小的整體,可讀性變強。
  4. 可複用性:模組間相互獨立,互不影響,可複用性高。
  5. 可維護性:依賴關係明確清晰,易於維護。

js的模組標準有兩種 commonjs module和es6之後開始支援的es module。node應用採用的commonjs module規範,web應用一般採用es module規範。

commonjs module和es module

commonjs module簡稱cjs,es module簡稱mjs,下同。
示例原始碼

cjs

  • require函式,用於匯入
  • module.exports或者exports變數,用於匯出
    例如
//add.js
function add(a,b){
  return a+b;
}

module.exports={add};

//index.js
const {add} =require('./add');
add(1,2);

模組的匯入匯出需要注意的問題

  • exports不能直接對其賦值,例如下程式碼是不會正常匯出的
//add.js
function add (a,b){
  return a+b
};
exports={add};
  • 在同一個檔案中,exports和module.exports儘量不要同時使用,例如下程式碼
exports.add=function(a,b){
  return a+b;
}
module.exports={
  name:'jack'
}
  • module.exports或者exports儘量放在檔案的末尾,可以提高程式碼的可讀性
  • require函式是CommonJS規範之中,用來載入其他模組的函式。它其實不是一個全域性函式,而是指向當前模組的module.require函式,而後者又呼叫Node的內部命令Module._load。
  • require函式有快取,當第一次載入某個模組時,node會快取該模組,以後再載入該模組時,就直接從快取中取出該模組的module.exports屬性
  • require函式的載入規則,根據引數的不同格式,require命令去不同路徑尋找模組檔案
    1. 如果引數字串以“/”開頭,則表示載入的是一個位於絕對路徑的模組檔案。
    2. 如果引數字串以“./”開頭,則表示載入的是一個位於相對路徑(跟當前執行指令碼的位置相比)的模組檔案
    3. 如果引數字串不以“./“或”/“開頭,則表示載入的是一個預設提供的核心模組(位於Node的系統安裝目錄中),或者一個位於各級node_modules目錄的已安裝模組(全域性安裝或區域性安裝)
    4. 如果引數字串不以“./“或”/“開頭,而且是一個路徑,比如require('example-module/path/to/file'),則將先找到example-module的位置,然後再以它為引數,找到後續路徑
    5. 如果指定的模組檔案沒有發現,Node會嘗試為檔名新增.js、.json、.node後,再去搜索。.js件會以文字格式的JavaScript指令碼檔案解析,.json檔案會以JSON格式的文字檔案解析,.node檔案會以編譯後的二進位制檔案解析
    6. 如果想得到require命令載入的確切檔名,使用require.resolve()方法

mjs

  • import命令用於匯入
  • export(命名匯出)或者export default(預設匯出)命令用於匯出
    例如
//add.js
function add(a,b){
  return a+b;
}
export {
  add
}

// index.js
import {add} from './add';
add(1,2);

到這裡可以思考一個問題,
我們可以直接export default一個字面量物件嗎?在匯入的時候能解構到想要到值嗎?來看下下面的程式碼

//add.js
function add(a,b){
  return a+b
}
export default {
  add
}

// index.js
import {add} from "./add.mjs";
add(1,2);

上面的程式碼其實是會報錯的,報錯的檔案是index.js,export default {...}這種寫法是沒有不會報錯的,只是不規範。那為啥index.js中解構的形式匯出會報錯呢,我們先來看看報的什麼錯誤

import { add } from './add.mjs';
         ^^^
SyntaxError: The requested module './add.mjs' does not provide an export named 'add'

看報錯資訊說沒有請求的模組中export沒有提供add。既然這樣,我們就直接給請求到的add模組賦一個值,然後我們打印出來看看究竟是一個什麼東西,add模組的程式碼不變,index.js修改後的程式碼如下

// index.js
import util from "./add.mjs";
cosnole.log(util);

執行之後我們可以看到控制檯輸出

> [lzm]% node index.mjs
> { add: [Function: add] }

我們可以看到是一個擁有add函式的物件,按理說應該可以結構賦值才對呀。其實從上面的報錯能看出來,import的解構會自動匹配export,如果沒有則會報錯。

可以再思考一個問題,在node環境下可以使用es module嗎?
答案是可以的,以上例子都是在node環境下執行的。現在node已經支援es module規範了,參考官網的解釋
esm_enabling

commonjs module和es module的異同

最本質的區別是模組依賴解析的時機不同

執行時和編譯時
所謂執行時即程式碼已經解析成機器可以識別的形態,程式碼可以直接執行的階段。而編譯時即我們的js程式碼解析成機器可以識別的形態的過程。

  • cjs在執行時解析模組依賴,mjs在編譯時解析模組依賴
    cjs的載入機制是輸入的是被輸出的值的拷貝。也就是說,一旦輸出一個值,模組內部的變化就影響不到這個值

cjs是在執行時才確定引入, 然後執行這個模組, 相當於是呼叫一個函式, 返回一個物件.
mjs是語言層面的, 匯入匯出是宣告式的程式碼集合. 宣告式的意思就是說, 直接利用關鍵字宣告說我要匯入/匯出一個模組啦, 而不是粗鄙(節目效果)地將一個物件賦值給一個變數

如何處理模組的迴圈載入?
CommonJS模組的重要特性是載入時執行,即指令碼程式碼在require的時候,就會全部執行,CommonJS的做法是,一旦出現某個模組被"迴圈載入",就只輸出已經執行的部分,還未執行的部分不會輸出。require函式第一次匯入時會自動快取結果,第二次再匯入模組時會直接給到結果。
import不會去執行模組,而是隻生成一個引用。等到真的需要用到時,再到模組裡面去取值

es 模組並沒有幫我們解決迴圈依賴,所以迴圈依賴問題需要開發者自己解決

參考