node.js監(jiān)聽文件變化的實現(xiàn)方法
前言
隨著前端技術(shù)的飛速發(fā)展,前端開發(fā)也從原始的刀耕火種,向著工程化效率化的方向發(fā)展。在各種開發(fā)框架之外,打包編譯等技術(shù)也是層出不窮,開發(fā)體驗也是越來越好。例如HMR,讓我們的更新可以即時可見,告別了手動F5的情況。其實現(xiàn)就是監(jiān)聽文件變化自動調(diào)用構(gòu)建過程。下面就關(guān)注下如何實現(xiàn)node監(jiān)聽文件變化。
場景
假定要監(jiān)聽index.js,每當內(nèi)容更改重新編譯。
我們就用簡單的console來標識執(zhí)行編譯。下面就是實現(xiàn)該功能。
node原生API
fs.watchFile
翻下node的文檔就會看到一個滿足我們需求的Apifs.watchFile(畢竟是文件相關(guān)的操作,很大可能就在fs模塊下面了)。
fs.watchFile(filename[, options], listener)
- filename 顯然就是文件名
- options 可選 對象 包含以下兩個屬性
- persistent 文件被監(jiān)聽時進程是否繼續(xù),默認true
- interval 多長時間輪訓一次目標文件,默認5007毫秒
- listener 事件回調(diào) 包含兩個參數(shù)
- current 當前文件stat對象
- prev 之前文件stat對象
看完參數(shù)信息,不知道大家有沒有從其參數(shù)屬性中得到點什么特別的信息。特別是interval選項和listener中的回調(diào)參數(shù)。
監(jiān)控filename對應文件,每當訪問文件時會觸發(fā)回調(diào)。
這里每當訪問文件時會觸發(fā),實際指的是每次切換之后再次進入文件,然后保存之后,無論是否做了修改都會出發(fā)回調(diào)。
另外輪詢事件和文件對象,是不是可以猜測,其實現(xiàn)監(jiān)聽的原理,固定時間輪詢文件狀態(tài),然后將前后的狀態(tài)返回,將判斷交給使用者。
所以node也建議,如果要獲取文件修改,那么需要根據(jù)stat對象的修改時間來進行對比,即比較 curr.mtime 和 prev.mtime。
這樣就有點問題,我們先看下例子,會更清晰一點。
const fs = require('fs') const filePath = './index.js' console.log(`正在監(jiān)聽 ${filePath}`); fs.watchFile(filePath, (cur, prv) => { if (filePath) { // 打印出修改時間 console.log(`cur.mtime>>${cur.mtime.toLocaleString()}`) console.log(`prv.mtime>>${prv.mtime.toLocaleString()}`) // 根據(jù)修改時間判斷做下區(qū)分,以分辨是否更改 if (cur.mtime != prv.mtime){ console.log(`${filePath}文件發(fā)生更新`) } } })
然后測試結(jié)果如下:
// 運行
node watch1.js
// 1、訪問index.js 不做修改,然后保存
// 2、切換文件,再次訪問,不做修改,只報錯
// 3、編輯內(nèi)容,并保存
可以看到1、2兩步,并沒有實際修改內(nèi)容,然而我們并沒有辦法區(qū)分。只要你是切換之后再保存,修改時間戳mtime就發(fā)生變化。
另外響應時間真的很慢,畢竟是輪詢。
對于這些問題,其實官網(wǎng)也給了一句話:
Using fs.watch() is more efficient than fs.watchFile and fs.unwatchFile. fs.watch should be used instead of fs.watchFile and fs.unwatchFile when possible.
能用fs.watch的情況就不要用watchFile了。一是效率,二是不能準確獲知修改狀態(tài) 三是只能監(jiān)聽單獨文件
對于實際開發(fā)過程中,顯然我們想要關(guān)注的是源文件夾的變動。
fs.watch
首先用法如下:
fs.watch(filename[, options][, listener])
跟fs.watchFile比較類似。
- filename 顯然就是文件名
- options 可選 對象或者字符串 包含以下三個屬性
- persistent 文件被監(jiān)聽時進程是否繼續(xù),默認true
- recursive 是否監(jiān)控所有子目錄,默認false 即當前目錄,true為所有子目錄。
- encoding 指定傳遞給回調(diào)事件的文件名稱,默認utf8
- listener 事件回調(diào) 包含兩個參數(shù)
- eventType 事件類型,rename 或者 change
- filename 當前變更文件的文件名
options如果是字符串,指的是encoding。
監(jiān)聽filename對應的文件或者文件夾(recursive參數(shù)也體現(xiàn)出來這一特性),返回一個fs.FSWatcher對象。
該功能的實現(xiàn)依賴于底層操作系統(tǒng)的對于文件更改的通知。 所以就存在一個問題,可能不同平臺的實現(xiàn)不太相同。
如下示例1:
const fs = require('fs') const filePath = './' console.log(`正在監(jiān)聽 ${filePath}`); fs.watch(filePath,(event,filename)=>{ if (filename){ console.log(`${filename}文件發(fā)生更新`) } })
一個比較明顯的優(yōu)勢就體現(xiàn)出來了:響應比較及時,相比于輪詢,效率肯定更高。
不過這樣修改并保存的時候回發(fā)現(xiàn)同樣有點問題。
直接保存,顯示兩次更新
修改文件之后,同樣顯示兩次更新(mac系統(tǒng)上是兩次,其他系統(tǒng)可能有所差別)
這樣可能是于操作系統(tǒng)對文件修改的事件支持有關(guān),在保存的時候出發(fā)了不止一次。
下面聚焦于回調(diào)事件的參數(shù),event對應事件類型,是否可以判斷事件類型為change呢,才執(zhí)行呢,忽略空保存。
const fs = require('fs') const filePath = './' console.log(`正在監(jiān)聽 ${filePath}`); fs.watch(filePath,(event,filename)=>{ console.log(`event類型${event}`) if (filename && event == 'change') { console.log(`${filename}文件發(fā)生更新`) } })
不過實際上,空的保存event也是change,另外不同平臺event的實現(xiàn)可能也有所不同。這種方式要pass掉。
校驗變更時間
顯然從上面的例子可以看到,變更時間依然不可控。因為每次保存,node對應stat對象依然會修改。
對比文件內(nèi)容
只能選擇這種方式來判斷是否是否更新。例如md5:
const fs = require('fs'), md5 = require('md5'); const filePath = './' let preveMd5 = null console.log(`正在監(jiān)聽 ${filePath}`); fs.watch(filePath,(event,filename)=>{ var currentMd5 = md5(fs.readFileSync(filePath + filename)) if (currentMd5 == preveMd5) { return } preveMd5 = currentMd5 console.log(`${filePath}文件發(fā)生更新`) })
先保存當前文件md5值,每次文件變化時(即保存操作響應之后),每次都獲取文件的md5然后進行對比,看是否發(fā)生變化。
不過這樣可以看到,當初次保存時,都會執(zhí)行一次,因為初始值為null的緣故。這樣可以加個兼容,根據(jù)是否第一次保存來判斷好了。
優(yōu)化
對于不同的操作系統(tǒng),可能保存時觸發(fā)的回調(diào)不止一個(mac上到?jīng)]出現(xiàn))。為了避免這種實時響應對應的頻繁觸發(fā),可以引入debounce函數(shù)來保證性能。
const fs = require('fs'), md5 = require('md5'); let preveMd5 = null, fsWait = false const filePath = './' console.log(`正在監(jiān)聽 ${filePath}`); fs.watch(filePath,(event,filename)=>{ if (filename){ if (fsWait) return; fsWait = setTimeout(() => { fsWait = false; }, 100) var currentMd5 = md5(fs.readFileSync(filePath + filename)) if (currentMd5 == preveMd5){ return } preveMd5 = currentMd5 console.log(`${filePath}文件發(fā)生更新`) } })
結(jié)束語
到這里,node監(jiān)聽文件的實現(xiàn)就結(jié)束了。做個學習筆記,來做個參考記錄。實現(xiàn)起來并不難,但是要實際應用的話需要考慮的方面就比較多了。還是推薦開源框架node-watch、chokidar等,各方面實現(xiàn)的都比較完善。
好了,以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對腳本之家的支持。
參考文章
相關(guān)文章
node.js中使用node-schedule實現(xiàn)定時任務實例
這篇文章主要介紹了node.js中使用node-schedule實現(xiàn)定時任務實例,包括安裝方法和4種使用例子,需要的朋友可以參考下2014-06-06使用node-media-server搭建一個簡易的流媒體服務器
這篇文章主要介紹了使用node-media-server搭建一個簡易的流媒體服務器,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-01-01Express + Session 實現(xiàn)登錄驗證功能
本文主要介紹在 Express 框架中,如何使用 Session 來實現(xiàn)用戶登錄身份驗證。對express session實現(xiàn)登錄驗證相關(guān)知識,感興趣的朋友一起看看吧2017-09-09使用node.js半年來總結(jié)的 10 條經(jīng)驗
從3月初來到帝都某創(chuàng)業(yè)公司的服務器團隊實習,到現(xiàn)在已接近半年的時間。PS: 已轉(zhuǎn)正,服務器端用的 Node。2014-08-08Node.js如何使用Diffie-Hellman密鑰交換算法詳解
Deffie-Hellman(簡稱 DH) 密鑰交換是最早的密鑰交換算法之一,它使得通信的雙方能在非安全的信道中安全的交換密鑰,用于加密后續(xù)的通信消息。下面這篇文章主要給大家介紹了關(guān)于Node.js如何使用DiffieHellman密鑰交換算法的相關(guān)資料,需要的朋友可以參考下。2017-09-09Node.js中Path 模塊的介紹和使用示例小結(jié)
Node.js path 模塊提供了一些用于處理文件路徑的小工具,下面通過本文給大家介紹Node.js中Path 模塊的介紹和使用示例小結(jié),感興趣的朋友跟隨小編一起看看吧2024-05-05node.js連接MongoDB數(shù)據(jù)庫的2種方法教程
這幾天一直在學習mongdb的基礎(chǔ)知識,跟著網(wǎng)上大神的腳步(代碼)去模擬連接mongodb數(shù)據(jù)庫,下面這篇文章就給大家總結(jié)介紹了node.js連接MongoDB數(shù)據(jù)庫的2種方法教程,文中介紹的非常詳細,需要的朋友可以參考借鑒,下面來一起看看吧。2017-05-05