那是個夜黑風高的夜晚,我遇到了一個按鈕:html
<button type="submit">搜索</button>
嗯,我要選中它,我敲下了一行代碼:程序員
const submitButton = document.querySelector('button[type="submit"]');
這對於精通 document.querySelector
的函數名書寫方式的我來講,簡直就像吃下四兩飯同樣簡單!數組
可是。瀏覽器
咱們知道,document.querySelector
接收一個選擇器字符串,返回第一個匹配的 DOM
元素,因此若是頁面上只有一個 button[type="submit"]
或者這個 button[type="submit"]
在 html 中是第一個時,我這個方法是無懈可擊的。app
而後,我發現頁面上居然存在兩個 button[type="submit"]
類型的按鈕(黑人問號???)。函數
我對比了一下差別:oop
<button type="submit">提交</button> <button type="submit">搜索</button>
先不八卦爲何頁面上有兩個差很少的按鈕,但能初步斷定的是,他們長得很像,嗯。測試
那麼,問題來了,我怎麼選中那個搜索框,我把問題拋給了自覺得是的本身,獲得了幾個回答。prototype
分身 A:改用 selectorSelectorAll
拿到第二個不就好了嘛!code
const allSubmitButton = document.querySelectorAll('button[type="submit"]'); const submitButton = allSubmitButton[1];
這方法貌似不錯,但萬一這兩個按鈕對調位置了怎麼辦?
分身 B: 那選擇器寫全一點,也匹配『搜索』這兩個字不就能夠把『提交』那個按鈕給拍除掉了嘛!
const submitButton = document.querySelector('button[type="submit"][innerText="搜索"]');
打印出來一看,嗯,怎麼輸出是 null
???
分身 B: 那個,那個。。。innerText
是我編出來的,我不知道正確的要怎麼寫。
嗯,雖然 B 寫錯了語法(哪一個程序員會不寫錯語法?),但限定了兩個條件,type="submit"
和 innerText="搜索"
, 能惟一匹配搜索按鈕了,這個思路是對的。
那麼,正確的語法是什麼呢?
苦苦搜索半天,無果。
分身 C: 那就是寫不出這樣的選擇器吧,仍是用 JS 來作吧 :)
分身 A: 這麼簡單的需求,CSS 是萬能的,爲何要用 Javascript?
分身 B: 對,Hail CSS!
。。。那咱們就藉此機會溫習一下找到一個 DOM 元素有哪些方法吧。
Id 爲網頁全局惟一。
document.getElementById('id');
注意,與其餘方法不同,getElementById
只能被全局對象 document
調用,不然會報錯:
document.getElementById('parentId').getElementById('childId'); // Uncaught TypeError: document.getElementById(...).getElementById is not a function
由於 Id 是全局惟一的,因此上面第二種寫法天然也顯得沒有必要。
Id 對大小寫敏感(除非文檔被聲明爲特殊類型如: XHTML, XUL),因此如下兩種寫法並不等同:
document.getElementById('id'); documen.getElementById('ID');
這裏的 element
指代網頁中有效的 DOM 元素,包含 document
。
返回一個 HTMLCollection
。
// 匹配 class 包含 'submit' 的元素 document.getElementsByClassName('submit'); // 匹配 class 包含 'submit' 和 'button' 的元素 document.getElementsByClassName('submit button'); // 級聯 cons selectedElement = document.getElementById('app-container'); selectedElement.getElementsByClassName('submit button');
用法和 getElementsByClassName 類似
, 返回一個 HTMLCollection
。
用法和 getElementsByClassName 類似
, 返回一個 HTMLCollection
。
再熟悉不過的接口了,傳入 CSS 選擇器,返回第一個找到的元素。
以上返回數組的方法,getElementsByName
、getElementsByTagName
、getElementsByTagNameNS
、querySelectorAll
返回的都是 Array-like 的 HTMLCollection
。
要像對數組同樣對 HTMLCollection
進行操做(使用數組的 forEach
、filter
等方法),則須要將他們轉化爲數組:
const buttonCollection = document.getElementsByClassName('submit'); // const buttonArray = Array.prototype.slice.call(buttonCollection); const buttonArray = [].slice.call(buttonCollection); buttonArray.forEach(item => { // do something });
注意,對於都是返回 HTMLCollection
的方法,與 querySelector
不一樣的是,getElementsByName
、getElementsByClassName
、getElementsByTagName
返回的 HTMLCollection
是一個引用類型的 HTMLCollection
。
也就是:
querySelectorAll
獲取的是當時文檔中匹配選擇器的 DOM 元素,不包含後面插入到文檔中的元素,若是文檔有更新,則須要從新獲取getElementsByName
、getElementsByClassName
和 getElementsByTagName
則能隨時反映文檔中匹配對應規則的元素,包含後面插入到文檔中的元素,若是文檔有更新,不須要從新獲取這個說法有點相似拷貝和引用的關係。
話很少說,咱們直接寫點代碼作個測試:
const createElement = (func) => { const key = 'myCustomElement'; let element; switch (func) { case 'getElementsByName': element = document.createElement('div'); element.setAttribute('name', key); break; case 'getElementsByClassName': element = document.createElement('div'); element.setAttribute('class', key); break; case 'getElementsByTagName': element = document.createElement(key); break; case 'querySelectorAll': element = document.createElement('div'); element.className = key; break; default: element = document.createElement('div'); } return element; } const getCollection = (root, func) => { const key = 'myCustomElement'; let element; if (func === 'getElementsByName') { return document.getElementsByName(key); } if (func === 'querySelectorAll') { return root.querySelectorAll(`.${key}`); } return root[func](key); } const testFunc = (func) => { const result = []; // 避免 getElementsByClassName 和 querySelectorAll 在統一環境的影響,建立一個獨立的容器測試 const container = document.createElement('div'); document.body.append(container); // 1. 插入一個 container.append(createElement(func)); // 2. 看下如今有多少個 const collection = getCollection(container, func); result.push(collection.length); // 3. 繼續插入一個 container.append(createElement(func)); // 4. 看下如今有多少個 result.push(collection.length); return result; }; console.log('getElementsByName', testFunc('getElementsByName')); // [1, 2] console.log('getElementsByClassName', testFunc('getElementsByClassName')); // [1, 2] console.log('getElementsByTagName', testFunc('getElementsByTagName')); // [1, 2] console.log('querySelectorAll', testFunc('querySelectorAll')); // [1, 1] // 注意這個輸出 // 輸出的是: /* getElementsByName [1, 2] getElementsByClassName [1, 2] getElementsByTagName [1, 2] querySelectorAll [1, 1] */
回到本文討論的問題,貌似瀏覽器原生並無提供相似 document.getElementByInnerText
?
找了一圈貌似沒有,那隻能借用 Javascript 了。
回憶一下須要找到的元素是:
<button type="submit">搜索</button>
可使用 querySelectorAll
:
let foundElement = null; const elementsCollection = document.querySelectorAll('button[type="submit"]'); const elementArray = [].slice.call(elementsCollection); /* // 使用 Array.forEach 遍歷,缺點是找到後無法 break elementArray.forEach(element => { if (element.innerText.trim() === '搜索') { foundElement = element; } // 或者使用正則 /* if (/^\s{0,}搜索\s{0,}$/.test(element.innerText)) { foundElement = element; } */ }); */ /* // 或使用 for-loop 找到後提早 break const len = inputElementArray.length; for (let i = 0; i < len; i++) { if (elementArray[i].innerText.trim()) { foundElement = elementArray[i]; break; } } */ // 或使用 filter const foundElementArray = elementArray.filter(element => element.innerText.trim() === '搜索'); if (foundElementArray.length) { foundElement = foundElementArray[0]; }
簡而言之:
innerText 獲取元素第一層的文本內容,不包含標籤;
innerHTML 如其名獲取內部 HTML 片斷,包含標籤;
textContent 相似 innerText,不過會在去掉標籤後將嵌套的純文本內容也獲取出來。