摘要:重要的模塊化規范有幾個,模塊機制,,。模塊化的目的在于營造安全封閉的作用域且具有易于引用接口,按我的理解可分為模塊定義模塊引入兩部分。它的定義如下模塊標識符模塊對外輸出的值調用該模塊的模塊。在中,有一部分模塊由提供,稱之為核心模塊。
重要的模塊化規范有幾個:commonjs,ES6模塊機制,AMD,CMD。由于業務中一直接觸的都是Vue+webpack+babel架構的項目,在封裝代碼時用的比較的多還是ES6規范,對其他模塊化規范不熟悉,因此在這里記錄一下學習過的模塊化知識。
CommonJS模塊化的目的在于營造安全封閉的作用域、且具有易于引用接口,按我的理解可分為模塊定義、模塊引入兩部分。
在模塊中存在著一個module對象,它代表著模塊本身,將需要導出的api掛載于其中的exports屬性上即可以定義導出的接口;CommonJS規范中存在require()方法,用于接受模塊標識,引入某個模塊到當前的上下文。
1. 模塊定義要理解模塊如何定義,那必須要先理解module對象。在Node中,每一個文件模塊都是一個對象,即module對象。它的定義如下:
function Module(id, parent){ this.id = id //模塊標識符 this.exports = {} //模塊對外輸出的值 this.parent = parent //調用該模塊的模塊。parent為null時意味著模塊為入口模塊 if(parent && parent.children){ parent.children.push(this) } this.filename = filename //文件名 this.loaded = false //是否已加載 this.children = [] //表示該模塊調用的其他模塊 }
定義模塊的目的其實在于定義輸出的值。寫法非常簡單,舉個?
function sayHello(){ console.log("hello") } module.exports = sayHello //或exports.sayHello = sayHello
為了方便導出接口,Node還定義了一個exports變量,但有個容易踩的坑是,exports只是一個引用,本來指向module.exports,假如只是給exports變量賦值則exports變量會失去對module.exports的指向。說到底,必須對module.exports定義接口才能真正導出值。
先說解決方法,常見的寫法為:
exports = module.exports = sayHello //或嚴格地只給exports變量添加屬性 exports.sayHello = sayHello
再舉個例子說一下犯錯的具體場景:
//a.js exports.name = "kent" exports.sayHi = function(){ console.log("hi") } console.log(module)// { exports: { name: "kent", sayHi: function(){ console.log("hi") } } } //假如給exports重新賦值 =_= exports = { name: "nicolas", sayBye: function(){ console.log("bye") } } //module中的exports屬性不會有任何變化 console.log(module)// { exports: { name: "kent", sayHi: function(){ console.log("hi") } } } console.log(exports)// { name: "nicolas", sayBye: function(){ console.log("bye") } }
//b.js //因此require的時候讀取的name仍然為kent var person = require("a.js") console.log(person.name)//kent
具體的原因也可以從模塊機制中看出來
function require(...) { var module = { exports: {} }; ((module, exports) => { // Your module code here. In this example, define a function. function some_func() {}; exports = some_func; // At this point, exports is no longer a shortcut to module.exports, and // this module will still export an empty default object. module.exports = some_func; // At this point, the module will now export some_func, instead of the // default object. })(module, module.exports); return module.exports; }2. 模塊引入
模塊引入的語法也非常簡單。上一節也簡單提過。這里再舉個?
//book.js exports.name = "javascript" exports.logName = function(){ console.log("javascript") }
//main.js var book = require("./book.js")//require的參數即模塊標識符 console.log(book.name)//"javascript" book.logName()//"javascript"
下面詳情談談模塊引入經歷哪些步驟。但在此之前需要先了解兩個概念:核心模塊與文件模塊。
在Node中,有一部分模塊由Node提供,稱之為核心模塊。在Node進程啟動的時候,核心模塊就直接加載至內存中。因此引入核心模塊只需要走路徑分析一個步驟,其加載速度最快。
另一部分則是運行時動態加載,常見的有用戶定義帶路徑標識符的模塊,或自定義模塊(如三方提供的包)。這類模塊需要完整地走完以下三個步驟:路徑分析、文件定位與編譯執行。
①路徑分析:
路徑分析可以理解為模塊標識符的分析。模塊標識符在Node中主要有:
·核心模塊,如:http, fs等等; ·以"./"或"../"開頭的相對路徑模塊,相對于當前的目錄位置; ·以"/"開頭的絕對路徑模塊; ·非路徑形式的文件模塊,與核心模塊的標識符類似。Node會搜索各級的node_modules目錄。
· 核心模塊:核心模塊經過路徑分析之后會直接加載。需要注意的是,自定義的文件模塊不能與核心模塊標識符相同,要不更換不同的標識符要么使用相對路徑或絕對路徑標識符。
· 路徑形式的文件模塊:在分析文件模塊的時候,require方法將會把路徑轉換為真實路徑并以此為索引編譯模塊并存放到緩存中(緩存加載將在下文介紹)。
· 非路徑形式的文件模塊(自定義模塊):自定義模塊的路徑分析在我們引用三方庫的時候經常會碰到。這類非路徑形式的文件模塊加載時將會以模塊路徑為線索逐級搜索。舉個? :
//在"/Users/zhazheng/Documents/www"下新建一個module_path.js //module_path.js console.log(module.paths) //再執行module_path.js node module_path //得出以下log [ "/Users/zhazheng/Documents/www/node_modules", "/Users/zhazheng/Documents/node_modules", "/Users/zhazheng/node_modules", "/Users/node_modules", "/node_modules" ]
可見,這類模塊會從當前文件目錄往上逐級遞歸直到根目錄下的node_modules目錄。因此這類模塊的路徑分析是最費時的。
②文件定位:文件定位主要包括文件擴展名分析、目錄和包的處理。
·文件擴展名分析:分析標識符的過程中出現不包含文件擴展名的情況非常常見。在標識符不包含文件擴展名的情況下,Node會依次嘗試以下三種擴展名:.js、.json、.node。由于嘗試解析的過程是同步阻塞進行的,因此大量的分析文件擴展名會產生性能問題,這種情況下可以嘗試添加擴展名或充分利用緩存加載的優勢。
·目錄分析與包的處理: 假如分析完擴展名后仍然沒有找到對應的文件而只得出一個目錄,那么Node會將此目錄當做一個包來處理。首先會查找當前目錄下是否有package.json文件,假如有則檢查是否具有main屬性(main屬性即指向入口文件)。假如沒有package.json文件或package.json中不具備main屬性,那么Node則按index為默認的文件名,最后再重復“文件擴展名分析”這個步驟。
3.緩存加載事實上Node的模塊,無論是核心模塊還是文件模塊,第一次加載之后都會被緩存。require()方法將會對二次加載的模塊進行緩存。因此假如有多次加載模塊的需求,那么就需要記得先從緩存中刪除模塊。
緩存均保存在require.cache對象中,需要刪除單個模塊或全部模塊的緩存可以這樣寫:
//刪除單個模塊緩存 delete require.cache[moduleName] //刪除全部模塊緩存 Object.getOwnPropertyNames(require.cache).forEach(key => { delete require.cache[key] })
當然,一般情況下緩存是可以帶來性能優勢的。對于路徑套得非常深的自定義文件模塊來說尤甚。
4.循環加載循環加載是避免不了的問題。在Node中需要了解一下循環加載的表現。首先要理解的是,require是一個同步加載的過程,讀取的接口僅僅是指向exports對象中的屬性,舉個? :(以下三個模塊均在同一目錄下)
//a.js exports.name = "a1" console.log(`a.js, ${require("./b.js").name}`) exports.name = "a2"
//b.js exports.name = "b1" console.log(`b.js, ${require("./a.js").name}`) exports.name = "b2"
//main.js console.log(`main.js, ${require("./a.js").name}`) console.log(`main.js, ${require("./b.js").name}`)
nvm run node然后.load main.js得出以下的結果
b.js, a1 a.js, b2//這兩行結果應該大致可以理解兩個模塊的require方法發生了什么 main.js a2 main.js b2
再次執行.load main.js會讀取緩存結果
main.js a2 main.js b2
循環加載示例代碼可到我的github查看
AMD...未完待續
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/91938.html
摘要:容器自動完成裝載,默認的方式是這部分重點在常用模塊的使用以及的底層實現原理。 對于那些想面試高級 Java 崗位的同學來說,除了算法屬于比較「天方夜譚」的題目外,剩下針對實際工作的題目就屬于真正的本事了,熱門技術的細節和難點成為了主要考察的內容。 這里說「天方夜譚」并不是說算法沒用,不切實際,而是想說算法平時其實很少用到,甚至面試官都對自己出的算法題一知半解。 這里總結打磨了 70 道...
摘要:原文地址游客前言金三銀四,很多同學心里大概都準備著年后找工作或者跳槽。最近有很多同學都在交流群里求大廠面試題。 最近整理了一波面試題,包括安卓JAVA方面的,目前大廠還是以安卓源碼,算法,以及數據結構為主,有一些中小型公司也會問到混合開發的知識,至于我為什么傾向于混合開發,我的一句話就是走上編程之路,將來你要學不僅僅是這些,豐富自己方能與世接軌,做好全棧的裝備。 原文地址:游客kutd...
摘要:當你真正到公司里面從事了幾年開發之后,你就會同意我的說法利用找工作,需要的就是項目經驗,項目經驗就是理解項目開發的基本過程,理解項目的分析方法,理解項目的設計思 Java就是用來做項目的!Java的主要應用領域就是企業級的項目開發!要想從事企業級的項目開發,你必須掌握如下要點: 1、掌握項目開發的基本步驟 2、具備極強的面向對象的分析與設計技巧 3、掌握用例驅動、以架構為核心的主流開發...
摘要:中的有多重意義。它可能是一個構造器,承擔起對象模板的作用可能是對象的方法,負責向對象發送消息。語義匿名函數處理某些特殊效果如處理一些數據又不想暴露過多的變量判斷版本的方式最終只要一個結果,匿名函數內部用到了一些局部變量全部可以隔離開。 JavaScript 中的 function 有多重意義。它可能是一個構造器(constructor),承擔起對象模板的作用; 可能是對象的方法(met...
閱讀 3729·2021-09-22 15:49
閱讀 3300·2021-09-08 09:35
閱讀 1422·2019-08-30 15:55
閱讀 2321·2019-08-30 15:44
閱讀 714·2019-08-29 16:59
閱讀 1597·2019-08-29 16:16
閱讀 479·2019-08-28 18:06
閱讀 890·2019-08-27 10:55