node.js监听文件变化的实现方法


Posted in Javascript onApril 17, 2019

前言

随着前端技术的飞速发展,前端开发也从原始的刀耕火种,向着工程化效率化的方向发展。在各种开发框架之外,打包编译等技术也是层出不穷,开发体验也是越来越好。例如HMR,让我们的更新可以即时可见,告别了手动F5的情况。其实现就是监听文件变化自动调用构建过程。下面就关注下如何实现node监听文件变化。

场景

假定要监听index.js,每当内容更改重新编译。

我们就用简单的console来标识执行编译。下面就是实现该功能。

node原生API

fs.watchFile

翻下node的文档就会看到一个满足我们需求的Apifs.watchFile(毕竟是文件相关的操作,很大可能就在fs模块下面了)。

fs.watchFile(filename[, options], listener)
  • filename 显然就是文件名
  • options 可选 对象 包含以下两个属性
    • persistent 文件被监听时进程是否继续,默认true
    • interval 多长时间轮训一次目标文件,默认5007毫秒
  • listener 事件回调 包含两个参数
    • current 当前文件stat对象
    • prev 之前文件stat对象

看完参数信息,不知道大家有没有从其参数属性中得到点什么特别的信息。特别是interval选项和listener中的回调参数。

监控filename对应文件,每当访问文件时会触发回调。

这里每当访问文件时会触发,实际指的是每次切换之后再次进入文件,然后保存之后,无论是否做了修改都会出发回调。

另外轮询事件和文件对象,是不是可以猜测,其实现监听的原理,固定时间轮询文件状态,然后将前后的状态返回,将判断交给使用者。

所以node也建议,如果要获取文件修改,那么需要根据stat对象的修改时间来进行对比,即比较 curr.mtime 和 prev.mtime。

这样就有点问题,我们先看下例子,会更清晰一点。

const fs = require('fs')
const filePath = './index.js'
console.log(`正在监听 ${filePath}`);
fs.watchFile(filePath, (cur, prv) => {
 if (filePath) {
  // 打印出修改时间
  console.log(`cur.mtime>>${cur.mtime.toLocaleString()}`)
  console.log(`prv.mtime>>${prv.mtime.toLocaleString()}`)
  // 根据修改时间判断做下区分,以分辨是否更改
  if (cur.mtime != prv.mtime){
   console.log(`${filePath}文件发生更新`)
  }
 }
})

然后测试结果如下:

// 运行
node watch1.js
// 1、访问index.js 不做修改,然后保存
// 2、切换文件,再次访问,不做修改,只报错
// 3、编辑内容,并保存

node.js监听文件变化的实现方法

可以看到1、2两步,并没有实际修改内容,然而我们并没有办法区分。只要你是切换之后再保存,修改时间戳mtime就发生变化。

另外响应时间真的很慢,毕竟是轮询。

对于这些问题,其实官网也给了一句话:

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了。一是效率,二是不能准确获知修改状态 三是只能监听单独文件
对于实际开发过程中,显然我们想要关注的是源文件夹的变动。

fs.watch

首先用法如下:

fs.watch(filename[, options][, listener])

跟fs.watchFile比较类似。

  • filename 显然就是文件名
  • options 可选 对象或者字符串 包含以下三个属性
    • persistent 文件被监听时进程是否继续,默认true
    • recursive 是否监控所有子目录,默认false 即当前目录,true为所有子目录。
    • encoding 指定传递给回调事件的文件名称,默认utf8
  • listener 事件回调 包含两个参数
    • eventType 事件类型,rename 或者 change
    • filename 当前变更文件的文件名

options如果是字符串,指的是encoding。

监听filename对应的文件或者文件夹(recursive参数也体现出来这一特性),返回一个fs.FSWatcher对象。

该功能的实现依赖于底层操作系统的对于文件更改的通知。 所以就存在一个问题,可能不同平台的实现不太相同。
如下示例1:

const fs = require('fs')
const filePath = './' 
console.log(`正在监听 ${filePath}`);
fs.watch(filePath,(event,filename)=>{
 if (filename){
  console.log(`${filename}文件发生更新`)
 }
})

一个比较明显的优势就体现出来了:响应比较及时,相比于轮询,效率肯定更高。

不过这样修改并保存的时候回发现同样有点问题。

直接保存,显示两次更新

修改文件之后,同样显示两次更新(mac系统上是两次,其他系统可能有所差别)

node.js监听文件变化的实现方法

这样可能是于操作系统对文件修改的事件支持有关,在保存的时候出发了不止一次。

下面聚焦于回调事件的参数,event对应事件类型,是否可以判断事件类型为change呢,才执行呢,忽略空保存。

const fs = require('fs')
const filePath = './'  
console.log(`正在监听 ${filePath}`);
fs.watch(filePath,(event,filename)=>{
  console.log(`event类型${event}`)
  if (filename && event == 'change') {
    console.log(`${filename}文件发生更新`)
  }
})

不过实际上,空的保存event也是change,另外不同平台event的实现可能也有所不同。这种方式要pass掉。

校验变更时间

显然从上面的例子可以看到,变更时间依然不可控。因为每次保存,node对应stat对象依然会修改。

对比文件内容

只能选择这种方式来判断是否是否更新。例如md5:

const fs = require('fs'),
  md5 = require('md5');
const filePath = './'  
let preveMd5 = null

console.log(`正在监听 ${filePath}`);
fs.watch(filePath,(event,filename)=>{
  var currentMd5 = md5(fs.readFileSync(filePath + filename))
  if (currentMd5 == preveMd5) {
    return
  }
  preveMd5 = currentMd5
  console.log(`${filePath}文件发生更新`)
})

先保存当前文件md5值,每次文件变化时(即保存操作响应之后),每次都获取文件的md5然后进行对比,看是否发生变化。

node.js监听文件变化的实现方法

不过这样可以看到,当初次保存时,都会执行一次,因为初始值为null的缘故。这样可以加个兼容,根据是否第一次保存来判断好了。

优化

对于不同的操作系统,可能保存时触发的回调不止一个(mac上到没出现)。为了避免这种实时响应对应的频繁触发,可以引入debounce函数来保证性能。

const fs = require('fs'),
  md5 = require('md5');
let preveMd5 = null,
  fsWait = false
const filePath = './'  
console.log(`正在监听 ${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}文件发生更新`)
  }
})

结束语

到这里,node监听文件的实现就结束了。做个学习笔记,来做个参考记录。实现起来并不难,但是要实际应用的话需要考虑的方面就比较多了。还是推荐开源框架node-watch、chokidar等,各方面实现的都比较完善。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。

参考文章

  • node文档
  • How to Watch for Files Changes in Node.js
  • Nodejs Monitor File Changes
Javascript 相关文章推荐
JQuery从头学起第一讲
Jul 04 Javascript
浅析jQuery中常用的元素查找方法总结
Jul 04 Javascript
js 时间格式与时间戳的相互转换示例代码
Dec 25 Javascript
js+div实现图片滚动效果代码
Feb 10 Javascript
js数组中如何随机取出一个值
Jun 13 Javascript
jquery插件unobtrusive实现片段式加载
Jun 15 Javascript
JavaScript队列函数和异步执行详解
Jun 19 Javascript
基于iScroll实现下拉刷新和上滑加载效果
Jul 18 Javascript
原生JS实现的双色球功能示例
Feb 02 Javascript
不得不知的ES6小技巧
Jul 28 Javascript
微信二次分享报错invalid signature问题及解决方法
Apr 01 Javascript
Vue实现图片轮播组件思路及实例解析
May 11 Javascript
vue中格式化时间过滤器代码实例
Apr 17 #Javascript
postman自定义函数实现 时间函数的思路详解
Apr 17 #Javascript
vue指令之表单控件绑定v-model v-model与v-bind结合使用
Apr 17 #Javascript
记一次Vue.js混入mixin的使用(分权限管理页面)
Apr 17 #Javascript
详解js获取video任意时间的画面截图
Apr 17 #Javascript
解决vue跨域axios异步通信问题
Apr 17 #Javascript
自定义Vue中的v-module双向绑定的实现
Apr 17 #Javascript
You might like
php去掉字符串的最后一个字符附substr()的用法
2011/03/23 PHP
php中常用的预定义变量小结
2012/05/09 PHP
使用PHP和JavaScript判断请求是否来自微信内浏览器
2015/08/18 PHP
PHP生成唯一ID之SnowFlake算法
2016/12/17 PHP
解决thinkphp5未定义变量会抛出异常,页面错误,请稍后再试的问题
2019/10/16 PHP
javascript demo 基本技巧
2009/12/18 Javascript
使用javascript过滤html的字符串(注释标记法)
2013/07/08 Javascript
jQuery Migrate 1.1.0 Released 注意事项
2014/06/14 Javascript
JQuery.get提交页面不跳转的解决方法
2015/01/13 Javascript
js跨域请求的5中解决方式
2015/07/02 Javascript
javascript实现鼠标移到Image上方时显示文字效果的方法
2015/08/07 Javascript
Bootstrap源码解读媒体对象、列表组和面板(10)
2016/12/26 Javascript
nodejs+express搭建多人聊天室步骤
2018/02/12 NodeJs
微信小程序按钮点击动画效果的实现
2019/09/04 Javascript
解决vue更新路由router-view复用组件内容不刷新的问题
2019/11/04 Javascript
vue集成openlayers加载geojson并实现点击弹窗教程
2020/09/24 Javascript
[05:35]DOTA2英雄梦之声_第13期_拉比克
2014/06/21 DOTA
[18:32]DOTA2 HEROS教学视频教你分分钟做大人-谜团
2014/06/12 DOTA
[42:25]2018DOTA2亚洲邀请赛 4.5 淘汰赛 LGD vs Liquid 第三场
2018/04/06 DOTA
python模块导入的细节详解
2018/12/10 Python
python 实现得到当前时间偏移day天后的日期方法
2018/12/31 Python
python 获取图片分辨率的方法
2019/01/08 Python
python制作抖音代码舞
2019/04/07 Python
python中正则表达式与模式匹配
2019/05/07 Python
python循环定时中断执行某一段程序的实例
2019/06/29 Python
纯css3制作煽动翅膀的蝴蝶的示例
2018/04/23 HTML / CSS
使用postMessage让 iframe自适应高度的方法示例
2019/10/08 HTML / CSS
俄语翻译实习生的自我评价分享
2013/11/06 职场文书
农林环境专业求职信
2014/03/13 职场文书
部门活动策划方案
2014/08/16 职场文书
2014年创卫工作总结
2014/11/24 职场文书
营销计划书
2015/01/17 职场文书
2016党员干部政治学习心得体会
2016/01/23 职场文书
浅谈MySQL之浅入深出页原理
2021/06/23 MySQL
Redis读写分离搭建的完整步骤
2021/09/14 Redis
python编程项目中线上问题排查与解决
2021/11/01 Python