文章目录
- 查找元素
- 按HTML特征查找
- 购物车
- 按HTML特征查找 & 按选择器查找
- 查找时的优化
- 问题1: 在事件处理函数中如何获得当前正在触发的事件?
- 修改: innerHTML / textContent
- 带过渡的开关门效果
- 属性
- 旧核心DOM函数
- HTML DOM属性
- 手风琴菜单效果
- Bool类型的HTML标准属性
- 全选和取消全选
- 获取或修改自定义扩展属性的值 HTML5中的简写
- 修改样式
- 读取并修改元素的属性: 实现多标签页效果
- 总结: 修改元素
- 修改样式
- 优化: 尽量用最少的语句和修改次数来修改样式:
- 使用class,实现带样式的表单验证
- 添加删除元素
- 动态生成表格
- 优化: 尽量减少操作DOM树的次数,为了减少重排重绘
- 两个select级联选择
- 总结: 增删改查 优化
- HTML DOM常用对象
Document Object Model
什么是: 专门操作网页内容的一套对象和函数的标准
为什么: ES标准仅仅定义了js语言的语法规则(怎么写对怎么写错!),但是没有提供操作网页内容的任何函数和对象
何时: 只要操作网页内容,都必须用DOM
DOM标准: W3C指定,几乎所有浏览器100%兼容
包括: 增删改查 + 事件绑定
DOM树
什么是: 在内存中保存一个网页所有内容的树形结构
为什么: 因为网页结构包含多级嵌套关系,而树形结构是描述多级嵌套包含关系最直接的结构。
何时: 今后查找元素时,都要按照树形结构中的关系来查找!
DOM树如何形成:
① 当浏览器读取到一个.html文件后,首先会在内存中创建一个唯一树根节点对象: document
② 浏览器开始扫描.html文件内容,每扫描到一个内容,就在DOM树中对应位置创建一个节点对象,保存当前扫描到的内容。
名词解释:
节点 (node) 对象: 网页中每一项内容(元素, 文本, 属性, 注释, …),都会成为DOM树上的一个对象,对象中保存该内容的属性和方法,称为节点对象
下图中,所有方块都是节点对象
标签(tag): 一对<>
包裹的一个名称标记,称为一个标签 , 比如: <div> <h1>
下图中不存在标签概念,标签只存在于HTML代码
元素(element): 由开始标签到结束标签及其之间的内容形成的整体,称为元素。所有元素,才会成为DOM树上的元素节点对象!元素节点对象中保存的是元素的内容和属性。
比如: <h1>hello</h1>
元素
下图中,所有红方块的节点对象都是元素节点对象
单标记特殊: <input/>
,<br/>
既是一个标签,又是一个元素节点对象
查找元素
- 不需要查找就可直接获得的元素对象:
①获得元素对象: document.documentElement
②获得元素对象: document.head
③获得元素对象: document.body - 按节点间关系查找:
节点树: 保存所有节点(元素,文本,。。。)对象的完整树结构,2大类关系:
父子关系
①获得一个元素的父节点: 元素.parentNode
②获得一个元素下的所有直接子节点: 元素.childNodes
强调: 返回类数组对象
③ 获得一个元素下的第一个直接子节点: 元素.firstChild
④ 获得一个元素下的最后一个直接子节点: 元素.lastChild
兄弟关系
① 获得一个元素前一个兄弟节点: 元素.previousSibling
② 获得一个元素后一个兄弟节点: 元素.nextSibling
坑: 节点树甚至连看不见的空格,缩进,换行都看作节点对象,极大的干扰了我们的查找!
元素树
仅包含元素节点的树结构
说明: 元素树不是一棵新树,元素树仅是在节点树内部添加了一个专门指向元素节点对象的新特殊属性。如果单看这些专门指向元素的新属性,形成的子树,就称为元素树。
2大类关系: (常用)
父子关系
①获得一个元素的父元素对象: 元素.parentElement
说明: 在HTML中能当爹的注定已经是一个元素对象了!所以其实.parentNode还是可以用的!
② 获得一个元素下的所有直接子元素: 元素.children
强调: 返回类数组对象
③ 获得一个元素下的第一个直接子元素:
元素.firstElementChild
④获得一个元素下的最后一个直接子元素:
元素.lastElementChild
兄弟关系
①获得一个元素前一个兄弟元素:元素.previousElementSibling
②获得一个元素后一个兄弟元素:元素.nextElementSibling
示例: 按节点间关系查找元素
//想先找body
var body=document.body;
console.log(body);
//获得body下所有直接子元素
var children=body.children;
console.log(children);
//再找body的第一个直接子元素span
var span=body.firstElementChild;
//body.children[0]
console.log(span);
//再找body的最后一个直接子元素script
var script=body.lastElementChild;
//body.children[body.children.length-1]
console.log(script);
//再找h1: span的下一个兄弟,script的前一个兄弟,body的第二个孩子
var h1=span.nextElementSibling;
//script.previousElementSibling
//body.children[1]
console.log(h1);
何时使用按节点间关系查找: 如果已经获得一个元素,找这个元素周围附近的元素时
按HTML特征查找
何时: 如果刚开始查找元素时,手里暂时没有获得任何元素,要查找第一个元素,就用HTML特征查找。
按id查找一个元素
a. var 元素=document.getElementById("id名")
b. 查找一个id为指定"id名"的元素
c. 强调:
① 只能用document
对象直接调用,不能用任意一个父元素调用
②按id查找永远只能返回一个符合条件的元素,如果没找到,返回null
按标签名查找元素
a. var 类数组对象=任意父元素.getElementsByTagName("标签名"
)
b. 在指定父元素范围内查找标签名为指定标签的多个元素
c. 强调
①可用任意父元素调用,限制查找的范围
②返回一个类数组对象,包含多个符合条件的元素。如果找不到,返回空类数组对象: {}.length=0
③不止查找直接子元素,而是在所有后代中查找符合条件的元素
按class名查找
a. var 类数组对象=任意父元素.getElementsByTagName("class名")
b. 在指定父元素范围内查找class名为指定class的多个元素
c. 强调
①可用任意父元素调用,限制查找的范围
②返回一个类数组对象,包含多个符合条件的元素。如果找不到,返回空类数组对象: {}.length=0
③不止查找直接子元素,而是在所有后代中查找符合条件的元素
④如果一个元素被多个class同时修饰,则只用其中一个class名就可找到该元素!
按name属性查找
a. 何时: 专门查找又name属性的表单元素
b. var 类数组对象=document.getElementsByName(“name名”)
c. 在当前页面范围内查找name名为指定名称的多个表单元素
d. 强调
① 只能用document调用
② 返回一个类数组对象,包含多个符合条件的元素。如果找不到,返回空类数组对象: {}.length=0
按HTML特征查找的问题: 用一个函数只能按一种条件查找元素。如果查找元素的条件很复杂,就被迫要写很多代码才能找到想要的元素
用选择器查找
仅查找一个元素:
a. var 元素=任意父元素.querySelector("任意选择器")
b. 强调:
① 可在任意父元素调用,仅在当前指定父元素内查找符合条件的一个元素
② 不仅查找直接子元素,而是在所有后代中查找!
③ 选择器必须从主语父元素下一级元素开始写,不能包含.前的主语父元素!
④ 如果找不到,返回null
查找多个符合条件的元素
a. var 类数组对象=任意父元素.querySelctorAll("任意选择器")
b. 强调:
① 可在任意父元素调用,仅在当前指定父元素内查找符合条件的一个元素
② 不仅查找直接子元素,而是在所有后代中查找!
③ 选择器必须从主语父元素下一级元素开始写,不能包含.前的主语父元素!
④ 如果找不到,返回空类数组对象:{}.length=0
购物车
<!DOCTYPE HTML>
<html>
<head>
<title>使用Selector API实现购物车客户端计算</title>
<meta charset="utf-8" />
<style>
table{width:600px; text-align:center;
border-collapse:collapse;
}
td,th{border:1px solid black}
td[colspan="3"]{text-align:right;}
/*让tbody中每行最后一个td的背景色变为黄色
tbody td:last-child{
background-color:yellow
}*/
/*tfoot下最后一个td背景变为粉色
tfoot td:last-child{
background-color:pink
}*/
</style>
</head>
<body>
<table id="data">
<thead>
<tr>
<th>商品名称</th>
<th>单价</th>
<th>数量</th>
<th>小计</th>
</tr>
</thead>
<tbody>
<tr>
<td>iPhone6</td>
<td>¥4488.00</td>
<td>
<button>-</button>
<span>1</span>
<button>+</button>
</td>
<td>¥4488.00</td>
</tr>
<tr>
<td>iPhone6 plus</td>
<td>¥5288.00</td>
<td>
<button>-</button>
<span>1</span>
<button>+</button>
</td>
<td>¥5288.00</td>
</tr>
<tr>
<td>iPad Air 2</td>
<td>¥4288.00</td>
<td>
<button>-</button>
<span>1</span>
<button>+</button>
</td>
<td>¥4288.00</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="3">Total: </td>
<td>¥14064.00</td>
</tr>
</tfoot>
</table>
<script>
//DOM4步
//1. 查找触发事件的元素
//本例中: 查找table中所有可以点的按钮元素
//1.1先查找id为data的table元素
var table=document.getElementById("data");
console.log(table);
//1.2再在table范围内找所有按钮元素
var btns=table.getElementsByTagName("button");
console.log(btns);
//2. 绑定事件处理函数
//本例中: 当单击每个按钮时,触发变化
//2.1 为找到的每个按钮对象都绑定单击事件的处理函数
//遍历btns类数组对象中每个按钮
for(var btn of btns){
//每遍历一个按钮,就为当前按钮对象的onclick事件属性赋值一个函数
btn.onclick=function(){//函数内无局部变量,去全局找btn,此时循环到最后一个btn
//alert("疼!");
//点哪个按钮,就让当前按钮变为红色
//btn.style.backgroundColor="red"; 点击任何一个按钮都是最后一个变红
//this.style.backgroundColor="red";
//<button style="background-color:red">
//3. (在事件处理函数中)查找要修改的元素
//本例中: 找当前按钮旁边的span
//最优: 先统一找当前按钮的爹,再向下找爹的第2个孩子
var span=this.parentNode.children[1];
//span.style.backgroundColor="red";
//4. 修改元素
//本例中:修改span的内容
//4.1. 先取出span的内容,转为整数
//凡是从页面上拿到的只能是字符串!
var n=parseInt(span.innerHTML);
//4.2. 如果当前按钮的内容是+,就直接数量+1,否则数量>1,才数量-1
if(this.innerHTML=="+"){
n++;
}else if(n>1){
n--;
}
//4.3. 将修改后的数量,放回span的内容中
span.innerHTML=n;
/************修改小计*************/
//3. 查找要修改的元素
//本例中:查找当前按钮的爹的下一个兄弟
var td=
this.parentNode.nextElementSibling;
//td.style.backgroundColor="red"
//4. 修改元素
//本例中: 用单价*数量=小计,放入小计格td中
//4.1 获得单价: 获得当前按钮的爹的前一个兄弟的内容,选取1位置到结尾的剩余子字符串,转为浮点数
var price=parseFloat(
this.parentNode
.previousElementSibling
.innerHTML
.slice(1)
);
//4.2 计算小计:
var sub=price*n;
//4.3 将小计放入小计格td的内容中,拼上¥,再保留两位小数
td.innerHTML=`¥${sub.toFixed(2)}`
/***********计算总计*************/
//3. 查找要修改的元素
//本例中: 修改tfoot中最后一个td
var lastTd=table.querySelector(
//也可用document.querySelector 因为 任意父元素.querySelctor table缩小范围
"tfoot td:last-child"
)
//lastTd.style.backgroundColor="pink"
//4. 修改元素
//本例中: 获得tbody中每行最后一个td,遍历这些td,并对td的内容求和,最后放入tfoot中最后一个td中
//4.1 获得tbody中每行最后一个td
var tds=table.querySelectorAll(
"tbody td:last-child"
);
//4.2 先创建一个总计变量,暂时值为0
var total=0;
//4.3 遍历tds类数组对象中每个td对象,每遍历一个td就将其内容累加到总计变量上
for(var td of tds){
//td.style.backgroundColor="yellow"
//获取当前td的内容,选取1位置到结尾的子字符串,转为浮点数,累加到变量total上
total+=parseFloat(
td.innerHTML.slice(1)
);
}
//4.4 将总计保存到tfoot中最后一个td的内容中,拼接¥,再保留两位小数
lastTd.innerHTML=`¥${total.toFixed(2)}`
}
}
//去全局找btn
//for循环结束后,用户才点第三个按钮!
//才自动执行第三个按钮的onclick()
//第三个按钮.onclick()
// function(){
//this->点前的第三按钮
//this.style.backgroundColor="red";
// }
</script>
</body>
</html>
按HTML特征查找 & 按选择器查找
返回值:
-
按HTML特征查找,返回动态集合 动态集合: 不返回完整的数据,仅返回本次所需的个别数据。如果再次访问集合,还需要重新去DOM查找一遍!
-
按选择器查找,返回非动态集合
非动态集合: 一次性返回完整的数据,即使反复访问集合,也不会反复查找DOM树。效率:
- 按HTML查找: 首次查找效率高!
- 按选择器查找: 首次查找效率低
易用性: - 按HTML查找,一次只能按一个条件查找,如果查找条件复杂,就需要多句话才能找到结果——慢
- 按选择器查找,一次可按多个条件同时查找,只需要一句话!——快
查找时的优化
- 如果只凭一个条件就可找到想要的元素时,首选按HTML查找 ( getElement…)
- 如果查找条件复杂时,首选按选择器查找!
事件:
- 什么是事件: 浏览器自动触发的或用户手动触发的页面中内容或状态的改变
- 什么是事件属性: 其实每个元素对象中都有一批on开头的特殊属性,属性值默认暂时都为null
- 何时使用事件属性:
① 当浏览器中发生一个事件后,会自动找到触发事件的这个元素上对应的事件属性,执行事件属性中保存的内容!
② 如果希望在单击某个按钮时,能执行一项任务,就可将这项任务保存在这个元素的事件属性中 - 什么是事件处理函数: 当事件发生时,希望自动执行的一个函数。
- 如何绑定事件处理函数: 其实就是将要执行的函数,提前保存在元素的事件属性上,等待事件触发时自动执行。
元素.on事件名=function(){ … }
强调: =后的事件处理函数不会立刻执行!只有在事件触发后才自动执行。
问题1: 在事件处理函数中如何获得当前正在触发的事件?
① 错误: 用外部的全局变量!
因为全局变量极有可能在事件赋值后,发生了很多变化!极其不确定!
②正确: this
因为: 浏览器触发事件处理函数时是这样执行的:
当前触发事件的那个元素.on事件名() 事件处理函数中的this
修改: innerHTML / textContent
内容: 3种:
Ⅰ 获取或修改原始的HTML片段: 元素.innerHTML
- innerHTML: 指元素开始标签到结束标签之间的HTML代码内容
- 获取元素的HTML内容时: 返回原始的HTML内容,不做任何加工
- 修改元素的HTML内容时: HTML内容会被浏览器解析为人可以看得网页内容。
Ⅱ 获取或修改纯文本内容: 元素.textContent
- textContent: 将元素的HTML内容编译后,给人看得纯文本内容
- 获取元素的纯文本内容时: 返回经过编译后的可以给人看的内容 ①去掉内嵌的标签 ②特殊符号翻译为正文!
- 修改元素的纯文本内容时: 无论提供的纯文本内容是什么,都不会被浏览器编译!而是原样显示!
innerHTML | textContent |
---|---|
获取: 不会翻译,原样返回 | 编译后,返回给人看的结果 |
修改:会被翻译,显示给人看 | 不会翻译,保持原样显示 |
Ⅲ 获取或修改表单元素的值:
- 不能用innerHTML和textContent,因为表单元素,比如是单标记,没有结束标签
- 应该: 表单元素
.value
Ⅳ 示例: 获取或修改元素的内容
<!--做死链接: 只有链接样子,但是点击不跳转到任何地方
错误: "#" 会返回页面顶部,且会在地址栏末尾追加#
正确: "javascript:;" 让a什么也不做!
javascript: 是在告诉a元素,不要跳转而要执行一条js语句
; : 是一条什么也不做的空语句!
-->
<p id="msg">来自<a href="javascript:;"><<新华社>></a>的消息</p>
<input id="txt"/> <button id="btn">百度一下</button>
<script>
//获取id为msg的元素的HTML内容:不会编译
console.log(msg.innerHTML);
//修改id为msg的HTML内容:会被编译
//msg.innerHTML="<ul><li>亮亮</li><li>然然</li></ul>"
//获得id为msg的元素的纯文本内容:会被编译
console.log(msg.textContent);
//修改id为msg的元素的纯文本内容:不会编译
msg.textContent="<ul><li>亮亮</li><li>然然</li></ul>"
//想点击按钮,获得表单元素<input/>中的值
//DOM4步:
//1. 查找触发事件的元素
//2. 绑定事件处理函数
btn.onclick=function(){
//3. 查找要修改的元素
//4. 修改元素
//获得txt的内容
//输出`查询 xxx 相关的信息...`
console.log(`查询 ${txt.value} 相关的信息...`)
}
带过渡的开关门效果
<head>
<title>读取并修改元素的内容</title>
<meta charset="utf-8" />
<style>
div{float:left; height: 100px; line-height: 100px; }
#d1,#d3{ background-color: #ccff00; }
#d2{ cursor: pointer; background-color: #ffcc00; }
#d1{
/*transition要求,必须显式指定动画开始的起始值*/
width:64px;
/*防止d1大小变化时,内容溢出*/
overflow:hidden;
/*当修改d1的大小时,自带过渡效果!*/
transition:all .5s linear;
}
</style>
</head>
<body>
<div id="d1">树形列表</div>
<div id="d2"><<</div>
<div id="d3">内容的主体</div>
<script>
//DOM4步
//1. 查找触发事件的元素
//本例中: 点d2触发变化,所以查找id为d2的元素
var d2=document.getElementById("d2");
//2. 绑定事件处理函数
//本例中: 点d2触发变化,所以为d2绑定单击事件处理函数
d2.onclick=function(){
//this->d2
//3. 查找要修改的元素
//本例中: 让id为d1的元素切换显示隐藏,所以,查找id为d1的元素
var d1=document.getElementById("d1")
//4. 修改元素
//本例中: 点d2时,暂时让d1隐藏
//html中: <div id="d1" style="display:none">
//js中:
//如果当前d2的内容是<<,就隐藏d1
//if(this.textContent=="<<"){ //可能有兼容性问题
if(this.innerHTML=="<<"){
//因为display属性不支持transition过渡
//d1.style.display="none";
//所以改用修改宽度控制d1显式隐藏
d1.style.width=0;//隐藏
//将当前d2的内容改为>>
this.innerHTML=">>"
}else{//否则(如果当前d2的内容不是<<),就显示d1
//d1.style.display="block"
d1.style.width="64px";//显式
//将当前d2的内容再回复回<<
this.innerHTML="<<"
}
}
</script>
</body>
属性
Ⅰ 字符串类型的HTML标准属性:
① HTML标准属性: HTML标准中规定的,所有元素都有的属性
② 如何获取和修改属性值: 2种:
旧核心DOM函数
i. var 属性值=元素.getAttribute("属性名")
获得页面上当前元素上指定"属性名"的属性值
ii. 元素.setAttribute("属性名", "新值")
修改页面上当前元素上指定"属性名"的属性值为新值
iii. 元素.removeAttribute("属性名")
删除页面上当前元素上指定"属性名"的属性
iv. 元素.hasAttribute("属性名")
判断页面上当前元素上是否包含指定"属性名"的属性
v. 示例: 使用核心DOM函数操作元素的属性
<a href="http://tmooc.cn" target="_block" id="a1">click me</a>
<script>
//获得a1的href属性值
var href=a1.getAttribute("href")
console.log(href);
//修改a1的href属性值
a1.setAttribute("href","http://code.tarena.com.cn");
//判断是否包含title属性
var bool=a1.hasAttribute("title");
console.log(bool)//false
//判断是否包含id属性
var bool=a1.hasAttribute("id");
console.log(true)//true
//移除target属性
a1.removeAttribute("target");
</script>
HTML DOM属性
①什么是HTML DOM: 新DOM标准中退出的对原DOM中常用函数和属性的简化版本。
② HTML DOM已经把所有标准属性提前定义在了内存里的元素对象上!只不过默认值暂时都为""。
③ 程序员就可直接用.访问标准属性:
获取属性值: 元素.属性名
修改属性值: 元素.属性名=新值
移除属性: 元素.属性名=""
判断是否包含: 元素.属性名!=="" 说明包含该属性!
示例: 使用HTMLDOM操作元素的属性
<a href="http://tmooc.cn" target="_block" id="a1">click me</a>
<script>
//输出内存中a1对象的样子:
//console.log(a1);
console.dir(a1); //专门输出对象在内存中的存储结构
//获得a1的href属性值
console.log(a1.href);
//修改a1的href属性值
a1.href="http://code.tarena.com.cn"
//判断是否包含title属性
console.log(a1.title!=="") //false
//判断是否包含id属性
console.log(a1.id!=="") //true
//移除target属性
a1.target="";
</script>
④ 特例: class属性:
因为ES标准中已经规定所有js的对象内部已经有一个内部属性class,用来标记一个对象的类型名(见isArray)。DOM就不能再class属性表示元素的样式了!所以DOM中的表示元素样式的class属性,都要更名为className
比如: 元素.className=“btn”
相当于: <元素 class=“btn”
⑤ className的问题: 以赋值方式,整体替换原元素上的class属性
比如: <元素 class=“btn btn-danger”
希望再追加一个active样式类
错误: 元素.className=“active” —— 整体替换
正确: 元素.className+=" 空格 active"
HTML5: 元素.classList.add(“active”) ——兼容性问题
vi. 示例: 操作元素的class
<button id="btn" class="btn btn-danger">click me</button>
<script>
//想给btn追加一个active class
//错: btn.className="active"
//对: btn.className+=" active";
//新HTML5标准中
console.log(btn.classList);
//为btn添加active class
btn.classList.add("active");
//想移除btn-danger class
btn.classList.remove("btn-danger");
</script>
手风琴菜单效果
<style>
li{ list-style:none; }
/*一级标题的初始样式*/
li span{
padding-left: 20px;
cursor: pointer;
background: url("../images/add.png") no-repeat center left;
}
/*二级菜单默认隐藏*/
li ul{
/* display: none; */
height:0;
overflow:hidden;
transition:all .5s linear;
}
/*定义open样式类,谁加谁就变-号*/
.open{
background: url("../images/minus.png") no-repeat center left;
}
/*谁是open旁边的ul,谁就跟着显示出来*/
.open+ul{
/* display: block; */
/* height:108px; */
}
/*如果打开的是第1个li下的span,则它旁边的ul高变为88px*/
li:nth-child(1)>span.open+ul{
height:88px;
}
/*如果打开的是第2个li下的span,则它旁边的ul高变为66px*/
li:nth-child(2)>span.open+ul{
height:66px;
}
/*如果打开的是第3个li下的span,则它旁边的ul高变为110px*/
li:nth-child(3)>span.open+ul{
height:110px;
}
</style>
</head>
<body>
<ul class="tree">
<li>
<span class="open">考勤管理</span>
<ul>
<li>日常考勤</li>
<li>请假申请</li>
<li>加班/出差</li>
<li>辞职</li>
</ul>
</li>
<li>
<span>信息中心</span>
<ul>
<li>通知公告</li>
<li>公司新闻</li>
<li>规章制度</li>
</ul>
</li>
<li>
<span>协同办公</span>
<ul>
<li>公文流转</li>
<li>文件中心</li>
<li>内部邮件</li>
<li>即时通信</li>
<li>短信提醒</li>
</ul>
</li>
</ul>
<script>
//DOM4步:
//1. 查找触发事件的元素
//本例中: 点每个span触发变化,所以查找class为tree的ul下的li下的span
var spans=document.querySelectorAll(
"ul.tree>li>span"
);
//2. 绑定事件处理函数
//本例中: 遍历spans中每个span,为每个span绑定单击事件处理函数
for(var span of spans){
//span.style.backgroundColor="red"
span.onclick=function(){
//点哪个span,让当前span变为红色
//this.style.backgroundColor="red";
//3. 查找要修改的元素
//本例中: 要修改当前点击的span
//4. 修改元素
//如果当前span是开着的,就关上自己,且不用考虑别人!因为别人一定是关着的!
if(this.className=="open"){
this.className=""
}else{
//否则(如果当前span是关着的)
//先查找另一个正在打开的span,把它关上
//本例中: 查找class为tree下的li下的class为open的span
var openSpan=document.querySelector(
"ul.tree>li>span.open"
);//如果找不到,返回null
//如果找到,才关闭这个开着的span
if(openSpan!=null){
openSpan.className=""
}
//再打开自己
this.className="open"
}
}
}
</script>
</body>
Bool类型的HTML标准属性
① 什么样的标准属性是bool类型:
比如: checked disabled selected
② 如何操作:
- 不能用核心DOM四个函数(getAttribute()、setAttribute()…),因为核心DOM四个函数只能操作字符串类型的属性值!
- 只能用HTML DOM的.访问: 比如: input.checked
btn.disabled - 值只可能是bool类型
③ 示例:
<input type="checkbox" id="chb" checked>同意
<script>
//想获得chb现在的选中状态
//错:console.log(chb.getAttribute("checked"));
console.log(chb.checked)
//想chb取消选中:
//错: chb.setAttribute("checked",false)
//对:不推荐chb.removeAttribute("checked")
chb.checked=false;
</script>
③补: css中其实有一组状态伪类:
- 包括: :checked :disabled …
- 专门选择处于某种状态的元素
全选和取消全选
<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>
/*点表头中的checkbox,全选表体中的每行第一个格中的checkbox*/
//DOM4步
//1. 查找出发事件的元素
//本例中: 点表头中的input
var chbAll=document.querySelector(
"thead input"
)
//2. 绑定事件处理函数
//本例中: 绑定单击事件
chbAll.onclick=function(){
//3. 查找要修改的元素
//本例中: 要修改tbody中每行第一个input
var chbs=document.querySelectorAll(
"tbody>tr>td:first-child>input"
);
//4. 修改元素
//本例中: 让每个chb和上方的chbAll(当前点的)的checked属性值保持一致!
for(var chb of chbs){
chb.checked=this.checked;
}
}
/*点击下方每一个checkbox,都可能改变上边的表头中checkbox的选中状态*/
//DOM4步
//1. 查找触发事件的元素
//本例中: tbody中每行第一个格中的checkbox都可点击
var chbs=document.querySelectorAll(
"tbody>tr>td:first-child>input"
);
//2. 绑定事件处理函数
//本例中: 为tbody中每行第一个格中的checkbox都要绑定单击事件
for(var chb of chbs){
chb.onclick=function(){
//this->当前点中的一个checkbox
//3. 查找要修改的元素
//本例中: 点下方的任意一个checkbox,可能影响上方的表头中的checkbox
var chbAll=document.querySelector(
"thead input"
)
//4. 修改元素
//本例中: 尝试去查找tbody中那个未选中的checkbox
var unchecked=document.querySelector(
"tbody>tr>td:first-child>input:not(:checked)"
)//但是不一定总能找到!
//如果找到tbody中至少还有一个未选中的checkbox,则表头的chbAll就不能选中
if(unchecked!=null){
chbAll.checked=false;
}else{//否则(如果tbody中找不到未选中的checkbox了!则表头的chbAll才能选中)
chbAll.checked=true;
}
}
}
</script>
获取或修改自定义扩展属性的值 HTML5中的简写
① 什么是自定义扩展属性: HTML标准中没有规定的,程序员自发添加的属性
②. 何时:
- 在网页中的元素上缓存自己要频繁使用的业务数据
- 代替其他选择器,用来作为查找条件使用,专门选择触发事件的元素,并绑定事件
1.id选择器
: 一次只能选一个元素
2.class选择器
: 本职工作是定义元素的样式,本身与交互行为无关。所以元素的class,可能会频繁变动
3.元素选择器
: 实现同一种效果,不一定所有人都用同一种元素!
4.自定义属性作为查找条件的好处
: 不会被轻易修改!不受标签名、class样式等的影响
③ 如何:
1. 添加自定义扩展属性: - 传统的HTML中:
<元素 自定义属性名="属性值">
- HTML5中:
<元素 data-自定义属性名="属性值">
2. 获取或修改属性值: - 万能: 用核心DOM4个函数:
getAttribute() setAttribute() ...
强调: 不能用.访问自定义扩展属性: 因为自定义扩展属性时后天补充的,名字也是不固定的,所以HTMLDOM标准不可能提前预知一个元素上将来肯定有那种属性!所以,元素对象的内存中是没有自定义扩展属性的,所以,不能用.访问
示例: 使用核心DOM方式操作自定义属性
<button sname="lilei" sage="11" city="北京">lilei</button><br>
<button sname="hmm" sage="12" city="杭州">hmm</button>
<script>
//查找所有带有sname属性的按钮,都可点击
var btns=document.querySelectorAll(
"[sname]" //属性选择器!
)
for(var btn of btns){
btn.onclick=function(){
//获得当前按钮上缓存在sname属性中的学生姓名
var sname=this.getAttribute("sname")
//获得当前按钮上缓存在sage属性中的学生年龄
var sage=this.getAttribute("sage")
//获得当前按钮上缓存在city属性中的城市名
var city=this.getAttribute("city")
console.log(`当前点击的是:
${sname} ${sage}岁 来自${city}`)
}
}
</script>
如果定义属性时,使用HTML5标准定义的(又data-前缀),则可以简写为: 元素.dataset.自定义属性名
强调:
①. Dataset只会自动收集data-前缀的属性,所以只有data-前缀的自定义属性,才能用dataset.xxx访问
②. Dataset.后不要再加data-,直接写自定义属性名即可!
示例: 使用HTML5方式使用自定义扩展属性
<button data-sname="lilei" data-sage="11" data-city="北京">lilei</button><br>
<button data-sname="hmm" data-sage="12" data-city="杭州">hmm</button>
<script>
//查找所有带有data-sname属性的按钮,都可点击
var btns=document.querySelectorAll(
"[data-sname]" //属性选择器!
)
for(var btn of btns){
btn.onclick=function(){
//获得当前按钮上缓存在sname属性中的学生姓名
//获得当前按钮上缓存在sage属性中的学生年龄
//获得当前按钮上缓存在city属性中的城市名
console.log(`当前点击的是:
${this.dataset.sname} ${this.dataset.sage}岁 来自${this.dataset.city}`)
}
}
修改样式
① 今后修改样式只能修改内联样式,优先级最高,且不影响其他元素!
② 不能修改样式表中的样式,怕牵一发而动全身
③ 如何: 元素.style.css属性="属性值"
等效于为HTML元素添加:<元素 style="css属性:属性值">
③ 强调:
- 必须有
.style
- Css属性名如果带-,会和减法的-冲突,所以所有css带横线的属性名必须去-,变驼峰!
比如: list-style-type -> listStyleType
background-color -> backgroundColor
text-decoration -> textDecoration - CSS属性值必须是字符串。如果是长度,大小,距离等数字值,则必须带单位!
读取并修改元素的属性: 实现多标签页效果
<!DOCTYPE HTML>
<html>
<head>
<title>读取并修改元素的属性</title>
<meta charset="utf-8" />
<style>
*{
margin:0;
padding: 0;
}
#tab li{
float: left; list-style: none;
}
#tab li a{
display:inline-block;
text-decoration:none;
width: 80px; height: 40px;
line-height: 40px;
text-align: center;
color:#000;
}
#container{
position: relative;
}
#content1,#content2,#content3{
width: 300px;
height: 100px;
padding:30px;
position: absolute;
top: 40px;
left: 0;
}
#tab li:first-child,#content1{
background-color: #ffcc00;
}
#tab li:first-child+li,#content2{
background-color: #ff00cc;
}
#tab li:first-child+li+li,#content3{
background-color: #00ccff;
}
</style>
</head>
<body>
<h2>实现多标签页效果</h2>
<div class="tabs">
<ul id="tab">
<li><a data-click="tab" href="javascript:;" data-content="content1">10元套餐</a></li>
<li><a data-click="tab" href="javascript:;" data-content="content2">30元套餐</a></li>
<li><a data-click="tab" href="javascript:;" data-content="content3">50元包月</a></li>
</ul>
<div id="container">
<div id="content1">
10元套餐详情:<br /> 每月套餐内拨打100分钟,超出部分2毛/分钟
</div>
<div id="content2">
30元套餐详情:<br /> 每月套餐内拨打300分钟,超出部分1.5毛/分钟
</div>
<div id="content3">
50元包月详情:<br /> 每月无限量随心打
</div>
</div>
</div>
<script>
//DOM4步
//1. 查找触发事件的元素
//本例中: 所有拥有data-click属性,且属性值为tab的元素都可以点
var tabs=document.querySelectorAll(
"[data-click=tab]"
)
//console.log(tabs);
//先定义一个变量,逐步递增zIndex的值
zIndex=10;
//2. 绑定事件处理函数
//本例中: 为每个标签都要绑定单击事件
for(var tab of tabs){
tab.onclick=function(){
//this->当前点中的tab元素
//alert("疼")
//this.style.backgroundColor="red";
//3. 查找要修改的元素
//本例中: 点击一个tab时,只要修改当前这个tab对应的div的zIndex即可
//先获得当前tab中缓存的div-content中的对应id
var id=this.getAttribute("data-content");
// this.dataset.content
//console.log(id);
var div=document.getElementById(id);
//4. 修改元素
//本例中: 将当前div的zIndex改为最大值
//先将当前zIndex值++
zIndex++;
//再将+1后的zIndex设置给当前div
div.style.zIndex=zIndex;
}
}
</script>
</body>
</html>
总结: 修改元素
获取或修改内容
- 原始HTML内容:
元素.innerHTML
- 纯文本内容:
元素.textContent
- 表单元素的值:
表单元素.value
获取或修改属性
字符串格式的标准属性:
旧核心DOM
- 获取属性值:
元素.getAttribute("属性名")
- 修改属性值:
元素.setAttribute("属性名", "属性值")
- 移除属性值:
元素.removeAttribute("属性名")
- 是否包含属性:
元素.hasAttribute("属性名")
HTML DOM
- 获取属性值:
元素.属性名
- 修改属性值:
元素.属性名="新值"
- 移除属性值:
元素.属性名=""
- 是否包含属性:
元素.属性名!==""
.class
要换成.className
- HTML5中:
元素.classList.add("class名")
,元素.classList.remove("class名")
- bool类型的标准属性:
元素.状态属性
HTML5中的简写
定义自定义属性时:<元素 data-属性名="值">
访问:元素.dataset.属性名
dataset会自动收集所有data-开头的属性集中保存起来 , 只有data-开头的属性才能用dataset访问 , 自定义属性名在进入dataset后,就去到了data-前缀,所以dataset
访问自定义属性时,无需加data-
前缀
查找带有自定义扩展属性的元素 : CSS属性选择器:[data-属性名=值]
获取或修改样式 - 修改内联样式:
元素.style.css属性名="属性值"
修改样式
修改内联样式: 元素.style.css属性="属性值"
获取样式:
问题: 因为元素.style仅代表内联样式,不包含内部或外部样式表中的样式
解决: 今后,只要获取样式,必须获得计算后的样式(computed):
什么是计算后的样式: 最终应用到这个元素上的所有样式的总和
如何:
-
先获得计算后的样式的总和:
var style=getComputedStyle(元素对象)
-
从获得的计算后的样式总和中提取出想用的个别css属性
var 属性值=style.css属性
-
坑: 计算后的样式,都是只读的,禁止修改!
因为计算后的样式来源不确定,万一修改,很可能牵一发而动全身,所以计算后的样式都是只读的,禁止修改!示例: 获取样式:
<style>
h1{
background-color:yellow
}
</style>
<body>
<h1 id="h1" style="color:red">Welcome</h1>
<script>
//先获得h1计算后的样式总和:
var style=getComputedStyle(h1);
console.log(style);
//想获得h1的背景颜色
//console.log(h1.style.backgroundColor);
console.log(style.backgroundColor)
//想获得h1的字体大小
//console.log(h1.style.fontSize);
console.log(style.fontSize);
//想修改h1的字体大小
//错:style.fontSize="64px";
//对:
h1.style.fontSize="64px";
</script>
</body>
优化: 尽量用最少的语句和修改次数来修改样式:
重排重绘:
① 浏览器渲染(render)网页的过程:
② 重绘: 重新绘制网页
只要对元素做了只影响样式,不影响布局的修改,浏览器只需要重新绘制一遍网页,即可无需重新排版
③ 重排重绘: 重新排版
如果对元素做的样式修改,影响了网页的布局,浏览器就被迫需要重新排版+重新绘制
问题: 如果程序频繁对元素的样式执行修改操作,可能会导致浏览器频繁重排重绘,严重可导致闪屏。
优化: 能用一句话修改的样式,就绝不用多句话修改
① 如果非要修改内联样式:
元素.style.cssText=“css属性:值; css属性:值; …”
只需要一次重排重绘
问题: 如果一次性批量修改多个css属性,代码会很繁琐!
何时: 今后只有精细的修改个别css属性时,才被迫用style修改!
②批量修改多个css属性: 用.className
使用class,实现带样式的表单验证
<head>
<meta charset="UTF-8">
<title>实现带样式的表单验证</title>
<style>
table{width:700px}
td:first-child{width:60px}
td:nth-child(2){width:200px}
td:first-child+td{width:200px}
td span{color:red}
.vali_info{
display:none;
}
.txt_focus{
border-top:2px solid black;
border-left:2px solid black;
}
.vali_success,.vali_fail{
background-repeat:no-repeat;
background-position:left center;
display:block;
}
/*如果验证通过,则文本框旁边的div应用这个class*/
.vali_success{
background-image:url("../images/ok.png");
padding-left:20px;
width:0px;height:20px;
overflow:hidden;
}
/*如果验证不通过,则文本框旁边的div应用这个class*/
.vali_fail{
background-image:url("../images/err.png");
border:1px solid red;
background-color:#ddd;
color:Red;
padding-left:30px;
}
</style>
</head>
<body>
<form id="form1">
<h2>增加管理员</h2>
<table>
<tr>
<td>姓名:</td>
<td>
<input name="username"/>
<span>*</span>
</td>
<td>
<div class="vali_info">
10个字符以内的字母、数字或下划线的组合
</div>
</td>
</tr>
<tr>
<td>密码:</td>
<td>
<input type="password" name="pwd"/>
<span>*</span>
</td>
<td>
<div class="vali_info">6位数字</div>
</td>
</tr>
<tr>
<td></td>
<td colspan="2">
<input type="submit" value="保存"/>
<input type="reset" value="重填"/>
</td>
</tr>
</table>
</form>
<script>
/*先实现文本框获得焦点时,让旁边的提示信息div,显示出来*/
//DOM4步
//1. 查找触发事件的元素
//本例中: 当文本框获得焦点时, 就应该查找name属性为username和pwd的两个文本框
//先查找name属性为username的文本框
var txtName=document.getElementsByName("username")[0];
/*如果一个函数规定返回类数组对象,则永远返回类数组对象,即使只找到一个元素,也会放在类数组对象中返回!
所以,如果想取出本次找到的唯一的一个DOM元素
执行后续DOM操作,必须用[0]才能取出来!*/
//console.log(txtName);
//txtName.style.border="2px solid red";
//再查找name属性为pwd的元素
var txtPwd=document.getElementsByName("pwd")[0];
//txtPwd.style.border="2px solid red";
//2. 绑定事件处理函数
//本例中: 当两个文本框获得焦点时,所执行的操作代码完全一样!所以,用连等方式,共同使用同一个函数对象!
//连等示例:
txtPwd.onfocus=txtName.onfocus=function(){
//3. 查找要修改得元素
//本例中: 要修改文本框右侧的div
//查找当前文本框的爹的下一个兄弟的第一个孩子
this.parentNode
.nextElementSibling
.children[0]
//4. 修改元素
//本例中: 清除div的className,让其显示
.className="";
}
/*当文本框失去焦点时,用程序验证文本框中的内容,如果验证通过,则修改旁边div为验证通过的样式,如果验证不通过,则修改旁边div为验证不通过的样式*/
//2. 绑定事件
//本例中: 当文本框失去焦点时验证
txtName.onblur=function(){
//this->txtName
//先定义正则表达式
var reg=/^\w{1,10}$/;
//调用同一的验证函数
//用外部正确的this代替函数内不想要的this
/**/vali.call(this,reg);
}
//只要程序中一段代码可能被反复使用,都应该封装在一个函数中,然后再反复调用函数
//如果函数中的this不是想要的,可在调用时用call临时替换为正确的this
//如果函数内缺少必须的变量和数据,可定义形参变量,要求调用者调用时传入——函数的特权!
function vali(reg){
//3. 查找要修改的元素
var div=this.parentNode
.nextElementSibling
.children[0]
//4. 修改元素
//先验证当前文本框的内容是否符合格式要求
//再用正则验证当前文本框的内容
var bool=reg.test(this.value);
if(bool==true){//如果验证通过
//就修改div的class为vali_success
div.className="vali_success";
}else{//否则
//就修改div的class为vali_fail
div.className="vali_fail";
}
}
//当密码框失去焦点时,验证密码框内容
txtPwd.onblur=function(){
//this->txtPwd
//也要先定义正则表达式
var reg=/^\d{6}$/;
//也要调用公共的验证函数,但是要替换this为自己,也要传入自己专门的正则表达式。
vali.call(this,reg);
}
//将来如果txtName获得焦点
//浏览器会自动调用txtName.onfocus()
// ↑
//function中的this自动指
//将来如果txtPwd获得焦点
//浏览器会自动调用txtPwd.onfocus()
// ↑
//function中的this自动指
</script>
</body>
添加删除元素
添加元素: 3步:
1. 创建新的空元素对象: var 元素=document.createElement("元素名");
比如: var a=document.createElement(“a”);
结果: <a></a>
2. 为新元素设置关键属性
比如: a.innerHTML="go to tmooc"
a.href="http://tmooc.cn"
结果: go to baidu ( <a href="http://baidu.com">go to baidu</a>
)
3. 将新元素添加到DOM树: 3种:
① 在指定父元素下所有直接子元素末尾追加新元素父元素.appendChild(新元素)
② 在指定父元素下的某个直接子元素前插入新元素父元素.inertBefore(新元素, 现有子元素)
③ 在指定父元素下用新元素替换某个直接子元素父元素.replaceChild(新元素, 现有子元素)
示例: 创建两个新元素:
//1. 创建新的空元素
var a=document.createElement("a");
//2. 设置关键属性
a.href="http://tmooc.cn";
a.innerHTML="go to tmooc";
console.log(a);
//3. 想在body中添加一个a元素
document.body.appendChild(a)
//想在a之前插入一个<input>
var input=document.createElement("input")
document.body.insertBefore(input,a);
//.replaceChild(input,a)
动态生成表格
<div id="data">
<table>
<thead>
<tr>
<th>ename</th>
<th>salary</th>
<th>age</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}
];
//1. 创建<tbody>,但是,暂不追加到table下
var tbody=document.createElement("tbody");
//2. 遍历json数组中每个员工对象
for(var emp of json){
//3. 每遍历一个员工对象就创建一个<tr>,追到<tbody>下
var tr=document.createElement("tr");
tbody.appendChild(tr);
//4. 遍历当前员工对象中每个属性
for(var key in emp){
//5. 每遍历一个员工对象的属性,就创建一个<td>,设置当前员工的当前属性值为td的内容,并将td追加到<tr>下
var td=document.createElement("td");
td.innerHTML=emp[key];
tr.appendChild(td);
}
}
//最后再查找table: id为data下的直接子元素table
var table=document.querySelector(
"#data>table"
);
//最后再将包含tr的tbody追加到table中
table.appendChild(tbody);
优化: 尽量减少操作DOM树的次数,为了减少重排重绘
使用添加元素三步每操作一次DOM树,都会导致重排重绘,降低页面加载效率
解决: 如何向页面添加的内容不少,但是次数少!
· 如果同时添加父元素和子元素时,应该现在内存中,将所有子元素都添加到父元素下。最后,再将父元素一次性添加到DOM树。——只需要一次重排重绘!
· 如果父元素已经在页面上了,要添加多个平级子元素,就要用文档片段来添加子元素。
文档片段: 内存中临时存储多个平级子元素的虚拟父元素
何时: 只要同时添加多个平级子元素到页面时,都用文档片段
如何: 3步:
① 先创建一个文档片段对象: var frag=document.createDocumentFragment();
②将多个平级子元素添加到文档片段对象中: frag.appendChild(子元素)
③最后,将整个文档片段对象添加到DOM树指定父元素下。
强调: 文档片段对象将子元素运送到DOM树上指定父元素下之后,就释放了!不会占用页面元素空间。
两个select级联选择
<body>
<select name="provs">
<option>—请选择—</option><!--0-->
<option>北京市</option><!--1-->
<option>天津市</option>
<option>河北省</option>
</select>
<select name="cities" class="hide">
</select>
<script>
/*实现“省”和“市”的级联下拉列表*/
var cities=[
[
{"name":'东城区',"value":101},
{"name":'西城区',"value":102},
{"name":'海淀区',"value":103},
{"name":'朝阳区',"value":104}
],
[
{"name":'河东区',"value":201},
{"name":'河西区',"value":202},
{"name":'南开区',"value":303}
],
[
{"name":'石家庄市',"value":301},
{"name":'廊坊市',"value":302},
{"name":'保定市',"value":303},
{"name":'唐山市',"value":304},
{"name":'秦皇岛市',"value":304}
]
];
//DOM4步:
//1. 查找触发事件的元素
//本例中: 点省份select,触发城市列表select的变化,所以查找name名为provs的select元素
var selProvs=document.getElementsByName("provs")[0];
//2. 绑定事件处理函数
//本例中: 只有当select中的选中项发生改变时才触发第二个select的变化:
selProvs.onchange=function(){
//this->selProvs
//3. 查找要修改的元素
//本例中: 触发的是第二个城市列表select的变化。应该查找name名为cities的select
var selCts=document.getElementsByName("cities")[0];
//4. 修改元素
//如果用户点的是请选择(0位置),则仅隐藏第二个select,不再执行后续任何操作!
if(this.selectedIndex==0){
selCts.className="hide";
return;
}
//4.1 获得selectedIndex
//alert(this.selectedIndex)
//4.2 -1后,获得新下标i
var i=this.selectedIndex-1;
//4.3 获得cities数组中i位置的子数组城市列表
var cts=cities[i];
//4.4 创建文档片段
var frag=document.createDocumentFragment();
//补: 在开始遍历城市列表之前,先加入一个请选择选项:
var opt=document.createElement("option")
opt.innerHTML="-请选择-"
frag.appendChild(opt);
//4.5 遍历子数组cts中每个城市对象
for(var c of cts){
//4.6 每遍历一个城市对象,就创建一个option对象,设置option对象的内容为当前城市对象的名字,再将option对象追加到文档片段中
var opt=document.createElement("option");
opt.innerHTML=c.name;
frag.appendChild(opt)
}
//补: 先清空selCts中旧内容,再追加新内容
selCts.innerHTML="";
//4.7 将frag一次性添加到selCts中
selCts.appendChild(frag);
//4.8 让selCts显示出来(清除class)
selCts.className="";
}
</script>
</body>
HTML DOM常用对象
HTML DOM不仅提供了用.访问属性的简化方法,而且还对个别常用的DOM元素提供了一套简化版的函数
-
Image对象: 代表页面上一个元素
唯一的简化: 创建时:
核心DOM: var img=document.createElement(“img”)
HTML DOM: var img=new Image();
强调: 只有Image和Option可以new,其余元素不能new! -
Select对象: 代表页面上一个元素
(1). 属性上的简化:
a. .selectedIndex 获得本次选中的option的下标位置
b. .value 获得当前选中的option的value属性
强调: 万一当前选中的option没有value属性,则.value会用option的innerHTML代替!
c. .options 获得当前select下所有的option对象的集合
d. .length 获得当前select下所有option的个数:
等效于: .options.length的简写
(2). 方法上的简化:
a. .add(option对象) 代替 .appendChild(option对象)
问题: 不支持文档片段
b. .remove(i) 移除i位置的option对象
(3). Option对象: 代表页面上一个元素
唯一简写: 创建:
a. 核心DOM:
var opt=document.createElement(“option”)
opt.innerHTML=文本;
opt.value=值
b. HTML DOM:
var opt=new Option(文本,值) -
Table对象: 代表页面上一个table元素:
Table对象的层级多,所以采用逐级管理的方式
(1). Table管着行分组:
a. 添加行分组:
var thead=table.createTHead();
相当于: var thead=document.createEelement(“thead”);
table.appendChild(thead)
var tbody=table.createTBody();
var tfoot=table.createTFoot();
b. 删除行分组:
table.deleteTHead()
table.deteteTFoot()
因为tbody不直接隶属于table,还包在一层数组中,所以table无权直接删除tbody对象。
c. 获取行分组:
table.tHead
table.tFoot
table.tBodies[i]
因为HTML 标准中规定table中却是可以又多个tbody,所以tbody对象是放在数组中保存的
(2). 行分组管着行:
a. 向行分组中添加一行: var tr=行分组.insertRow(i)
1). 在行分组中i位置插入一个新行
2). 如果i位置已经有旧行,则将旧行向后挤。
3). 固定套路:
i. 末尾追加一个新行: var tr=行分组.insertRow()
ii. 开头插入一个新行: var tr=行分组.insertRow(0)
b. 从行分组中删除一行: 行分组.deleteRow(i)
强调: 如果用行分组作为主语删除行,则参数i,要求是行在行分组内的相对位置
删除行:
-
通常删除行时,都不知道用户要删哪一行
-
其实可以通过用户点击的按钮,逐级查找父元素,来找到要删除的行对象
-
如果可以获得要删除的行对象tr,其实每个tr上都有一个内置的属性: .rowIndex 记录着当前行在整个表中是第几行!
-
问题:行分组.deleteRow()时,要求传入行在行分组内的相对下标位置,而不是这一行在整个表中的下标位置
-
错误: 行分组.deleteRow(tr.rowIndex)
结果: 总是删除下一行,而不是当前行
因为表头行占着0位置,rowIndex获得的是行在整个表中的位置,比在行分组中的相对位置多1! -
解决: 今后删除行,不要用行分组删除!
应该用table删除: table.deleteRow(i)
i 要求传入行在整个表中的位置,刚好可用tr.rowIndex
所以: table.deleteRow(tr.rowIndex)c. 获取行: 行分组.rows[i]
(3). 行管着格:
a. 行可以添加格: var td=tr.insertCell(i)
固定套路: var td=tr.insertCell() 末尾追加一个新格
b. 行可以删除格: tr.deleteCell(i)
c. 行可以获取格: tr.cells[i]
补: 浏览器三大对话框: BOM
-
prompt() 输入框: 带一个可输入内容的文本框,点确定后,可将用户输入的内容,返回到程序中的变量中。
-
alert() 警告框: 只有一个按钮的提示框!
-
confirm() 确认框: 有两个按钮: 确认、取消。如果用户点确认,说明希望继续执行,则确认框返回true。如果用户点取消,说明后悔了,不想继续,则确认框返回false。
(4). 示例: 用HTMLDOM简化1_createTable代码,并实现删除行的功能
<body>
<div id="data">
<table>
<thead>
<tr>
<th>ename</th>
<th>salary</th>
<th>age</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: id为data下的直接子元素table
var table=document.querySelector(
"#data>table"
);
//1. 创建<tbody>,但是,暂不追加到table下
var tbody=document.createElement("tbody");
//2. 遍历json数组中每个员工对象
for(var emp of json){
//3. 每遍历一个员工对象就创建一个<tr>,追到<tbody>下
// var tr=document.createElement("tr");
// tbody.appendChild(tr);
var tr=tbody.insertRow();
//4. 遍历当前员工对象中每个属性
for(var key in emp){
//5. 每遍历一个员工对象的属性,就创建一个<td>,设置当前员工的当前属性值为td的内容,并将td追加到<tr>下
// var td=document.createElement("td")
// tr.appendChild(td);
var td=tr.insertCell();
td.innerHTML=emp[key];
}
//在这一行中所有内容格添加完之后,再多添加一个td
var td=tr.insertCell();
//创建一个按钮,放入td中
var btn=document.createElement("button")
btn.innerHTML="x";
td.appendChild(btn);
//为按钮绑定单击事件:
btn.onclick=function(){
//删除按钮所在行:
//问题:如何知道当前按钮在第几行?
//解决:通过当前按钮找到它所在的行对象
var tr=this.parentNode.parentNode;
//td tr
//获得当前行中第1个格的内容中的员工名
var ename=tr.cells[0].innerHTML;
//在删除之前,先向用户确认,是否继续删除
var bool=confirm(`是否继续删除 ${ename} 吗?`);
//如果用户同意删除,才执行删除操作
if(bool==true){
//今后删除行都用table来删除,传入tr.rowIndex作为参数
table.deleteRow(tr.rowIndex);
}//否则,什么也不做
}
}
//最后再将包含tr的tbody追加到table中
table.appendChild(tbody);
</script>
</body>
总结: 增删改查 优化
- 不需要查找就可直接获得的元素:
document.documentElement
document.head
document.body
- 按节点间关系查找:
父子关系
元素.parentNode
元素.children
元素.firstElementChild
元素.lastElementChild
兄弟关系
元素.previousElementSibling
元素.nextElementSibling
按HTML特征查找
var 元素=document.getElementById("id名")
var 类数组对象=任意父元素.getElementsByTagName("标签名")
var 类数组对象=任意父元素.getElementsByClassName("class名")
var 类数组对象=document.getElementsByName("name名")
用选择器查找
var 元素=任意父元素.querySelector("任意选择器")
var 类数组对象=任意父元素.querySelectorAll("任意选择器")
修改
内容: 元素.innerHTML
元素.textContent
元素.value
属性:
字符串类型的标准属性: 元素.get/set/has/removeAttribute()
, 元素.属性名
(.class →.className)
Bool类型的标准属性: 元素.属性名
自定义扩展属性: 元素.get/set/has/removeAttribute()
, HTML5: 元素.dataset.属性名
样式
修改内联样式: 元素.style.css属性="属性值"
如果批量修改样式 可用元素.className="class名"
获取样式: var style=getComputedStyle(元素对象)
, var 属性值=style.css属性
添加和删除
只添加一个元素: 3步
①var a=document.createElement(“a”)
②设置关键属性
③ 父元素.appendChild(a);
父元素.insertBefore(a, 现有子元素)
父元素.replaceChild(a, 现有子元素)
同时添加多个子元素:
① var frag=document.createDocumentFragment()
② frag.appendChild(子元素)
③ 父元素.appendChild(frag)
删除元素: 父元素.removeChild(子元素)
HTML DOM的简写
-
Image: var img=new Image();
-
Select:
sel.selectedIndex
sel.value
sel.options
sel.length
sel.add(opt)
sel.remove(i)
Option
:
var opt=new Option(文本, 值) -
Table
:
① table管着行分组:
创建:table.createTHead/TBody/TFoot()
删除:table.deleteTHead/TFoot()
获取:table.tHead/tFoot
table.tBodies[i]
② 行分组管着行:
创建: 行分组.insertRow(i)
.insertRow()
.insertRow(0)
删除: 行分组.deleteRow(i)
table.deleteRow(tr.rowIndex)
获取: 行分组.rows[i]
③行管着格:
创建: tr.insertCell()
删除: tr.deleteCell()
获取: tr.cells[i] -
Form:
获取:var form=document.forms[i]
属性: form.elements form.length表单元素对象:
获取: form.elements[i/id/name]
form.name名
方法: 表单元素.focus()
优化
- 查找:
(1). 如果只用一个条件就可找到元素,则首先按HTML特征查找
(2). 如果查找条件复杂,首选按选择器查找! - 修改样式: 尽量减少修改样式的次数,减少重排重绘!
(1). 修改内联样式: 元素.style.cssText=“css属性:属性值; …”
(2). 用修改className的方式批量修改元素的样式! - 添加元素: 尽量减少操作DOM树的次数,减少重排重绘!
(1). 如果同时添加父元素和子元素,应该先在内存中,将子元素都添加到父元素中,最后再将父元素一次性添加到DOM树。——只需要一次重排重绘
(2). 如果父元素已经在页面上,需要添加多个平级子元素,则需要先将平级子元素添加到文档片段对象中,最后,再将文档片段对象添加到DOM树——也只需要一次重排重绘。 - 事件委托: 尽量减少事件监听对象的个数
(1). 当多个平级子元素需要绑定相同的事件时,只要将事件统一绑定在父元素上一份即可
(2). 事件处理函数中用e.target,代替this
(3). 事件处理函数中要判断目标元素的特征,只允许具有指定特征的主角元素才能执行事件代码。
HTML DOM常用对象
- Form对象: 代表页面上一个元素
(1). 获取: var form=document.forms[i]
因为表单元素对页面来说非常重要,所以document专门用一个集合forms,保存住了当前页面中所有表单元素!可用下标来访问第i个表单元素
(2). 属性:
a. form.elements 获得当前表单中所有表单元素的集合
包括: input select textarea button
b. form.length 获得当前表单中所有表单元素的个数
其实是 form.elements.length的简写!
(3). 表单元素对象: 代表着表单中某个表单元素
a. 获取:
1). 获取表单元素的标准方法:
var 表单元素=form.elements[i/id/name]
2). 如果表单元素有name属性,可简写为:
form.name名
b. 方法: 表单元素.focus() 让表单元素自动获得焦点!
(4). 示例: 使用HTML DOM优化带样式的表单验证案例
<head>
<meta charset="UTF-8">
<title>实现带样式的表单验证</title>
<style>
table{width:700px}
td:first-child{width:60px}
td:nth-child(2){width:200px}
td:first-child+td{width:200px}
td span{color:red}
.vali_info{
display:none;
}
.txt_focus{
border-top:2px solid black;
border-left:2px solid black;
}
.vali_success,.vali_fail{
background-repeat:no-repeat;
background-position:left center;
display:block;
}
/*如果验证通过,则文本框旁边的div应用这个class*/
.vali_success{
background-image:url("images/ok.png");
padding-left:20px;
width:0px;height:20px;
overflow:hidden;
}
/*如果验证不通过,则文本框旁边的div应用这个class*/
.vali_fail{
background-image:url("images/err.png");
border:1px solid red;
background-color:#ddd;
color:Red;
padding-left:30px;
}
</style>
</head>
<body>
<form id="form1">
<h2>增加管理员</h2>
<table>
<tr>
<td>姓名:</td>
<td>
<input name="username"/>
<span>*</span>
</td>
<td>
<div class="vali_info">
10个字符以内的字母、数字或下划线的组合
</div>
</td>
</tr>
<tr>
<td>密码:</td>
<td>
<input type="password" name="pwd"/>
<span>*</span>
</td>
<td>
<div class="vali_info">6位数字</div>
</td>
</tr>
<tr>
<td></td>
<td colspan="2">
<input type="button" value="保存"/>
<input type="reset" value="重填"/>
</td>
</tr>
</table>
</form>
<script>
/*先实现文本框获得焦点时,让旁边的提示信息div,显示出来*/
//DOM4步
//1. 查找触发事件的元素
//本例中: 当文本框获得焦点时, 就应该查找name属性为username和pwd的两个文本框
//先找当前页面中唯一的一个form元素
var form=document.forms[0];
//再查找name属性为username的文本框
var txtName=form.username;
//再查找name属性为pwd的元素
var txtPwd=form.pwd;
//2. 绑定事件处理函数
//本例中: 当两个文本框获得焦点时,所执行的操作代码完全一样!所以,用连等方式,共同使用同一个函数对象!
txtPwd.onfocus=txtName.onfocus=function(){
//3. 查找要修改得元素
//本例中: 要修改文本框右侧的div
//查找当前文本框的爹的下一个兄弟的第一个孩子
this.parentNode
.nextElementSibling
.children[0]
//4. 修改元素
//本例中: 清除div的className,让其显示
.className="";
}
/*当文本框失去焦点时,用程序验证文本框中的内容,如果验证通过,则修改旁边div为验证通过的样式,如果验证不通过,则修改旁边div为验证不通过的样式*/
//2. 绑定事件
//本例中: 当文本框失去焦点时验证
txtName.onblur=function(){
//this->txtName
//先定义正则表达式
var reg=/^\w{1,10}$/;
//调用同一的验证函数
//用外部正确的this代替函数内不想要的this
/**/vali.call(this,reg);
}
//只要程序中一段代码可能被反复使用,都应该封装在一个函数中,然后再反复调用函数
//如果函数中的this不是想要的,可在调用时用call临时替换为正确的this
//如果函数内缺少必须的变量和数据,可定义形参变量,要求调用者调用时传入——函数的特权!
function vali(reg){
//3. 查找要修改的元素
var div=this.parentNode
.nextElementSibling
.children[0]
//4. 修改元素
//先验证当前文本框的内容是否符合格式要求
//再用正则验证当前文本框的内容
var bool=reg.test(this.value);
if(bool==true){//如果验证通过
//就修改div的class为vali_success
div.className="vali_success";
return true
}else{//否则
//就修改div的class为vali_fail
div.className="vali_fail";
return false
}
}
//当密码框失去焦点时,验证密码框内容
txtPwd.onblur=function(){
//this->txtPwd
//也要先定义正则表达式
var reg=/^\d{6}$/;
//也要调用公共的验证函数,但是要替换this为自己,也要传入自己专门的正则表达式。
vali.call(this,reg);
}
//将来如果txtName获得焦点
//浏览器会自动调用txtName.onfocus()
// ↑
//function中的this自动指
//将来如果txtPwd获得焦点
//浏览器会自动调用txtPwd.onfocus()
// ↑
//function中的this自动指
/*单击保存按钮,先验证所有文本框,再提交表单*/
//先查找保存按钮: 表单中倒数第二个表单元素
var btn=form.elements[form.length-2];
//为btn绑定单击事件:
btn.onclick=function(){
//this->保存按钮btn
//先验证用户名
var bool=vali.call(txtName,/^\w{1,10}$/)
//如果用户名验证不通过
if(bool==false){
//就让用户名文本框自动获得焦点
txtName.focus();
}else{//否则(如果用户名验证通过)
//再验证密码框
var bool=vali.call(txtPwd,/^\d{6}$/);
//如果密码框验证不通过
if(bool==false){
//就让密码框自动获得焦点
txtPwd.focus();
}else{//否则(用户名和密码都验证通过了)
//才提示保存成功!
alert("保存成功!");
//将来在这里发送ajax请求,提交数据
}
}
}
</script>
</body>