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 相关文章推荐
大家未必知道的Js技巧收藏
Apr 07 Javascript
javascript 硬盘序列号+其它硬件信息
Dec 23 Javascript
让IE8支持DOM 2(不用框架!)
Dec 31 Javascript
JavaScript中for..in循环陷阱介绍
Nov 12 Javascript
javascript检测是否联网的实现代码
Sep 28 Javascript
jQuery插件实现表格隔行变色及鼠标滑过高亮显示效果代码
Feb 25 Javascript
浅谈JS之iframe中的窗口
Sep 13 Javascript
详解网站中图片日常使用以及优化手法
Jan 09 Javascript
jQuery实现限制文本框的输入长度
Jan 11 Javascript
详解原生js实现offset方法
Jun 15 Javascript
Angularjs 手写日历的实现代码(不用插件)
Oct 18 Javascript
基于JavaScript 性能优化技巧心得(分享)
Dec 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作的文本留言本的例子(六)
2006/10/09 PHP
PHP时间戳 strtotime()使用方法和技巧
2013/10/29 PHP
ThinkPHP的URL重写问题
2014/06/22 PHP
php数组键值用法实例分析
2015/02/27 PHP
PHP中字符串长度的截取用法示例
2017/01/12 PHP
JavaScript 基础知识 被自己遗忘的
2009/10/15 Javascript
JS在TextArea光标位置插入文字并实现移动光标到文字末尾
2013/06/21 Javascript
javascript中with()方法的语法格式及使用
2014/08/04 Javascript
node.js中的http.response.write方法使用说明
2014/12/14 Javascript
javascript定时器完整实例
2015/02/10 Javascript
jQuery横向擦除焦点图特效代码分享
2015/09/06 Javascript
JS中对数组元素进行增删改移的方法总结
2016/12/15 Javascript
[原创]js实现保存文本框内容为本地文件兼容IE,chrome,火狐浏览器
2018/02/14 Javascript
详解如何给React-Router添加路由页面切换时的过渡动画
2019/04/25 Javascript
vue中利用Promise封装jsonp并调取数据
2019/06/18 Javascript
在Vue环境下利用worker运行interval计时器的步骤
2019/08/01 Javascript
原生JS无缝滑动轮播图
2019/10/22 Javascript
python中的__slots__使用示例
2015/02/26 Python
使用Python标准库中的wave模块绘制乐谱的简单教程
2015/03/30 Python
Python函数可变参数定义及其参数传递方式实例详解
2015/05/25 Python
详解如何利用Cython为Python代码加速
2018/01/27 Python
Python中矩阵创建和矩阵运算方法
2018/08/04 Python
python Flask 装饰器顺序问题解决
2018/08/08 Python
python3 实现对图片进行局部切割的方法
2018/12/05 Python
python监控nginx端口和进程状态
2019/09/06 Python
python cv2在验证码识别中应用实例解析
2019/12/25 Python
Jupyter notebook运行Spark+Scala教程
2020/04/10 Python
人力资源行政经理自我评价
2013/10/23 职场文书
优秀员工表扬信
2014/01/17 职场文书
写求职信有哪些注意事项
2014/05/08 职场文书
党员廉洁自律承诺书
2014/05/26 职场文书
淘宝店策划方案
2014/06/07 职场文书
演讲比赛的活动方案
2014/08/28 职场文书
大学三好学生主要事迹范文
2015/11/03 职场文书
js Proxy的原理详解
2021/05/25 Javascript
Python List remove()实例用法详解
2021/08/02 Python