【使用 DOM】使用 DOM 元素
1. 使用元素对象
HTMLElement对象提供了一组属性,可以用它们来读取和修改被代表的数据。下表介绍了这些属性。
下面代码展示了如何使用表中所列的一些基本属性。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>使用基本元素数据属性</title> <style> p {border: medium double black;} </style> </head> <body> <p id="textblock" dir="ltr" lang="en-US"> There are lots of different kinds of fruit - there are over 500 varieties of <span id="banana">banana</span> alone. Bt the time we add the countless types of <span id="apple">apples</span>, <span id="orange">oranges</span>, and other well-known fruit, we are faced with thousands of choices. </p> <pre id="results"></pre> <script> var results =document.getElementById("results"); var elem = document.getElementById("textblock"); results.innerHTML += "tag: " + elem.tagName + "\n"; results.innerHTML += "id: " + elem.id + "\n"; results.innerHTML += "dir: " + elem.dir + "\n"; results.innerHTML += "lang: " + elem.lang + "\n"; results.innerHTML += "hidden: " + elem.hidden + "\n"; results.innerHTML += "disabled: " + elem.disabled + "\n"; </script> </body> </html>
从下图可以看到浏览器为这些属性所提供的结果。
1.1 使用类
可以用两种方式处理某个元素所属的类。第一种方式是使用className属性,它会返回类名的字符串。通过改变这个字符串的值,就能添加或移除类。
PS:类的一个常见用途是有针对性地给元素应用样式。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>使用className属性</title> <style> p {border: medium double black;} p.newclass {background-color: grey; color: white;} </style> </head> <body> <p id="textblock"> It's now or never,never tomorrow,and never next second! </p> <button id="pressme">Press Me</button> <script> document.getElementById("pressme").onclick = function(e){ document.getElementById("textblock").className += " newclass"; } </script> </body> </html>
此例中,点击按钮会触发脚本,然后使一个新的类被附加到元素的类列表上。请注意,需要给附加到className属性的值添加一个前置空格。这是因为浏览器期望获得由空间间隔的类列表。当做出这样的修改后,浏览器就会应用那些基于类选择器的样式,这就意味着示例会发生明显的视觉变换,如下图所示:
当想要快速给某个元素添加类时,className属性是易于使用的,但如果想要做别的事情(比如移除一个类),用它就很困难了。不过,还可以使用classList属性,它返回的是一个DOMTokenList对象。这个对象定义了一些有用的方法和属性来管理类列表,如下表所示:
除了这些属性和方法,还可以使用数组风格的表示法,通过索引来获得类。下面代码展示了如何使用DOMTokenList对象。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>使用className属性</title> <style> p {border: medium double black;} p.newclass {background-color: grey; color: white;} </style> </head> <body> <p id="textblock" class="class1 class2"> It's now or never,never tomorrow,and never next second! </p> <pre id="results"></pre> <button id="toggle">Toggle Class</button> <script> var results = document.getElementById("results"); document.getElementById("toggle").onclick = toggleClass; listClasses(); function listClasses(){ var classlist = document.getElementById("textblock").classList; results.innerHTML = "Current classes: "; for(var i=0;i<classlist.length;i++){ results.innerHTML += classlist[i] + " "; } } function toggleClass(){ document.getElementById("textblock").classList.toggle("newclass"); listClasses(); } </script> </body> </html>
此例中,listClasses 函数使用classList属性来获取和枚举p元素所属的类,并使用数组风格的索引表示法来得到类名。
toggleClass 函数会在点击按钮时被调用,它使用toggle方法添加和移除一个名为newclass的类。这个类关联了一个样式,从下图可以看到类的变化所带来的视觉效果。
1.2 使用元素属性
HTMLElement对象既有一些属性来对应最重要的HTML全局属性,又支持对单个元素的任意属性进行读取和设置。下表介绍了HTMLElement对象为这个目的所定义的可用方法和属性。
这四种操作属性的方法易于使用,所表现的行为也是可预料的。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>使用属性方法</title> <style> p {border: medium double black;} </style> </head> <body> <p id="textblock" class="class1 class2"> It's now or never,never tomorrow,and never next second! </p> <pre id="results"></pre> <script> var results = document.getElementById("results"); var elem = document.getElementById("textblock"); results.innerHTML = "Element has lang attribute: " + elem.hasAttribute("lang") + "\n"; results.innerHTML += "Adding lang attribute\n"; elem.setAttribute("lang","en-US"); results.innerHTML += "Attr value is: " + elem.getAttribute("lang") + "\n"; results.innerHTML += "Set new value for lang attribute\n"; elem.setAttribute("lang","en-UK"); results.innerHTML += "Value is now: " + elem.getAttribute("lang") + "\n"; </script> </body> </html>
此例中,检查、添加并修改了lang属性的值。
(1)使用data-开头的属性
在DOM里可以通过dataset属性来操作这些自定义属性,它会返回一个包含值的数组,其索引根据是名称的自定义部分。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>使用dataset属性</title> <style> p {boeder:medium double black;} </style> </head> <body> <p id="textblock" class="class1 class2" data-sentence="encouragement" data-sentiment="like"> It's now or never,never tomorrow,and never next second! </p> <pre id="results"></pre> <script> var results = document.getElementById("results"); var elem = document.getElementById("textblock"); for(var attr in elem.dataset){ results.innerHTML += attr + "\n"; } results.innerHTML += "Value of data-sentence attr: " + elem.dataset["sentence"]; </script> </body> </html>
dataset属性返回的数值不像通常的数组那样根据位置进行索引。如果想要枚举以data-* 开头的各个属性,可以使用一条for...in语句,如示例所示。除此之外,还可以通过名称来请求值。请注意只需要提供属性名称的自定义部分。例如,如果想要获得data-sentence 属性的值,请应该请求dataset["sentence"]的值。此例的脚本效果如下图所示:
(2)使用所有属性
可以通过attributes属性获得一个包含某元素所有属性的集合,它会返回一个由Attr对象构成的数组。
下面的例子展示了如何使用attributes属性和Attr对象来读取与修改某个元素的属性。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>使用attributes属性</title> <style> p {border: medium double green;} </style> </head> <body> <p id="textblock" class="class1 class2" data-sentence="encouragement" data-sentiment="like"> It's now or never,never tomorrow,and never next second! </p> <pre id="results"></pre> <script> var results = document.getElementById("results"); var elem = document.getElementById("textblock"); var attrs = elem.attributes; for(var i=0; i<attrs.length;i++){ results.innerHTML += "Name: " + attrs[i].name + " Value: " + attrs[i].value + "\n"; } attrs["data-sentence"].value = "now"; results.innerHTML += "Value of data-sentence attr: " + attrs["data-sentence"].value; </script> </body> </html>
正如从此例中看到的,Attr对象数组中的各个属性同时根据位置和名称进行索引。此例中,枚举了应用到某个元素上的属性名称和值,然后修改了其中一个值。
2. 使用 Text 对象
元素的文本内容是由Text对象代表的,它在文档模型里表现为元素的子对象。下面的代码展示了带有一段文本内容的元素。
<p id="textblock" class="class1 class2" data-sentence="encouragement" data-sentiment="like"> It's now or never,never tomorrow,and never next second! </p>
当浏览器在文档模型里生成p元素的代表时,元素自身会有一个HTMLElement对象,内容则会有一个Text对象,如下图所示:
如果一个元素拥有多个子对象且它们都包含文本,那么这些对象都会以同样的方式进行处理。下面代码给段落添加了一个元素:
<p id="textblock" class="class1 class2" data-sentence="encouragement" data-sentiment="like"> It's <b>now</b> or never,never tomorrow,and never next second! </p>
b元素的添加改变了用于代表p元素及其内容的节点层级结构,如下图所示:
p元素的第一个子对象是个Text对象,它代表从文本块开头到b元素的文本。然后是b元素,它有着自己的Text子对象,代表开始标签和结束标签之间的文本。接下来是p元素的最后一个子对象,这个Text对象代表b元素之后直到文本块末尾的文本。
不幸的是,没有什么方便的方法能定位Text元素,只能先找到它们的父元素对象,然后在其子对象中查找。这使得Text元素不必要地难以使用。
下面的例子展示了某些Text元素方法和属性的用法。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>处理文本</title> <style> p {border: medium double green;} </style> </head> <body> <p id="textblock" class="class1 class2" data-sentence="encouragement" data-sentiment="like"> It's <b>now</b> or never,never tomorrow,and never next second! </p> <button id="pressme">Press Me</button> <pre id="results"></pre> <script> var results = document.getElementById("results"); var elem = document.getElementById("textblock"); document.getElementById("pressme").onclick = function(){ var textElem = elem.firstChild; results.innerHTML = "The element has " + textElem.length + " chars\n"; textElem.replaceWholeText("It is "); } </script> </body> </html>
当button元素被按下,会显示吃p元素第一个Text子对象里的字符数量。并用 replaceWholeText 方法修改它的内容。
PS:操作文本时需要注意:空白字符是不会被压缩的。这就意味着用来组织HTML结构的空格和其他空白字符都会被计算为文本的一部分。
3. 修改模型
前面有展示如何使用DOM来修改各个元素。比如,可以修改它们的属性和文本内容。能够这么做的原因是文档自身与DOM之间有着实时的连接。一旦对DOM做了改动,浏览器就会让文档发生相应的变化。可以进一步利用这种连接来改变文档自身的结构,按照任何想要的方式添加、移除和复制元素。具体而言就是改动DOM的层级结构,因为连接是实时的,所以对层级结构所做的改动会立即反映到浏览器中。下表介绍了可用于修改DOM层级结构的属性和方法。
这些属性和方法对所有元素对象都是可用的。另外,document对象定义了两个允许创建新元素的方法。当想给文档添加内容时它们至关重要。下表介绍了这些创建方法。
3.1 创建和删除元素
需要通过document对象创建新的元素,然后找到一个现存的HTMLElement,并使用之前介绍的方法来插入它。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>创建和删除元素</title> <style> table{ border: solid thin green; border-collapse: collapse; margin: 10px;} td { padding: 4px 5px;} </style> </head> <body> <table border="1"> <thead><th>Name</th><th>Color</th></thead> <tbody id="fruitsBody"> <tr><td>Banana</td><td>Yellow</td></tr> <tr><td>Apple</td><td>Red/Green</td></tr> </tbody> </table> <button id="add">Add Element</button> <button id="remove">Remove Element</button> <script> var tableBody = document.getElementById("fruitsBody"); document.getElementById("add").onclick = function(){ var row = tableBody.appendChild(document.createElement("tr")); row.setAttribute("id","newrow"); row.appendChild(document.createElement("td")).appendChild(document.createTextNode("Plum")); row.appendChild(document.createElement("td")).appendChild(document.createTextNode("Purple")); } document.getElementById("remove").onclick = function(){ var row = document.getElementById("newrow"); row.parentNode.removeChild(row); } </script> </body> </html>
此例使用DOM来添加和移除一张HTML table的行。在添加行时,会首先创建一个tr元素,然后把它作为td和Text对象的父元素。
3.2 复制元素
可以使用 cloneNode 方法来复制现有的元素。这个方法有时候很方便,因为它允许不必从头开始创建想要的元素。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>复制元素</title> <style> table{ border: solid thin green; border-collapse: collapse; margin: 10px;} td { padding: 4px 5px;} </style> </head> <body> <table border="1"> <thead><tr><th>Multiply</th><th>Result</th></tr></thead> <tbody id="fruitsBody"> <tr><td class="sum">1 × 1</td><td class="result">1</td> </tr> </tbody> </table> <button id="add">Add Row</button> <script> var tableBody = document.getElementById("fruitsBody"); document.getElementById("add").onclick = function(){ var count = tableBody.getElementsByTagName("tr").length + 1; var newElem = tableBody.getElementsByTagName("tr")[0].cloneNode(true); newElem.getElementsByClassName("sum")[0].firstChild.data = count + " × " + count; newElem.getElementsByClassName("result")[0].firstChild.data = count * count; tableBody.appendChild(newElem); } </script> </body> </html>
此例通过复制表格里现有的一行来创建更多行。cloneNode 方法的布尔值参数指定了是否应该同时复制所有该元素的所有子元素。
3.3 移动元素
要把元素从文档的移除移到另一处,需要做的仅仅是把待移动的元素关联到新的父元素上,而不需要让该元素脱离它的初始位置。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>移动元素</title> <style> table{ border: solid thin green; border-collapse: collapse; margin: 10px;} td { padding: 4px 5px;} p { clear: left;} </style> </head> <body> <table border="1"> <thead><tr><th>Fruit</th><th>Color</th></tr></thead> <tbody> <tr><td>Banana</td><td>Yellow</td></tr> <tr id="apple"><td>Apple</td><td>Red/Green</td></tr> </tbody> </table> <table border="1"> <thead><tr><th>Fruit</th><th>Color</th></tr></thead> <tbody id="fruitsBody"> <tr><td>Plum</td><td>Purple</td></tr> </tbody> </table> <p> <button id="move">Move Row</button> </p> <script> document.getElementById("move").onclick = function(){ var elem = document.getElementById("apple"); document.getElementById("fruitsBody").appendChild(elem); } </script> </body> </html>
当button按钮被按下后,脚本会移动id为apple的tr元素,具体做法是在id为fruitsBody的tBody元素上调用appendChild方法。这么做就实现了把该行从一个表格移动到另一个表格的效果。
3.4 比较元素对象
可以用两种方式来比较元素对象。第一种方式是简单地检查它们是否代表了同一个元素,用 isSameNode方法可以做到这一点。这能够比较从不同查询中获得的对象。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>比较元素对象</title> <style> table{ border: solid thin green; border-collapse: collapse; margin: 10px;} td { padding: 4px 5px;} </style> </head> <body> <table border="1"> <thead><tr><th>Fruit</th><th>Color</th></tr></thead> <tbody id="fruitsBody"> <tr id="plumrow"><td>Plum</td><td>Purple</td></tr> </tbody> </table> <pre id="results"></pre> <script> var elemByID = document.getElementById("plumrow"); var elemByPos = document.getElementById("fruitsBody").getElementsByTagName("tr")[0]; if (elemByID.isSameNode(elemByPos)){ document.getElementById("results").innerHTML = "Objects are the same"; } </script> </body> </html>
此例中的脚本用了两种不同的技巧i定位元素对象:通过id搜索和通过父元素里的标签类型搜索。isSameNode 方法在比较这些对象时会放回true,因为它们代表的是同一个元素。
另一种方式是测试元素对象是否相同,可以用isEqualNode 方法做到这一点。如果多个元素具有相同的类型,带有同样的属性值,其子元素也相同并且顺序一致,那么它们就是相同的。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>使用相同元素</title> <style> table{ border: solid thin green; border-collapse: collapse; margin: 10px;} td { padding: 4px 5px;} </style> </head> <body> <table border="1"> <thead><tr><th>Fruit</th><th>Color</th></tr></thead> <tbody> <tr class="plumrow"><td>Plum</td><td>Purple</td></tr></tbody> </table> <table border="1"> <thead><tr><th>Fruit</th><th>Color</th></tr></thead> <tbody> <tr class="plumrow"><td>Plum</td><td>Purple</td></tr></tbody> </table> <pre id="results"></pre> <script> var elems = document.getElementsByClassName("plumrow"); if (elems[0].isEqualNode(elems[1])){ $("results").innerHTML = "Elements are equal"; }else { $("results").innerHTML = "Element are not equal"; } function $(id){ return document.getElementById(id); } </script> </body> </html>
此例中,虽然两个tr元素各自独立存在并处于文档的不同位置,但它们是相同的。如果改变了其中的任何属性或者td子元素里的内容,那么这两个元素就不再是想通了。
3.5 使用HTML片段
innerHTML属性,outerHTML属性和insertAdjacentHTML方法都是便利的语法捷径,它们能让你能够使用HTML片段。从而不再需要创建元素和文本对象的详细层级结构。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>使用innerHTMLh和outerHTML属性</title> <style> table{ border: solid thin green; border-collapse: collapse; margin: 10px;} td { padding: 4px 5px;} p { clear: left;} </style> </head> <body> <table border="1"> <thead><tr><th>Fruit</th><th>Color</th></tr></thead> <tbody> <tr id="applerow"><td>Plum</td><td>Purple</td></tr> </tbody> </table> <textarea rows="3" id="results"></textarea> <p> <button id="inner">Inner HTML</button> <button id="outer">Outer HTML</button> </p> <script> var results = $("results"); var row = $("applerow"); $("inner").onclick = function(){ results.innerHTML = row.innerHTML; } $("outer").onclick = function(){ results.innerHTML = row.outerHTML; } function $(id){ return document.getElementById(id); } </script> </body> </html>
outerHTML 属性返回一个字符串,它包含定义这个元素及其所有子元素的HTML。innerHTML属性则只返回子元素的HTML。此例中,定义了一对按钮来显示某个表格行的内部和外部HTML。选择在一个textarea元素里显示内容,这样浏览器就会把这些属性返回的字符串视作文本,而非HTML。此例的效果如下:
(1)改变文档结构
也可以使用outerHTML和innerHTML属性来改变文档的结构。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>修改文档结构</title> <style> table{ border: solid thin green; border-collapse: collapse; margin: 10px;} td { padding: 4px 5px;} p { clear: left;} </style> </head> <body> <table border="1"> <thead><tr><th>Fruit</th><th>Color</th></tr></thead> <tbody> <tr><td>Banana</td><td>Yellow</td></tr> <tr id="apple"><td>Apple</td><td>Red/Green</td></tr> </tbody> </table> <table border="1"> <thead><tr><th>Fruit</th><th>Color</th></tr></thead> <tbody id="fruitsBody"> <tr><td>Plum</td><td>Purple</td></tr> <tr id="targetrow"><td colspan="2">This is the placeholder</td> </tr> </tbody> </table> <p> <button id="move">Move Row</button> </p> <script> document.getElementById("move").onclick = function(){ var source = document.getElementById("apple"); var target = document.getElementById("targetrow"); target.innerHTML = source.innerHTML; source.outerHTML = '<tr id="targetrow"><td colspan="2">This is the placeholder</td> </tr>'; } </script> </body> </html>
此例中,用innerHTML属性设置了某个表格行的子元素,并用outerHTML内联替换了某个元素。这些属性处理的是字符串,这就意味着可以通过读取属性值或从头创建字符串来得到HTML片段。
(2)插入HTML片段
innerHTML和outerHTML属性对于替换现有的元素而言是很有用的,但是如果想要用HTML片段来插入新元素,就必须使用insertAdjacentHTML方法。这个方法需要两个参数:第一个参数是下表中的某个值,它指明片段应该被插入到相对于当前元素的哪个位置,第二个参数是要插入的片段。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>使用insertAdjacentHTML方法</title> </head> <body> <table border="1"> <thead><tr><th>Fruit</th><th>Color</th></tr></thead> <tbody id="fruitsBody"> <tr id="targetrow"><td>Placeholder</td></tr> </tbody> </table> <p> <button id="ab">After Begin</button> <button id="ae">After End</button> <button id="bb">Before Begin</button> <button id="be">Before End</button> </p> <script> var target = document.getElementById("targetrow"); var buttons = document.getElementsByTagName("button"); for(var i=0;i<buttons.length;i++){ buttons[i].onclick =handlerButtonPress; } function handlerButtonPress(e){ if(e.target.id == "ab"){ target.insertAdjacentHTML("afterbegin","<td>After Begin</td>"); }else if(e.target.id == "ae"){ target.insertAdjacentHTML("afterend","<td>After End</td>"); }else if(e.target.id == "bb"){ target.insertAdjacentHTML("beforebegin","<tr><td colspan='2'>Before Begin</td></tr>"); }else if(e.target.id == "be"){ target.insertAdjacentHTML("beforeend","<tr><td colspan='2'>Before End</td></tr>"); } } </script> </body> </html>
此例中,使用不同的定位值来演示如何将HTML片段插入不同的位置。这个例子最好的理解是在浏览器中实际操作一下,下图可以看到其基本效果:
3.6 向文本块插入元素
修改模型的另一种方式是向由Text代表的文本块添加元素。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>将一个元素插入文本</title> </head> <body> <p id="textblock"> It's now or never,never tomorrow,and never next second! </p> <p> <button id="insert">Insert Element</button> </p> <script> document.getElementById("insert").onclick = function(){ var textBlock = document.getElementById("textblock"); textBlock.firstChild.splitText(10); var newText = textBlock.childNodes[1].splitText(4).previousSibling; textBlock.insertBefore(document.createElement("b"),newText).appendChild(newText); } </script> </body> </html>
此例中,从现有的文本取出一个单词,然后让它变成新元素b的一个子元素。