獲取網頁指定元素的原生方法回顧

那是個夜黑風高的夜晚,我遇到了一個按鈕: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 元素有哪些方法吧。

瀏覽器原生提供的幾個找到 DOM 元素的方法

document.getElementById

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.getElementsByClassName

這裏的 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');

element.getElementsByName

用法和 getElementsByClassName 類似, 返回一個 HTMLCollection

element.getElementsByTagName

用法和 getElementsByClassName 類似, 返回一個 HTMLCollection

element.querySelector

再熟悉不過的接口了,傳入 CSS 選擇器,返回第一個找到的元素。

element.querySelectorAll

以上返回數組的方法,getElementsByNamegetElementsByTagNamegetElementsByTagNameNSquerySelectorAll 返回的都是 Array-like 的 HTMLCollection

要像對數組同樣對 HTMLCollection 進行操做(使用數組的 forEachfilter 等方法),則須要將他們轉化爲數組:

const buttonCollection = document.getElementsByClassName('submit');

// const buttonArray = Array.prototype.slice.call(buttonCollection);
const buttonArray = [].slice.call(buttonCollection);

buttonArray.forEach(item => {
    // do something
});

querySelector 與 getElementsByName、getElementsByClassName 和 getElementsByTagName 的細微差別

注意,對於都是返回 HTMLCollection 的方法,與 querySelector 不一樣的是,getElementsByNamegetElementsByClassNamegetElementsByTagName 返回的 HTMLCollection 是一個引用類型的 HTMLCollection

也就是:

  1. querySelectorAll 獲取的是當時文檔中匹配選擇器的 DOM 元素,不包含後面插入到文檔中的元素,若是文檔有更新,則須要從新獲取
  2. getElementsByNamegetElementsByClassNamegetElementsByTagName 則能隨時反映文檔中匹配對應規則的元素,包含後面插入到文檔中的元素,若是文檔有更新,不須要從新獲取

這個說法有點相似拷貝和引用的關係。

話很少說,咱們直接寫點代碼作個測試:

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]
*/

還有什麼方法能夠根據 innerText 找到指定元素

回到本文討論的問題,貌似瀏覽器原生並無提供相似 document.getElementByInnerText

找了一圈貌似沒有,那隻能借用 Javascript 了。

回憶一下須要找到的元素是:

<button type="submit">搜索</button>

使用原生 Javascript

可使用 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、textContent 的差別?

簡而言之:

innerText 獲取元素第一層的文本內容,不包含標籤;
innerHTML 如其名獲取內部 HTML 片斷,包含標籤;
textContent 相似 innerText,不過會在去掉標籤後將嵌套的純文本內容也獲取出來。

MDN textContent's difference from innerText and innerHTML

相關文章
相關標籤/搜索