DOM 节点操作
获取元素通常使用两种方式:
(1)利用 DOM 提供的方法获取元素(逻辑性不强,繁琐)
(2)利用节点层级关系获取元素(逻辑性强,但兼容性稍差)
1. DOM 树
2. 节点属性
(1)nodeType 节点的类型,属性值为数字,表示不同的节点类型,共 12 种,只读
- 1 → 元素节点
- 2 → 属性节点
- 3 → 文本节点 (包含文字、空格、换行等)
(2)nodeName 节点的名称(标签名称),只读
(3)nodeValue 节点值,返回或设置当前节点的值
注意:元素节点的 nodeValue 始终是 nul
3. 父子节点常用属性
(1)parentNode ***
- 返回一个当前节点的父节点(离元素最近的父级节点)
- 如果没有这样的节点,比如说像这个节点是树结构的顶端或者没有插入一棵树中,这个属性返回 null
(2)parentElement
- 返回当前节点的父元素节点
- 如果该元素没有父节点,或者父节点不是一个 DOM 元素,则返回 null
(3)childNodes
- 只读属性
- 获取一个节点所有子节点(包括元素节点、文本节点等)的实时的集合
- 集合是动态变化的。
- 标准,不提倡使用
<div id="box">
<p>段落</p>
</div>
<script>
console.log(box.childNodes); // NodeList(3) [text, p, text]
</script>
(4)children ***
- 只读属性
- 返回一个节点所有的子元素节点集合
- 是一个动态更新的 HTML 元素集合。
- 非标准,但得到各个浏览器的支持,常用
(5)firstChild
- 只读属性
- 返回该节点的第一个子节点,如果该节点没有子节点则返回 null。
- 不区分节点类型
(6)lastChild
- 只读属性
- 返回该节点的最后一个子节点,如果该节点没有子节点则返回 null
- 不区分节点类型
(7)firstElementChild
- 只读属性
- 返回该节点的第一个子元素节点,如果没有则返回 null。
- 有兼容问题,IE9 以上才支持
(8)lastElementChild
- 只读属性
- 返回该节点的最后一个子元素节点,如果没有则返回 null。
- 有兼容问题,IE9 以上才支持
注:实际开发中的写法,既没有兼容问题又返回第一个或最后一个子元素
console.log(box.children[0]); // 返回 box 中的第一个子元素
console.log(box.children[box.children.length - 1]); // 返回 box 中的最后一个子元素
案例:隔行变色案例重写
// 获取 tbody
var tb = document.getElementById("tb");
// 获取 tbody 里的所有子元素节点 tr
var trs = tb.children;
for (var i = 0; i < trs.length; i++) {
if (i % 2 === 0) {
trs[i].style.backgroundColor = "pink";
} else {
trs[i].style.backgroundColor = "lightgray";
}
}
案例:新浪下拉菜单
结构:大盒子 ul (.nav)包含3个 li ,每个 li 包含上下两部分,上边一个 a 链接,下边 ul 包括3个 li
效果:当鼠标经过 .nav 的 li 时,让 li 的第二个子元素显示,即下边的 ul 显示
var nav = document.querySelector(".nav");
var lis = nav.children; // 得到3个小li
for (var i = 0; i < lis.length; i++) {
lis[i].onmouseover = function () {
this.children[1].style.display = "block";
};
lis[i].onmouseout = function () {
this.children[1].style.display = "none";
};
}
4. 兄弟节点常用属性
(1)nextSibling
- 只读属性
- 返回与该节点同级的下一个节点,如果没有返回null
- 不区分节点类型
(2)previousSibling
- 只读属性
- 返回与该节点同级的上一个节点,如果没有返回null
- 不区分节点类型
(3)nextElementSibling
- 只读属性
- 返回与该节点同级的下一个元素节点,如果没有返回null。
- 有兼容性问题,IE9以后才支持
(4)previousElementSibling
- 只读属性
- 返回与该节点同级的上一个元素节点,如果没有返回null。
- 有兼容性问题,IE9以后才支持
可以封装一个解决兼容性问题的函数:
function getNextElementSibling(element) {
var el = element;
while ((el = el.nextSibling)) {
if (el.nodeType === 1) {
return el;
}
}
return null;
}
5. 创建新节点的方法
(1)document.createElement(“div”) → 创建元素节点
(2)document.createAttribute(“id”) → 创建属性节点
(3)document.createTextNode(“hello”) → 创建文本节点
注意:
- 一般将创建的新节点存在变量中,方便使用。
- 创建的新的节点是存储在内存中的,但是并没有添加到 DOM 树上
6. 节点常用操作方法1
(1)parentNode.appendChild(child)
- 将一个节点添加到指定父节点的子节点列表末尾。
- 文本节点也可以添加到元素内部
- 自己创建的元素节点本身也是一个对象,也可以去添加一些新的属性和方法,这些操作将来在元素加载到 DOM 树时,依旧保留
注意:DOM 中原有的节点也可以传给 appendChild 的参数(相当于剪切)
- 先将节点从原始位置删除,然后添加到新的指定位置。
- 原因:内存中这个原有节点只有一个,渲染时只能有一个位置
(2)parentNode.replaceChild(newChild, oldChild)
- 用指定的节点替换当前节点的一个子节点
- 并返回被替换掉的节点。
(3)parentNode.insertBefore(newNode, referenceNode)
- 在参考节点之前插入一个拥有指定父节点的子节点
- referenceNode 必须设置,如果 referenceElement 为 null 则 newNode 将被插入到子节点的末尾。
(4)parentNode.removeChild(child)
- 移除当前节点的一个子节点。
- 这个子节点必须存在于当前节点中。
7. 节点常用操作方法2
Node.cloneNode() (相当于复制)
- 克隆一个节点,并且可以选择是否克隆这个节点下的所有内容。
- 参数为 Boolean 布尔值,表示是否采用深度克隆
- 如果为 true,深度克隆,则该节点的所有后代节点也都会被克隆
- 如果为 false,浅度克隆,则只克隆该节点本身
- 默认值为 false,节点下的内容不会被克隆。(建议写的时候标明true / false)
注意:克隆时,标签上的属性和属性值也会被复制,写在标签行内的绑定事件可以被复制, 但是通过 JavaScript 动态绑定的事件不会被复制。
(克隆时,为了防止 id 名和之前的冲突,可以更改 id 名)
8. 节点常用操作方法3
(1)Node.hasChildNodes()
- 没有参数,返回一个 Boolean 布尔值
- 表示该元素是否包含有子节点。
- 判断有没有子节点,不区分节点类型(若有个空格也显示为true)
(2) Node.contains(child)
- 返回一个 Boolean 布尔值
- 表示传入的节点是否为该节点的后代节点
- 不局限于父子关系,只要包含就返回true
9. 判断方法总结
判断当前节点是否有子节点(若返回 true 就表示有)
- node.firstChild !== null (判断节点内部第一个子节点是否不为空)
- node.childNodes.length > 0 (判断子节点的数组对象的长度是否为0)
- node.hasChildNodes() (判断有没有子节点,不区分节点类型)
判断当前节点是否有子元素节点(若返回 true 就表示有)
- node.firstElementChild !== null
- node.children.length > 0
10. 三种动态创建元素区别
(1)document.write()
很少用;直接将内容写入页面的内容流,但文档流执行完毕,则它会导致页面全部重绘。
btn.onclick = function () {
document.write("<div>123</div>");
};
// 点击按钮后会形成一个新的页面,页面中只有一个 div 元素,其他都没有了
注意:如果页面文档流加载完毕,再调回这句话会导致页面重绘。
(2)element.innerHTML
innerHTML 是将内容写入某个 DOM 节点,不会导致页面全部重绘
var inner = document.querySelector(".inner");
// for (var i = 0; i <= 100; i++) {
// inner.innerHTML += "<a href='#'>百</a>";
// }
// 因为拼接字符串,根据字符串的不可变性,需要开辟新的内容,所以比较耗时
var arr = [];
for (var i = 0; i <= 100; i++) {
arr.push("<a href='#'>百</a>");
}
inner.innerHTML = arr.join("");
// 数组转换为字符串,耗时最短
(3)document.createElement()
var tr = document.createElement("tr");
tb.appendChild(tr);
总结:不同浏览器下,innerHTML 效率要比 createElement 高(前提:不要拼接字符串,采取数组形式拼接)
- innerHTML 创建多个元素效率更高,(采取数组形式拼接),结构稍复杂
- createElement() 创建多个元素效率稍低一点点,但结构更清晰
案例:动态创建表格
<body>
<h1>动态创建表格</h1>
<table border="1">
<thead>
<tr>
<th>姓名</th>
<th>科目</th>
<th>成绩</th>
<th>操作</th>
</tr>
</thead>
<tbody id="tb"></tbody>
</table>
<script>
var tb = document.getElementById("tb");
// 模拟后台传输的数据
var datas = [
{ name: "zs", subject: "语文", score: 80 },
{ name: "ls", subject: "数学", score: 90 },
{ name: "ww", subject: "英语", score: 70 },
{ name: "cc", subject: "物理", score: 88 },
{ name: "lb", subject: "化学", score: 57 },
{ name: "gy", subject: "生物", score: 100 },
{ name: "sq", subject: "历史", score: 93 },
];
// 根据数组中的数据个数生成对应个数的 tr
// 将生成的 tr 添加到 tbody 中
// 数组遍历
for (var i = 0; i < datas.length; i++) {
// 每一个数据都要生成一个 tr
var tr = document.createElement("tr");
// 添加到 tbody 中
tb.appendChild(tr);
// 每一个 tr 中还需要添加对应的 td
// 每一行内部的 tbody 中的数据来自于数组中的每一项
var data = datas[i]; // { name: "zs", subject: "语文", score: 80 }
// 遍历 data 对象,根据它的项数来确定添加的 td 的个数
for (var k in data) {
// 生成一个 td
var td = document.createElement("td");
// 添加到 tr 中
tr.appendChild(td);
// 添加给每一个 td 数据
td.innerText = data[k];
}
// 除了前面动态获取的数据 td 之外,还要添加一个删除的 td
// 循环内的变量是全局变量
td = document.createElement("td");
// 添加到 tr 中
tr.appendChild(td);
// 最后一个 td 中需要添加一个 a 标签
var a = document.createElement("a");
a.innerText = "删除";
a.href = "javascript:void(0);";
// 将 a 添加到 td 中
td.appendChild(a);
// 给生成的每个 a 添加一个点击事件,移除当前所在的行
a.onclick = function () {
// 找到所在的行 tr
// this.parentNode.parentNode
// 从 tbody 中移除对应的 tr
tb.removeChild(this.parentNode.parentNode);
};
}
</script>
</body>
案例:选择水果
<body>
<!-- multiple 属性规定可选择多个选项 -->
<!-- size 规定下拉列表中可见选项的数目 -->
<select id="all" size="5" multiple="multiple"></select>
<input type="button" value=">>" id="btn1" />
<input type="button" value="<<" id="btn2" />
<input type="button" value=">" id="btn3" />
<input type="button" value="<" id="btn4" />
<select id="choose" multiple="multiple"></select>
<script>
var all = document.getElementById("all");
var btn1 = document.getElementById("btn1");
var btn2 = document.getElementById("btn2");
var btn3 = document.getElementById("btn3");
var btn4 = document.getElementById("btn4");
var choose = document.getElementById("choose");
var datas = ["苹果", "橘子", "梨", "西瓜", "水蜜桃"];
// 动态创建 option
for (var i = 0; i < datas.length; i++) {
var opts = document.createElement("option");
all.appendChild(opts);
opts.innerText = datas[i];
}
// 给第一个按钮添加点击事件,让 all 中的所有子元素移动到 choose 中
btn1.onclick = function () {
// 获取 all 中所有的子元素
var opts = all.children; // 内部的元素是动态添加的
// 获取最开始的数组个数
var n = opts.length;
// 将所有的 opts 中的元素添加给 choose
for (var i = 0; i < n; i++) {
choose.appendChild(opts[0]);
}
};
btn2.onclick = function () {
var opts = choose.children;
var n = opts.length;
for (var i = 0; i < n; i++) {
all.appendChild(opts[0]);
}
};
// 单选移动
btn3.onclick = function () {
var opts = all.children;
// 移动的内容希望可以是固定的一个数组中的项
var arr = [];
// 通过判断条件,往 arr 中添加需要移动的元素
for (var i = 0; i < opts.length; i++) {
if (opts[i].selected === true) {
arr.push(opts[i]);
}
}
for (var j = 0; j < arr.length; j++) {
choose.appendChild(arr[j]);
arr[j].selected = false;
}
};
btn4.onclick = function () {
var opts = choose.children;
// 移动的内容希望可以是固定的一个数组中的项
var arr = [];
// 通过判断条件,往 arr 中添加需要移动的元素
for (var i = 0; i < choose.length; i++) {
if (opts[i].selected === true) {
arr.push(choose[i]);
}
}
for (var j = 0; j < arr.length; j++) {
all.appendChild(arr[j]);
arr[j].selected = false;
}
};
</script>
</body>
/* css 代码 */
select {
width: 200px;
height: 200px;
background-color: #33cccc;
font-size: 20px;
}
案例:简单版发布留言及删除留言
<body>
<textarea cols="30" rows="10"></textarea>
<button>发布</button>
<ul></ul>
<script>
var text = document.querySelector("textarea");
var ul = document.querySelector("ul");
var btn = document.querySelector("button");
// 发布留言
btn.onclick = function () {
if (text.value == "") {
alert("您还没有输入内容!");
return false; // 终止函数继续执行
} else {
var li = document.createElement("li");
// 文本域的内容赋给 li
// a 的样式可以写在 CSS 中,如 li a{}
li.innerHTML = text.value + "<a href='javascript:void(0);'>删除</a>";
// 每次发布都在前面
ul.insertBefore(li, ul.children[0]);
// var a = document.createElement("a");
// a.href = "javascript:void(0);";
// a.innerText = "删除";
// li.appendChild(a);
// 删除留言
var as = document.querySelectorAll("a");
for (var i = 0; i < as.length; i++) {
as[i].onclick = function () {
// 删除的是当前 a 的父亲 li
ul.removeChild(this.parentNode);
};
}
}
};
</script>
</body>
案例:每点击一次按钮,就删除一个元素,当没有元素可删除时,按钮被禁用
var ul = document.querySelector("ul");
var btn = document.querySelector("button");
btn.onclick = function () {
if (ul.children.length == 0) {
this.disabled = true;
} else {
ul.removeChild(ul.children[0]);
}
};
注:阻止链接跳转 <a href='javascript:void(0);'></a>
或 <a href='javascript:;'></a>
DOM 重点核心总结
对于 JavaScript ,为了能使 JavaScript 操作 HTML,JavaScript就有了一套自己的 DOM 编程接口
对于 HTML,DOM 使得 HTML 形成一棵 DOM 树,包括文档、元素、节点
注意:我们获取过来的 DOM 元素是一个对象(object),所以称为文档对象模型
关于 DOM 操作,我们主要针对于元素的操作,主要有创建、增删改查、属性操作、事件操作
(1)创建
- document.write()
- element.innerHTML
- document.createElement()
(2)增
- parentNode.appendChild(child) (后)
- parentNode.insertBefore(newNode, referenceNode) (前)
(3)删
- parentNode.removeChild(child)
(4)改
- 修改元素属性:src、href、title等
- 修改普通元素内容:innerHTML、innerText
- 修改表单元素:value、type、disabled等
- 修改元素样式:style、className
(5)查(主要获取查询 DOM 的元素)
- DOM 提供的 API 方法:getElementById、getElementsByTagName
- H5 提供的新方法:querySelector、querySelectorAll
- 利用节点操作获取元素:父(parentNode)、子(children)、兄(previousElementSibling、nextElementSibling)
(6)属性操作(主要针对于自定义属性)
- setAttribute:设置 DOM 的属性值
- getAttribute:得到 DOM 的属性值
- removeAttribute:移除属性
(7)事件操作
- 给元素注册事件
- 采取 “事件源.事件类型 = 事件处理程序”
tBefore(newNode, referenceNode) (前)
(3)删
- parentNode.removeChild(child)
(4)改
- 修改元素属性:src、href、title等
- 修改普通元素内容:innerHTML、innerText
- 修改表单元素:value、type、disabled等
- 修改元素样式:style、className
(5)查(主要获取查询 DOM 的元素)
- DOM 提供的 API 方法:getElementById、getElementsByTagName
- H5 提供的新方法:querySelector、querySelectorAll
- 利用节点操作获取元素:父(parentNode)、子(children)、兄(previousElementSibling、nextElementSibling)
(6)属性操作(主要针对于自定义属性)
- setAttribute:设置 DOM 的属性值
- getAttribute:得到 DOM 的属性值
- removeAttribute:移除属性
(7)事件操作
- 给元素注册事件
- 采取 “事件源.事件类型 = 事件处理程序”