jQuery

June 16, 2022

在很久很久以前,工程师们通过 jQuery 操作 DOM 来在网页上实现一些交互。

后来,工程师们捣鼓出了框架。React 和 Vue 之类的前端框架让工程师们可以专注于描述数据和 UI 之间的关系,而不是直接去操作它们。渐渐地,没有人用 jQuery 了。

但万一我还是需要操作 DOM 呢?

几个月前我做了一个脚本,可以给网页版微博增加一些一键转发,可以给网页版微博增加一些一键转发(一键 awsl)的功能。由于涉及到大量直接操作 DOM 的需求,让我不得不又考虑引入 jQuery 来简化这些操作。

且慢。

1202 年了,谁还用 jQuery 啊。

于是我开始思考,1202 年了,如果我需要一个「jQuery」那么它应该具备什么特性?

现代的模块化系统,而不是像 jQuery 那样直接暴露一个全局的 $ 必须是 TypeScript,JS 之光。裸写 JavaScript 的都是恶魔。 Less is more 一顿编码之后,我做出来一个原型。我将它取名叫 dro。其实本来想叫 tquery 的,已经被抢注了。至于为啥叫 dro,我也忘了。但是它足够短。

基础查询 import { $, $$ } from 'dro'; const container = $<HTMLElement>(document, '.foo'); const children = $$(document, '.foo a'); 我认为像 jQuery 那样 $() 永远返回一个数组是为了简化问题而带来更多问题。所以参照以前使用 MooTools 的经历,决定用 $() 和 $$() 来区分需要得到一个元素还是多个元素的场景(本质上就只是 querySelector 和 querySelectorAll 的 alias 罢了)。

你还注意到,dro 利用了 TypeScript 的泛型。$() 可以带上泛型参数来取得这个类型的结果。比如 $<HTMLInputElement>(document, '#username')。

批量查询 import { $H } from 'dro';

interface MyForm { username: HTMLInputElement; password: HTMLInputElement; submit: HTMLElement; }

const form = $H<MyForm>(document, { username: '.my-form input.user', password: '.my-form input.pass', submit: '.my-form button[type="submit"]', });

console.log(form?.username.value); 我写那个脚本的时候发现了一个需求。我经常需要同时 query 多个元素,并且一一对它们判空。有些时候,它们也不是简单的 HTMLElement,而是 HTMLInputElement 之类带有更多特性的元素。于是我带来了 $H() 这个函数。

它同样利用了 TypeScript 的泛型特性。你需要先声明一个 interface 来描述你需要的字段和对应的类型,然后通过参数传给函数。同时传给函数的还有对应 interface 中每个字段的 CSS selector(利用 TypeScript 的特性,编辑器会提示你需要哪些字段,确保没有遗漏或拼错)。

如果 interface 中声明的元素通过对应的 CSS selector 全部能得到,那么 $H() 会返回这个对象。否则,只要任意一个 CSS selector 不满足,返回 null。

其它一些 HTML 操作函数 import { append, create, html, attrs, style, on } from 'dro';

append(document.body, () => { const el = create('div'); html(el, 'foo'); attrs(el, { 'class': 'foo', }); style(el, { 'margin-top': '200px', }); on(el, 'click', () => console.log('lol!')); return el; }); 这一部分只满足了我自己开发那个脚本时的需求。以后可能会增改。

这里的 append() 传入的不是一个已经创建好的元素,而是一个 creator 闭包。这是因为我不希望这个需要被 append 到某处的元素创建过程中用到的一些变量被暴露到外面来。