你不需要 jQuery,但你需要一個 DOM 庫

敘帝利發表於2019-03-27

寫這篇文章的目的,一方面是介紹一下自己編寫的模組化 DOM 庫 domq.js,另一方面是希望大家對 jQuery 有一個正確的認識,即使 jQuery 已經逐漸退出歷史舞臺,但是它的 API 將會以另外一種形式存在下去。

GitHub:github.com/nzbin/domq

文件:nzbin.gitbooks.io/domq-api/us…

jQuery 不會死去

從 GitHub 放棄 jQuery,再到 Bootstrap 5 宣佈移除 jQuery,看來一個時代終究要落下帷幕。

為什麼我們會放棄 jQuery 呢?原因無非這樣幾個:不需要再進行瀏覽器的相容,原生 DOM 查詢已經很方便,AJAX 請求有更好的替代方式等等。

在我看來 jQuery 最大的弊端是無法分模組引入,直接引入整個庫實在有些不妥,畢竟太多功能已經沒有用武之地。但是 jQuery 的 DOM 操作依然很有必要。很多人對我的這個觀點有些疑問。其實在使用 MVVM 框架的時候,DOM 操作確實已經很少。但是我們也不可能總是做一些 CRUD 的功能。對於複雜的業務需求仍然需要一些 DOM 操作。

假如 jQuery 可以把 DOM 操作相關的功能模組分離出來,或許還有很大的使用空間。

原生當道

在平時的專案中,越來越多的人選擇用原生 JS 去操作物件,比如獲取元素屬性,寬高,定位等等。

早在幾年前,github 上就有很多文章介紹如何用原生 JS 代替 jQuery,比如 YouDontNeedJQueryYouMightNotNeedjQuery等。就我個人而言,純 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 等。

但是 Zepto 也有一些顯而易見的缺陷,畢竟還是上個時代的產物,首先就是無法按需載入,現在我們在寫專案的時候更願意根據自己的需要引入某些方法,而不是將整個庫全部引入,雖然 Zepto 的體積不大,但是作為強迫症還是有一些厭惡。另外就是 Zepto 本身也有一些 bug,比如 scrollTopscrollLeft 方法。其它不同參見原始碼。

// 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()
...
複製程式碼

Domq 沒有太多新的東西,所以也沒有太多可以介紹的,它已經在外掛 PhotoViewer 以及實際專案中得以運用,歡迎大家下載使用。

總結

這是一個好的時代,也是一個壞的時代,jQuery 的落幕確實讓人感嘆,但是我們完全沒必要因為 jQuery 的落幕而放棄 jQuery 的使用方式。正如前文所說,jQuery 的 DOM 操作在我看來依然是最好用的,所以,你不需要 jQuery,但你需要一個 DOM 庫。

GitHub:github.com/nzbin/domq

相關文章