@(node,watcher)

watcher,在如今的前端领域已经数见不鲜了。目前流行的gulp流程工具提供了watcher的选项,是我们在开发过程中不需要手动进行触发构建流程,转而根据文件(目录)内容改变来触发。

深入到watcher实现层,其实是基于node的fs.watch API,但是fs.watch有很多“不确定性”,下文会一一解答。


fs.watch

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

watch API很简单,接受三个参数,并返回一个FSWatcher对象。

filename可以是文件,也可是目录;

options为可选对象,默认为 { persistent: true, recursive: false },其中persistent属性意味着:watcher进程会一直watch该文件(目录),即watcher进程阻塞;recursive属性意味着:如果监听的是目录,则目录下属的目录和文件也会被监听,recursive属性存在兼容性问题,在linux系统下无效,在windows和OSX下正常。

listener为回调函数,接受两个参数,分别为event和filename,其中事件有两种类型,“rename”和“change”,而filename也有兼容性问题,在使用时也要注意兼容性判断。

问题

在上一节中简单介绍了watch API,也简单提到了一些兼容性问题,在此列举出来:

  • recursive属性在linux下失效;
  • watch目录时,回调函数中的filename只在linux和windows下可以获取;
  • node在任何情况下都不确保filename可以获取到

解决方案

轮训

node提供了另一个接口,

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

返回值同为FSWatcher,参数filename可为目录和文件,options默认为

{ persistent: true, interval: 5007 },其中interval则为node轮训该文件的时间间隔,listener接受两个参数,即类行为fs.Stat的curr和prev对象,我们可通过

	curr.mtime == prev.mtime

判断文件是否发生改动。

不管在何种系统设计中,轮训的方式都是兼容性保底方案,只要我们的系统支持fs.watch方法,就不用采用该种方式进行兼容。

那么合适可以采用轮训呢?我认为,大概分两种情况:

  • 需要针对文件的元信息判断是否触发事件
  • 监控的文件所在的操作系统,如果是NFS, SMB等网络文件系统,fs.watch并不提供功能,因此只能使用轮训方式(watch方法是基于文件系统的特性编写的,在linux下基于“inotify”,windows下基于“ReadDirectoryChangesW”)

手动适配

针对非网络文件系统,watch API的兼容性就在于是否递归watch以及OSX下filename获取的问题,因此我们可以通过编码方式解决:

  • 采用默认的options配置,即{ persistent: true, recursive: false },通过walker便利目录,针对单个文件作watcher
  • 针对单个文件做watch,OSX可以获取到filename

通过简单的处理,一个简易的watcher就实现了,配合着EventEmit,就可以通过事件的方式完成watcher任务。

参考代码:

'use strict';

var fs = require('fs');
var path = require('path');
var os = require('os'); var watchList = {};
var timer = {}; var walk = function (dir, callback, filter) {
fs.readdirSync(dir).forEach(function (item) {
var fullname = path.join(dir, item); if (fs.statSync(fullname).isDirectory()){ if (!filter(fullname)){
return;
} watch(fullname, callback, filter);
walk(fullname, callback, filter);
}
});
}; var watch = function (name, callback, filter) { if (watchList[name]) {
watchList[name].close();
} watchList[name] = fs.watch(name, function (event, filename) { if (filename === null) {
return;
} var fullname = path.join(name, filename);
var type;
var fstype; if (!filter(fullname)) {
return;
} // 检查文件、目录是否存在
if (!fs.existsSync(fullname)) { // 如果目录被删除则关闭监视器
if (watchList[fullname]) {
fstype = 'directory';
watchList[fullname].close();
delete watchList[fullname];
} else {
fstype = 'file';
} type = 'delete'; } else { // 文件
if (fs.statSync(fullname).isFile()) { fstype = 'file';
type = event == 'rename' ? 'create' : 'updated'; // 文件夹
} else if (event === 'rename') { fstype = 'directory';
type = 'create'; watch(fullname, callback, filter);
walk(fullname, callback, filter);
} } var eventData = {
type: type,
target: filename,
parent: parent,
fstype: fstype
}; if (/windows/i.test(os.type())) {
// window 下的兼容处理
clearTimeout(timer[fullname]);
timer[fullname] = setTimeout(function() {
callback(eventData);
}, 16); } else {
callback(eventData);
} }); }; /**
* @param {String} 要监听的目录
* @param {Function} 文件、目录改变后的回调函数
* @param {Function} 过滤器(可选)
*/
module.exports = function (dir, callback, filter) { // 排除“.”、“_”开头或者非英文命名的目录
var FILTER_RE = /[^\w\.\-$]/;
filter = filter || function (name) {
return !FILTER_RE.test(name);
}; watch(dir, callback, filter);
walk(dir, callback, filter);
};

node实现watcher的困境的更多相关文章

  1. Zookeeper——Watcher原理详解

    文章目录 引言 正文 一.如何注册监听 二.如何触发监听事件 三.事件类型有哪些 四.Watcher可以被无限次触发么?为什么要这么设计? 五.Watcher实现原理 1. 客服端发送请求 a. 初始 ...

  2. [转载] 跟着实例学习zookeeper 的用法

    原文: http://ifeve.com/zookeeper-curato-framework/ zookeeper 的原生客户端库过于底层, 用户为了使用 zookeeper需要编写大量的代码, 为 ...

  3. zookeeper 学习笔记 (C语言版本)

    1.zookeeper简介 zookeeper是Hadoop的子项目,在大型分布式系统中,zookeeper封装好了一些复杂易出错的服务,提供简单易用的接口,给使用者提供高效稳定的服务.这些服务包括配 ...

  4. Vue2.0源码阅读笔记--双向绑定实现原理

    上一篇 文章 了解了Vue.js的生命周期.这篇分析Observe Data过程,了解Vue.js的双向数据绑定实现原理. 一.实现双向绑定的做法 前端MVVM最令人激动的就是双向绑定机制了,实现双向 ...

  5. JavaScript 实现一个简单的MVVM前端框架(ES6语法)

    前言 随着前端各大框架的崛起,为我们平时的开发带来了相当的便利,我们不能一直停留在应用层面,今天就自己动手实现一个乞丐版的MVVM小框架 完整代码github地址 效果 html代码 <div ...

  6. CuratorFramework使用

    CuratorFrameworkFramework是ZooKeeper Client更高的抽象API 自动连接管理: 1. 当ZooKeeper客户端内部出现异常, 将自动进行重连或重试, 该过程对外 ...

  7. vue中的双向数据绑定详解

    前言 什么是数据双向绑定? vue是一个mvvm框架,即数据双向绑定,即当数据发生变化的时候,视图也就发生变化,当视图发生变化的时候,数据也会跟着同步变化.这也算是vue的精髓之处了.值得注意的是,我 ...

  8. Vue 双向绑定原理

    Vue.js最核心的功能有两个,一是响应式的数据绑定系统,二是组件系统. 一.访问器属性:Object.defineProperty ECMAScript 262v5带来的新东西,FF把它归入为jav ...

  9. vue 之 双向绑定原理

    一.实现双向绑定 详细版: 前端MVVM实现双向数据绑定的做法大致有如下三种: 1.发布者-订阅者模式(backbone.js) 思路:使用自定义的data属性在HTML代码中指明绑定.所有绑定起来的 ...

随机推荐

  1. ABP文档 - 目录

    ABP框架 概览 介绍 多层结构 模块系统 启动配置 多租户 集成OWIN 共同结构 依赖注入 会话 缓存 日志 设置管理 时间 领域层 实体 值对象(新) 仓储 领域服务 工作单元 领域事件(Eve ...

  2. Sublime Text3配置在可交互环境下运行python快捷键

    安装插件 在Sublime Text3下面写代码感觉很不错,但是写Python的时候遇到了一些问题. 用Sublime Text3打开python文件,或者在Sublime Text3下写好pytho ...

  3. 如何在nuget上传自己的包+搭建自己公司的NuGet服务器(新方法)

    运维相关:http://www.cnblogs.com/dunitian/p/4822808.html#iis 先注册一个nuget账号https://www.nuget.org/ 下载并安装一下Nu ...

  4. 一道返回num值的小题目

    题目描述: 实现fizzBuzz函数,参数num与返回值的关系如下: .如果num能同时被3和5整除,返回字符串fizzbuzz .如果num能被3整除,返回字符串fizz .如果num能被5整除,返 ...

  5. jquery屏幕滚动计算事件总结

    获取浏览器显示区域(可视区域)的高度 : $(window).height(); 获取浏览器显示区域(可视区域)的宽度 : $(window).width(); 获取页面的文档高度: $(docume ...

  6. Struts2实现ajax的两种方式

    基于Struts2框架下实现Ajax有两种方式,第一种是原声的方式,另外一种是struts2自带的一个插件. js部分调用方式是一样的: JS代码: function testAjax() { var ...

  7. gulp批量打包文件并提取公共文件

    gulp是前端开发过程中对代码进行构建的工具,是自动化项目的构建利器. browseriyf是模块化打包工具. 一般情况下,Browserify 会把所有的模块打包成单个文件.单个文件在大多数情况下是 ...

  8. 编译器开发系列--Ocelot语言7.中间代码

    Ocelot的中间代码是仿照国外编译器相关图书Modern Compiler Implementation 中所使用的名为Tree 的中间代码设计的.顾名思义,Tree 是一种树形结构,其特征是简单, ...

  9. Android Weekly Notes Issue #236

    Android Weekly Issue #236 December 18th, 2016 Android Weekly Issue #236 本期内容包括: Google的物联网平台Android ...

  10. 分享在Linux下使用OSGi.NET插件框架快速实现一个分布式服务集群的方法

    在这篇文章我分享了如何使用分层与模块化的方法来设计一个分布式服务集群.这个分布式服务集群是基于DynamicProxy.WCF和OSGi.NET插件框架实现的.我将从设计思路.目标和实现三方面来描述. ...