1. 什么是DOM: document object model
DOM: 专门操作网页内容的一套函数和对象
DOM还是一个标准,由W3C制定
为什么:
广义JS=ECMAScript + DOM + BOM
核心语法 操作网页内容 访问浏览器软件
要想操作网页内容,为页面添加交互效果,其实只能用DOM函数和对象。
问题: 早起的DOM没有标准
解决: W3C制定了统一的DOM函数和对象的标准。
几乎所有浏览器100%兼容。
特例: IE8
何时: 只要操作网页内容,为网页添加交互行为,只能用DOM
包括: 5件事: 增删改查 事件绑定
2. DOM树:
什么是: 在内存中,集中保存一个网页中所有内容的树形结构
为什么: 树形结构是最直观的保存上下级包含关系的数据结构。
而网页中的HTML标签,刚好也是父子嵌套的上下级包含关系。
所以,网页中每一项内容,在内存中,都是存在一棵树形结构上的。
如何:
1. 当浏览器读取到一个.html文件时,会先在内存中创建一个document对象,
作为整棵树的树根对象
2. 开始扫描.html中每个元素,文本等内容。每扫描到一项内容,
就在document下对应位置创建一个节点(node)对象。
3. 查找元素:
1. 不需要查找,就可直接获得:
document.documentElement <html>
document.head <head>
document.body <body>
2. 按节点间关系查找:
树上的每个节点都不是孤立存在的。都和上下左右的节点之间有各种各样的关系,可以互相访问到。
包括:
节点树: 包含所有节点内容的完整树结构
2大类关系:
1. 父子关系:
节点.parentNode 获得当前节点的父节点
父节点.childNodes 获得当前父节点下的所有直接子节点的集合。
强调: childNodes返回的是一个类数组对象,今后我都简称集合。
父节点.firstChild 获得当前父节点下的第一个直接子节点。
父节点.lastChild 获得当前父节点下的最后一个直接子节点。
2. 兄弟关系:
节点.previousSibling 获得当前节点平级的前一个相邻的兄弟节点
节点.nextSibling 获得当前节点平级的下一个相邻的兄弟节点
问题: 同时包含看不见的换行和空字符,也是节点对象,严重干扰查找。
元素树: 仅包含元素节点的树结构。
说明: 元素树不是一棵新树,而是原来节点树中添加了仅指向元素节点的新属性而形成一棵子树结构。
2大类关系:
1. 父子关系:
元素.parentElement 获得当前元素的父元素。
说明: 其实也可以用parentNode。
父元素.children 获得当前父元素下的所有直接子元素的集合。
强调: children返回的是一个类数组对象,今后我都简称集合。
父元素.firstElementChild 获得当前父素下的第一个直接子元素。
父元素.lastElementChild 获得当前父元素下的最后一个直接子元素。
2. 兄弟关系:
元素.previousElementSibling 获得当前元素平级的前一个相邻的兄弟元素
元素.nextElementSibling 获得当前元素平级的下一个相邻的兄弟元素
总结: 今后只要按节点间关系查找,都用元素树的属性代替节点树的旧属性。
何时: 今后只要已经获得一个节点对象,找周围附近的节点时。就用节点间关系查找。
定义一个函数,遍历一个指定的父元素下的所有后代元素:
1. 定义一个函数,仅遍历直接子元素
2. 如果当前子元素有更下级直接子元素,则对当前子元素继续调用当前函数,查找子元素的直接子元素
function getChildren(parent){
//arguments.callee->getChildren
//1. 先获得当前父元素下的所有直接子元素
var children=parent.children;
//遍历所有直接子元素
for(var child of children){
//输出当前子元素的标签名
console.log( child.nodeName )
//元素的标签名
//2. 如果child有下级直接子元素
if(child.children.length>0){
//就对child继续调用getChildren
arguments.callee(child)
}
}
}
//查找body下所有后代元素
getChildren(document.body);
3. 按HTML特征查找:
4种:
1. 按id属性查找
var 元素=document.getElementById("id")
在整个页面中查找id为指定名称的一个元素
返回值: 如果找到,返回一个元素对象
如果没找到,返回null
强调: 只能用document调用。不能再随意父元素上调用。
2. 按标签名查找
var 集合=父元素.getElementsByTagName("标签名")
在指定父元素下查找所有标签名为指定标签名的后代元素。
返回值: 如果找到,返回多个元素组成的集合
如果没找到,返回空集合: [].length=0
强调:
1. 可在任意父元素下查找。通常指定在某个父元素下查找后代,是为了减少查找范围,提高查找效率。
2. 不止查找直接子元素,而是在所有后代中查找。
比如: nav.getElementsByTagName("li")
<ul id="nav">
<li>
<ul>
<li></li>
<li></li>
<li></li>
</ul>
</li>
<li>
<ul>
<li></li>
<li></li>
<li></li>
</ul>
</li>
</ul>
3. 按class属性查找
var 集合=父元素.getElementsByClassName("类名")
在指定父元素内,查找class属性中包含指定类名的所有元素
返回值: 如果找到,返回多个元素组成的集合
如果没找到,返回空集合: [].length=0
强调:
1. 可限制在任意父元素内查找,减少查找范围。
2. 不仅查找直接子元素,且在所有后代中查找。
3. 如果一个元素同时被多个class修饰,则使用其中一个class,就可找到该元素。无需所有class都满足
比如: nav.getElementsByClassName("active")
nav.getElementsByClassName("child")
<ul id="nav">
<li class="item parent">
<ul>
<li class="item child active"></li>
<li class="item child"></li>
<li class="item child"></li>
</ul>
</li>
<li>
<ul>
<li class="item child active"></li>
<li class="item child"></li>
<li class="item child"></li>
</ul>
</li>
</ul>
4. 按name属性查找
var 集合=document.getElementsByName("name名")
在整个网页中查找name属性值为指定name名的元素。
返回值: 如果找到,返回多个元素组成的集合
如果没找到,返回空集合: [].length=0
强调: 只能用document调用,不能用随意一个父元素调用。
何时: 在表单中查找表单元素时
问题: 所有返回集合的查找函数,在任何情况下都会返回一个集合。即使只找到一个元素,也放在一个集合中返回。
比如: <form>
<input name="uname">
var 集合=document.getElementsByName("uname")
集合: [ <input> ]
如果想使用集合中返回的这唯一的一个对象,应该?
解决: var 文本框对象=
document.getElementsByName("uname")[0]
其中: [0]表示取出集合中仅有的一个元素对象,单独使用。
以上四种查找方式,最大的问题是:
一次只能用一个特征作为查找条件。
如果查找条件非常复杂时,代码会非常繁琐
解决: 4. 用选择器当条件查找元素: 2个函数:
1. 只查找一个符合条件的元素:
var 元素=父元素.querySelector("任意复杂的选择器")
在指定父元素下查找符合选择器要求的一个元素
返回值: 如果找到,返回一个元素对象
如果没找到,返回-1
2. 查找多个符合条件的元素:
var 集合=父元素.querySelectorAll("任意复杂的选择器")
在指定父元素下查找符合选择器要求的多个元素
返回值: 如果找到,返回多个元素的集合
如果没找到,返回空集合: [].length=0
强调: 1. 可以以任意父元素调用来限制查找的范围
2. ()中的选择器参数,不用每次都写完整。只要以.前的父元素为起点开始写就行!
ul.querySelectorAll("li li")
<ul>
<li>
<ul>
<li>
<li>
<li>
- 不仅查找直接子元素,且在所有后代中查找符合条件的。
总结:
1. 不需要查找就可直接获得:
document.documentElement <html>
document.head <head>
document.body <body>
2. 按节点间关系查找:
1. 父子关系;
元素.parentNode
父元素.children
元素.firstElementChild
元素.lastElementChild
2. 兄弟关系:
元素.previousElementSibling
元素.nextElementSibling
3. 按HTML特征查找:
var 元素=document.getElementById("id")
var 集合=父元素.getElementsByTagName("标签名")
var 集合=父元素.getElementsByClassName("class名")
var 集合=document.getElementsByName("name名")
4. 按选择器查找:
var 元素=父元素.querySelector("任意选择器")
var 集合=父元素.querySelectorAll("任意选择器")
DOM优化之一:
如果只用一个条件就可以找到想要的元素时
首选使用getElementsByXXX()函数查找——效率高
只有查找条件复杂时,才选择按选择器查找——效率低
1. 修改: 3样:
1. 内容: 3个属性:
1. 获取或修改原始HTML片段: 元素.innerHTML
2. 获取或修改纯文本内容: 元素.textContent
vs innerHTML
1. textContent去掉了内嵌的标签
2. textContent将特殊符号翻译为正文
以上两个获得的都是双标记中,开始标记和结束标记之间的内容
问题: 表单元素是单标记,没有结束标记,所以也就没有innerHTML和textContent
3. 获取或设置表单元素的内容: 表单元素.value
2. 属性: 3种:
1. HTML标准属性:
什么是: HTML标准中规定的那些字符串类型的属性。比如: id, title, class, name, value, src, href...
获取或修改HTML标准属性: 2种方式:
1. 早期的核心DOM函数: 4个:
var 属性值=元素.getAttribute("属性名") 获取属性值
元素.setAttribute("属性名","新值") 修改属性值
var bool=元素.hasAttribute("属性名") 判断元素是否包含该属性
元素.removeAttribute("属性名") 移除元素上的指定属性
2. 后来的HTML DOM函数:
什么是HTML DOM: 是在旧版基础上,对部分常用函数和常用对象提供的简化版函数。
做的第一个简化: 每个元素对象中都提前预置好了所有HTML标准属性,只不过,值暂时为""。
今后在操作HTML标准属性时,只要用: 元素.属性名
特例:
html中: <元素 class="类名"
js中不能用.class,因为class是js的关键词。所以,凡是操作html中的class属性,一律更名为className。js中操作className等效于操作HTML中的class。
获取属性值: 元素.属性名
修改属性值: 元素.属性名=值
强调: 只要修改标准属性,值必须是字符串。如果给的不是字符串,DOM会自动转为字符串。
判断是否包含指定属性: 元素.属性名!=="",说明包含。因为所有标准属性的默认值是""。
移除属性: 元素.属性名="";
只要把元素的属性值改为空字符串,则当前属性失效。
问题: 以上函数,只能操作字符串类型的HTML标准属性。无法操作bool类型的HTML属性:
2. 状态属性:
什么是: HTML标准中规定的,值为bool类型的属性: disabled selected checked multiple ...
特征: 所有状态属性在HTML中使用时,都不带=和属性值。加上,就起作用。去掉就失去作用。
如何操作:
不能用核心DOM的4个函数(getAttribute(), setAttribute(), hasAttribute(), removeAttribute()),因为核心DOM的四个函数只能操作字符串类型的属性
只能用.来访问,值都是bool类型:
比如:
禁用一个按钮: btn.disabled=true
相当于:
<button disabled>...
启用一个按钮:btn.disabled=false
选中一个checkbox:
chb.checked=true;
相当于:
<input type="checkbox" checked>
取消选中一个checkbox:
chb.checked=false;
补: CSS中有一组状态伪类:
:disabled :checked :selected
专门用于选择处于某种状态的元素
比如: input:checked 选择的是被选中的input元素
button:disabled 选择的是被禁用的按钮
3. 自定义扩展属性:
什么是自定义扩展属性: HTML标准中没有规定的,程序员自发添加的属性。
何时: 2个典型用途:
1. 在客户端html元素上临时缓存数据
比如: data-target="id值";
2. 代替其他选择器,用来选择触发事件的元素,为元素绑定事件。
比如: data-toggle="dropdown"
如何:
1. 为元素添加自定义扩展属性:
行业规范:
<元素 data-自定义属性名="值"
2. 用自定义扩展属性作为查询条件,只能用属性选择器:
[data-属性名=值]
3. 获取或修改自定义属性的值:
不能用.访问自定义扩展属性:
因为自定义扩展属性是程序员在html标准之外,后天添加的自定义属性。则HTML DOM的元素对象中就不包含这些自定义扩展属性。
1. 可以用核心DOM: ——没有兼容性问题
元素.getAttribute("data-属性名")
元素.setAttribute("data-属性名",值)
为什么: getAttribute()每次都是去HTML代码中查找属性。
2. HTML5标准中规定: 有兼容性问题
可以 元素.dataset.属性名
其中dataset可以自动收集页面上所有data-开头的自定义扩展属性。这就是为什么自定义扩展属性名习惯上都要带data-前缀的原因!
3. 样式:
1. 修改内联样式:
每个元素都有一个style属性,代表html中开始标签中的style属性。
元素的style属性中又包含了所有css样式属性
修改元素对象的style属性中的css样式属性,等效于直接修改html中开始标签中的style=""
比如: 元素.style.backgroundColor="red"
等效于: <元素 style="background-color:red">
再比如: 显示隐藏:
元素.style.display="block"
等效于: <元素 style="display:block"
元素.style.display="none"
等效于: <元素 style="display:none"
强调: 所有带-的css属性,一律去横线,变驼峰命名。
比如: list-style-type -> listStyleType
background-color -> backgroundColor
2. 获取样式:
问题: 元素.style仅包含内联样式,不包含样式表中的样式。所以,如果用元素.style.css属性,也只能获得内联样式,无法获得样式表中定义的样式。
解决: 获得计算后的样式:
什么是计算后的样式: 最终应用到这个元素上的所有样式的集合。
何时: 今后,只要获取样式,都要获取计算后的样式。
如何: 2步:
1. 先获得计算后的样式对象
var style=getComputedStyle(元素)
获得指定元素计算后的完整样式对象
强调: getComputedStyle是内置的全局函数,可直接使用!
返回值: 是一个包含所有css属性的巨大的对象
2. 才是从样式对象中提取想要的css属性:
var 属性值=style.css属性
强调: 计算后的样式,都是只读的。
因为样式的来源不确定,不确定有多少元素正在共享该属性。所以不允许擅自修改。以免牵一发而动全身。
总结: 想修改一个元素的css属性:
元素.style.css属性=值
想获取一个元素的css属性:
getComputedStyle(元素).css属性
问题: 修改样式时,style.css属性一句话只能改一个css属性。而网页中一个效果的变化,可能同时涉及多个css说行。如果用style.css属性=值 修改,代码会很繁琐。而且,加样式时繁琐一次,去掉样式时同样繁琐!
解决: 当批量修改样式时,用className修改是最划算的!
总结:修改:
1. 内容:
1. 原始html 片段: .innerHTML
2. 纯文本: .textContent
3. 表单元素的内容: .value
2. 属性:
1. HTML标标准属性:
1. 核心DOM: .getAttribute()
.setAttribute()
.hasAttribute()
.removeAttribute()
2. HTML DOM: 也可用.改
2. 状态属性: 只能用.改
3. 自定义扩展属性:
定义: <元素 data-自定义属性名="值"
获取或修改:
元素.getAttribute("data-属性名")
元素.setAttribute("data-属性名","值")
HTML5: 元素.dataset.属性名
3. 样式:
1. 修改内联样式: 元素.style.css属性="值"
2. 获取: getComputedStyle(元素).css属性
1. 添加/删除
1. 添加: 3步:
1. 创建一个空元素
var a=document.createElement("a");
创建 元素
强调: 只能用document调用
问题: <a></a>
2. 为空元素添加关键属性和内容
a.href="http://tmooc.cn"
a.innerHTML="go to tmooc"
<a href="http://tmooc.cn">go to tmooc</a>
问题: 网页上依然看不见a
因为: 网页的排版和绘制,都是以DOM树为依据。而新创建的元素对象,还没有加载到DOM树上。排版引擎和绘图引擎不知道多了新元素,自然就不会画到页面上
3. 将空元素挂载到DOM树
3种:
1. 追加到一个父元素下的所有子元素末尾
父元素.appendChild(新元素)
追加
2. 插入到一个父元素下的某个子元素之前
父元素.insertBefore(新元素, 现有元素)
插入 之前
3. 替换父元素下一个现有的旧元素
父元素.replaceChild(新元素,旧元素)
替换
强调: 将新元素挂到DOM树上,都要先找到它的父元素,由父元素调用函数,将新元素添加到自己的子元素中。
2. 优化: 尽量减少操作DOM树的次数
为什么: 只要修改一次DOM树,就会导致重排重绘
如果频繁重排重绘严重: 会闪屏
解决:
1. 如果同时添加父元素和子元素,应该先在内存中将子元素先添加到父元素中,最后再将父元素整体一次性添加到DOM树——只重排重绘一次!
2. 如果父元素已经在页面上了,要添加多个平级子元素。应该找一个临时的爹: 文档片段对象。先将多个平级子元素,在内存中,添加到文档片段对象中,再将文档片段对象整体一次性添加到DOM树上——也只重排重绘一次
什么是文档片段: 内存中临时保存多个平级子元素的虚拟父元素
何时: 只要父元素已经在页面上了,要添加多个平级子元素
如何: 3步:
1. 创建文档片段
var frag=
document.createDocumentFragment();
创建 文档 片段
2. 将子元素添加到文档片段中
frag对象是一个虚拟的父元素,所以,用法和真实父元素完全一样。
比如: frag.appendChild(新元素)
3. 将文档片段对象一次性添加到DOM树
真实父元素.appendChild(frag)
问题: 文档片段会不会成为页面上实际的元素?
答: 不会,文档片段在将子元素添加到DOM树后,就自动释放。不会成为实际页面元素。
3. 删除: 父元素.removeChild(子元素)
2. HTML DOM常用对象:
HTML DOM对部分常用的复杂的元素对象,提供了一些简写的函数和属性:
1. Image对象:代表页面上一个元素
唯一的简化: 创建<img>元素: var img=new Image()
代替: var img=document.createElement("img");
强调: new Image()只创建<img>元素,不负责挂载。还需要用多一句appendChild()。。。将<img>元素挂载到DOM树上。
2. Select/Option:
1. Select 对象: 代表页面上一个<select>元素
属性:
.selectedIndex 获得当前选中的option在整个select下的位置下标。
.value 获得当前选中的option的value属性值。如果option上没有value属性,则返回option的内容文本代替。
.options 获得当前select下所有option对象的集合。
.options.length 获得当前select下共有几个option对象
.length == .options.length 也可以获得当前select下共有几个option对象
方法:
.add(option) 向select下添加一个option对象
.remove(i) 移除i位置的option对象。
2. Option对象: 代表页面上一个<option>元素
唯一的简化: 创建<option>元素:
var opt=new Option(内容文本, value属性值)
相当于:
var opt=
document.createElement("option")
opt.innerHTML=内容文本;
opt.value=value属性值;
3. Table/…:
Table从上到下完整结构4级: table 行分组 tr td
Table对象采用逐级管理的方式:
Table管着行分组:
添加行分组:
var thead=table.createTHead();
一句话顶两句话
1. var thead=
document.createElement("thead")
2. table.appendChild(thead)
var tbody=table.createTBody();
var tfoot=table.createTFoot();
删除行分组:
table.deleteTHead();
table.deleteTFoot();
获取行分组:
table.tHead
table.tFoot
table.tBodies[0]
|-tBodies:
[
|-tbody
|-tbody
]
因为HTML中规定,一个table下可以有多个tbody!所以,tbody对象都是放在table对象的tBodies集合中管理的,并不直接隶属于table。
行分组管着行:
添加行: var tr=行分组.insertRow(i)
在当前行分组内i位置,添加一个新行
等于以前两句话:
var tr=
document.createElement("tr")
行分组.appendChild(tr)
强调: 如果i位置已经有行,则现有行向后顺移一行。新行插入才当前i位置
固定套路: 2个
1. 末尾追加一行:
var tr=行分组.insertRow()
2. 开头插入一行:
var tr=行分组.insertRow(0)
删除行: 行分组.deleteRow(i)
删除行分组中的第i行
问题: i 要求必须是当前行在行分组内的相对下标位置。行在行分组内的相对位置,只能靠肉眼判断,无法用程序自动获得!
其实: 每个行对象tr,都有一个.rowIndex属性,自动获得这一行在整个表中的下标位置
问题2: 行分组.deleteRow()需要一个行分组内的相对下标。而tr.rowIndex自动获得的是行在整个表中的绝对下标位置。很有可能错位!因为表格中很可能有表头行。所有tr.rowIndex不能用在行分组.deleteRow()中
解决: 今后只要删除行:
table.deleteRow(tr.rowIndex)
因为主语换成table后,deleteRow需要的,刚好是行在table内的位置。
而tr.rowIndex获得的刚好也是行在table中的位置。两者就配对儿了。
获取行: 行分组.rows[i]
行分组中将所有行对象,都保存在一个rows集合中管理。
行管着格:
添加格: var td=tr.insertCell(i)
格
一句话顶两句话:
var td=
document.createElement("td")
tr.appendChild(td);
固定套路: 行末尾追加一格:
var td=tr.insertCell()
删除格: tr.deleteCell()
获取格: tr.cells[i]
tr将内部所有td元素对象,都集中保存在一个cells集合中,统一管理。
补: 确认框:
什么是确认框: 有两个选项的对话框
如何:
var bool=confirm("消息提示")
确认
如果用户点确定,就返回true
如果用户点取消,就返回false
4. Form/表单元素
Form对象:
获取: document对象已经将当前页面中所有的form对象收集在了forms数组中。
document.forms[i 或 id]
属性:
.elements 获得表单中所有表单元素对象的集合。
强调: .elements中只能获得表单元素
.elements.length 可获得表单中表单元素的个数
.length == .elements.length 获得表单中表单元素的个数
表单元素对象:
获取:
不带name的表单元素:
form.elements[i 或 id 或 name]
有name的表单元素:
form.name名
方法: 表单元素.focus()
让当前表单元素自动获得焦点
总结: 优化:
1. 查找:
如果只要一个条件就可以找到元素时:
首先按HTML特征查找: getElementsByXXX
如果查找条件复杂时: 用选择器查找
querySelector()和querySelectorAll()
2. 尽量减少操作DOM树的次数
1. 添加删除:
如果同时添加父元素和子元素,应该先在内存中将子元素添加到父元素中。最后,再将父元素一次性添加到DOM树
如果父元素已经在页面上了,应该先在内存中创建文档片段对象,将所有平级子元素先添加到文档片段中。最后,再将文档片段一次性添加到父元素上
2. 修改时: 能一次修改完成的,就不分两句话修改!
比如: 元素.style.width="200px"
元素.style.height="100px"
其实: 元素.style.cssText=`width:200px; height:100px`;