在我硬盘安监控了?纯 JS 监听本地文件的一举一动

💰 点进来就是赚到知识点!本文带你了解如何用 JS 监控本地文件点赞收藏评论更能促进消化吸收!

🚀 想解锁更多 Web 文件系统技能吗?快来订阅专栏「Web 玩转文件操作」!

📣 我是 Jax,在畅游 Web 技术海洋的又一年,我仍然是坚定不移的 JavaScript 迷弟,Web 技术带给我太多乐趣。如果你也和我一样,欢迎关注私聊

开门见 demo

先来玩玩这个 demo ------ 在 Chrome 中监控本地文件夹

在上面的 demo 中,点击按钮选择一个本地文件夹后,无论是在该文件夹中新增、修改还是删除内容,网页端都能够感知到每一步操作的细节,包括操作时间、操作对象是文件还是文件夹、具体执行了什么操作等等。

如果你的感觉是:"哟?有点儿意思!" 那么这篇文章就是专门为你而写的,读下去吧。

本专栏的前几篇文章中,我们已经知道,Web 应用能对本地文件进行各种花式操作,例如选择文件/文件夹、增/删/改/查文件等等。网页好像能伸出长长的手臂,穿过浏览器触摸到了用户的本地文件。但你可能还不知道,网页也能长出千里眼、顺风耳,本地文件有什么风吹草动,都能被网页端监控到。如此灵通的耳目,它的名字就是 File System Observer API(文件系统观察者)。

API 简介

现在想象我们要开发一个 Web 端相册应用,展示用户本地文件夹中的图片。我们希望这个相册能实时响应用户的操作,例如增加/删掉几张图片后,无需用户手动在 Web 端刷新,就能自动更新到最新状态。

如果请你来实现自动刷新,阁下又该如何应对?

经典思路可能会是以短时间间隔轮询文件夹状态,读取并缓存每个文件的 lastModified 时间戳,如果前后两次轮询的时间戳发生了变化,再把前后差异更新到 Web 视图中。这种实现方式能达到效果,但还是有一些缺点,比如不能真正做到即时响应,且会有很大的性能问题等。

其实咱们都知道,最优雅高效的做法是仅在文件被操作时触发更新。原生操作系统如 WIndows 和 MacOS 都有这样的文件监听机制,但显然目前 Web 端还无法享受其便利性。除了在用户端,Node.js 应用也面临这样的问题。开发者苦此久矣。

直到 2023 年 6 月,来自谷歌的贡献者们开始推进一项 W3C 提案 ------ File System Observer(为方便叙述,下文将简称其为 FSO),旨在从浏览器层面向 Web 应用提供跨平台的文件监听支持。如果这项提案能够顺利进入 ECMAScript 标准,那么 Web 文件系统的又一块重要功能版图将得以补全,Web 生态将会变得更友好、更强大。

解锁尝鲜:加入 Origin Trial

FSO 还是一套崭新的 API,有多新呢?MDN 和 CanIUse 中还没有建立关于它的词条。但这并不意味着我们完全无法用于生产环境 ------ 正如你在本文开头的 demo 中体验到的,我已经用到线上功能中了。只要做一点配置工作,你和你的用户就能成为全球第一批享受到 FSO 的人 😎。

Chrome 已经对 FSO 开启了试用,版本范围是 129 到 134,你可以为你的 Web App 域名注册一个试用 token,你可以跟着我一步一步操作:

首先我们访问 https://developer.chrome.com/origintrials/#/view_trial/59109745109237761 并登录账号。

点击界面下方的「REGISTER」按钮,进入表单页:

按照上图的标注填写信息。每一个域名都需要单独注册一次。例如我本地开发调试时用的是localhost:3000,而线上域名是 rejax.fun,那么就需要给这两个域名分别走一遍 REGISTER 流程。

填写信息后提交表单,你会得到一串字符串 token:

将 token 复制出来,写到 Web App 的 html 文件中,像这样:

html 复制代码
<meta http-equiv="origin-trial" content="把 token 粘贴到这里" />

或者用 JavaScript 动态插入:

JavaScript 复制代码
const meta = document.createElement('meta')
meta.httpEquiv = 'origin-trial'
meta.content = token
document.head.appendChild(meta)

最后,在 Chrome 中打开你注册的域名所在的页面,在 Console 中输入 FileSystemObserver 并回车:

如果打印出了「native code」而不是「undefined」,那么恭喜,你已经成功解锁了 FSO 试用!

监听一个文件

有了试用资格,我们来监听一个文件,边调试代码边研究 FSO 的设计和实现。

实例化

上一小节的最后,我们用来测试是否解锁成功的 FileSystemObserver 就是 FSO 的构造函数,它接收一个回调函数作为参数。我们可以像这样实例化一个观察者:

JavaScript 复制代码
function callback (params) {
    console.log(params)
}
const observer = new FileSystemObserver(callback)

callback 函数会在被监听的文件发生变动时被执行,所以我们可以把响应变动的业务处理逻辑放在其中。

绑定目标文件

实例 observer 有一个 observe 方法,它接收两个参数。第二个参数暂且按下不表,我们先专心看第一个参数。

这个参数是一个 FileSystemHandle 格式的对象,代表着本地文件在 JavaScript 运行时中的入口。我们可以通过 showOpenFilePicker 来选择一个文件(假如我们选择了文件 a.js),并获取到对应的 FileSystemHandle

JavaScript 复制代码
const [fileHandle] = await window.showOpenFilePicker()
observer.observe(fileHandle)

如果你想看 FileSystemHandleshowOpenFilePicker 的详解,可以移步至本专栏的上一篇文章谁也别拦我们,网页里直接增删改查本地文件!

调用 observe 方法后,这个文件就算是进入了我们的监控区域 📸 了,直到我们主动解除监听或者网页被关闭/刷新。

监听文件操作

当我们编辑文件 a.js 的内容时,给 observe() 传入的回调函数被调用,并且会接收到两个参数,第一个是本次的变动记录 records,第二个是实例 observer 本身。我们打印 records 可以看到如下结构:

records 是一个数组,其元素是 FileSystemChangeRecord 类型的对象,我们重点关注以下几个属性:

  • changedHandle:可以理解为这就是我们绑定的文件。

  • type:变动类型,可取值及对应含义如下:

    type 值 含义
    appeared 新建文件,或者移入被监听的根目录
    disappeared 文件被删除,或者移出被监听的根目录
    modified 文件内容被编辑
    moved 文件被移动
    unknown 未知类型
    errored 出现报错

一般情况下,如果我们监听的是单个文件而不是一个目录,那么无论是把文件移走、重命名、删除, record 中的 type 值都会是 disappeared。

监听一个文件夹

监听文件夹的方式和监听文件类似,我们先用 showDirectoryPicker 选择一个文件夹(以文件夹 foo 为例),再把 DirectoryHandle 传入 observe 方法。

为方便描述,我们假设文件夹 foo 的结构如下:

/foo

├── 文件夹 dir1

├── 文件夹 **dir2**

└── 文件 a.js

JavaScript 复制代码
const dirHandle = await window.showDirectoryPicker()
observer.observe(dirHandle)

与文件有所不同的是,文件夹会有子文件夹和子文件,这是一个树形结构。如果我们只想监听 foo 下面的一级子内容,那么使用像上方代码块那样的调用方式就可以了。但如果我们想密切掌控每一子级的变动,就需要额外的配置参数,也就是前文提到的第二个参数:

JavaScript 复制代码
observer.observe(dirHandle, {
    recursive: true
})

此时你可以在 foo 文件夹里面任意增、删、改子文件或文件夹,一切操作都能在回调函数里以 record 的形式被捕获到。子文件和子文件夹所支持的操作类型,record 值也具有相同结构,因此接下来我们从监听子文件的视角来观察 FSO。

监听子文件

创建和移入、删除和移出 a.js 的情况,record.type 的值分布如下:

文件移入 foo 在 foo 中创建文件 文件从 foo 中移出 删除文件
appeared appeared disappeared disappeared

其中移出和删除的表现,与监听单文件的情况是相同的。

我们来试试把 a.js 移到与它同级的文件夹 dir1 中,看看会得到怎样的 record

有几个点值得我们注意:

  • type 的值是 moved,说明只要 a.js 还在 foo 内,不管处于第几层,都不会触发 type: appeared/disappeared
  • relativePathMovedFrom 是一个单元素数组,它代表移动前 a.js 的文件路径
  • relativePathComponents 有两个数组元素,代表被移动文件的新路径是 dir1/a.js

但重命名子文件和监听单文件时不同。例如我们将 a.js 更名为 b.js,会监听到如下 record

我们本以为 type 的值是 renamed,但其实是 moved,确实有点反直觉。从 record 上来看,与真正的移动操作相比,重命名的不同之处在于:

  • changedHandle 指向了重命名后的新文件 b.js
  • relativePathMovedFromrelativePathComponents 分别包含的是旧名和新名

FSO 在状态设计上并没有直接定义一个重命名状态,但我们可以自己来区分。重命名的响应数据有这样的特征:

  • relativePathMovedFromrelativePathComponents 这两个数组的 length 一定相等
  • 除了最后一个元素,两个数组的其他元素一定是一一对应相等的

因此我们可以这样判断重命名操作:

JavaScript 复制代码
const { oldList: relativePathMovedFrom, newList: relativePathComponents } = recors[0]
let operation = '是常规的移动操作'
// 重命名前后,文件的目录路径没变,只是文件名变了
if (oldList.length === newList.length) {
  const len = newList.length
  for (let i = 0; i < len; i++) {
    // 相同序号的新旧路径是否一样
    const isEqual = newList[i] === oldList[i]
    if (i < len - 1) {
      if (!isEqual) break
    } else if (!isEqual) {
      operation = '是重命名操作,不是移动操作'
    }
  }
}

至此,我们已经摸清了如何监听子文件上的不同操作,除了监听单文件部分已经覆盖到的内容,增量知识点仅有移动和重命名这两块。

监听子文件夹

对子文件夹的操作,也不外乎新建、删除、移动、重命名,和子文件在逻辑上基本一致,我们可以直接复用子文件的监听逻辑,再加上用 record.changedHandle.kind === 'directory' 来判断是否是文件夹即可。

解除监听

当我们想主动解除对文件或文件夹的监听时,只需要调用对应 observerdisconnect 即可:

JavaScript 复制代码
observer.disconnect()

结语

恭喜你读完了本文,你真棒!

这一次,我们勇敢地品尝了一只新鲜生猛的螃蟹,对 File System Observer API 进行了较为深入的理解和实践。如果你之前一直苦于 JS 无法监听文件,无法带给用户完备的功能和极致的体验,那么从现在开始,你可以开始着手准备升级你的 Web App 了!

这套船新版本的 API 有力地补齐了 Web 文件系统 API 的短板,增强了 Web App 的实现能力,提升了开发者和用户的体验。它还在不断修改完善中,非常需要我们开发者积极参与到标准的制定中来,让 Web 技术栈变得更高效、更易用!

相关推荐
gqkmiss几秒前
Chrome 浏览器 131 版本开发者工具(DevTools)更新内容
前端·chrome·浏览器·chrome devtools
Summer不秃6 分钟前
Flutter之使用mqtt进行连接和信息传输的使用案例
前端·flutter
旭日猎鹰10 分钟前
Flutter踩坑记录(二)-- GestureDetector+Expanded点击无效果
前端·javascript·flutter
Viktor_Ye17 分钟前
高效集成易快报与金蝶应付单的方案
java·前端·数据库
hummhumm19 分钟前
第 25 章 - Golang 项目结构
java·开发语言·前端·后端·python·elasticsearch·golang
乐闻x1 小时前
Vue.js 性能优化指南:掌握 keep-alive 的使用技巧
前端·vue.js·性能优化
一条晒干的咸魚1 小时前
【Web前端】创建我的第一个 Web 表单
服务器·前端·javascript·json·对象·表单
花海少爷1 小时前
第十章 JavaScript的应用课后习题
开发语言·javascript·ecmascript
Amd7941 小时前
Nuxt.js 应用中的 webpack:compiled 事件钩子
前端·webpack·开发·编译·nuxt.js·事件·钩子
生椰拿铁You1 小时前
09 —— Webpack搭建开发环境
前端·webpack·node.js