javascript
JS高级——模块化学习笔记
一、什么是模塊化?
到底什么是模塊化、模塊化開發呢?
- 事實上模塊化開發最終的目的是將程序劃分成一個個小的結構;
- 這個結構中編寫屬于自己的邏輯代碼,有自己的作用域,不會影響到其他的結構;
- 這個結構可以將自己希望暴露的變量、函數、對象等導出給其結構使用;
- 也可以通過某種方式,導入另外結構中的變量、函數、對象等;
上面說提到的結構,就是模塊;按照這種結構劃分開發程序的過程,就是模塊化開發的過程;
二、沒有模塊化帶來的問題
早期沒有模塊化帶來了很多的問題:比如命名沖突的問題
當然,我們有辦法可以解決上面的問題:立即函數調用表達式(IIFE)
- IIFE (Immediately Invoked Function Expression)
但是,我們其實帶來了新的問題:
- 第一,我必須記得每一個模塊中返回對象的命名,才能在其他模塊使用過程中正確的使用;
- 第二,代碼寫起來混亂不堪,每個文件中的代碼都需要包裹在一個匿名函數中來編寫;
- 第三,在沒有合適的規范情況下,每個人、每個公司都可能會任意命名、甚至出現模塊名稱相同的情況;
所以,我們會發現,雖然實現了模塊化,但是我們的實現過于簡單,并且是沒有規范的。
- 我們需要制定一定的規范來約束每個人都按照這個規范去編寫模塊化的代碼;
- 這個規范中應該包括核心功能:模塊本身可以導出暴露的屬性,模塊又可以導入自己需要的屬性;
- JavaScript社區為了解決上面的問題,涌現出一系列好用的規范,接下來我們就學習具有代表性的一些規范。
三、CommonJS規范和Node關系
CommonJS是一個規范,最初提出來是在瀏覽器以外的地方使用,并且當時被命名為ServerJS,后來為了體現它的廣泛性,修改為CommonJS,平時我們也會簡稱為CJS。
- Node是CommonJS在服務器端一個具有代表性的實現;
- Browserify是CommonJS在瀏覽器中的一種實現;
- webpack打包工具具備對CommonJS的支持和轉換;
所以,Node中對CommonJS進行了支持和實現,讓我們在開發node的過程中可以方便的進行模塊化開發:
- 在Node中每一個js文件都是一個單獨的模塊;
- 這個模塊中包括CommonJS規范的核心變量:exports、module.exports、require;
- 我們可以使用這些變量來方便的進行模塊化開發;
前面我們提到過模塊化的核心是導出和導入,Node中對其進行了實現:
- exports和module.exports可以負責對模塊中的內容進行導出;
- require函數可以幫助我們導入其他模塊(自定義模塊、系統模塊、第三方庫模塊)中的內容;
四、exports導出
注意:exports是一個對象,我們可以在這個對象中添加很多個屬性,添加的屬性會導出;
上面的代碼意味著:
- main中的why變量等于exports對象;
- 也就是require通過各種查找方式,最終找到了exports這個對象;
- 并且將這個exports對象賦值給了why變量;
- why變量就是exports對象了;
五、module.exports
但是Node中我們經常導出東西的時候,又是通過module.exports導出的,module.exports和exports有什么關系或者區別呢?
我們追根溯源,通過維基百科中對CommonJS規范的解析:
- CommonJS中是沒有module.exports的概念的;
- 但是為了實現模塊的導出,Node中使用的是Module的類,每一個模塊都是Module的一個實例,也就是module;
- 所以在Node中真正用于導出的其實根本不是exports,而是module.exports;
- 因為module才是導出的真正實現者;
但是,為什么exports也可以導出呢?
- 這是因為module對象的exports屬性是exports對象的一個引用;
- 也就是說 module.exports = exports = main中的why;
六、require細節
我們現在已經知道,require是一個函數,可以幫助我們引入一個文件(模塊)中導出的對象。那么,require的查找規則是怎么樣的呢?
https://nodejs.org/dist/latest-v14.x/docs/api/modules.html#modules_all_together
這里總結比較常見的查找規則:導入格式如下:require(X)
七、模塊的加載過程
結論一:模塊在被第一次引入時,模塊中的js代碼會被運行一次
結論二:模塊被多次引入時,會緩存,最終只加載(運行)一次
- 為什么只會加載運行一次呢?
- 這是因為每個模塊對象module都有一個屬性:loaded。
- 為false表示還沒有加載,為true表示已經加載;
結論三:如果有循環引入,那么加載順序是什么?
如果出現下圖模塊的引用關系,那么加載順序是什么呢?
- 這個其實是一種數據結構:圖結構;
- 圖結構在遍歷的過程中,有深度優先搜索(DFS, depth first search)和廣度優先搜索(BFS, breadth first search);
- Node采用的是深度優先算法:main -> aaa -> ccc -> ddd -> eee ->bbb
八、CommonJS規范缺點
CommonJS加載模塊是同步的:
- 同步的意味著只有等到對應的模塊加載完畢,當前模塊中的內容才能被運行;
- 這個在服務器不會有什么問題,因為服務器加載的js文件都是本地文件,加載速度非常快;
如果將它應用于瀏覽器呢?
- 瀏覽器加載js文件需要先從服務器將文件下載下來,之后再加載運行;
- 那么采用同步的就意味著后續的js代碼都無法正常運行,即使是一些簡單的DOM操作;
所以在瀏覽器中,我們通常不使用CommonJS規范:
- 當然在webpack中使用CommonJS是另外一回事;
- 因為它會將我們的代碼轉成瀏覽器可以直接執行的代碼;
在早期為了可以在瀏覽器中使用模塊化,通常會采用AMD或CMD:
- 但是目前一方面現代的瀏覽器已經支持ES Modules,另一方面借助于webpack等工具可以實現對CommonJS或者ES Module代碼的轉換;
- AMD和CMD已經使用非常少了,所以這里我們進行簡單的演練;
AMD主要是應用于瀏覽器的一種模塊化規范:
- AMD是Asynchronous Module Definition(異步模塊定義)的縮寫;
- 它采用的是異步加載模塊;
- 事實上AMD的規范還要早于CommonJS,但是CommonJS目前依然在被使用,而AMD使用的較少了;
規范只是定義代碼的應該如何去編寫,只有有了具體的實現才能被應用:
- AMD實現的比較常用的庫是require.js和curl.js;
CMD規范也是應用于瀏覽器的一種模塊化規范:
- CMD 是Common Module Definition(通用模塊定義)的縮寫;
- 它也采用了異步加載模塊,但是它將CommonJS的優點吸收了過來;
- 但是目前CMD使用也非常少了;
CMD也有自己比較優秀的實現方案: SeaJS
九、認識 ES Module
ES Module和CommonJS的模塊化有一些不同之處:
- 一方面它使用了import和export關鍵字;
- 另一方面它采用編譯期的靜態分析,并且也加入了動態引用的方式;
ES Module模塊采用export和import關鍵字來實現模塊化:
- export負責將模塊內的內容導出;
- import負責從其他模塊導入內容;
了解:采用ES Module將自動采用嚴格模式:use strict
十、exports關鍵字
export關鍵字將一個模塊中的變量、函數、類等導出;
我們希望將其他中內容全部導出,它可以有如下的方式:
- 方式一:在語句聲明的前面直接加上export關鍵字
- 方式二:將所有需要導出的標識符,放到export后面的 {}中
注意:這里的 {}里面不是ES6的對象字面量的增強寫法,{}也不是表示一個對象的;
所以: export {name: name},是錯誤的寫法;
- 方式三:導出時給標識符起一個別名
十一、import關鍵字
import關鍵字負責從另外一個模塊中導入內容
導入內容的方式也有多種:
方式一:import {標識符列表} from ‘模塊’;
- 注意:這里的{}也不是一個對象,里面只是存放導入的標識符列表內容;
方式二:導入時給標識符起別名
方式三:通過 * 將模塊功能放到一個模塊功能對象(a module object)上
十二、export和import結合使用
補充:export和import可以結合使用
為什么要這樣做呢?
- 在開發和封裝一個功能庫時,通常我們希望將暴露的所有接口放到一個文件中;
- 這樣方便指定統一的接口規范,也方便閱讀;
- 這個時候,我們就可以使用export和import結合使用;
十三、default用法
前面我們學習的導出功能都是有名字的導出(named exports):
- 在導出export時指定了名字;
- 在導入import時需要知道具體的名字;
還有一種導出叫做默認導出(default export)
- 默認導出export時可以不需要指定名字;
- 在導入時不需要使用 {},并且可以自己來指定名字;
- 它也方便我們和現有的CommonJS等規范相互操作;
注意:在一個模塊中,只能有一個默認導出(default export);
十四、import函數
通過import加載一個模塊,是不可以在其放到邏輯代碼中的,比如:
為什么會出現這個情況呢?
- 這是因為ES Module在被JS引擎解析時,就必須知道它的依賴關系;
- 由于這個時候js代碼沒有任何的運行,所以無法在進行類似于if判斷中根據代碼的執行情況;
- 甚至下面的這種寫法也是錯誤的:因為我們必須到運行時能確定path的值;
但是某些情況下,我們確確實實希望動態的來加載某一個模塊:
- 如果根據不同的條件,動態來選擇加載模塊的路徑;
- 這個時候我們需要使用 import() 函數來動態加載;
import.meta是一個給JavaScript模塊暴露特定上下文的元數據屬性的對象。 - 它包含了這個模塊的信息,比如說這個模塊的URL;
- 在ES11(ES2020)中新增的特性;
總結
以上是生活随笔為你收集整理的JS高级——模块化学习笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 二、Vue基础语法学习笔记——事件监听v
- 下一篇: 网际控制报文协议ICMP(Interne