之前我们有提到,JavaScript 如何与浏览器沟通,於是我们讨论到透过 JavaScript 取得 DOM API 节点,使浏览器可以处理使用者行为与互动。
DOM 是 W3C 制定的一个规范,是 HTML 与 XML 文档的编程接口,一个 HTML 文件,我们可以用编辑器以代码的形式展示,也可以用浏览器以页面的形式展示,DOM 将文件档解析为一个由节点和对象 ( 包含属性和方法 ) 组成的结构集合,因此开发者能够在一个规范及方法之下改变内容、结构及样式。
我们可以透过 JavaScript 来获取 DOM 的 document 和 window 节点 API,来操作里面的文件以及讯息。而 DOM 的节点大致归纳为:元素节点、文字节点以及属性节点。
以下为常见的 DOM 选取方法:
// 根据传入的值,找到 DOM 中 id 为 'xxx' 的元素。
document.getElementById('xxx');
// 针对给定的 tag 名称,回传所有符合条件的 NodeList 物件
document.getElementsByTagName('xxx');
// 针对给定的 class 名称,回传所有符合条件的 NodeList 物件。
document.getElementsByClassName('xxx');
// 针对给定的 Selector 条件,回传第一个 或 所有符合条件的 NodeList。
document.querySelector('xxx');
document.querySelectorAll('xxx');
Node 是一个接口,有许多接口都从 Node 继承方法与属性,下面为常用的 DOM 节点:
节点 | 数值 | 说明 |
---|---|---|
Node.ELEMENT_NODE |
1 | 一个元素节点,例如 <p> 和 <div> |
Node.TEXT_NODE |
3 | 实际文字节点,包括了换行与空格 |
Node.COMMENT_NODE |
8 | 一个 Comment 节点 |
Node.DOCUMENT_NODE |
9 | 一个 Document 节点 |
Node.DOCUMENT_TYPE_NODE |
10 | 描述文档类型的 DocumentType 节点。例如 <!DOCTYPE html> 就是用於 HTML5 的 |
Node.DOCUMENT_FRAGMENT_NODE |
11 | 一个 DocumentFragment 节点 |
假如我们要判断一个 Node 是否为一个元素,可以透过以上表格得知属性值,并使用 nodeType
:
if(X.nodeType === 1){
console.log('X 是一个元素');
}
先前我们也有谈论到「DOM Tree」,因此可以知道 DOM 是一层一层的,需要有效的抓取元素则需要弄清楚这一层一层的关系。
父子关系:
除了 document
之外,每一个节点都会有上层与下层的关系,上层称为「父节点 ( Parent node ) 」,下曾则是「子节点 ( Child node ) 」。
兄弟关系:
故名思义就是同一个父亲底下的产物(?),拥有同一个「父节点」的节点,他们的关系就是「兄弟节点 ( Siblings node ) 」。
父关系 API
parentNode
/ 每一个节点都会有一个 parentNode
属性,它表示元素的父节点;我们可以透过 Node.parentNode 来取得父元素,回传的值可能会是元素节点 ( Element )、根节点 ( Document ) 或 DocumentFragment 节点。
<ul><li>text 1</li><li>text 2</li><li>text 3</li></ul>
<script>
var li = document.querySelector('li');
console.log(li.parentNode.nodeName); // "ul"
</script>
parentElement
/ 回传当前节点的父元素节点,如果该元素没有父节点或者父节点不是一个 DOM 元素,则回传 null。
子关系 API
childNodes
/ 回传一个即时的 NodeList,表示元素的子节点列表,子节点可能会包含文本节点,注释节点等,这边我们可以透过 Node.hasChildNodes()
来检查看看 DOM 节点是否有子节点:
var node = document.querySelector('#hi');
// 如果 node 内有子元素
if( node.hasChildNodes() ) {
// 可以透过 node.childNodes[n] (n 为数字索引) 取得对应的节点
// 注意,NodeList 物件内容为即时更新的集合
for (var i = 0; i < node.childNodes[i].length; i++) {
// ...
};
}
Node.childNodes
可能会回传的有:
firstChild
/ 取得 node
第一个子节点,如果没有子节点,则回传 null
。
底下可以看到子节点也会抓取空白行,因此这里抓到的会是 ul
与 li
中间的空白行,所以回传 undefined
。
<ul>
<li>text 1</li>
<li>text 2</li>
<li>text 3</li>
</ul>
<script>
var ul = document.querySelector('ul');
// tagName 属性可以取得 node 的标签名称
console.log(ul.firstChild.tagName); // undefined
</script>
如果我们改成这样:
<ul><li>text 1</li><li>text 2</li><li>text 3</li></ul>
<script>
var ul = document.querySelector('ul');
console.log(ul.firstChild.tagName); // LI
</script>
少了空白行就可以正确抓取我们要的第一个子节点。
lastChild
/ 取得 node
最後一个子节点,如果没有子节点,则回传 null。
<ul>
<li>text 1</li>
<li>text 2</li>
<li>text 3</li>
</ul>
<script>
var ul = document.querySelector('ul');
// textContent 属性可以取得节点内的文字内容
console.log(ul.lastChild.textContent); // '\n\t' (换行字元)
</script>
这边跟 firstChild
****的情况一样,所以我们同样改成底下范例,把空行去掉:
<ul><li>text 1</li><li>text 2</li><li>text 3</li></ul>
<script>
var ul = document.querySelector('ul');
console.log(ul.lastChild.textContent); // 'text 3'
</script>
也成功抓取到我们要的最後一个子节点。
hasChildNodes
/ 这个方法会回传一个布林值,用途在上面有说到,他替我们抓取当前是否含有子节点。
底下给一个范例,如果 id 为 a 的这个元素有子节点,则从 DOM 中删除第一个子节点:
var a = document.getElementById("a");
if ( foo.hasChildNodes() ) {
foo.removeChild(a.childNodes[0]);
}
兄弟关系 API
previousSibling
/ 回传当前节点的前一个兄弟节点,没有则回传 null
。
<ul><li>text 1</li><li>text 2</li><li>text 3</li></ul>
<script>
var li = document.querySelector('li');
// 这里已经没有前一个兄弟节点
console.log(li.previousSibling); // null
// document.querySelectorAll 会取得所有条件符合的元素
// document.querySelectorAll('span')[2] 指的是「第三个」符合条件元素
var li2 = document.querySelectorAll('li')[2];
console.log(li2.previousSibling.textContent); // 'text 2'
</script>
nextSibling
/ 回传当前节点的下一个兄弟节点,没有则回传 null
。
<ul><li>text 1</li><li>text 2</li><li>text 3</li></ul>
<script>
var li = document.querySelector('li');
// 这里直接抓取 text 1 的下一个兄弟节点
console.log(li.nextSibling.textContent); // 'text 2'
</script>
参考资料:
重新认识 JavaScript: Day 12 透过 DOM API 查找节点
JavaScript操作DOM常用的API
<<: Day 20 : 动态爬虫-利用webdriver达到自动登入
>>: [第18天]理财达人Mx. Ada-证券扣款帐户银行余额资讯
制作 VR 虚拟实境游戏控制玩家在 3D 空间移动,实际上是一个重要的问题,使用键盘滑鼠缺乏真实体验...
本篇将简单介绍 TCP 与 UDP,藉由介绍两者的差异,来解释为何 TCP 为什麽会比较耗时间,然而...
After introducing about the 2 methods for timing e...
在了解 JavaScript 如何运行前,首先要先知道,我们所撰写的 JavaScript 是无法直...
本文将要介绍由Andrew Y. Cui撰写的《Forecasting Outcomes of Ma...