9.1 冯娜
学习日志
今天学习了构建DOM树,查找元素、修改元素、添加元素、删除元素。
一、构建DOM树(先在头脑中构建DOM树)
1、查找触发事件的元素
var a=document.body.childNodes[i];
绑定事件处理函数
-
在事件处理函数内部,查找要操作的元素,对元素进行修改/增加/删除
a.onclick=function(){
document.body.innerHTML="html"
}
注意:事件处理函数中的this可以自动获得触发事件的当前元素,this ==> 标签a
二、查找元素
1、不需要查找可直接获得的节点
-
document
根节点
-
document.documentElement
获取html中包含的所有内容
-
document.head
获取head中包含的所有内容
-
document.body
获取body中包含的所有内容
-
document.forms[i]
获取页面中所有的表单元素
二、按节点间关系查找
按节点间关系查找是已经获得一个树上的节点对象,通过关系,逐渐找到想要的另一个元素的过程
-
节点树
- 什么是节点树?
包含所有网页内容(节点)的树结构
-
使用(两大类关系,六个属性)
-
父子
- elem.parentNode
获得当前节点的父节点
- elem.childNodes
获得当前节点下的直接子节点的集合,返回一个类数组对象包含该父节点下所有直接子节点
- elem.firstChild
获得当前节点下的第一个直接子节点
- elem.lastChild
获得当前节点下的最后一个直接子节点
-
兄弟
- elem.previousSibling
获得当前节点的前一个平级兄弟节点
- elem.nextSibling
获得当前节点的后一个平级兄弟节点
-
节点树优点
网页结构完整
- 节点树问题
我们在写代码的时候为了代码格式,会添加很多空格,缩进,换行,这些都是字符。在节点树中,字符也是节点对象,称为 " #text " 。这些看不见的文本节点,会极大的妨碍正常的查找
- 解决节点树问题
新DOM: 推出了一种新的树,元素树
-
元素树
- 什么是元素树?
仅包含元素节点的树结构,不是一棵新树,仅是节点树的子集(其实内存中只有一棵完整的节点树。而元素树只不过是在节点树的基础上,新增了一些仅指向元素节点的新属性而已。所以,元素树只是节点树中的一个子集)
- 什么时候使用?
所有的按关系查找,都用元素树,而不用节点树(只关心元素,不关心文本时)
- 为什么使用?
元素树不包含节点,只包含元素,查找时无干扰
-
使用( 2大类关系,六个属性)
-
父子
- elem.parentElement
获得当前元素的父元素(因为能当父元素的通常都是元素节点,所以也可以用parentNode代替)
- elem.children
获得当前元素下的直接子元素的集合,返回一个类数组对象包含该父元素下所有直接子元素(IE8+)
- elem.firstElementChild
获得当前元素下的第一个直接子元素
- elem.lastElementChild
获得当前元素下的最后一个直接子元素
-
兄弟
- elem.previousElementSibling
获得当前元素的前一个平级兄弟元素
- elem.nextElementSibling
获得当前元素的后一个平级兄弟元素
-
优点
不包含看不见的文本节点,不干扰查找
- 缺点
不包含一切文本节点,如果想获取文本可用 “ elem.innerHTML ”获取文本
实例:elem.innerHTML
兼容性问题:IE9+ -
childNodes和children的区别
childNodes和children都返回动态集合(live collection),不实际存储数据,每次访问集合,都是重新查找DOM树
优点:首次查找返回速度快
缺点:反复访问集合,会导致反复查找DOM树
遍历:for(var i=0,len=children.length;i<len;i++)
区别:
1.childNodes是标准属性,返回指定元素的子元素集合,包括HTML节点,所有属性,文本节点。可以通过nodeType判断是哪种类型的节点;
2.children是非标准属性,返回指定元素的子元素集合。但是它只返回HTML几点,甚至不返回文本节点,虽然不是标准的DOM属性,但是却得到几乎所有浏览器的支持 -
递归遍历
- 什么时候使用?
当需要遍历一个父节点下的所有后代节点时使用递归遍历
- 使用递归遍历
var body=document.body;
function getChild(parent){
// Step1:遍历parent的直接子节点
var child=parent.childNodes;
for(var i=0;i<child.length;i++){
// 输出查找到的子元素
console.log(child[i])
//Step2: 为每个子节点调用和父节点完全相同的函数
console.log(arguments);
arguments.callee(child[i]);
}
}
getChild(body)- 遍历原理:深度优先遍历
1.什么是深度优先遍历(DFS算法)?
深度优先遍历是从某个顶点出发,访问此顶点,然后从未被访问的邻接点出发深度优先遍历,直到所有和邻接点相同的路径都被访问到了。回溯到某个未被访问的分支,继续执行遍历。
DOM遍历:是当同时有子节点和兄弟节点时,优先遍历子节点。只有所有子节点遍历完后,才能遍历兄弟节点
2.什么是广度优先遍历(BFS算法)?
广度优点遍历是从某个顶点出发,访问此顶点,然后访问所有未被访问的邻接点进行广度优点遍历,遍历完成后继续访问未被访问的分支节点执行遍历- 递归问题
递归的效率极低
- 解决递归问题
解决:可用循环代替递归
注意:createNodeIterator遍历的时候包含自身元素
- 循环问题
只能遍历所有,如需筛选,得自己写判断
- 解决循环问题
使用按条件查询
1、按HTML查找(4个特征)
-
什么时候使用?
当我们一个元素都没有获得,首次查找元素时使用HTML查找
-
按ID查找
var elem=document.getElementById("id")
提示:
1.只能用document调用,不能换成其他元素,使用document意为在整个网页范围内查找;
2.只能找到一个元素对象,如果找不到返回null,如果不小心两个HTML元素id名相同,则getElementById只能返回第一个找到的元素
问题:
1.按id查找一次只能找一个元素对象,无法同时找多个元素对象;
2.网页中标有id的元素并不多,因为没有那么多英文单词名字可用 -
按标签名查找
var elems=parent.getElementsByTagName("标签名")
提示:
1.可在任意父元素下查找。主语是哪个父元素,就只在哪个父元素内查找符合条件的元素;
2.返回多个符合条件的元素组成的类数组对象,也是动态集合,如果找不到,返回空类数组对象:{ length:0 };
3.getElementsByTagName()不仅查找直接子元素,而是在所有后代中查找符合条件的元素 -
按class
var elems=parent.getElementsByClassName("class")
提示:
1.可以在任意父元素上调用,控制查找的范围;
2.返回类数组对象,也是动态集合,如果没找到,返回空类数组对象{ length:0 };
3.不只是在直接子元素中查找,而是在所有后代子元素中查找符合条件的元素;
4.如果一个元素有多个class修饰,而查找时,只要用其中一个class,就能找到该元素兼容性问题:IE9+
-
按name
var elems=document.getElementsByName("name")
提示:
1.只能在document上调用;
2.返回类数组对象,也是动态集合,如果没找到,返回空类数组对象{ length:0 };
3.因为只有表单元素才会有name属性,所以,getElementsByName几乎专门用于查找指定name属性的表单元素
注意:ByTagName、ByClassName、 ByName三个函数注定返回类数组对象,即使只找到一个元素,也会放在类数组对象中返回,无法直接返回一个元素对象,所以,如果只找到一个元素对象时,应该再加[0],才能取出类数组对象中保存的惟一的这个元素对象 -
按HTML查找问题
一次只能按一个条件查找。如果查找条件复杂,元素藏得很深,还需要筛选时,按HTML特征查找,就会需要很多次查找,才能找到想要的元素,这样代码就过于繁琐
-
解决按HTML查找问题
按选择器查找
2、按选择器查找(Selector API)
1.可在任意父元素上调用;
2.返回类数组对象,但是是非动态集合;
3.受制于浏览器对选择器的兼容性(IE8+);
4.“ () ”中的选择器,是相对于“ . ” 前的父元素下的相对选择器,而不是完整的选择器
-
什么时候使用?
当查找条件非常复杂时,使用选择器查找,无论藏的多深的元素,css的选择器永远只需要一句很简洁的话就可找到元素
-
查找一个元素
var elem=parent.querySelector("selector")
-
查找多个元素
var elems=parent.querySelectorAll("selector")
返回值总结
1.如果该函数规定只返回一个元素对象,则如果找不到,都返回null(getElementById、querySelector)
2.如果该函数规定会返回一个类数组对象,则如果找不到,都返回空类数组对象:{ length: 0 }
3、按HTML vs 按选择器
-
返回值
1.按HTML,返回动态集合,不实际存储所有内容,只返回本次所需的内容,如果重复访问集合,就需要重新查找DOM树,优点是首次查找速度快,缺点是反复访问集合,会导致反复查找DOM树(会根据页面元素的变化而变化);
2.selector API,返回非动态集合,可以直接存储所有数据,即使反复访问集合,也不需要反复查找DOM树 -
首次查询效率
1.按HTML效率更高,仅返回需要的内容,不需要准备完整数据;
2.selector API低,第一次要返回完整数据 -
易用性
1.按HTML繁琐;
2.selector API简单 -
什么时候使用
1.如果只凭一个条件即可获得想要的元素时,首选按HTML查找;
2.如果需要多级复杂条件查找才能获得想要的元素时,用selector API
三、修改元素
1、内容
-
elem.innerHTML
获取或设置开始标签到结束标签之间的原始的html代码片段,获取元素的HTML内容时,返回原始的HTML代码内容,修改元素的HTML内容时,先将新的HTML代码内容提交给浏览器解析后,再显示在元素内部
实例:
p.innerHTML="hello world" -
elem.textContent
获取或设置开始标签到结束标签之间的纯文本内容,获取元素的纯文本内容时,textContent会去掉内嵌标签,还会将特殊符号翻译为正文,修改元素的纯文本内容时,不会将新内容交给浏览器解析,而是直接原样显示在元素内部
实例:
p.textContent=“hello world”- 对比innerHTML
innerHTML设置文本的时候是去掉所有标签,只显示文本,同时翻译转义字符为正文,而textContent是我们写的是什么就显示什么
-
elem.innerText
innerText用法同textContent一样,只不过具有兼容性要求(IE8+)
-
elem.value
获取或修改表单元素的值
注意:获取表单元素的值不能用 “.innerHTML",因为表单元素多数是单标记,没有innerHTML,表单元素的内容都是用“.value”属性来获取或修改的
2、属性
-
HTML标准属性
HTML标准中规定的属性,比如:id, title, class, href, src, ...,属性值为字符串类型
- 核心DOM(4个)
操作一切结构化文档的通用API,即可操作HTML,又可操作XML
优点:万能
缺点:繁琐,单词太长-
获取属性
var value=elem.getAttribute("属性名")
实例:获得a元素的href属性
a.getAttribute("href")
了解:
var attrNode=elem.attributes[ i / 属性名 ];
// var attrNode=elem.getAttributeNode("属性名");
var value=attrNode.value; -
修改属性
elem.setAttribute("属性名",属性值)
注意:如果属性不存在,也可以使用setAttribute,此时会自动添加该属性
实例:修改a元素的title属性
a.setAttribute("title","hello world") -
移除属性
elem.removeAttribute("属性名")
提示:只移除开始标签中的attribute,不删除内存中对象的property
实例:移除a元素的title属性
a.removeAttribute("title") -
判断是否含有属性
var bool=elem.hasAttribute("属性名")
实例:判断a元素是否有id属性,如果含有返回true,否则返回false
a.hasAttribute("id") -
HTML DOM
什么是HTML DOM?
HTML DOM是专门操作HTML文档的简化版API,只对部分常用API进行简化。所有HTML标准属性都被封装在HTML DOM对象中,可直接用 . 访问,用法普通对象的属性完全一样
原理:HTML DOM提前将所有的HTML标准属性都定义在了元素对象上。只不过,属性值暂时是 " ",当手动在<开始标签>中加入了属性值或用程序在内存中添加了属性值,则该属性的值变为指定的新值
优点:简单
缺点:不是万能的,需要核心DOM的补充-
获取属性
elem.属性名
-
修改属性
elem.属性名=值
-
判断是否包含属性
elem.属性名!==""
-
移除属性
elem.属性名=""
-
特例:class属性
class属性,不能直接用 “ . ” 访问,因为class属性是ES中的关键词,所以DOM不能再使用class作为属性。如果操作页面上的class属性,只能改名为“ .className”。使用"元素“.className"属性,等效于使用<元素 class="xxx">属性,直接修改class
实例:
元素.className="xxx"
提示:元素.classList
1.元素.classList.add("class名")
2.元素.classList.remove("class名")
-
状态属性
HTML标准中规定的,只要放在元素上就起作用,不在元素上就没作用。且不需要指定属性值,包含:disabled, checked, selected(bool类型)
- HTML
在开始标签中加上,就有用,不加,就无效,不需要给属性值
- CSS
css选择器,查找指定状态的元素,状态伪类,例如:
:disabled 专门匹配禁用的元素
:checked 专门匹配选中的checkbox或radio
[selected] 专门匹配选中的option
实例:
document.querySelector(“input:not(:checked)”)-
JS
在JS中disabled、checked、selected 其实都是bool类型
- 问题
核心DOM的四个函数,只支持字符串类型的属性,因为三大状态属性都是布尔型,所以不支持三大状态属性
- 解决
使用HTML DOM,只要使用三大状态属性,只能用“ . ”来访问,且值必须是bool类型
提示:elem.状态
实例:
var option=document.querySelector("select>option");
console.log(option.selected) -
扩展(自定义)属性
-
什么是自定义属性?
HTML标准中没有规定的,程序员自发添加到元素上的自定义属性
-
什么时候使用?
1.代替id、元素、class选择器,给多个元素添加行为,代替其他选择器,作为选择触发事件的元素添加行为的标志。可让行为和样式彻底分离(id选择器一次只能选一个元素,元素选择器,实现一种效果,不一定非要用一种标签,比如:实现按钮样子,可用 a input button 都行。class选择器,专门用于修饰样式,修改会很频繁);
2.在客户端网页中临时缓存之后操作频繁使用的业务数据,为了减少ajax请求的次数,提高加载速度和用户体验(比如: 放大镜效果时,虽然第一次不需要所有的小图片,中图片和大图片,但是会在首次请求时,就把所有图片的路径都下载到客户端,缓存在每个小图片上,当用户在小图片之间切换时,无需重复ajax请求,就可直接获得对应的中图片和大图片路径) -
自定义属性的好处
自定义属性和标签名无关,和样式无关,自成体系,不受别人修改的影响
-
普通自定义属性
- 定义自定义扩展属性
- 访问自定义扩展属性
不能用HTML DOM打 . 的方式访问自定义扩展属性,因为自定义扩展属性是后天添加的,HTML DOM 无法提前预知自定义属性的名称,自然就不能提前在元素对象中准备好属性,所以不能用 "元素.自定义属性"方式访问。
只能用核心DOM any.getAttribute("属性名") 和 any.setAttribute("属性名","值")
因为getAttribute和setAttribute()和内存中的元素对象无关,而是每次都临时去页面上的HTML代码中开始标签中查找属性,而不是在对象中直接读取现成的属性- 问题
HTML DOM无法访问扩展属性
- 解决
核心DOM
-
HTML5自定义属性
- 定义自定义扩展属性
所有自定义扩展属性名前必须加data-前缀:
- 访问自定义扩展属性
如果添加自定义属性时,添加了data-前缀,则可用dataset自动收集所有data-开头的属性,然后用 . 访问
提示:elem.dataset.属性名
实例:
//如果想获得或修改md和lg属性的值
img.dataset.md
img.dataset.lg
注意:dataset会自动收集所有data-开头的属性,不带data-前缀的属性,dataset是收集不到的。dataset. 后只需要写data-后的属性名即可,不用再写data-前缀 -
查找带有自定义扩展属性的元素
用自定义扩展属性作为条件,查找触发事件的元素,可用自定义扩展属性
- CSS属性选择器
[data-属性名=值]
实例:<ul>
- click me!
- click me!
...
// 找到所有 i
var is=document.querySelectorAll(
"ul>li>[data-toggle=tab]"
)
3、样式
- 内联样式
elem.style.css属性名=“属性值”
注意:
1.有些css属性名中带 -,而程序中不能随意出现 -,会出现减号冲突,所以所有带 - 的css属性名必须去横线变驼峰(首字母小写,之后每个单词首字母大写);
2.如果属性值是长度或大小属性值,必需拼接 "px" 单位作为结尾,不能只写数值
-
什么时候使用?
专门用于修改内联样式,不影响其他元素的样式,优先级最高
-
读取问题
style如果用于获取样式属性值时,只能获得内联样式中的属性值。无法获得从样式表或浏览器默认样式中继承或重叠来的样式。所以,将来读取一个元素的完整css样式时,不能用 " 元素.style.css属性 "
-
解决读取问题
获取样式时,要用专门的方法getComputedStyle(元素),获得计算后的完整样式
- 什么是计算样式?
计算后的样式,最终应用到这个元素上的完整样式的集合,包括所有内联,内部,外部样式,将相对单位的值,计算为绝对单位(F12->Elements->Computed)
- 什么时候使用?
只要想获取css属性值,又不用担心丢失样式时,都用getComputedStyle(元素)
- 先获得完整的style对象
var style=getComputedStyle(elem对象)
注意:getComputedStyle()所有浏览器自带,不用自己定义- 从style对象中获取想要的css属性值
var value=style.样式属性名
注意:
1.计算后的样式,不仅收集这个元素的所有样式到一个对象中集中保存,而且还会将相对单位(em)以及不是数值的属性值,换算回绝对单位和数值方式;
2.所有通过计算后的样式获得css属性都是只读的。不能修改。因为计算后的样式来源不确定,一旦修改,很可能牵一发而动全身 -
修改问题
使用元素.style.css属性一句话只能修改一个css属性,如果在一次效果变化中,需要同时修改这个元素的多个css属性,代码会很繁琐,且效率低
-
解决修改问题
如果想批量应用css属性,应该使用class,代替 “ 元素.style.css属性 ”
-
内部/外部样式表(不建议)
提示:获取的是style标签中和link标签中的样式
实例:
// 获取页面中的所有样式
var sheet=document.styleSheets[i];
// 获取页面中的具体样式表,如果获得的是keyframes,就需要继续找子rule
var rule=sheet.cssRules[i];
// 通过rule下的style属性修改属性值
rule.style.样式属性=值
- 最好的修改样式的方法
如果批量修改多个css属性时,首选用class,修改class属性,批量应用样式
实例:
var d1=ducment.getElementById("d1");
d1.className="disBlock";
d1.className="disNone";
- 优化样式修改
因为每修改一次样式,都可能导致重排重绘网页,所以,应该尽量减少修改样式的次数,使用 “ 元素.style.cssText属性 ” 代替,单独设置的 “ 元素.style.css属性 ”
实例:
元素.style.cssText="width:100px; height:100px"
注意:此时浏览器只需要一次重排重绘网页,对比单独设置:
元素.style.width="100px"
元素.style.height="100px"
单独设置导致网页在短时间内进行两次重排重绘
四、添加和删除元素
1、添加
- 创建新元素对象
var elem=document.createElement("标签名")
实例:创建一个页面元素对象
var a=document.createElement("a")
- 设置关键属性
实例:为创建好的对象添加属性
a.href="url";
a.innerHTML="文本"
问题:此时新创建的元素都没有在DOM树上。自然也就无法显示在网页中
- 将元素添加到DOM树
1.将新元素放在父元素下最后一个直接子元素后,末尾追加
实例:
parent.appendChild(child)
2.将新元素放在父元素下一个现有旧元素之前
实例:
parent.insertBefore(child,oldChild)
3.将新元素替换父元素下一个现有旧元素
实例:
parent.replaceChild(child,oldChild)
- 添加问题
每操作一次DOM树,都会导致重排重绘。如果数据量大,又频繁操作DOM树,会导致重排重绘不过来
- 解决添加问题
优化,尽量少的操作DOM树,如果同时添加父元素和子元素,应该先暂存内存中,将所有子元素添加到父元素,最后将父元素一次性添加到DOM树上,只需要重排重绘一次。如果父元素已经在DOM树上了,而要添加多个平级子元素,需要借助文档片段对象
-
文档片段
- 什么是文档片段对象?
内存中临时存储多个子元素的虚拟父元素
- 什么时候使用?
当父元素已经挂在DOM树上了,而需要添加多个平级子元素的时候,就使用文档片段
- 创建文档片段对象
var frag=document.createDocumentFragment();
- 将多个平级子元素临时添加到文档片段对象中
frag.appendChild(child)
- 将文档片段整体一次性添加到DOM树上指定父元素下
parent.appendChild(frag)
注意:frag不会成为页面元素,添加子元素后,frag自动释放
2、删除
parent.removeChild(child)
感觉今天学的没听懂。。。。
评论