在我硬盘安监控了?纯 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 文件中,像这样:

<meta http-equiv="origin-trial" content="把 token 粘贴到这里" />

或者用 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 的构造函数,它接收一个回调函数作为参数。我们可以像这样实例化一个观察者:

function callback (params) {
    console.log(params)
}
const observer = new FileSystemObserver(callback)

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

绑定目标文件

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

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

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

const dirHandle = await window.showDirectoryPicker()
observer.observe(dirHandle)

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

observer.observe(dirHandle, {
    recursive: true
})

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

监听子文件

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

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

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

我们来试试把 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 一定相等
  • 除了最后一个元素,两个数组的其他元素一定是一一对应相等的

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

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 即可:

observer.disconnect()

结语

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值