1:簡述require()模塊加載機制
答:在node中文件即模塊,分為原生模塊和3種文件模塊。
具體加載順序如下圖

2:node導出模塊有哪2種方式,說說它們的區(qū)別?

答:真正的接口是module.exports, 初始值為{}。exports是對module.exports的引用,指向module.exports。在require()的時候,返回的是module.exports而不是exports。
3:請簡述下Node事件循環(huán)機制
答:Node采用的是單線程異步非阻塞I/O模式。
主線程執(zhí)行同步任務,異步I/O交給libuv處理。
libuv將建立的所有I/O操作內容綁定到單個線程上。只要每個事件循環(huán)在不同的線程中,就可以運行多個事件循環(huán)。
libuv將不同的任務交給不同的線程來處理。處理完成后會將對應事件的狀態(tài)轉為pending。
然后在下一次事件循環(huán)的時候按順序取來執(zhí)行。每一次事件循環(huán)都只會執(zhí)行一個回調,從而避免了競爭的發(fā)生。
libuv解釋:libuv是一個多平臺支持庫,主要關注異步I/O。它主要為node.js的使用而開發(fā),同時也被Luvit、Julia、pyuv等使用。libuv為Node.js提供了跨平臺、線程池、事件池、異步I/O等能力,是Node.js如此強大的關鍵。
每個事件循環(huán)包含的7階段(每個階段結束后都會執(zhí)行所有的微任務):
1:run due timers 執(zhí)行到期的計時器,如:setTimeout、setInterval等。
2:call pending callbacks 執(zhí)行轉入pending狀態(tài)的觀察者的回調函數(shù)。大多數(shù)情況下,在輪詢I/O之后立即調用所有I/O回調。但是在某些情況下,調用此類回調會被推遲到下一個循環(huán)迭代去。如果先前的迭代推遲了任何I/O回調,它在將在此時運行。
3:run idle handles 如果idle句柄是活躍的,每一次迭代循環(huán)都會執(zhí)行。
4:run prepare handles 執(zhí)行prepare句柄的回調。在循環(huán)阻止I/O前執(zhí)行prepare 句柄的回調函數(shù)。
5:poll for I/O 計算出輪詢超時時間,并以此時間作為阻塞I/O的持續(xù)時間,等待還未返回的I/O回調。
6:run check handles 執(zhí)行check handles的回調函數(shù)。check handles本質上是prepare的對應物。
7:call close callbacks 執(zhí)行關閉回調函數(shù)。如果一個句柄被uv_close關閉將執(zhí)行關閉回調函數(shù)。
注意:node事件循環(huán)比js事件循環(huán)多了兩個異步,setImmediate(宏任務)、process.nextTick(微任務)。process.nextTick的優(yōu)先級高于promise().then()。



3:談談V8的垃圾回收機制
答:V8是由google公司開發(fā)的javascript和webAssembly引擎,用c++編寫。它被用在了Chrome瀏覽器和Node中。它實現(xiàn)了ECMAScript和WebAssembly, 可以運行在Win7+(含Win7)、macOS10.12+、Linux等系統(tǒng)上。V8也可以三都運行,或者被嵌入到任何由C++編寫的程序中。
內存限制:
V8只能使用部分內存,默認大小為64位系統(tǒng)1.4G,32位系統(tǒng)為0.7G。當然你也可以自定義大小。建議是不要超過1.5G。因為垃圾回收1.5G的堆內存V8需要花費50毫秒以上,做一次非增量式的垃圾回收甚至需要1秒以上,性能響應明顯下降。
內存分類:
1:新生代內存空間 (主要用來存放存活時間較短的對象)
2:老生代內存空間 (主要用來存放存活時間較長的對象)
新生代垃圾回收:
主要通過Scavenge算法進行,主要通過停止-復制的方式實現(xiàn)垃圾回收,具體采用了Cheney算法。
Cheney將內存空間一分為二,被分出來的空間叫做semi-space(半空間),一個半空間處于使用中叫做From,一個半空間處于閑置狀態(tài)叫做To。
垃圾回收在運行時,會檢查From空間的存活對象,將存活對象復制到To中,而死亡對象占用的空間就會被釋放。然后交換From和To角色繼續(xù)重復上邊的操作。當一個對象經(jīng)過多次復制后依然存活,我們就認為該對象生命周期較長,會被移到老生代內存中去。
老生代垃圾回收:
老生代垃圾回收分為Mark Sweep(標記清除)和 Mark Compact(標記合并)兩種方式。
Mark Sweep:標記需要回收的對象,然后在垃圾回收的時直接清除釋放空間。這個有個問題就是,釋放后的空閑時間是碎片化的,再次分配的時候可能因為單個碎片塊大小小于需要分配的空間大小而造成無法再次分配內存。
Mark Compact:標記存活的對象統(tǒng)一移到一邊,死亡對象移動到另外一邊,然后垃圾回收清除死亡對象釋放空間。
以上三種垃圾回收算法比較:
在 Mark-Sweep 和 Mark-Compact 之間,由于 Mark-Compact 需要移動對象,所以它的執(zhí)行速度不可能很快,所以在取舍上,V8 主要使用 Mark-Sweep,在空間不足以從新生代中晉升過來的對象進行分配時才使用 Mark-Compact 。為了避免出現(xiàn) JavaScript應用邏輯與垃圾回收器看到的不一致的情況,垃圾回收的3種算法都需要將應用邏輯暫停下來,這種行為稱為“全停頓” (stop-the-world)。由于新生代配置的空間較小,存活對象較少,全停頓對新生代影響不大。但老生代通常配置的空間較大,且存活對象較多,全堆垃圾回收(full垃圾回收)的標記、清除、整理等動作造成的停頓就會比較可怕。
增量標記:
為了降低全堆垃圾回收帶來的停頓時間,V8先從標記階段入手,將原本要一口氣停頓完成的動作改成增量標記(Incremental Marking),也就是拆分為許多小“步進”,每做完一“步進”就讓JavaScript應用邏輯執(zhí)行一小會兒,垃圾回收和應用邏輯交替執(zhí)行直到標記階段完成。
增量標記允許堆的標記發(fā)生在幾次5-10毫秒(移動設備)的小停頓中。
增量標記在堆的大小達到一定的閾值時啟用,啟用之后每當一定量的內存分配后,腳本的執(zhí)行就會停頓并進行一次增量標記。就像普通的標記一樣,增量標記也是一個深度優(yōu)先搜索,并同樣采用白灰黑機制來分類對象。但增量標記和普通標記不同的是,對象的圖譜關系可能發(fā)生變化!
查看V8內存使用情況:
查看內存使用情況:process.memoryUsage()
查看操作系統(tǒng)總內存:os.totalmem()
查看操作系統(tǒng)總空閑內存:os.freemem()


4:說說排查內存泄露的方法
答:內存泄露通常有兩種情況,一種是容易復現(xiàn)的,一種是不容易復現(xiàn)的。
對于容易復現(xiàn)的我們在測試環(huán)境中模擬排查即可。
對于不容易復現(xiàn)的我們就要借助內存快照,可以使用devtool查看內存快照,heapdump保存內存快照。heapdump保存的內存快照只會有Node環(huán)境中的對象,如果使用node-inspector快照中就會包含前端變量,容易造成干擾。
推薦使用:npm install heapdump -target=Node.js 進行安裝。