详解nodejs 文本操作模块-fs模块(五)


Posted in NodeJs onDecember 23, 2016

fs模块是一个比较庞大的模块,在前面也介绍了该模块中最核心的一点东西,虽然核心的这点东西,在整个fs模块中占据的比例比较小,但是如果只是我们平常使用的话,基本已经够用了,其他的一些方法,属于能力提升时需要学习的的内容了,所以在后面就不再继续了,本篇属于fs模块中的最后一篇,也不是把fs模块中的其他API都给一一列举出来,这里再说最后一个我看来很重要的方法,监听文件或者目录的的方法watchFile。

概总

这里之所以在最后把这个watchFile方法写入到这里,是因为在前端的一个流行的构建工具grunt中,有一个grunt-contrib-watch模块,可以用于监听整个项目中,文件是否有变化,不知道有没有人去看过该部分的源码,是如何实现这个模块的呢?(我是还没有去看过,基础学习完成之后,再去研究下)

所以,这里提前看下,fs模块中的watchFile是如何实现的,等以后去看grunt中的watch模块时,就可以更得心应手了,所以,想法和我相同的朋友们,就继续看下去吧。。

fs.watchFile方法

该方法是用于监听指定文件的一个方法,其使用方法为

fs.watchFile(filename,[option],listener);

其中:

1:filename:必须,需要被监听的文件的完整的路径以及文件名

2:option:可选,option支持两个参数,persistent属性和interval属性:

  • interval属性用于指定每隔多少毫秒监听一次文件的是否发生了改变,以及发生了什么改变,默认为5007(毫秒)
  • persistent属性,用于指定了,当指定了被监视的文件后,是否停止当前正在运行的应用程序,默认为true

3:listener:必须,被监听文件发生改变时调用的回调函数

回调函数传入两个参数callback(curr,prev),它们都是fs.Stats的实例,关于该实例的详细介绍,请参考前篇文章,curr表示修改之后的的信息对象,prev表示本次修改之前的信息对象。

下面看下,一个示例:

var fs = require("fs"); 
 
fs.watchFile("./message.txt",function(curr,prev){ 
  if(Date.parse(prev.ctime) == 0){ 
    console.log("message.txt被创建"); 
  }else if(Date.parse(curr.ctime) == 0){ 
    console.log("message.txt被删除"); 
  }else if(Date.parse(prev.mtime) != Date.parse(curr.mtime)){ 
    console.log("message.txt被修改"); 
  } 
});

运行上述代码,然后在与你.js的文件的同目录下,进行操作,创建message.txt,修改,删除等操作,来查看控制台的显示。这只是一个简单的演示,如果需要其他的数据,那么就可以查看curr和prev中,能携带的数据,然后根据不同的数据,完成不同的操作。也就自己实现一些插件的功能。

当然,也可以通过设置option的属性值,使用不同的配置来监听对应的文件,这里关于配置新的示例,就不再占用篇幅了,有兴趣的可以自己测试一下。

watchFile的源码实现

看完了示例,接下来就是源码了,只有了解了最根本的源码实现,才能更好更高效的使用对应的API,请认真看源码中的注释:

// Stat Change Watchers 
// StatWatcher构造函数定义 
function StatWatcher() { 
  //把EventEmitter内部的实例化属性添加到this对象上去。 
  //而EventEmitter的原型链属性和方法,不会被添加到this对象 
  //所以,基本上,也就是把EventEmitter实例中的domain,_events,_maxListeners这三个属性 
  //添加到了this对象上去了。 
  EventEmitter.call(this); 
 
  //把this缓存到self变量中,便于下面的闭包回调使用该创建闭包时的this对象 
  var self = this; 
 
  //调用C++实现的StatWatcher构造函数,并把返回的对象,赋值到this对象的_handle属性上 
  this._handle = new binding.StatWatcher(); 
 
  // uv_fs_poll is a little more powerful than ev_stat but we curb it for 
  // the sake of backwards compatibility 
  var oldStatus = -1; 
 
  //当C++中实现的StatWatcher实例化后的对象,定义它的onchange事件。 
  // 我测试过new binding.StatWatcher();实例化之后,是没有onchange属性的 
  // 所以,这里应该是属于直接定义改属性的,那么定义之后,在nodejs的C++代码实现中 
  // 是如何判断这个属性存在,然后在符合一定的条件下,又去执行这个属性的呢? 
  // 这是我疑惑的地方,这个需要当学习到更多这方面的知识后,再去了解一下。 
  // 经过我的测试,这个属性,是在实例的start执行时需要的,如果没有定义该属性 
  // 那么,在使用start方法,开始监听事件时,会被抛出异常的 
  // 抛出异常,是因为你监听的文件,当前不存在~~ 
  // 如果监听的文件,当前已经存在,则不会执行onchange的回调 
  this._handle.onchange = function(current, previous, newStatus) { 
    // 当实例被话之后,当被监听的文件,被更改时,都会触发该属性的回调函数 
    // 并且传入三个参数 
 
    // 这里的三个判断,当前不知道为什么会在这个时候,不执行~~ 
    if (oldStatus === -1 && 
      newStatus === -1 && 
      current.nlink === previous.nlink) return; 
 
    oldStatus = newStatus; 
 
    // 触发self对象的中的change事件,并且把current和previous对象, 
    // 传入到change事件的回调函数 
    // 在本构造函数内部,是没有继承EventEmitter构造函数原型链中的方法的 
    // 但是这里,却使用了原型链中的emit方法。why? 
    self.emit('change', current, previous); 
  }; 
 
  this._handle.onstop = function() { 
    self.emit('stop'); 
  }; 
} 
 
// 把EventEmitter原型链的属性和方法,扩展到StatWatcher对象的原型链中 
// 更确切的说明就是,StatWatcher.prototype = EventEmitter.prototype; 
util.inherits(StatWatcher, EventEmitter); 
 
// 在StatWatcher重新定义来原型链之后,再执行其他的扩展,以防止原型链断链的情况 
StatWatcher.prototype.start = function(filename, persistent, interval) { 
  nullCheck(filename); 
  this._handle.start(pathModule._makeLong(filename), persistent, interval); 
}; 
 
StatWatcher.prototype.stop = function() { 
  this._handle.stop(); 
}; 
 
//缓存Watcher的一个对象 
var statWatchers = {}; 
function inStatWatchers(filename) { 
  //判断filename是否在statWatchers中,如果是则返回缓存的实例 
  return Object.prototype.hasOwnProperty.call(statWatchers, filename) && 
  statWatchers[filename]; 
} 
 
fs.watchFile = function(filename) { 
  //判断fileName是否合法 
  //如果不合法,则抛出一个异常然后停止执行 
  nullCheck(filename); 
 
  //调用path模块的方法,返回文件的绝对路径 
  filename = pathModule.resolve(filename); 
  var stat; 
  var listener; 
 
  //默认的配置信息,这里也说明来下,为何监听间隔为5007ms, 
  //只是,我表示,我是没有看懂下面的英文注释要说啥的 
  var options = { 
    // Poll interval in milliseconds. 5007 is what libev used to use. It's 
    // a little on the slow side but let's stick with it for now to keep 
    // behavioral changes to a minimum. 
    interval: 5007, 
    persistent: true 
  }; 
 
  //对参数进行判断,判断是否有自定义的option,如果有,使用自定义的 
  //没有定义的,使用默认值 
  //回调函数赋值 
  if (util.isObject(arguments[1])) { 
    options = util._extend(options, arguments[1]); 
    listener = arguments[2]; 
  } else { 
    listener = arguments[1]; 
  } 
 
  //回调函数是必须的,如果没有回调函数,则直接抛出一个异常,并停止运行 
  if (!listener) { 
    throw new Error('watchFile requires a listener function'); 
  } 
 
  //看完上面的inStatWatchers的源码之后,觉得这里是否可以再次优化一次? 
  //stat =inStatWatchers(filename); 
  //if(!stat){ 
    //stat = statWatchers[filename] = new StatWatcher(); 
    //stat.start(filename,options.persistent,options.interval); 
  //} 
  //这样的话,就可以节省一次对象的查找和取值了 
 
  //判断该文件,是否已经创建了StatWatcher实例 
  if (inStatWatchers(filename)) { 
    //如果之前已经创建过了,则使用之前创建过的StatWatcher实例 
    stat = statWatchers[filename]; 
  } else { 
    //如果没有,则 重新创建一个,并把创建的示例,保存下来 
    //用于接下来的,玩意又对该文件再次添加watchFile时,使用 
    stat = statWatchers[filename] = new StatWatcher(); 
    //并且,对实例执行start的方法,应该是启动该实例不 
    stat.start(filename, options.persistent, options.interval); 
  } 
 
  //对该实例,监听change事件,并设置回调函数 
  stat.addListener('change', listener); 
 
  return stat; 
};

注:在前面的源码解释中,出现了几个概念,我认为是有必要在这里补充一下的,因为如果对这几个概念的了解不深刻,那么久可能不理解为什么会这么写,或者意识不到,必须这么写才能保证代码正确的执行的。

1:只继承实例内部属性和方法,涉及代码:EventEmitter.call(this);

2:原型链的断链概念,涉及代码:util.inherits(StatWatcher, EventEmitter);

上述两个概念的一些相关内容,请参考:对象继承的方法小结,原型链断链的原因,浅析赋值表达式?JS基础核心之一。

3:nodejs中的event模块,也就是EventEmitter构造函数的相关信息:nodejs核心模块?events。

4:util模块,也就是util.inherits所在的模块,nodejs核心模块?util。
关于watchFile的源码,到这也就结束了,接下来,看unwatchFile的方法,用于解除对某个文件的监听。

fs.unwatchFile方法

该方法是用于解除一个文件的监听事件,其使用方法为

fs.unwatchFile(filename,[listener]);

其中:

1:filename:必须,需要被监听的文件的完整路径以及文件名

3:listener:可选,被监听文件发生改变时调用的回调函数

回调函数传入两个参数callback(curr,prev),它们都是fs.Stats的实例,关于该实例的详细介绍,请参考前篇文章,curr表示修改之后的的信息对象,prev表示本次修改之前的信息对象。

这里不在给出示例了,其实unwatchFile就相当于我们常用的off事件(jQuery),所以,这里也有个相同的问题,那就是,如果您不指定listenter的话,那么会把之前绑定的所有的watchFile的回调函数,都去除掉的,再者,匿名函数,是无法单独解除绑定的,所以请注意。

fs.unwatchFile源码

unwatchFile的实现原理是比较简单的,所以,这里就不过多的说明,细节部分,请查看源码中的注释。

fs.unwatchFile = function(filename, listener) { 
  //判断filename是否合法,不合法,则抛出异常 
  nullCheck(filename); 
 
  // 把filename转换成绝对地址 
  filename = pathModule.resolve(filename); 
 
  // 判断需要解除绑定文件,是否有缓存StatWatchers的实例, 
  // 如果没有,则表示,没有绑定过监听事件,则不需要去解除绑定 
  if (!inStatWatchers(filename)) return; 
 
  var stat = statWatchers[filename]; 
  // 依然觉得,上两行代码,是可以优化一下的,如下: 
  // var stat = inStatWatchers(filename); 
  //if(!stat){return "";} 
  //依然是两行代码,可是却少了一次作用链的查找,和遍历对象的取值 
 
  // 判断,第二个参数,是否为一个function,如果是,则移除该function的回调 
  // 否则,移除该文件所有的监听回调函数 
  if (util.isFunction(listener)) { 
    stat.removeListener('change', listener); 
  } else { 
    stat.removeAllListeners('change'); 
  } 
 
  // 判断该文件,是否还有监听的回调函数,如果没有了,则清除 
  // listenerCount方法,是继承自EventEmitter的原型链中的方法ßßß 
  if (EventEmitter.listenerCount(stat, 'change') === 0) { 
    stat.stop(); 
    statWatchers[filename] = undefined; 
  } 
};

看到这里,想必您也会想到一个问题吧,如果我要监听的文件很多,不是要给所有的文件都绑定这个事件,而只要其中一个文件出现了问题,就会导致整个程序崩溃,这样的话,维护成本也太高了吧?

所以说呢,Nodejs的开发者们,也不会做这么为难自己的事情不是,它们提供了一个监听目录的方法,即fs.watch方法。接下来,就看下这个惊人的watch的方法,是如何使用以及实现的吧。

fs.watch方法

该方法是用于监听指定文件或者目录是否修改的方法,其使用方法为:

fs.watch(filename,[option],listener);

其中:

1:filename:必须,需要被监听的文件的完整路径的目录或者文件名

2:option:可选,option支持两个参数,persistent属性和recursive属性:

  • recursive属性用于没有找到它具体完成的是哪个功能,是在源码中看到有设置该属性的,默认值为false,在中文文档:Node.js API 中文版,也没有找到对应的解释,估计只有去翻看C++的源码,才能了解到这些了吧。
  • persistent属性,用于指定了,当指定了被监视的文件后,是否停止当前正在运行的应用程序,默认为true

3:listener:必须,被监听文件发生改变时调用的回调函数

回调函数传入两个参数callback(event,filename),其中event取值为“rename”(目录下有文件被重命名)和“change”(目录下有文件内容被更改)。

看下一个示例:

var fs = require("fs"); 
 
var f = fs.watch("./",function(event,filename){ 
  console.log("event="+event); 
  console.log("filename="+filename); 
});

这个示例,不知道您是否会运行一下,我这里测试本地的,发现一个问题,就是不管在什么时候,event的值都是rename,根本就没有change的时候,这也可以说明一个问题,确实如Node.js API 中文版中所说的一样,这几种方法,确实是不稳定的,所以,还是谨慎使用,当然,我们还是要看下源码中的实现的,因为这是在学习~

fs.watch源码

watch方法的源码中的逻辑,和watchFile方法的源码逻辑基本相同,只是继承的构造函数不同而已。下面就看下源码的实现吧。

// watch方法使用到的构造函数 
function FSWatcher() { 
  // 继承事件模块中的实例内部属性和方法 
  EventEmitter.call(this); 
 
  // 缓存this对象,在闭包的回调函数中使用 
  var self = this; 
 
  // 获取C++中实现的FSEvent构造函数, 
  // 该获取是否应该放到函数外部去,优化代码 
  // 就像在StatWatcher构造函数内部的binding.StatWatcher();方法 
  var FSEvent = process.binding('fs_event_wrap').FSEvent; 
  this._handle = new FSEvent(); 
  this._handle.owner = this; 
 
  //绑定回调,onchange事件 
  this._handle.onchange = function(status, event, filename) { 
    // event变了好像是有问题的,我本地测试,event的值一直是“rename” 
    // 根据status的值,作出不同的处理 
    if (status < 0) { 
      self._handle.close(); 
      self.emit('error', errnoException(status, 'watch')); 
    } else { 
      self.emit('change', event, filename); 
    } 
  }; 
} 
 
// FSWatcher的原型链继承EventEmitter构造函数的原型链的属性和方法 
util.inherits(FSWatcher, EventEmitter); 
 
// start方法,当初始化构造函数之后,要执行该方法,才能开始监听filename指向的目录或者文件 
FSWatcher.prototype.start = function(filename, persistent, recursive) { 
  nullCheck(filename); 
 
  //调用C++中实现的方法,开始执行监听动作 
  var err = this._handle.start(pathModule._makeLong(filename), 
    persistent, 
    recursive); 
 
  // 如果监听时,没有能正确的执行,则关闭该实例,并且抛出一个异常 
  if (err) { 
    this._handle.close(); 
    throw errnoException(err, 'watch'); 
  } 
}; 
 
// 关闭监听 
FSWatcher.prototype.close = function() { 
  this._handle.close(); 
}; 
 
fs.watch = function(filename) { 
 
  // 判断filename是否合法 
  nullCheck(filename); 
  var watcher; 
  var options; 
  var listener; 
 
  // 判断是否有参数 
  // 初始化参数和回调函数 
  if (util.isObject(arguments[1])) { 
    options = arguments[1]; 
    listener = arguments[2]; 
  } else { 
    options = {}; 
    listener = arguments[1]; 
  } 
 
  // 给persistent和recursive设置默认值,这里我依然觉得是可以优化的。 
  // 既然在后面只是使用persistent和recursive,那么为何不定义着凉变量 
  // var persistent = options.persistent, 
  //   recursive = options.recursive; 
  // if(util.isUndefined(persistent)) persistent = true; 
  // if(util.isUndefined(recursive)) recursive = false; 
  // ... 
  // watcher.start(filename,persistent,recursive); 
  // 我会认为,去一个对象中查找变量,比直接查找到一个变量,要花费多一点点的时间。 
 
  if (util.isUndefined(options.persistent)) options.persistent = true; 
  if (util.isUndefined(options.recursive)) options.recursive = false; 
 
  // 创建FSWatcher的实例,并执行实例中的start方法,开始监听filename的更新事件 
  watcher = new FSWatcher(); 
  watcher.start(filename, options.persistent, options.recursive); 
 
  // 如果有回调函数,则添加一个绑定事件,并设置它的回调函数 
  if (listener) { 
    watcher.addListener('change', listener); 
  } 
 
  // 返回该实例 
  return watcher; 
};

到这里,基本上,监听目录和文件相关的API就说完来,这里继续一些补充信息,是之前并没有说到的,首先,watchFile方法的关闭,以及再次绑定事件等,看下面的示例吧。

var fs = require("fs"); 
 
var f = fs.watch("./",function(event,filename){ 
  console.log("event="+event); 
  console.log("filename="+filename); 
}); 
 
f.on("change",function(event,filename){ 
  console.log("change12"); 
  f.close(); 
  //关闭 
}); 
 
 
var ff = fs.watchFile("./message.txt",function(curr,prev){ 
  console.log("change21"); 
}); 
 
ff.on("change",function(){ 
  console.log("change22"); 
  ff.stop(); 
  //关闭 
});

因为在watch和watchFile使用时,都是返回的它们使用的构造函数的实例,所以,我们自己也可以根据这个实例,进行一些扩展,而且,这个实例,是包含整个event模块的所有的方法的,所以,可以直接绑定一些自定义事件,甚至直接触发一些自定义事件,比如,再看下面的代码:

var fs = require("fs"); 
 
var f = fs.watch("./",function(event,filename){ 
console.log("change11"); 
}); 
 
 
var ff = fs.watchFile("./message.txt",function(curr,prev){ 
console.log("change21"); 
}); 
 
f.emit("change"); 
ff.emit("change");

基本上,这个实例,就是一个EventEmitter的实例。OK,这个说到这里,就基本上结束了。

总结

注:本文将到的watchFile,watch等方法,都是属于不稳定的方法,在有些平台下,或者一些系统的设置下,可能出现获取不到准确值的情况,所以谨慎使用,这个如果可以,应该去花点时间,研究下grunt中,是如何监听文件变化的,进而补充本文中没有实现的一些功能(不稳定)。

关于Nodejs的fs模块的内容,到这里就结束了,接下来就是TCP和UDP的数据通信相关的内容了,也是创建服务器的相关内容,本模块的周期过长,中间工作有点招架不住,一直没有调节好,一直处于比较疲惫的状态下,中间的间隔竟然达到三个月之久,希望以后不会再这样来。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

NodeJs 相关文章推荐
nodejs之请求路由概述
Jul 05 NodeJs
基于promise.js实现nodejs的promises库
Jul 06 NodeJs
NodeJS制作爬虫全过程
Dec 22 NodeJs
NodeJS Web应用监听sock文件实例
Feb 18 NodeJs
Nodejs实战心得之eventproxy模块控制并发
Oct 27 NodeJs
Nodejs实现短信验证码功能
Feb 09 NodeJs
详解nodeJS中读写文件方法的区别
Mar 06 NodeJs
nodejs使用express获取get和post传值及session验证的方法
Nov 09 NodeJs
解决Nodejs全局安装模块后找不到命令的问题
May 15 NodeJs
基于Nodejs的Tcp封包和解包的理解
Sep 19 NodeJs
Nodejs中获取当前函数被调用的行数及文件名详解
Dec 12 NodeJs
Nodejs技巧之Exceljs表格操作用法示例
Nov 06 NodeJs
详解Nodejs的timers模块
Dec 22 #NodeJs
详解nodejs 文本操作模块-fs模块(四)
Dec 22 #NodeJs
详解nodejs 文本操作模块-fs模块(三)
Dec 22 #NodeJs
详解nodejs 文本操作模块-fs模块(一)
Dec 22 #NodeJs
详解nodejs 文本操作模块-fs模块(二)
Dec 22 #NodeJs
学习 NodeJS 第八天:Socket 通讯实例
Dec 21 #NodeJs
详解Nodejs基于mongoose模块的增删改查的操作
Dec 21 #NodeJs
You might like
php下判断数组中是否存在相同的值array_unique
2008/03/25 PHP
mac下安装nginx和php
2013/11/04 PHP
php字符集转换
2017/01/23 PHP
游戏人文件夹程序 ver 4.03
2006/07/14 Javascript
通过JAVASCRIPT读取ASP设定的COOKIE
2007/02/15 Javascript
基础的prototype.js常用函数及其用法
2007/03/10 Javascript
div移动 输入框不能输入的问题
2009/11/19 Javascript
js实现跟随鼠标移动且带关闭功能的图片广告实例
2015/02/26 Javascript
JavaScript中的函数声明和函数表达式区别浅析
2015/03/27 Javascript
javascript使用avalon绑定实现checkbox全选
2015/05/06 Javascript
js 递归和定时器的实例解析
2017/02/03 Javascript
Vue cli+mui 区域滚动的实例代码
2018/01/25 Javascript
vue中$set的使用(结合在实际应用中遇到的坑)
2018/07/10 Javascript
angularjs http与后台交互的实现示例
2018/12/21 Javascript
layui 数据表格 点击分页按钮 监听事件的实例
2019/09/02 Javascript
jQuery 添加元素和删除元素的方法
2020/07/15 jQuery
在vue中使用cookie记住用户上次选择的实例(本次例子中为下拉框)
2020/09/11 Javascript
Python实现字典按照value进行排序的方法分析
2017/12/23 Python
Python爬豆瓣电影实例
2018/02/23 Python
Python pymongo模块常用操作分析
2018/09/01 Python
Python中修改字符串的四种方法
2018/11/02 Python
Python3几个常见问题的处理方法
2019/02/26 Python
python 使用装饰器并记录log的示例代码
2019/07/12 Python
python多线程同步实例教程
2019/08/11 Python
Python中pyecharts安装及安装失败的解决方法
2020/02/18 Python
HTML5如何使用SVG的方法示例
2019/01/11 HTML / CSS
域名注册、建站工具、网页主机、SSL证书:Dynadot
2017/01/06 全球购物
CLR与IL分别是什么含义
2016/08/23 面试题
2014年高三毕业生自我评价
2014/01/11 职场文书
会计自我鉴定
2014/02/04 职场文书
企业法人授权委托书范本
2014/09/23 职场文书
逃课检讨书怎么写
2015/01/01 职场文书
小学生一年级(书信作文)
2019/08/13 职场文书
Python+Tkinter打造签名设计工具
2022/04/01 Python
Python函数对象与闭包函数
2022/04/13 Python
html,css,javascript是怎样变成页面的
2023/05/07 HTML / CSS