那是个夜黑风高的夜晚,我遇到了一个按钮:
<button type="submit">搜索</button>
嗯,我要选中它,我敲下了一行代码:
const submitButton = document.querySelector('button[type="submit"]');
这对于精通 document.querySelector
的函数名书写方式的我来说,简直就像吃下四两饭一样简单!
但是。
我们知道,document.querySelector
接收一个选择器字符串,返回第一个匹配的 DOM
元素,所以如果页面上只有一个 button[type="submit"]
或者这个 button[type="submit"]
在 html 中是第一个时,我这个方法是无懈可击的。
然后,我发现页面上竟然存在两个 button[type="submit"]
类型的按钮(黑人问号???)。
我对比了一下差异:
<button type="submit">提交</button>
<button type="submit">搜索</button>
先不八卦为什么页面上有两个差不多的按钮,但能初步判定的是,他们长得很像,嗯。
那么,问题来了,我怎么选中那个搜索框,我把问题抛给了自以为是的自己,得到了几个回答。
分身 A:改用 selectorSelectorAll
拿到第二个不就行了嘛!
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
以上返回数组的方法,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
});
querySelector 与 getElementsByName、getElementsByClassName 和 getElementsByTagName 的细微差异
注意,对于都是返回 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]
*/
还有什么方法可以根据 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,不过会在去掉标签后将嵌套的纯文本内容也获取出来。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。