DOM是专门操作网页内容的对象和函数的集合
增删改查 事件绑定
DOM树
1.内存中保存网页所有内容的树形解构
2.网页标签非常复杂,树形解构刚好保存上下级包含关系
原理:
1.只要浏览器读取到html文件时,就会创建document对象,作为dom唯一的跟节点
2.扫描内容 每扫到一个元素或者文本都会在内存中创建一个节点对象来保存这个元素或文本的内容和属性
查找元素
一.不需要查找可以获取的元素:
(1). document 根节点对象
(2). document.documentElement <html>元素
(3). document.head <head>元素
(4). document.body <body>元素
二. 按节点间关系查找:
(1)节点树:因为看不见的换行和空字符都会被认为是节点对象,干扰了查找结果,所以我们不用这个方法查找
(2)元素树:仅包含元素节点的树结构,不包含空字符,不会干扰查找结果
父子关系:
①获得一个元素的父元素:元素.parentElement 返回值:元素对象
②获得一个元素对象下所有子元素:元素.children 返回值:类数组对象
③获得一个元素对象下的第一个直接子元素:元素.firstElementChild 返回值:第一个元素
④获得一个元素对象下的最后一个直接子元素: 元素.lastElementChild 返回值:最后一个元素
兄弟关系:
①获得一个元素对象相邻的前一个兄弟:元素.previousElementSibling 返回值:前一个元素
②获得一个元素对象相邻的后一个兄弟:元素.nextElementSibling 返回值:下一个元素
(3)总结:只要按节点间关系查找,都首选元素树的属性,而不会选择节点树的旧属性。
(4)使用:如果已经获得了一个元素,找它周围附近的元素时,才用节点间关系查找。
//示例:
var body=document.body; //获得body元素;
var children=body.children //body下所有子元素
var span=body.firstElementChild //获得body下的第一个子元素 span
var script=body.lastElementChild //获得body下的最后一个子元素 script
var h1=span.nextElementSibling //获得span的下一个兄弟元素 h1
三.按HTML特征查找:
(1)ID查找一个元素对象:var 变量=document.getElementById("ID") 返回值:找到返回元素,没找到返回null;
(2)标签名查找多个元素:var 类数组对象=任意父元素.getElementsByTagName("标签名") 返回值:找到返回类数组对象,没找到返回空类数组对象
(3)class查找多个元素:var 类数组对象=任意父元素.getElementsByClassName("class名") 返回值:找到返回类数组对象,没找到返回空类数组对象
(4)name名查找元素:var 类数组对象=document.ElementsByNames("name名字") 返回值:找到返回类数组对象,没找到返回空类数组对象
//示例:
<body>
<span>Hello World !</span>
<form>
用户名:<input name="uname"><br/>
性别:<label><input type="radio" name="sex" value="1">男</label>
<label><input type="radio" name="sex" value="0">女</label>
</form>
<ul id="nav">
<li class="item parent">电影</li>
<li class="item parent">综艺
<ul>
<li class="item child active">跑男</li>
<li class="item child">爸爸</li>
<li class="item child">极限</li>
</ul>
</li>
<li class="item parent">剧集</li>
</ul>
<script>
//想查找id为nav的一个ul元素
var ul=document.getElementById("nav");
//想找ul下所有li
var lis=ul.getElementsByTagName("li");
//想找body下的span
var arr=document.body.getElementsByTagName("span");
var span=arr[0];//取出0位置的一个DOM元素对象
//想让span的内容变成❀
span.innerHTML="❀";
//想在ul下查找class为item的li
var lis=ul.getElementsByClassName("item");
//想在ul下查找class为child的li
var lis=ul.getElementsByClassName("child");
//想在ul下查找class为active的li,并让它的内容变成❀
var arr=ul.getElementsByClassName("active");
var li=arr[0];//获得0位置的一个DOM元素对象
li.innerHTML="❀";
//想查找name名为sex的表单元素
var radios=document.getElementsByName("sex");
//想查找name名为uname的表单元素,并设置其内容为❀
var arr=document.getElementsByName("uname");
var input=arr[0];//取出类数组对象中0位置的唯一一个DOM元素对象
//因为input是表单元素,所以修改内容必须用value,而不能用innerHTML(待续...)
input.value="❀";
</script>
</body>
四.选择器查找元素
(1)有些元素要么藏的很深,要么离当前元素很远,如果用节点间关系查找,代码会很繁琐!
(2)解决: 如果将来要通过复杂的查找条件才能找到想要的元素时,我们都首选按选择器查找元素
(3)包括2个函数:
a. 只查找一个符合条件的元素:
var 一个元素对象=任意父元素.querySelector("任意css选择器");
b. 查找所有符合条件的元素:
var 类数组对象=任意父元素.querySelectorAll("任意css选择器");
修改
修改内容:
1.innerHTML:获取HTML内容,不加工,原始的HTML内容
2.textContent:将特殊符号翻译为正文,去掉内嵌的标签,只保留文字
3.修改表单的元素值,都是但标记,所以没有上面两个属性,想获得修改表单元素时,value代替,表单元素.value
//示:
<p id="p1">来自<<a href="">NBA</a>>的消息</p>
<script>
var p=document.getElementById("p1");
console.log(p.innerHTML) //来自<<a href="">NBA</a>>的消息
console.log(p.textContent) //来自<NBA>的消息
</script>
//举个栗子:
<body>
<div id="d1">树形列表</div>
<div id="d2"><<</div>
<div id="d3">内容主题</div>
<script>
var d2=document.getElementById("d2") //找到d2的元素
d2.onclick=function(){ //给d2添加事件
var d1=document.getElementById("d1"); //找到d1元素
if(this.textContent=="<<"){ //如果d2的文本内容是<<
d1.style.display="none" //隐藏d1
this.textContent=">>" //把d2的文本内容改成>>
}else{ //否则
d1.style.display="block" //d1显示
this.textContent="<<" //d2的文本内容改成<<
}
}
</script>
修改属性:
一.字符串类型的HTML标准属性;(例:id,title,href,name,src...)
1.旧核心DOM的函数:
获取一个属性的属性值:元素.getAttrbute("属性名")
修改一个属性的属性值:元素.setAttrbute("属性名","属性值")
判断是否包含的属性值:元素.hasAttrbute("属性名")
移除某个属性:元素.removeAttrbute("属性名")
//举个栗子(旧核心DOM的函数):
<a href="http://baidu.com" id="c1">go to baidu</a>
<script>
getElementById("c1") //找到a;
var http=a.getAttribute("href") //http://baidu.com;
a.setAttribute("title","欢迎访问") //添加title属性
a.hasAttribute("title")) //查找有没有title属性
a.removeAttribute("title") //移除title属性
</script>
2.简化HTMLDOM方式:对部分常用的属性提供了简化方式
HTML DOM已经提前将HTML标准规定的所有标准属性,保存在了内存中的元素对象身上 可以通过"元素对象.属性名"来访问标准属性。只不过,我们暂时没有赋值的标准属性值,默认都为""
获取属性值: 元素对象.属性名
修改属性值: 元素对象.属性名="新值"
判断是否包含某个属性: 元素对象.属性名!==""
移除属性: 元素对象.属性名="";
//举个栗子:
<a href="http://baidu.com" id="c1">go to baidu</a>
<script>
var a=document.getElementById("c1") //找到a;
a.href //http://baidu.com //获取属性
a.href="http://sina.com" //修改属性
a.href!=="" //判断是否包含属性
a.href="" //移除属性
</script>
***查看元素的所有标签 dir(元素)****
*******ECMAScript已经将class规定为关键字,无法再使用class这个词。于是DOM标准中被迫规定,将来只要操作一个元素的class属性,都要改名为className。将来只要操作className,都相当于操作HTML中的class样式类属性。****
2.bool类型的HTML标准属性;
HTML标准规定的不需要提供属性值,只要放在元素开始标签中就可以发挥作用的属性
a. 包括: checked, disabled, selected
b. 如何:
1). 不能用核心DOM4个函数去修改。核心DOM4个函数仅仅支持字符串类型的属性
2). 可以用"元素.属性名"方式去修改。而且,属性值必须是bool类型的值。
c.使用HTML DOM简写操作bool类型的HTML标准属性:
CSS中: 有一组选择器专门选择处于某种状态的元素——状态伪类
1). :checked 专门选择选中的checkbox或radio
2). :disabled 专门选择禁用的元素
3). :selected 专门选择被选中的option元素
4). 如果想选未选中的,或未禁用的:
:not(:checked) :not(:disabled)
//示例:
性别:
<label><input type="radio" name="sex" value="1" id="d1" checked>男</label>
<label><input type="radio" name="sex" value="0" id="d2">女</label>
<script>
var d1=document.getElementById("d1");
var d2=document.getElementById("d2");
console.log(d1.getAttribute("checked")) //
console.log(d2.getAttribute("checked")) //null
console.log(d1.checked,d2.checked) //true false
</script>
//全选
body>
<h2>管理员列表</h2>
<table border="1px" width="500px">
<thead>
<tr>
<th><input type="checkbox"/>全选</th>
<th>管理员ID</th>
<th>姓名</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr>
<td><input type="checkbox"/></td>
<td>1</td>
<td>Tester</td>
<td>修改 删除</td>
</tr>
<tr>
<td><input type="checkbox"/></td>
<td>2</td>
<td>Manager</td>
<td>修改 删除</td>
</tr>
<tr>
<td><input type="checkbox"/></td>
<td>3</td>
<td>Analyst</td>
<td>修改 删除</td>
</tr>
<tr>
<td><input type="checkbox"/></td>
<td>4</td>
<td>Admin</td>
<td>修改 删除</td>
</tr>
</tbody>
</table>
<button>删除选定</button>
<script>
/*功能1. 点全选,选择下方所有的checkbox*/
//DOM 4步
//1. 查找触发事件的元素
//本例中: 用户点击thead中的input触发全选
var chbAll=document.querySelector(
"thead input"
);
//2. 绑定事件处理函数
chbAll.onclick=function(){
//this->当前点的全选按钮chbAll
//3. 查找要修改的元素
//本例中: 点全选,要修改tbody中每行第一个td中input
var chbs=document.querySelectorAll(
"tbody td:first-child>input"
);
console.dir(chbs);
//4. 修改元素
//遍历tbody中的每个checkbox
for(var chb of chbs){
//每遍历一个checkbox,就让当前checkbox的选中状态与thead中的全选按钮的选中状态保持一致
chb.checked=this.checked;
}
}
/*功能2. 点击下方某一个checkbox也可能影响上方的全选按钮*/
//DOM 4步
//1. 查找触发事件的元素
//本例中: 因为tbody中每checkbox都可点击
var chbs=document.querySelectorAll(
"tbody td:first-child>input"
);
console.log(chbs);
//2. 绑定事件处理函数
//本例中: 遍历找到的每个checkbox
for(var chb of chbs){
//为每个checkbox绑定单击事件
chb.onclick=function(){
//3. 查找要修改的元素
//本例中: 无论点下方哪个checkbox,都会影响上方一个全选checkbox
var chbAll=document.querySelector(
"thead input"
);
//4. 修改元素
//尝试去查找tbody中未选中的input
//因为找到一个和找到多个未选中的input,结论是一样的!就是不全选!所以我找一个就足够了!
var unchecked=document.querySelector(
"tbody td:first-child>input:not(:checked)"
);
//如果找到未选中的checkbox,上边的chbAll就不全选
if(unchecked!=null){
chbAll.checked=false;
}else{//如果没找到未选中的checkbox,上边的chbAll就全选
chbAll.checked=true;
}
}
}
</script>
</body>
3.自定义拓展属性;
HTML标准中没有规定的,程序员根据自身需要自发添加的自定义属性
问题:
1). 如果所有数据,每使用一次,都要反复去服务器端请求结果,如果用户反复操作,就会导致反复发送请求——效率低!
2). 无论我们用元素选择器还是class选择器,都无法保证别人在维护页面时不随意篡改元素名或class名。如果我们js中用元素选择器或class选择器作为查找触发事件的元素的条件。那么一旦别人在维护页面时,擅自修改了标签名或class名,就会影响我们的js功能!
解决:
1). 一次性将所有要用的数据请求得客户端,然后将每项数据根据需要缓存在要操作的元素自己身上的自定义属性中。一旦用户执行操作,我们不用再反复发送请求到服务器端,只要从当前元素自己身上,就能获得想要的数据。
2). 在触发事件的元素上,添加自定义属性,另起炉灶!一般情况下开发人员看到不认识的属性时不会轻易删除的。反而最安全。在js中我们绑定事件时,可以使用属性选择器查找触发事件的元素。结果,即使将来人家修改了标签名,修改了class名,也不会影响我们的js功能!
何时:
1). 在客户端临时缓存业务相关数据时——减少请求的次数
2). 今后只要查找触发事件的元素,首选用属性选择器查找带有自定义属性的元素——避免标签名或class名变化而影响js功能!
如何:HTML标准有明确的规定:
1).<元素 data-自定义属性名="属性值">
2).无法用.去访问自定义拓展属性: 元素.dataset.自定义属性名
//例子:
<input type="button" data-click="say" value="james">
<script>
//var btn1=document.getElementById("btn1")
var btn1=document.querySelector(
"[data-click=say]"
)
//想给亮亮按钮添加data-age属性保存亮亮的年龄
// btn1.setAttribute("data-age",36);
btn1.dataset.age=45;
//点击,输出年龄:
btn1.onclick=function(){
//从当前按钮自己身上获得自定义age属性
//var age=this.getAttribute("data-age");
var age=this.dataset.age;
console.log(`年龄${age}`);
}
</script>
4.修改样式
(1)获取修改元素的内联样式:
a.元素对象.style.css属性名="属性值" //有些css属性名带- 但是js中不能随便写-
b.解决上面问题:驼峰命名
//示例:
<div id="d1">Lorem ipsum, dolor sit </div>
<script>
var d1=document.getElementById("d1");
d1.style.backgroundColor="red"; //驼峰命名
d1.style.color="#fff";
</script>
(2)获取样式:
a.用style获取样式,只能获取内联样式,无法获取内部/外部样式表中的样式
b.style属性仅仅代表元素开始标签中的内联样式。不包含内部/外部样式表中继承或层叠来的样式
c.解决上面问题:只要获取样式,就要用计算后的样式;
(3)如何:
i 获取计算后的样式对象:var style=getComputedStyle(元素对象)
ii 从style中取出想用的css属性:style.css属性名
//示例:
<style>h1{background-color:red}</style>
<body>
<h1 id="h1" style="color:#fff">Welcome</h1>
<p>welcome to my web site</p>
<script>
var h1=document.getElementById("h1");
//尝试用style方式获取h1的字体颜色,背景颜色,字体大小
//错误:
// console.log(h1.style.color);
// console.log(h1.style.backgroundColor);
// console.log(h1.style.fontSize);
//正确:
var style=getComputedStyle(h1);
console.log(style.color);
console.log(style.backgroundColor);
console.log(style.fontSize);
console.log(style);
//尝试修改计算后的样式中的fontSize
//错误:
// style.fontSize="64px";//报错:
//Uncaught DOMException: Failed to set the 'font-size' property on 'CSSStyleDeclaration': These styles are computed, and therefore the 'font-size' property is read-only.
//不能修改font-size属性,因为这些样式都是计算后的,因此font-size属性是只读的
//正确:
h1.style.fontSize="64px";
</script>
添加删除替换的元素
添加:
(1). 创建一个新元素
a. var 新元素=document.createElement("元素的标签名")
b. 比如: 想创建一个a元素:
var a=document.createElement("a");
结果: <a></a>
(2). 为元素设置必要属性:
a.href="http://tmooc.cn"
a.innerHTML="欢迎访问tmooc"
结果: <a href="http://tmooc.cn">欢迎访问tmooc</a>
问题: 虽然创建了一个a,但是a没有显示在页面上,用户用不了!
原因: 新创建的a元素,不在DOM树上,浏览器就无法把a绘制到页面上让人看到
解决: 今后只要创建一个新元素,都要手动将新元素添加到DOM树上指定位置,让浏览器知道,多了一个元素。
(3). 将新元素添加到DOM树: 3种:
a.在一个父元素下所有子元素末尾追加新元素: 父元素.appendChild(新元素)
b.在一个父元素下的一个现有子元素之前插入新元素: 父元素.insertBefore(新元素, 现有子元素)
c.替换一个父元素下的一个现有子元素: 父元素.replaceChild(新元素, 现有子元素)
//示例:
<script>
//想向body中添加一个新的a元素
var a=document.createElement("a");
a.href="http://tmooc.cn";
a.innerHTML="欢迎访问tmooc";
console.log(a);
document.body.appendChild(a);
//想创建一个input文本框
var input=document.createElement("input")
//想把input插入在a之前?
document.body.insertBefore(input, a);
//想把input追加在a之后?
document.body.appendChild(input)
//想用input替换a
document.body.replaceChild(input, a);
(4).动态生成表格内容:
<div id="data">
<table>
<thead>
<tr>
<th>姓名</th>
<th>薪资</th>
<th>年龄</th>
</tr>
</thead>
</table>
</div>
<script>
var json = [
{"ename": "Tom", "salary": 11000, "age": 25 },
{"ename": "John", "salary": 13000, "age": 28 },
{"ename": "Mary", "salary": 12000, "age": 25 }
];
//先假设table中没有tbody
//1. 先创建tbody
var tbody=document.createElement("tbody")
//但是,暂时不要添加到table中
//2. 遍历数组中每个员工对象
//因为json是索引数组,所以用for of
for(var emp of json){
//3. 每遍历一个员工对象,就创建一个tr,
var tr=document.createElement("tr");
//并加入到tbody中
tbody.appendChild(tr);
//4. 遍历当前员工对象中每个属性;
//因为每个员工对象是对象,所以用for in
for(var key in emp){
//5. 每遍历一个属性,就创建td
var td=document.createElement("td")
//并添加到tr
tr.appendChild(td);
//设置td的内容为当前对象的当前属性值
td.innerHTML=emp[key];
}
}
//最后,才将tbody追加到table中
var table=document.querySelector(
"#data>table"
);
table.appendChild(tbody);
运行结果:👇
问题: 只要用程序修改一个DOM树,浏览器都要被迫重排重绘。——效率很低!
优化: 尽量减少操作DOM树的次数!——减少重排重绘
如何优化: 2种:
(1)如果同时添加父元素和子元素时应该先将子元素在内存中都添加到父元素下。最后再一次性将父元素添加到DOM树——只需要一次重排重绘
(2).如果父元素已经在页面上了,我们应该先将多个平级子元素添加到文档片段对象中,最后,再将文档片段对象一次性添加到DOM树
a. 什么是文档片段对象: 在内存中,临时保存多个平级子元素的虚拟父元素。
b. 如何: 3步
1). 创建文档片段对象:
var 文档片段对象=document.createDocumentFragment()
创建 文档 片段
2). 将多个平级子元素添加到文档片段对象中:
文档片段对象.appendChild(子元素)
3). 将文档片段对象一次性添加到DOM树中指定父元素下
父元素.appendChild(文档片段对象)
d. 强调: 文档片段对象在将多个平级子元素添加到DOM树上之后,文档片段对象就自动释放了!不会成为页面上一级真正的元素。
e. 示例: 使用文档片段优化动态生成表格内容
var frag=document.createDocumentFragment();
//2. 遍历数组中每个员工对象
//因为json是索引数组,所以用for of
for(var emp of json){
//3. 每遍历一个员工对象,就创建一个tr,
var tr=document.createElement("tr");
//并加入到文档片段frag中
frag.appendChild(tr);
//4. 遍历当前员工对象中每个属性;
//因为每个员工对象是对象,所以用for in
for(var key in emp){
//5. 每遍历一个属性,就创建td
var td=document.createElement("td")
//并添加到tr
tr.appendChild(td);
//设置td的内容为当前对象的当前属性值
td.innerHTML=emp[key];
}
}
//6. 将整个frag追加到tbody中:
var tbody=
document.querySelector("#data>table>tbody")
tbody.appendChild(frag);
删除元素: 父元素.removeChild(子元素)