详解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框架Express的模板视图机制分析
Jul 19 NodeJs
利用NodeJS的子进程(child_process)调用系统命令的方法分享
Jun 05 NodeJs
nodejs npm install全局安装和本地安装的区别
Jun 05 NodeJs
nodejs利用ajax实现网页无刷新上传图片实例代码
Jun 06 NodeJs
NodeJS自定义模块写法(详解)
Jun 27 NodeJs
nodejs对express中next函数的一些理解
Sep 08 NodeJs
windows系统下更新nodejs版本的方案
Nov 24 NodeJs
详解Nodejs mongoose
Jun 10 NodeJs
nodejs 十六进制字符串型数据与btye型数据相互转换
Jul 30 NodeJs
nodejs中express入门和基础知识点学习
Sep 13 NodeJs
5分钟教你用nodeJS手写一个mock数据服务器的方法
Sep 10 NodeJs
nodejs处理tcp连接的核心流程
Feb 26 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 图像函数大举例(非原创)
2009/06/20 PHP
PHP删除数组中的特定元素的代码
2012/06/28 PHP
PHP常用数组函数介绍
2014/07/28 PHP
PHP正则表达式入门教程(推荐)
2016/05/18 PHP
thinkPHP批量删除的实现方法分析
2016/11/09 PHP
用JQuery调用Session的实现代码
2010/10/29 Javascript
jQuery根据纬度经度查看地图处理程序
2013/05/08 Javascript
返回顶部按钮响应滚动且动态显示与隐藏
2014/10/14 Javascript
node.js+Ajax实现获取HTTP服务器返回数据
2014/11/26 Javascript
jquery图形密码实现方法
2015/03/11 Javascript
深入理解AngularJS中的ng-bind-html指令和$sce服务
2016/09/08 Javascript
nodejs利用http模块实现银行卡所属银行查询和骚扰电话验证示例
2016/12/30 NodeJs
JavaScript表单验证实现代码
2017/05/22 Javascript
详解a++和++a的区别
2017/08/30 Javascript
详解小程序缓存插件(mrc)
2018/08/17 Javascript
微信小程序分享功能onShareAppMessage(options)用法分析
2019/04/24 Javascript
深入浅析Vue中mixin和extend的区别和使用场景
2019/08/01 Javascript
解决vue中使用less/sass及使用中遇到无效的问题
2020/10/24 Javascript
JavaScript实现滑块验证解锁
2021/01/07 Javascript
微信小程序 接入腾讯地图的两种写法
2021/01/12 Javascript
django自定义Field实现一个字段存储以逗号分隔的字符串
2014/04/27 Python
python查看模块安装位置的方法
2018/10/16 Python
Python openpyxl 遍历所有sheet 查找特定字符串的方法
2018/12/10 Python
Python面向对象实现一个对象调用另一个对象操作示例
2019/04/08 Python
PyCharm中代码字体大小调整方法
2019/07/29 Python
PyTorch里面的torch.nn.Parameter()详解
2020/01/03 Python
python+opencv实现移动侦测(帧差法)
2020/03/20 Python
VSCode基础使用与VSCode调试python程序入门的图文教程
2020/03/30 Python
Python过滤序列元素的方法
2020/07/31 Python
英国泽西岛植物:Jersey Plants Direct
2019/08/07 全球购物
护士的自我鉴定
2014/02/07 职场文书
统计系教授推荐信
2014/02/28 职场文书
群众路线教育实践活动自我剖析思想汇报
2014/10/04 职场文书
2014年宣传工作总结
2014/11/18 职场文书
ajax请求前端跨域问题原因及解决方案
2021/10/16 Javascript
Python干货实战之八音符酱小游戏全过程详解
2021/10/24 Python