🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# 节点层级 ## 父子节点 从DOM文档树中可以看出,DOM的节点之间存在着关联关系,我们把从上而下的关联关系叫做 "父子节点 ",把同一层级(平级)的关系叫做 "兄弟节点"。在实际开发中,我们经常通过操作父子节点来完成相应的页面效果。节点层级关系通过节点属性体现,如 parentNode(),childNodes(所有节点,包括换行),children(HTML元素节点)等,以下介绍几种常用的节点属性 html和css代码 ~~~ <div id="main">    <span>我是span标签文本</span>    <p>我是p标签文本</p>    <!--我是注释--> </div> ~~~ JavaScript代码 ~~~ var main = document.getElementById('main'); console.dir(main); // 1、节点中这么多属性,我么要学习哪些属性呢? // parentNode 父节点 // childNodes 子节点 ​ //获取父节点 console.log(main.parentNode); //节点类型 (nodeType) ,如下 // 元素节点 1 属性节点 2 文本节点 3 注释节点 8 // 2、获取所有子节点 console.log(main.childNodes); // 获取所有子元素节点 var nodes = main.childNodes; for (var i = 0; i < nodes.length; i++) {    var node = nodes[i];    //获取所有元素节点    if (node.nodeType === 1) {        console.log(node);   } } // 3、有没有其他更方便的方法获取到所有子元素节点? console.log(main.children);//返回 一个Node的所有子元素节点,是一个动态的集(HTMLCollection) ​ ~~~ **需求(熟悉)** 隔行变色 目的: 练习children, hasChildNodes()的使用 html和css代码 ~~~ <div>    <ul id="list">        <li>西施</li>        <li>昭君</li>        <li>貂蝉</li>        <li>玉环</li>        <li>芙蓉姐姐</li>    </ul> </div> ~~~ JavaScript代码 ~~~ // 1、以前的方式 // var list = document.getElementsByTagName('li'); ​ // 2、现在的方式 //通过子节点完成隔行变色,获取元素的子节点: // 方式一:childNodes   动态集合 // 方式二:children     动态集合 var list = document.getElementById('list'); // 判断是否有子节点(hasChildNodes()),遍历子元素节点,隔行变色 if (list.hasChildNodes()) {    for (var i = 0; i < list.children.length; i++) {        var li = list.children[i];        if (i % 2 === 0) {//奇数行            li.style.backgroundColor='lightblue';       }else { //偶数行            li.style.backgroundColor='lightyellow' ​       }   } } ~~~ ## **第一个和最后一个子节点(掌握)** 获取第一个和最后一个子节点: **firstChild 第一个子节点** ​ **lastChild 最后一个子节点** 获取第一个和最后一个子元素节点: ​ firstElementChild:有浏览器兼容性问题,从IE9开始支持 ​ lastElementChild:有浏览器兼容性问题,从IE9开始支持 html和css代码 ~~~ <div id="main">    <div>这是一个广告图片</div>    <ul>        <li>这是一个列表</li>    </ul>    <span>说明性文字</span> </div> ~~~ JavaScript代码 ~~~ //获取第一个和最后一个子节点 //firstChild   第一个子节点 //lastChild     最后一个子节点 var main = document.getElementById('main'); console.log(main.firstChild); ​ //获取第一个和最后一个子元素节点 //firstElementChild:有浏览器兼容性问题,从IE9开始支持 //lastElementChild:有浏览器兼容性问题,从IE9开始支持 console.log(main.firstElementChild); console.log(main.lastElementChild); ​ var firstEle = getFirstElementChild(main); console.log(firstEle); ~~~ **菜单** 目的: 练习parentNode,children,firstElementchild的使用,要理解"先清除后设置"的思想; 需求描述: 点击菜单时,先清除所有菜单的高亮效果,再设置当前点击的菜单为高亮显示。 需求分析: (1)遍历li, 给li中的第一个a标签注册点击事件; (2)先清除所有菜单的高亮效果, 再设置当前点击的菜单为高亮显示; html和css代码 ~~~ <style>    #menu ul li {        list-style-type: none;        width: 80px;        height: 30px;        line-height: 30px;        background-color: #595b5d;        text-align: center;        float: left;        margin-left: 5px;   } ​    #menu ul li.current {        background-color: thistle;   } ​    #menu ul li a {        text-decoration: none;        color: aliceblue;   } ​    #menu ul li a:hover {        color: red; ​   } ​    /*#menu ul li:hover {*/    /*background-color: aliceblue;*/    /*}*/ </style> <script src="common.js"></script> ​ <div id="menu">    <ul>        <li class="current"><a href="javascript:void(0)">首页</a></li>        <li><a href="javascript: undefined">叩丁狼</a></li>        <li><a href="javascript:void(0)">博客</a></li>        <li><a href="javascript:void(0)">相册</a></li>        <li><a href="javascript:void(0)">关于</a></li>        <li><a href="javascript:void(0)">帮助</a></li>    </ul> </div> ~~~ JavaScript代码 ~~~ var menu = my$('menu'); //firstChild:获取第一个节点 //firstElementChild:获取第一个元素节点 var ul = getFirstElementChild(menu); //遍历li中的a标签,并注册点击事件 for (var i = 0; i < ul.children.length; i++) {    var li = ul.children[i];    //获取a元素节点    var link = getFirstElementChild(li);    link.onclick = linkClick; } ​ function linkClick() {    //取消所有li的高亮显示    for (var i = 0; i < ul.children.length; i++) {        var li = ul.children[i];        li.className='';   }    //设置当前点击的li标签为高亮显示    this.parentNode.className = 'current'; } ~~~ **小结** >[info] 1、认识 javascript:void(0) > https: 协议 > javascript: 伪协议 > void(0): 运算符,对给定的表达式进行求值,始终返回 undefined。当返回undefined时,a标签不会做任何事情 > 2、获取子元素节点、父元素节点、第一个元素节点(解决浏览器兼容性问题) <br> ## 兄弟节点(掌握) 通过DOM文档树结构中上下节点是父子关系,水平(同级)节点是兄弟关系,那怎么获取一个节点的兄弟节点呢? nextSibling 获取下一个兄弟节点 nextElementSibling 获取下一个兄弟元素节点 previousSibling 获取上一个兄弟节点 previousElementSibling 获取上一个兄弟元素节点 通过以下代码了解获取兄弟节点的属性,以及解决该属性的浏览器兼容性问题。 html和css代码 ~~~ <div id="main">    <div>这是一个区域1</div>    <div>这是一个区域2</div>    <div id="c3">这是一个区域3</div>    <div>这是一个区域4</div>    <div>这是一个区域5</div> </div> ~~~ JavaScript代码 ~~~ var c3 = document.getElementById('c3'); //获取兄弟节点 console.log(c3.nextSibling);//获取上一个兄弟节点 console.log(c3.nextElementSibling);//获取上一个兄弟元素节点 ​ console.log(c3.previousSibling);//获取下一个兄弟节点 console.log(c3.previousElementSibling);//获取下一个兄弟元素节点 ​ var nextEle = getNextElementSibling(c3); console.log(nextEle); ​ //解决浏览器对nextElementSibling的兼容性问题 function getNextElementSibling(element) {    var el = element;    while (el = el.nextSibling) {        if (el.nodeType === 1) {            return el;       }   }    return null; } //同理,用同样的方式解决浏览器对previousElementSibling的兼容性问题 ​ ~~~ **小结** >[info] 1. childNodes和children的区别,childNodes获取的是所有的子节点,children获取的是子元素节点 > 2. nextSibling和previousSibling获取的是兄弟节点,nextElementSibling和previousElementSibling获取的是兄弟元素节点 > 3. nextElementSibling和previousElementSibling有兼容性问题,IE9以后才支持 # **创建元素的三种方式** **document.write()**:将一个文本字符串写入到由 document.open() 打开的一个文档流中; **innerHTML**:设置或获取HTML语法表示的元素的后代; **document.createElement( tagName )**:创建由 tagName 指定的HTML元素; ### document.write()(熟悉) 默认情况之下,页面由上而下地加载,形成一个文档流,当执行完毕时,文档流就会关闭。当使用documen.write()创建元素时,实际是开启一了个新的文档流,而将之前文档流冲刷掉,所以不推荐使用这种方法。 html和css代码 ~~~ <input type="button" id="btn" value="document.write()"/><br><br> <a id="ibangkf" href="http://www.ibangkf.com">网站客服</a> <div>    <span>我是span标签</span>    <b>我是b标签</b> </div> ~~~ JavaScript代码 ~~~ my$('btnSet').onclick=function () {    document.write('<p>我是p标签</p>'); } ~~~ ### innerHTML(掌握) 标签中会插入一个p标签,并在在页面上输出"新标签",当需要添加的标签比较多的时候使用这种方式。 html和css代码 ~~~ <input type="button" id="btn" value="innerHTML"/> <input type="button" id="btnSet" value="生成四大美女"/> <div id="main">    <span>我是span标签</span> </div> ~~~ JavaScript代码 ~~~ //点击按钮时,将li标签加入到ul标签中,生成美女列表 var array = ['西施', '昭君', '貂蝉', '玉环']; var main = my$('main'); my$('btnSet').onclick = function () {    main.innerHTML = '<ul>';    for (var i = 0; i < array.length; i++) {        main.innerHTML += '<li>' + array[i] + '</li>';   }    main.innerHTML += '</ul>'; } //问题:每次给innerHTML赋值时,浏览器就会重新绘制DOM树的结构,导致性能降低。 ~~~ <br> ## innerHTML性能问题(熟悉) innerHTML方法由于会对字符串进行解析,需要避免在循环内多次使用。可以借助字符串或数组的方式进行替换,再设置给innerHTML,优化后与document.createElement性能相近。 优化一:使用字符串方式 ~~~ var array = ['西施', '昭君', '貂蝉', '玉环']; var main = my$('main'); //优化一:使用字符串方式 ​ my$('btnSet').onclick = function () {    var str += '<ul>';    for (var i = 0; i < array.length; i++) {        str += '<li>' + array[i] + '</li>';   }    main.innerHTML = str + '</ul>'; } ~~~ 优化二:使用数组方式 ~~~ //优化二:使用数组方式 var array = ['西施', '昭君', '貂蝉', '玉环']; var main = my$('main'); my$('btnSet').onclick = function () {    var strArray = [];    strArray.push('<ul>');    for (var i = 0; i < array.length; i++) {        strArray.push('<li>' + array[i] + '</li>');   }    strArray.push('<ul>');    main.innerHTML = strArray.join(''); } ~~~ ## document.createElement()(掌握) 在内存中创建一个标签所对应的DOM对象,当需要动态创建结构比较复杂的标签时, 可使用这种方式。 html和css代码 ~~~ <input type="button" id="btn1" value="生成四大美女"/><br><br> <div id="main">    <span>我是span标签</span> </div> ~~~ JavaScript代码 ~~~ // 方式三:document.createElement() //在内存中创建一个p标签的DOM对象 my$('btnSet').onclick=function () {    var p = document.createElement('p');    p.innerText='我是p标签';    p.style.backgroundColor='red';    //将p标签设置到div中,appendChild方法:将子元素设置到当前元素(父元素)的子元素列表中的最后一个位置    my$('main').appendChild(p); } ~~~ # 节点操作方法介绍 类似于对象属性的增删改查,节点中也经常对节点属性进行增删改查,这需要用到节点方法,以下介绍常用的节点方法。 createElement(tagName) 创建指定名称tagName的HTML元素; appendChild(childNode) 将指定的 childNode 参数作为最后一个子节点添加到当前节点,返回 childNode。如果参数引用了 DOM 树上的现有节点,则节点将从当前位置分离,并附加到新位置; parentNode.insertBefore(newNode, oldSubNode) 在当前节点之前插入子节点。如果给定的子节点已存在当前文档中,则insertBefore()会将其从当前位置移动到新位置; removeChild(child) 从当前节点中删除指定的子节点child,并返回被删除的子节点; replaceChild(newChild, oldChild) 在当前节点中,用 newChild 替换 oldChild 并返回被替换掉的 oldChild; 以上节点操作相关的方法,通过下面的需求来练习、熟悉。 # 需求 ## 动态创建列表(熟悉) 需求:当点击按钮时, 创建美女列表,当鼠标经过列表时,高亮显示 ; 目的: 练习createElement(), appendChild()方法的使用; 需求分析: (1) 当点击按钮时, 使用createElement(),动态生成li ; (2) 动态生成li时, 给每个li注册鼠标移入移出事件,在事件处理函数中设置li的样式 ; html和css代码 ~~~ <input type="button" id="btnSet" value="生成美女列表"/> <div id="box"> ​ </div> ~~~ JavaScript代码 ~~~ //动态创建列表,当鼠标经过列表时,高亮显示 var array = ['西施', '昭君', '貂蝉', '玉环']; my$('btnSet').onclick = function () {    var box = my$('box');    var ul = document.createElement('ul');    for (var i = 0; i < array.length; i++) {        var li = document.createElement('li');     li.innerHTML = array[i];        //注册鼠标经过事件        li.onmouseover = fn1;        li.onmouseout = fn2;        //将li添加到ul中        ul.appendChild(li);   }    //将ul添加到div中    box.appendChild(ul); } ​ //获取页面中所有的li元素 var list = document.getElementsByTagName('li'); function fn1() {    //设置当前li的背景高亮样式    this.style.backgroundColor = 'lightblue'; } function fn2() {    //清空当前li的背景高亮样式    this.style.backgroundColor = ''; } ~~~ ### 动态创建表格(作业) 需求:根据表格数据动态创建表格 1、创建表格 table 设置表格样式:边框,边框间距, 宽高等; 2、创建表头 thead 将表头加入到表格中,设置表头的高,背景颜色; 3、创建表体 tbody (1)创建datas数组对象,用于模拟表格数据; (2)创建行,创建行中的列,并给行中的每列设置值;将每列加入到行中,将每行加入到tbody中; 4、在创建行时, 要在每行的最后一列设置a标签, 用于删除操作, 当点击a标签时, 删除当前行; 注意:此案例代码虽然多,但逻辑简单,大家只要清楚表格的结构,就很容易写出相应的代码。 html和css代码 ~~~ <style>  #box{    width: 500px;    margin: 0 auto; } </style> ​ <div id="box"></div> ~~~ JavaScript代码 ~~~ // 表体数据 var datas = [ {name: '刘备', subject: '语文', score: 80}, {name: '关羽', subject: '语文', score: 90}, {name: '张飞', subject: '语文', score: 50}, {name: '曹操', subject: '语文', score: 100}, {name: '诸葛亮', subject: '语文', score: 100}, ] ​ // 表头数据 var array = ['姓名', '学科', '成绩','操作']; ​ // 获取box盒子 var oDiv = document.getElementById('box'); ​ // 创建table并注入到oDiv中 var tableTag = document.createElement('table'); tableTag.style.width = '100%'; tableTag.style.borderCollapse = 'collapse'; oDiv.appendChild(tableTag); ​ /*------------------------为table中写入(thead>tr>th*3)------------------------*/ // 创建thead var theadTag = document.createElement('thead'); tableTag.appendChild(theadTag); ​ // 创建thead下的tr var theadTrTag = document.createElement('tr'); theadTag.appendChild(theadTrTag); ​ // 根据array的个数,创建th for(var i=0;i<array.length;i++){  var thTag = document.createElement('th');  thTag.innerHTML = array[i];  thTag.style.border = '1px solid #000';  theadTrTag.appendChild(thTag); } ​ /*------------------------为table中写入(tbody>tr>td*3)------------------------*/ // 创建tbody var tbodyTag = document.createElement('tbody'); tableTag.appendChild(tbodyTag); ​ // 根据datas的个数,创建tbody下的tr for(var i=0;i<datas.length;i++){  var myData = datas[i];  // 保存数组中每个项 ​  // 创建tbody下的tr  var tbodyTrTag = document.createElement('tr');  tbodyTag.appendChild(tbodyTrTag); ​  for(key in myData){    var tdTag = document.createElement('td');    tdTag.innerHTML = myData[key];    tdTag.style.textAlign = 'center';    tdTag.style.border = '1px solid #000';    tbodyTrTag.appendChild(tdTag); } ​  // 添加最后一个删除按钮  var tdTag1 = document.createElement('td');  var aTag = document.createElement('a');  aTag.href = 'javascript:void(0);';  aTag.innerHTML = '删除';  tdTag1.appendChild(aTag);  tdTag1.style.textAlign = 'center';  tdTag1.style.border = '1px solid #000';  tbodyTrTag.appendChild(tdTag1); ​  // 点击了删除按钮  aTag.onclick = deleteFn; } ​ // 删除行 function deleteFn(){  var trTag = this.parentNode.parentNode;  tbodyTag.removeChild(trTag); } ~~~ ## 常用DOM方法小结 >[info] createElement()、appendChild()、removeChild()、insertBefore()、replaceChild(),见**节点操作**一节。 > > 1、var insertedNode = parentNode.insertBefore(newNode, referenceNode); > 在当前节点中将newNode插入referenceNode之前。如果给定的子节点已存在当前文档中,则insertBefore()会将其从当前位置移动到新位置; > ​ > 2、var replacedNode = parentNode.replaceChild(newChild, oldChild); > 在当前节点中,用 newChild 替换 oldChild 并返回被替换掉的 oldChild; > > 3、var childNode = parentNode.appendChild(childNode); > 将指定的 childNode 参数作为最后一个子节点添加到当前节点,返回 childNode。如果参数引用了 DOM 树上的现有节点,则节点将从当前位置分离,并附加到新位置; > ## 选择水果(掌握) 需求:向左移动全部水果,向左移动选中的水果;向右移动全部水果,向右移动选中的水果; 全部向右移动:获取左边所有选项,并加入到右边的select中。 html和css代码 ~~~ <style>    select {        width: 200px;        height: 200px;        background-color: lightblue;        font-size: 20px;   } </style> ​ <select id="leftSelect" multiple="multiple">    <option>苹果</option>    <option>橘子</option>    <option>梨</option>    <option>西瓜</option>    <option>水蜜桃</option> </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="rightSelect" multiple="multiple"> ​ </select> ​ <script src="common.js"></script> ​ ~~~ JavaScript代码 需求分析: (1)全部向右移动,全部向左移动; (2)向右移动选中项,向左移动选中项; ~~~ var leftSelect = my$('leftSelect'); var rightSelect = my$('rightSelect'); ​ // ==========================全部向右移动============================ my$('btn1').onclick = function () {    //获取左边所有选项,并加入到右边的select中    for (var i = 0; i < leftSelect.children.length; i++) {        var option = leftSelect[i];        rightSelect.appendChild(option);   } ​ } ~~~ 以上代码,正向遍历,存在问题:每次移除左侧选项之后,左侧数据索引都要重新排序 ~~~ //逆向遍历 my$('btn1').onclick = function () {    for (var i = leftSelect.children.length-1; i >=0; i--) {        var option = leftSelect[i];        rightSelect.appendChild(option);   } } ~~~ 以上代码,逆向遍历,存在问题:选项顺序颠倒 ~~~ my$('btn1').onclick = function () {    for (var i = 0; i < leftSelect.children.length; i++) {        var option = leftSelect[0];        rightSelect.appendChild(option);   } } ~~~ 以上代码,只选择左侧下标为0的项,存在问题:i 的值和元素长度是动态变化的,导致i的值不满足循环条件。 (1)全部向右移动,全部向左移动。 由于select标签中的option的个数是动态变化的, 最后导致不满足循环条件了, 针对这个为题, 我们可以先将select中option的个数存储起来, 将该值当做循环中 i 的范围, 即var len =leftSelect.children.length, for循环中 i< len ; 参考代码如下 ~~~ // ==========================全部向右移动============================ my$('btn1').onclick = function () {    var len = leftSelect.children.length;    for (var i = 0; i < len; i++) {        var option = leftSelect.children[0];        // 将左边的option追加到右边select中        rightSelect.appendChild(option);        option.selected = false;   } } ​ // ==========================全部向左移动============================ ​ my$('btn2').onclick = function () {    var len = rightSelect.children.length;    for (var i = 0; i < len; i++) {        var option = rightSelect.children[0];        leftSelect.appendChild(option);        option.selected = false;   } } ~~~ (2)向右移动选中项,向左移动选中项。参考代码如下 需求分析: a、获取所有选中的option,存储到数组中 ​ b、遍历数组,将数组中的option加入到rightSelect中 ~~~ // ==========================向右移动选中项============================ //将已选中的option存储到数组中 my$('btn3').onclick = function () {    var array = [];    var len = leftSelect.children.length;    for (var i = 0; i < len; i++) {        var option = leftSelect[i];        if (option.selected) {            //将已选中的option加入到数组中            array.push(option);            //取消option的选中状态            option.selected = false;       }   } ​    //将数组中(即已选中的option)的option移动到rightSelect中    for (var i = 0; i < array.length; i++) {        var option = array[i];        rightSelect.appendChild(option);   } } ​ ​ // ==========================向左移动选中项============================ ​ my$('btn4').onclick = function () {    var array = [];    var len = rightSelect.children.length;    for (var i = 0; i < len; i++) {        var option = rightSelect[i];        if (option.selected) {            //将已选中的option加入到数组中            array.push(option);            //取消option的选中状态            option.selected = false;       }   } ​    //将数组中(即已选中的option)的option移动到rightSelect中    for (var i = 0; i < array.length; i++) {        var option = array[i];        leftSelect.appendChild(option);   } } ~~~ ## innerHTML实现选择水果(了解) 使用innerHTML实现选择水果的功能,存在以下的问题。 ~~~ //使用innerHTML实现 rightSelect.innerHTML = leftSelect.innerHTML; //清空做左边的选项 leftSelect.innerHTML = ''; ​ //存在的问题 // 1、如果子元素中注册了事件,则在移动子元素之后,子元素中的事件丢失。 // 2、使用leftSelect.innerHTML = '' 清空子元素之后,如果子元素中注册了事件,则事件仍然存在内存中,即此时发生内存泄漏。 ~~~ ## DOM对象总结 ~~~ ================================= 知识点总结 ================================= ​ ~~~