近年來,jQuery 已經成為 web(開發) 中實際意義上的 JavaScript 庫。它解決了許多跨瀏覽器的不相容性問題,同時新增了一層受歡迎的語法糖用於客戶端的指令碼編寫。它將 DOM 操作這一大痛處進行了抽象,但是自它出現以來,原生瀏覽器 API 已經有了顯著改善並且也許你並不需要 jQuery的想法開始被人們所接受。
下列是一些原因:
- jQuery 包含很多你並不需要或不會使用到的功能(因此略顯臃腫)。
- jQuery 很多人來說太過紛繁。通常體積較小的庫可以更好的完成特定任務。
- 就 DOM 操作而言,瀏覽器 API 如今可以做大多數 jQuery 可以做的事。
- 瀏覽器 API 現在更加同步,例如,使用
addEventListener 而非
attachEvent
。
那麼還有什麼問題呢?
目前的問題是使用普通的(或原始的)JavaScript 進行 DOM 操作同 jQuery 一樣令人厭煩。因為你不得不讀寫多餘的程式碼,並且處理瀏覽器中無用的 NodeList 。
讓我們先看看 MDN 的描述,什麼是 NodeList
:
NodeList 物件是節點集合,如 Node.childNodes 和 document.querySelectorAll 方法的返回值。
以及有時會出現的動態 NodeLists (令人困惑的):
在一些場景下,NodeList 是一個動態集合,也就是說在 DOM 上的操作都會反射到這個集合中。例如,Node.childNodes 就是動態的。
這是個問題因為你無法分辨哪些是動態的,哪些是靜態的。除非你移除 NodeList
中的每個節點並檢查該 NodeList
是否為空。如果是空的那你拿到的就是一個動態的 NodeList
(這並不是個好主意)。
瀏覽器也沒有提供任何有效的辦法來操作這些 NodeList
物件。
例如,很不巧這些節點沒法通過 forEach 來迴圈:
1 2 3 4 5 |
var nodes = document.querySelectorAll('div'); nodes.forEach(function(node) { // do something }); // Error: nodes.forEach is not a function |
所以你不得不這麼幹:
1 2 3 4 5 |
var nodes = document.querySelectorAll('div'); for(var i = 0, l = nodes.length; i < l; i++) { var node = nodes[i]; // do something } |
你甚至只能 “hack” 一下:
1 2 3 |
[].forEach.call(document.querySelectorAll('div'), function(node) { // do something }); |
瀏覽器的原生 NodeList
只有一個方法:item。該方法根據下標從 NodeList
返回一個節點。當我們可以通過像陣列那樣(使用 array[index]
)獲取到該節點時,這個方法完全無用:
1 2 |
var nodes = document.querySelectorAll('div'); nodes.item(0) === nodes[0]; // true |
NodeList.js 便應運而生——為了讓使用瀏覽器原生 API 進行 DOM 操作同使用 jQuery 操作一樣簡單,但壓縮後僅為 4k。
解決方案
我建立了 NodeList.js ,因為我一直在使用原生 DOM API,而我想讓它們更加簡潔,以便我在寫程式碼時能去掉很多冗餘部分(例如 for
迴圈)。
NodeList.js 是原生 DOM API 的一個封裝,它讓你在操作節點陣列(也就是我的 NodeList
)時像操作單個節點一樣。相比瀏覽器的原生 NodeList
物件,這給你帶來了更多的實用性。
如果聽上去覺得不錯,就到 GitHub 下載官方 repo 並跟隨以下教程。
使用:
選擇 DOM 節點很簡單:
1 |
$$(selector); // returns my NodeList |
這個方法底層實用的是 querySelectorAll(selector)
。
但它如何與 jQuery 競爭呢?
很高興你這麼問。讓我們對比一下 Vanilla JS,jQuery 和 NodeList.js 。
比如我們現在有三個按鈕:
1 2 3 |
<button></button> <button></button> <button></button> |
讓我們把每個按鈕的內容改為 “Click Me”:
原生 JS:
1 2 3 4 |
var buttons = document.querySelectorAll('button'); // returns browser's useless NodeList for(var i = 0, l = buttons.length; i < l; i++) { buttons[i].textContent = 'Click Me'; } |
jQuery:
1 |
$('button').text('Click Me'); |
NodeList.js:
1 |
$$('button').textContent = 'Click Me'; |
我們可以看到 NodeList.js 實際上把 NodeList
當做了單一節點。也就是說,我們引用了一個 NodeList 並只是設定它的 textContent 屬性為 “Click Me” 而已。隨後 NodeList.js 將為我們設定 NodeList
中的每一個節點。 棒極了,對吧?
如果我們想要使用jQuery 中的方法鏈,可以按下面這樣做,它會返回一個 NodeList
的引用:
1 |
$$('button').set('textContent', 'Click Me'); |
現在我們來給每個按鈕新增一個 click
事件監聽:
Vanilla JS:
1 2 3 4 5 6 |
var buttons = document.querySelectorAll('button'); // returns browser's useless NodeList for(var i = 0, l = buttons.length; i < l; i++) { buttons[i].addEventListener('click', function() { this.classList.add('clicked'); }); } |
jQuery:
1 2 3 4 5 |
$('button').on('click', function() { $(this).addClass('click'); // or mix jQuery with native using `classList`: this.classList.add('clicked'); }); |
NodeList.js:
1 2 3 |
$$('button').addEventListener('click', function() { this.classList.add('clicked'); }); |
好的,jQuery 的on
方法很不錯。 我的庫使用了瀏覽器的原生 DOM API(因此是 addEventListener
),但這並不妨礙我們為這個方法建立一個別名:
1 2 3 4 5 |
$$.NL.on = $$.NL.addEventListener; $$('button').on('click', function() { this.classList.add('clicked'); }); |
漂亮!而且這個確切地演示了新增自定義方法的方式:
1 2 3 4 5 |
$$.NL.myNewMethod = function() { // loop through each node with a for loop or use forEach: this.forEach(function(element, index, nodeList) {...} // where `this` is the NodeList being manipulated } |
NodeList.js 中的陣列方法
NodeList.js 確實繼承自 Array.prototype,但不是直接繼承,因為一些方法被修改了以便在NodeList
(節點陣列)上使用它們時更加合理。
Push 和 Unshift
例如: push 和 unshift 方法只能接收節點(Node)作為引數,否則它們會丟擲錯誤:
1 2 3 |
var nodes = $$('body'); nodes.push(document.documentElement); nodes.push(1); // Uncaught Error: Passed arguments must be a Node |
push
和 unshift
都會返回 NodeList
以便使用方法鏈,也就是說這與 JavaScript 的原生方法 Array#push
或 Array#unshift
不同,原生方法會接收任何引數並返回 Array
(操作後)的新長度。如果確實想要 NodeList
的長度,只需要使用 length
屬性即可。
這些方法都會像 JavaScript 的原生 Array
方法一樣更改 NodeList
。
Concat
concat 方法可接收以下引數:
Node
NodeList
(包括瀏覽器原生的和 NodeList.js 的)HTMLCollection
Node
陣列NodeList
陣列HTMLCollection
陣列
concat
是一個 遞迴方法,所以節點陣列可以隨意巢狀且會被扁平化(譯註:Array#reduce 陣列扁平化)。但是如果傳遞的陣列不是 Node
, NodeList
或HTMLCollection
,則會丟擲一個 Error
。
同 JavaScript 的
Array#concat
方法一樣,concat
也會返回一個新的 NodeList
。
Pop, Shift, Map, Slice, Filter
pop 和 shift 方法都可接收一個可選引數,指定從 NodeList
中 pop
或 shift
的節點數量。這點與 JavaScript 的原生方法 Array#pop
或 Array#shift
不同,原生方法始終會從陣列中 pop
或 shift
一個元素並忽略所傳遞的引數。
對於map 方法,當對映的值都為 Node
時會返回 NodeList`, 否則返回對映值對應的陣列。
而 slice 和 filter 方法表現得像真實陣列中的一樣,也會返回 NodeList
。
由於 NodeList.js 不直接繼承自 Array.prototype
, 所以如果有方法在 NodeList.js 載入後被新增到 Array.prototype
中,那麼該方法將不會被繼承。
更多關於 NodeList.js 陣列方法的詳情,請看這裡。
特殊方法
NodeList.js 有四個獨特的方法,以及一個叫做 owner
的屬性,該屬性等價於 jQuery 的 prevObject
屬性。
get
和 set
方法
有一些元素與普通元素不同,擁有獨特的屬性(例如 標籤的 href 屬性)。這就是為什麼 $$(‘a’).href 會返回 undefined —— 因為該屬性不是 NodeList 中的每個元素都會繼承的。所以我們將使用 get方法 來訪問那些屬性:
1 |
$$('a').get('href'); // returns array of href values |
set method 方法用來設定各元素的那些屬性:
1 |
$$('a').set('href', 'https://sitepoint.com/'); |
set
方法也會返回 NodeList
以方便使用方法鏈。我們可以在類似於 textContent
的屬性上使用這個方法(以下方法等效):
1 2 |
$$('button').textContent = 'Click Me'; $$('button').set('textContent', 'Click Me'); // returns NodeList so you can method chain |
我們在一次呼叫中設定多個屬性:
1 |
$$('button').set({ textContent: 'Click Me', onclick: function() {...} }); |
以上所有操作可在任意屬性上執行,例如 style
:
1 2 3 |
$$('button').style; // this returns an <code>Array</code> of <code>CSSStyleDeclaration</code> $$('button').style.set('color', 'white'); $$('button').style.set({ color: 'white', background: 'lightblue' }); |
call
方法
call 方法 允許你呼叫元素上那些獨特的方法(例如 video 元素上的 pause
方法):
1 |
$$('video').call('pause'); // returns NodeList back to allow Method Chaining |
item
方法
item 方法 等價於 jQuery 的 eq 方法。該方法返回一個 NodeList
(僅包含下標引數所對應的節點):
1 |
$$('button').item(1); // returns NodeList containing the single Node at index 1 |
owner
屬性
owner 屬性 等價於 jQuery 的 preObject
。
1 2 |
var btns = $$('button'); btns.style.owner === btns; // true |
btns.style
返回一個樣式陣列,而owner
則返回對映 style
的NodeList
。
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 使用那些方法/屬性。這就意味著你唯一需要擔心的就是被瀏覽器棄用的方法。這些(方法)通常是使用頻率很低的,畢竟我們不能逆著大流而上。
那麼你怎麼看呢?你會考慮使用這個庫麼?它是否缺少一些重要的功能呢?很樂意聽到你的評論。
打賞支援我翻譯更多好文章,謝謝!
打賞譯者
打賞支援我翻譯更多好文章,謝謝!