你不需要 jQuery,但你需要一个 DOM 库(收藏)

jQuery
350
0
0
2022-04-16

写这篇文章的目的,一方面是介绍一下自己编写的模块化 DOM 库 domq.js,另一方面是希望大家对 jQuery 有一个正确的认识,即使 jQuery 已经逐渐退出历史舞台,但是它的 API 将会以另外一种形式存在下去。

GitHub:https://github.com/nzbin/domq
文档:
https://nzbin.gitbooks.io/domq-api/usage.html

jQuery 不会死去

从 GitHub 放弃 jQuery,再到 Bootstrap 5 宣布移除 jQuery,看来一个时代终究要落下帷幕。

为什么我们会放弃 jQuery 呢?原因无非这样几个:不需要再进行浏览器的兼容,原生 DOM 查找已经很方便,AJAX 请求有更好的替代方式等等。

在我看来 jQuery 最大的弊端是无法分模块引入,直接引入整个库实在有些不妥,毕竟太多功能已经没有用武之地。但是 jQuery 的 DOM 操作依然很有必要。很多人对我的这个观点有些疑问。其实在使用 MVVM 框架的时候,DOM 操作确实已经很少。但是我们也不可能总是做一些 CRUD 的功能。对于复杂的业务需求仍然需要一些 DOM 操作。

假如 jQuery 可以把 DOM 操作相关的功能模块分离出来,或许还有很大的使用空间。

你不需要 jQuery,但你需要一个 DOM 库(收藏)

原生当道

在平时的项目中,越来越多的人选择用原生 JS 去操作对象,比如获取元素属性,宽高,定位等等。

早在几年前,github 上就有很多文章介绍如何用原生 JS 代替 jQuery,比如 YouDontNeedJQuery,YouMightNotNeedjQuery等。就我个人而言,纯 JS 操作确实很简单,但是并不是很优雅,复杂一点的操作还要经常翻 MDN。

// jQuery
$('.my #awesome selector');
// JS
document.querySelectorAll('.my #awesome selector');
// jQuery
$(el).hide();
// JS
el.style.display = 'none';
// jQuery
$(el).after(htmlString);
// JS
el.insertAdjacentHTML('afterend', htmlString);

以上是 jQuery 和原生 JS 对比的一个缩影,结果显而易见,jQuery 的 API 更加简洁。除此之外,jQuery API 的使用形式也非常统一。相反,原生 JS 的 API 使用方式就比较多样了,既有赋值,又有传参等。另外原生 JS 的 API 名称冗长,不方便记忆。这也是很多 JS 库诞生的意义。

很多插件一般都会有一个 utils 的文件,基本会对原生方法做一个简单封装并提供一些工具方法。

Zepto 的优势与弱势

Zepto 是一个思想超前的库,为什么我会有这样的结论?Zepto 对原生方法做了进一步的抽象,使用更简单。正如我在上文说过的,既然 jQuery 的 API 简洁易用,而且我们也更加熟悉,那我们为什么不将 jQuery 和原生 JS 结合起来呢?令人惊讶的是,早在 2010 年,Zepto 的作者就已经这样去做了。用原生 JS 实现了 jQuery 的大部分 API,可替代率接近九成吧,至少在我编写的插件中,几乎可以替换掉所有的 jQuery API。而且 Zepto 也不是一味的使用 document.querySelector 方法,而是根据性能优劣,有选择的使用 document.getElementById 以及 document.querySelector 等。

你不需要 jQuery,但你需要一个 DOM 库(收藏)

但是 Zepto 也有一些显而易见的缺陷,毕竟还是上个时代的产物,首先就是无法按需加载,现在我们在写项目的时候更愿意根据自己的需要引入某些方法,而不是将整个库全部引入,虽然 Zepto 的体积不大,但是作为强迫症还是有一些厌恶。另外就是 Zepto 本身也有一些 bug,比如 scrollTop、scrollLeft 方法。其它不同参见源码。

// Zepto
scrollTop: function(value) {
 if (!this.length) return
 var hasScrollTop = 'scrollTop' in this[0]
 if (value === undefined) return hasScrollTop ? this[0].scrollTop : this[0].pageYOffset
 return this.each(hasScrollTop ?
 function() { this.scrollTop = value } :
 function() { this.scrollTo(this.scrollX, value) })
}

document 元素无法获得正确的值,我对这个问题提过 pr 但是没有回应,Zepto 目前基本已经停止维护。正确的方法如下:

// Domq
function scrollTop(value) {
 if (!this.length) return
 var hasScrollTop = 'scrollTop' in this[0]
 if (value === undefined) return hasScrollTop
 ? this[0].scrollTop
 : isWindow(this[0])
 ? this[0].pageYOffset
 : this[0].defaultView.pageYOffset;
 return this.each(hasScrollTop ?
 function () { this.scrollTop = value } :
 function () { this.scrollTo(this.scrollX, value) })
}

Domq 的使命

形如 jQuery 的 DOM 操作库有很多,比如 bonzo、$dom,但是在我重构 jQuery 插件时,我发现没有办法用这些库直接替换 jQuery,只有 Zepto 相对完美,但是我又不希望引入额外的无用的方法。

最后我决定改造 Zepto,使之更符合现在的使用习惯。多说一点,个人觉得 Zepto 的核心函数稍显凌乱,命名空间既有 zepto、又有 $、Z,感觉非常混乱,而 domq 的核心函数只有 D 这一个命名空间,形态及功能和 jQuery 的核心函数几乎一样,可以认为是一个 mini 版的 jQuery。

// Zepto 核心方法
var Zepto = (function() {
 var zepto = {};
 ...
 zepto.Z = function(dom, selector) {
 return new Z(dom, selector)
 }
 ...
 $ = function(selector, context) {
 return zepto.init(selector, context)
 }
 ...
})()
// Domq 核心方法
var D = function (selector, context) {
 return new D.fn.init(selector, context);
}
D.fn = D.prototype = {
 ...
 init: function(){
 ...
 }
 ...
}

当然, Domq 最关键的还是按需加载,根据需要挂载方法,尽量减少不必要的代码。使用方式很简单,但是你需要创建一个独立文件,重新挂载需要的方法到 D 命名空间上,这在编写插件时非常有用。

import {
 D,
 isArray,
 addClass
} from 'domq.js/src/domq.modular';
// 静态方法
const methods = {
 isArray
}
// 原型方法
const fnMethods = {
 addClass
}
D.extend(methods);
D.fn.extend(fnMethods);

另外,在做项目时经常会用到一些工具方法,这时候用一个工具库暴露这些方法或许是最好的方式。Domq 也有一些常用的工具方法,不过还需要再迭代一下。

D.type()
D.contains()
D.camelCase()
D.isFunction()
D.isWindow()
D.isEmptyObject()
D.isPlainObject()
D.isNumeric()
D.isArray()
D.inArray()
...

安装

npm install domq.js --save

在 domq.js 的 dist 文件夹下有四个主要文件

dist
├── domq.js (UMD)
├── domq.common.js (CJS)
├── domq.esm.js (ESM)
└── domq.modular.js (MODULAR)

默认加载模块化方案 domq.modular.js。

import { D } from 'domq.js'

或者根据需要加载指定文件

import { D } from 'domq.js/dist/domq.esm.js'

模块化使用说明

按需加载需要手动将所需方法挂载到 D 函数上,示例如下:

import {
 D,
 isArray,
 addClass
} from 'domq.js/src/domq.modular';
// 静态方法
const methods = {
 isArray
}
// 原型方法
const fnMethods = {
 addClass
}
D.extend(methods);
D.fn.extend(fnMethods);

Domq 没有太多新的东西,所以也没有太多可以介绍的,它已经在插件 PhotoViewer 以及实际项目中得以运用,欢迎大家下载使用。

总结

这是一个好的时代,也是一个坏的时代,jQuery 的落幕确实让人感叹,但是我们完全没必要因为 jQuery 的落幕而放弃 jQuery 的使用方式。正如前文所说,jQuery 的 DOM 操作在我看来依然是最好用的,所以,你不需要 jQuery,但你需要一个 DOM 库。

你不需要 jQuery,但你需要一个 DOM 库(收藏)