拋棄臃腫的 jQuery,用 NodeList.js 操作 DOM

yuanzhang發表於2016-07-07

近年來,jQuery 已經成為 web(開發) 中實際意義上的 JavaScript 庫。它解決了許多跨瀏覽器的不相容性問題,同時新增了一層受歡迎的語法糖用於客戶端的指令碼編寫。它將 DOM 操作這一大痛處進行了抽象,但是自它出現以來,原生瀏覽器 API 已經有了顯著改善並且也許你並不需要 jQuery的想法開始被人們所接受。

下列是一些原因:

  1. jQuery 包含很多你並不需要或不會使用到的功能(因此略顯臃腫)。
  2. jQuery 很多人來說太過紛繁。通常體積較小的庫可以更好的完成特定任務。
  3. 就 DOM 操作而言,瀏覽器 API 如今可以做大多數 jQuery 可以做的事。
  4. 瀏覽器 API 現在更加同步,例如,使用 addEventListener 而非 attachEvent

那麼還有什麼問題呢?

目前的問題是使用普通的(或原始的)JavaScript 進行 DOM 操作同 jQuery 一樣令人厭煩。因為你不得不讀寫多餘的程式碼,並且處理瀏覽器中無用的 NodeList

讓我們先看看 MDN 的描述,什麼是 NodeList

NodeList 物件是節點集合,如 Node.childNodes 和 document.querySelectorAll 方法的返回值。

以及有時會出現的動態 NodeLists (令人困惑的):

在一些場景下,NodeList 是一個動態集合,也就是說在 DOM 上的操作都會反射到這個集合中。例如,Node.childNodes 就是動態的。

這是個問題因為你無法分辨哪些是動態的,哪些是靜態的。除非你移除 NodeList 中的每個節點並檢查該 NodeList 是否為空。如果是空的那你拿到的就是一個動態的 NodeList (這並不是個好主意)。

瀏覽器也沒有提供任何有效的辦法來操作這些 NodeList 物件。

例如,很不巧這些節點沒法通過 forEach 來迴圈:

所以你不得不這麼幹:

你甚至只能 “hack” 一下:

瀏覽器的原生 NodeList 只有一個方法:item。該方法根據下標從 NodeList 返回一個節點。當我們可以通過像陣列那樣(使用 array[index])獲取到該節點時,這個方法完全無用:

NodeList.js 便應運而生——為了讓使用瀏覽器原生 API 進行 DOM 操作同使用 jQuery 操作一樣簡單,但壓縮後僅為 4k。

解決方案

我建立了 NodeList.js ,因為我一直在使用原生 DOM API,而我想讓它們更加簡潔,以便我在寫程式碼時能去掉很多冗餘部分(例如 for 迴圈)。

NodeList.js 是原生 DOM API 的一個封裝,它讓你在操作節點陣列(也就是我的 NodeList)時像操作單個節點一樣。相比瀏覽器的原生 NodeList 物件,這給你帶來了更多的實用性。

如果聽上去覺得不錯,就到 GitHub 下載官方 repo 並跟隨以下教程。

使用:

選擇 DOM 節點很簡單:

這個方法底層實用的是 querySelectorAll(selector)

但它如何與 jQuery 競爭呢?

很高興你這麼問。讓我們對比一下 Vanilla JS,jQuery 和 NodeList.js 。

比如我們現在有三個按鈕:

讓我們把每個按鈕的內容改為 “Click Me”:

原生 JS:

jQuery:

NodeList.js:

我們可以看到 NodeList.js 實際上把 NodeList 當做了單一節點。也就是說,我們引用了一個 NodeList 並只是設定它的 textContent 屬性為 “Click Me” 而已。隨後 NodeList.js 將為我們設定 NodeList 中的每一個節點。 棒極了,對吧?

如果我們想要使用jQuery 中的方法鏈,可以按下面這樣做,它會返回一個 NodeList 的引用:

現在我們來給每個按鈕新增一個 click 事件監聽:

Vanilla JS:

jQuery:

NodeList.js:

好的,jQuery 的on 方法很不錯。 我的庫使用了瀏覽器的原生 DOM API(因此是 addEventListener),但這並不妨礙我們為這個方法建立一個別名:

漂亮!而且這個確切地演示了新增自定義方法的方式:

NodeList.js 中的陣列方法

NodeList.js 確實繼承自 Array.prototype,但不是直接繼承,因為一些方法被修改了以便在NodeList(節點陣列)上使用它們時更加合理。

Push 和 Unshift

例如: pushunshift 方法只能接收節點(Node)作為引數,否則它們會丟擲錯誤:

pushunshift 都會返回 NodeList 以便使用方法鏈,也就是說這與 JavaScript 的原生方法 Array#pushArray#unshift 不同,原生方法會接收任何引數並返回 Array (操作後)的新長度。如果確實想要 NodeList 的長度,只需要使用 length 屬性即可。

這些方法都會像 JavaScript 的原生 Array 方法一樣更改 NodeList

Concat

concat 方法可接收以下引數:

  • Node
  • NodeList (包括瀏覽器原生的和 NodeList.js 的)
  • HTMLCollection
  • Node 陣列
  • NodeList 陣列
  • HTMLCollection 陣列

concat 是一個 遞迴方法,所以節點陣列可以隨意巢狀且會被扁平化(譯註:Array#reduce 陣列扁平化)。但是如果傳遞的陣列不是 NodeNodeListHTMLCollection ,則會丟擲一個 Error

同 JavaScript 的 Array#concat 方法一樣,concat 也會返回一個新的 NodeList

Pop, Shift, Map, Slice, Filter

popshift 方法都可接收一個可選引數,指定從 NodeListpopshift 的節點數量。這點與 JavaScript 的原生方法 Array#popArray#shift 不同,原生方法始終會從陣列中 popshift 一個元素並忽略所傳遞的引數。

對於map 方法,當對映的值都為 Node 時會返回 NodeList`, 否則返回對映值對應的陣列。

slicefilter 方法表現得像真實陣列中的一樣,也會返回 NodeList

由於 NodeList.js 不直接繼承自 Array.prototype , 所以如果有方法在 NodeList.js 載入後被新增到 Array.prototype中,那麼該方法將不會被繼承。

更多關於 NodeList.js 陣列方法的詳情,請看這裡

特殊方法

NodeList.js 有四個獨特的方法,以及一個叫做 owner 的屬性,該屬性等價於 jQuery 的 prevObject 屬性。

getset 方法

有一些元素與普通元素不同,擁有獨特的屬性(例如 標籤的 href 屬性)。這就是為什麼 $$(‘a’).href 會返回 undefined —— 因為該屬性不是 NodeList 中的每個元素都會繼承的。所以我們將使用 get方法 來訪問那些屬性:

set method 方法用來設定各元素的那些屬性:

set 方法也會返回 NodeList 以方便使用方法鏈。我們可以在類似於 textContent 的屬性上使用這個方法(以下方法等效):

我們在一次呼叫中設定多個屬性:

以上所有操作可在任意屬性上執行,例如 style :

 

call 方法

call 方法 允許你呼叫元素上那些獨特的方法(例如 video 元素上的 pause 方法):

item 方法

item 方法 等價於 jQuery 的 eq 方法。該方法返回一個 NodeList(僅包含下標引數所對應的節點):

 

owner 屬性

owner 屬性 等價於 jQuery 的 preObject

btns.style 返回一個樣式陣列,而owner則返回對映 styleNodeList

NodeList.js 相容性

NodeList.js 相容所有主流的新瀏覽器,如下表所示。

Browser Version
FireFox 6+
Safari 5.0.5+
Chrome 6+
IE 9+
Opera 11.6+

結論

現在我們終於能使用一個令人滿意的 NodeList 物件了。

僅 4k 的壓縮檔案,你就可以獲得上述的所有功能,還有更多功能可以在 NodeList.js 的 GitHub 源進行了解。

由於 NodeList.js 依賴於瀏覽器,因此不需要任何的更新。無論瀏覽器何時新增新方法/屬性到 DOM 元素上,你都可以通過 NodeList.js 使用那些方法/屬性。這就意味著你唯一需要擔心的就是被瀏覽器棄用的方法。這些(方法)通常是使用頻率很低的,畢竟我們不能逆著大流而上。

那麼你怎麼看呢?你會考慮使用這個庫麼?它是否缺少一些重要的功能呢?很樂意聽到你的評論

打賞支援我翻譯更多好文章,謝謝!

打賞譯者

打賞支援我翻譯更多好文章,謝謝!

拋棄臃腫的 jQuery,用 NodeList.js 操作 DOM

相關文章