轉載自你可能從未聽說過這15個HTML元素方法!
首先讓我們來討論一下 HTML 與 DOM 之間的區別。
顯然,普通的<table>
元素就是一段 HTML 程式碼,它可以應用在任何一個以 .html 為副檔名的檔案中。元素自帶一系列特性(attribute),以控制它的顯示方式與行為。
這就是元素的全部內容,它與 JavaScript 沒有任何關聯。
而 DOM 的作用是將你的 JavaScript 程式碼與文件中的 HTML 元素關聯在一起,讓你能夠以物件的方式與這些元素進行互動。
這就是所謂的文件物件模型。
在 HTML 中的每個元素都對應著一個 DOM ‘介面’,其中定義了若干屬性(property,通常會對映至 HTML 元素上的特性)與方法。舉例來說,一個 <table>
元素對應著一個 HTMLTableElement 介面。
你可以按以下方式獲取某個元素的引用:
const searchBox = document.getElementById('search-box');
複製程式碼
現在,你就可以訪問該元素上定義的所有屬性與方法了。打個比方,你可以通過 searchBox.value 訪問它的 value 屬性,也可以呼叫 searchBox.focus() 方法讓游標移至輸入框上。
感謝你參加這個 58 秒的 DOM 入門培訓課程,哈哈。
現在的問題在於,大多數元素都沒有提供什麼有趣的方法。因此,除非你特意到官方文件規範上去搜尋那些可能永遠都用不到的東西,否則很容易忽略掉那些零散的小技巧。
幸運的是,瀏覽規範與整理小技巧正是我用於避免陷入困境的兩種最喜歡的方式。那麼,讓我們開始吧……
如果你也希望嘗試一下這些技巧,又恰好有一些瀏覽器 DevTools 可以使用的話,可以在元素樹型結構中先選中某個元素,然後在控制檯中輸入$0
,它會返回給你一個所選中元素的引用。如果你需要返回該元素的物件,請輸入dir($0)
。
在控制檯中,你可以實現各種豐富的功能。
1.table 的方法
原始的 table 元素(時至今日仍然是網站佈局方法裡的第一名)本身自帶許多精巧的方法,使用這些方法建立表格就像搭建宜家裡的桌子一樣簡單。
以下是部分實用的方法:
const tableEl = document.querySelector('table');
const headRow = tableEl.createHead().insertRow();
headerRow.insertCell().textContent = 'Make';
headerRow.insertCell().textContent = 'Model';
headerRow.insertCell().textContent = 'Color';
const newRow = tableEl.insertRow();
newRow.insertCell().textContent = 'Yes';
newRow.insertCell().textContent = 'No';
newRow.insertCell().textContent = 'Thank you';
複製程式碼
整段程式碼裡完全用不著使用 document.createElement() 方法。
如果你在一個 table 元素上直接呼叫 .insertRow() 方法,它甚至會自動為你插入一個 <tbody>
元素,是不是很棒?
2.scrollIntoView()
你知道嗎?當頁面的 URL 中包含 #something 元素時,一旦頁面載入,瀏覽器就會自動滾動至具有這個 ID 的元素之處。
這確實是一項很貼心的功能,但如果你在頁面載入之後再渲染元素,這項功能就不起作用了。
不過,你也可以通過以下方式,手動地讓這項功能重新生效:
document.querySelector(document.location.hash).scrollIntoView();
複製程式碼
3.hidden
好吧,hidden 或許不是一個方法,但如果你提出抗議,那我也要爭論一下:在 hidden 的背後很可能對應著一個 setter,這可是一個貨真價實的方法,對不對?
不管怎樣,你是否曾經為了隱藏某個元素而使用過 myElement.style.display = 'none' 這種方法呢?如果是的話,請別再這麼做了!
只需呼叫 myElement.hidden = true ,即可實現元素隱藏的功能。
4.toggle()
嗯,toggle 也不算是元素的方法,它實際上是元素屬性上的一個方法。嚴格來說,這是一種為元素新增或刪除某個 class 的方法,具體做法是 myElement.classList.toggle('some-class') 。
如果你曾經通過 if 條件語句為元素新增 class,那就應該趕緊改用這種做法。
正確的方式是為 toggle 方法傳入第二個引數,如果該引數返回 true ,則指定的 class 就會新增至元素上。
el.classList.toggle('some-orange-class', theme === 'orange');
複製程式碼
我知道你在想些什麼:這種寫法違背了 'toggle' 這個詞的本義(開關),那些從 IE 時代過來的開發者們都這麼想,他們斷言應當徹底摒棄使用第二個引數的做法。
所以,我收回我的話。不必堅持這種寫法了,各位請隨意!
5.querySelector()
好吧,你當然知道這個方法,但據我推測,應該只有 17% 的開發者才知道,該方法可以使用在任意元素上。
打個比方,myElement.querySelector('.my-class') 的作用是返回在 myElement 的子代中包含 my-class 這個 class 的所有元素。
6.closest
該方法可在任意元素上使用,它能夠向上查詢元素的樹型結構,可以理解為 與 querySelector() 相反的方法。因此,我可以通過以下方法獲取當前內容的對應標頭:
myElement.closest('article').querySelector('h1');
複製程式碼
這段方法首先向上找到最近的 <article>
元素,然後再向下找到最近的<h1>
元素。
7.getBoundingClientRect()
在對 DOM 元素呼叫該方法時,將返回一個包含其空間結構詳細資訊的簡單物件。
{
x: 604.875,
y: 1312,
width: 701.625,
height: 31,
top: 1312,
right: 1306.5,
bottom: 1343,
left: 604.875
}
複製程式碼
不過,在呼叫該方法時需要注意兩點:
呼叫該方法會導致元素的重繪,根據裝置與頁面複雜程度的不同,重繪的時間可能會佔用幾毫秒。因此,如果你需要重複地呼叫該方法,例如在使用動畫的場景下,需要特別注意這一點。
並非所有的瀏覽器都會返回這些值,他們有這個責任麼?
8.matches()
假設我需要檢查某個元素是否包括一個特定的 class。
這是最複雜的方式:
if (myElement.className.indexOf('some-class') > -1) {
// do something
}
複製程式碼
比上面的好一點,但和本文沒什麼關係:
最佳方式:
if (myElement.matches('.some-class')) {
// do something
}
複製程式碼
9.insertAdjacentElement()
我今天才剛學到這一條!它的作用類似於 appendChild() ,但能夠更好地控制插入子元素的具體位置。
parentEl.insertAdjacentElement('beforeend', newEl) 與 parentEl.appendChild(newEl) 的作用是一樣的,但除此之外,你還可以指定 beforebegin、afterbegin 或 afterend 這幾個引數值,元素將按這些值的名稱所示插入相應的位置。
多麼強大的控制能力!
10.contains()
你有沒有遇到過這樣的情形,需要知道某個元素是否被包含在另一個元素中?至少我本人經常會遇到這樣的問題。
打個比方,假設我在處理一個滑鼠點選事件時,需要知道它是發生在一個模態視窗中還是發生在外面(這樣我才能夠關閉這個視窗),我大概會這麼做:
const handleClick = e => {
if (!modalEl.contains(e.target)) modalEl.hidden = true;
};
複製程式碼
程式碼中的 modalEl 是模態視窗的引用,而 e.target 則代表各種發生點選事件的元素。
有趣的是,每當遇到這種情形,在我第一遍寫程式碼的時候,100% 的概率會將其中的判斷邏輯寫反。
哪怕是我提高了警惕,並在儲存程式碼之前嘗試將邏輯顛倒過來寫,仍然還是寫錯。
11.getAttribute()
這毫無疑問是所有元素方法中最沒用的一個,但有一個場景除外。
你是否記得,我在本文的開頭部分曾提到,物件的屬性 property 通常也會對映到它的特性 attribute 中(我在上文中特別用粗體強調了這一點,注意不是斜體)?
但在某一個場景中,這種假設並不成立,這就是某個元素的 href 特性,例如 <a href="/animals/cat">Cat</a>
。
呼叫 el.href 不會返回 /animals/cat,這可能與你的猜測不符。原因在於 元素實現了 HTMLHyperlinkElementUtils 介面,該介面提供了一系列輔助屬性,例如 prototol 與 hash 等等,以展現與連結的目標相關的值。
href 就是其中一個實用的屬性,它將返回完整的 URL,並去掉無用的空格,而不是返回在特性中所指定的相對 URL。
這樣一來,如果你需要獲取 href 特性中的字串字面值,就只能使用 el.getAttribute('href') 方法了。
12.dialog 元素的三大法寶
<dialog>
是一個相對較新的元素,它帶來了兩個還算能用的方法,和一個非常棒的方法。其中 show() 和 close() 方法的功能與你所想象的一樣,我感覺還算可以。
而 showModal() 方法能夠將 <dialog>
元素顯示在頁面的頂層,居中對齊,這正是所期望的模態視窗行為。你無需指定 z-index,或者手動新增一個灰色的背景,也不需要監聽 esc 按鍵以關閉此視窗。瀏覽器能夠理解模態視窗的工作方式,並自動完成你所期望的行為。
13.forEach()
某些情況下,當你獲取到一個元素列表的引用時,可以通過 forEach() 方法進行迭代式呼叫。
用 for() 進行迴圈已經是 2014 年代的老古董了。
假設你需要記錄頁面中所有連結的 URL,可以輸入以下程式碼,只要你不介意看到報錯。
document.getElementsByTagName('a').forEach(el ==> {
console.log(el.href);
});
複製程式碼
也可以這麼做:
document.querySelectorAll('a').forEach(el ==> {
console.log(el.href);
});
複製程式碼
問題出在 getElementsByTagName 與其他類似的 get... 方法返回的是一個 HTMLCollection 介面,而 querySelectorAll 返回的是一個 NodeList 介面。
而 NodeList 介面為我們提供了 forEach() 方法(此外還包括 keys()、values(),和 entries() 等方法 )。
理想的情況下,最好是每個方法都只返回簡單的陣列,而不是返回一些類似陣列的物件。不過別擔心,ECMA 大神為我們提供了 Array.from() 方法,它能夠把所有這些類陣列物件轉化為一個真正的陣列。
所以,這樣的程式碼就能夠正常工作:
Array.from(document.getElementsByTagName('a')).forEach(el ==> {
console.log(el.href);
});
複製程式碼
獎勵關卡:建立了一個陣列之後,你就能夠對其使用 map() 、filter() 和 reduce() 以及其他各種陣列方法了。打個比方,先不管目的是什麼,總之你可以按以下方式返回所有外部連結的陣列:
Array.from(document.querySelectorAll('a'))
.map(el => el.origin)
.filter(origin => origin !== document.origin)
.filter(Boolean);
複製程式碼
我最喜歡的一個方法是 .filter(Boolean),它肯定會給將來的我在除錯問題時帶來無窮的煩惱,哈哈。
14. 表單
或許你已經知道,< form> 有一個 submit() 方法。但或許你不知道表單還有一個 reset() 方法,而且當你需要對錶單元素進行驗證時,還可以呼叫 reportValidity() 方法。
此外,你也可以通過對錶單的 elements 屬性加上元素的 name 特性 的方式呼叫它的屬性。打個比方,myFormEl.elements.email 將返回屬於某個 < form> 中的 < input name="email" /> 元素(‘屬於’,並不代表它一定是一個‘子元素’)。
好吧,其實剛才我是騙你的。elements 並不會返回一個元素列表,而是返回一個控制元件列表(顯然它不是一個陣列,因為沒必要這麼做)。
舉例來說:假設你有三個單選按鈕,每個都有相同的名稱 animal,那麼 formEl.elements.animal 將返回一個單選按鈕集的引用(一個控制元件,三個元素)。
而 formEl.elements.animal.value 將返回所選中的單選按鈕的值。
這種語法看起來非常古怪,讓我們來分解一下看看:formEl 是一個元素,elements 則對應 HTMLFormControlsCollection 介面,這並非一個真正的陣列,其中的每一項內容也未必代表一個 HTML 元素。animal 是多個單選按鈕的集合,只是因為他們具有相同的 name 特性才聚集在一起(RadioNodeList 介面就是為此而生的),而 value 則返回該集合中所選中的那個單選按鈕的 value 特性。
15.select()
我或許應該重新組織一下本文,讓最後一條內容不會顯得過於無足輕重。不管怎樣,.select() 方法會將你指定的元素中的所有內容全選。