jq获取最后一个子节点_JavaScript(9) webapi操作节点

为啥要使用节点操作

主要是根据节点之间的关系来更方便的获取元素对象.

例如: 之前写过的点击计数示例, 前面的做法是使用 querySelector 获取两个 input 标签, 再进行操作.

但是两个 input 标签其实是兄弟关系, 咱们可以借助节点操作, 更方便的获取到 input 的元素对象.

 <input type="text" id="text" value="0"><input type="button" id="btn" value='点我+1'><script>    var text = document.querySelector('#text');    var btn = document.querySelector('#btn');    btn.onclick = function () {        var num = +text.value;        console.log(num);        num++;        text.value = num;    }script>

核心关系: 父子, 兄弟.

页面中的一切内容都是节点

文本, 元素, 属性等都是节点.

每个节点对象主要有三个属性:

  • 节点名称 (nodeName)

  • 节点类型 (nodeType): 元素节点为 1, 属性节点为 2, 文本节点为 3(文字, 空格, 换行都算文本节点) , 开发中主要操作元素节点.

  • 节点值 (nodeValue)

 <div class="box">div><script>    var div = document.querySelector('.box');    console.dir(div);script>

2c4a90b35a82efbd86568da7276696e6.png

获取父节点

体会 HTML 中的父子关系

 <html lang="en"><head>    <meta charset="UTF-8">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>父子关系title>head><body>    <div class="grandfather">        <div class="father">            <div class="son">div>        div>    div>body>html>

在这个 html 中:

  • html 是 head 和 body 的父节点.

  • head 是 meta, title 的父节点.

  • body 是 .grandfather 的父节点.

  • .grandfather 是 .father 的父节点.

  • .father 是 .son 的父节点.

使用 parentNode 属性获取到该节点的父节点.

 <div class="grandfather">    <div class="father">        <div class="son">div>    div>div><script>    var son = document.querySelector('.son');    console.log(son);    console.log(son.parentNode);    console.log(son.parentNode.parentNode);script>

获取所有子节点

1) 使用 childNodes 属性获取到该节点的子节点

 <ul>    <li>1li>    <li>2li>    <li>3li>ul><script>    var ul = document.querySelector('ul');    console.log(ul.childNodes);script>

注意! 结果是 NodeList 类型, 得到的是 "节点", 而不是 "元素" (节点的范围比元素更广)

d0a4aea2a744399dd80cb5028d552c87.png

第一个 li 和 ul 之间存在换行.

最后一个 li 和 ul 之间存在换行.

li 和  li 之间也存在换行.

这些换行也都是节点(文本节点).

如果想只获取元素节点, 可以手动写个函数, 把 type 为 1 的节点保留下来.

 function getElementNode(nodeList) {    var result = [];    for (var i = 0; i < nodeList.length; i++) {        if (nodeList[i].nodeType == 1) {            result.push(nodeList[i]);        }    }    return result;}console.log(getElementNode(ul.childNodes));

当然, 这样做比较麻烦.

b2eed257a4b8915f80414b5e7604578d.png

2) 使用 children 属性获取子节点中的元素节点(非标准, 但是各个浏览器都支持)

 <ul>    <li>1li>    <li>2li>    <li>3li>ul><script>    var ul = document.querySelector('ul');    console.log(ul.children);script>

children 属性得到的是一个 HTMLCollection , 只包含元素对象.

获取第一个节点/最后一个节点

  • firstChild: 获取到第一个节点(不一定是元素节点)

  • lastChild: 获取到最后一个节点(不一定是元素节点)

  • firstElementChild: 获取到第一个元素节点 (IE9+ 以上才支持)

  • lastElementChild: 获取到最后一个元素节点 (IE9+ 以上才支持)

 <ul>    <li>1li>    <li>2li>    <li>3li>    <li>4li>ul><script>    var ul = document.querySelector('ul');    console.log(ul.firstChild);    console.log(ul.lastChild);    console.log(ul.firstElementChild);    console.log(ul.lastElementChild);script>

实际开发更常用的办法是直接借助 children 得到子元素数组, 然后通过下标来操作.

 ul.children[0];             // 第一个节点ul.children[ul.length - 1]; // 最后一个节点

获取兄弟节点

  • nextSibling: 获取下一个兄弟节点(不仅仅是元素节点)

  • previousSibling: 获取上一个兄弟节点(不仅仅是元素节点)

  • nextElementSibling: 获取下一个兄弟元素节点 (非标准)

  • previousElementSibling: 获取上一个兄弟元素节点 (非标准)

 <div class="parent">    <div class="child1">11div>    <div class="child2">22div>div><script>    var child1 = document.querySelector('.child1');    console.log(child1.nextSibling);    console.log(child1.previousSibling);    console.log(child1.nextElementSibling);    console.log(child1.previousElementSibling);script>

sibling 单词本意就是 兄;弟;姐;妹;  

新增节点

分成两个步骤

  1. 创建元素节点

  2. 把元素节点插入到 dom 树中.

第一步相当于生了个娃, 第二步相当于给娃上户口.

1. 创建元素节点

使用 createElement 方法来创建一个元素. options 参数暂不关注.

 var element = document.createElement(tagName[, options]);

代码示例:

 <div class="container">div><script>    var div = document.createElement('div');    div.id = 'mydiv';    div.className = 'box';    div.innerHTML = 'hehe';    console.log(div);script>

此时发现, 虽然创建出新的 div 了, 但是 div 并没有显示在页面上. 这是因为新创建的节点并没有加入到 DOM 树中.

上面介绍的只是创建元素节点, 还可以使用:

  • createTextNode 创建文本节点

  • createComment 创建注释节点

  • createAttribute 创建属性节点

我们以 createElement 为主即可.

2. 插入节点到 dom 树中

1) 使用 appendChild 将节点插入到指定节点的最后一个孩子之后

 element.appendChild(aChild)
 <div class="container">div><script>    var div = document.createElement('div');    div.id = 'mydiv';    div.className = 'box';    div.innerHTML = 'hehe';    var container = document.querySelector('.container');    container.appendChild(div);script>

b8309eff32cd7714196d8dad2c06bf5c.png

2) 使用 insertBefore 将节点插入到指定节点之前.

 var insertedNode = parentNode.insertBefore(newNode, referenceNode);
  • insertedNode 被插入节点(newNode)

  • parentNode 新插入节点的父节点

  • newNode 用于插入的节点

  • referenceNode newNode 将要插在这个节点之前

如果 referenceNodenullnewNode 将被插入到子节点的末尾.

注意: referenceNode 引用节点不是可选参数.

 <div class="container">    <div>11div>    <div>22div>    <div>33div>    <div>44div>div><script>    var newDiv = document.createElement('div');    newDiv.innerHTML = '我是新的节点';    var container = document.querySelector('.container');    console.log(container.children);    container.insertBefore(newDiv, container.children[0]);script>

2283e740d4621bca1dafdb36c50686fd.png

注意1: 如果针对一个节点插入两次, 则只有最后一次生效(相当于把元素移动了)

 <div class="container">    <div>11div>    <div>22div>    <div>33div>    <div>44div>div><script>    var newDiv = document.createElement('div');    newDiv.innerHTML = '我是新的节点';    var container = document.querySelector('.container');    console.log(container.children);    // 此处的 children 里有 4 个元素    container.insertBefore(newDiv, container.children[0]);    // 此处的 children 里有 5 个元素(上面新插了一个), 0 号元素是 新节点,     // 1 号元素是 11, 2号节点是 22, 所以是插入到 22 之前.     container.insertBefore(newDiv, container.children[2]);script>

669a683bca1bb067e68278bcb24f401d.png

注意2: 一旦一个节点插入完毕, 再针对刚刚的节点对象进行修改, 能够同步影响到 DOM 树中的内容.

 <div class="container">    <div>11div>    <div>22div>    <div>33div>    <div>44div>div><script>    var newDiv = document.createElement('div');    newDiv.innerHTML = '我是新的节点';    var container = document.querySelector('.container');    console.log(container.children);    container.insertBefore(newDiv, container.children[0]);        // 插入完毕后再次修改 newDiv 的内容    newDiv.innerHTML = '我是新节点2';script>

b9f1044077d913035887b0eecc868868.png

代码示例: 待办事项

5385e5e68f4770720657dbbc706015e3.png

功能:

  1. 界面上包含一个输入框和新增任务按钮. 点击新增则把输入框中的任务加入到任务列表中.

  2. 任务列表中显示当前待办的所有任务.

  3. 每个任务前面包含一个复选框, 勾选则表示任务完成, 任务自动被放到另外一个已完成列表.

a) 页面布局

 <div class="container">    <div class="todo">        <h3>未完成h3>            div>    <div class="done">        <h3>已完成h3>    div>div><div class="footer">    <input type="text">    <button>新建任务button>div>

b) 页面样式

 * {    margin: 0;    padding: 0;    box-sizing: border-box;}body,html {    height: 100%;}body,html,input,button {    font-size: 20px;    line-height: 50px;}body {    background-color: #f3f3f3;}.container {    width: 800px;    height: 100%;    margin: 0 auto;    background-color: #fff;}.todo,.done {    float: left;    width: 50%;    height: 100%;    overflow: auto;}.container h3 {    height: 50px;    text-align: center;    font-weight: 400;    background-color: #333;    color: #fff;}.container .todo h3 {    border-right: 1px solid #fff;}.footer {    width: 800px;    height: 50px;    position: absolute;    bottom: 0;    /* 水平居中 */    left: 50%;    margin-left: -400px;}.footer input {    float: left;    width: 600px;    height: 50px;    outline: none;}.footer button {    float: left;    width: 200px;    height: 50px;    border: none;    background-color: skyblue;    color: #fff;}

c) 实现新增任务

 // 实现新增任务var addTaskButton = document.querySelector('.footer>button');addTaskButton.onclick = function () {    // 1. 获取到任务内容的输入框    var input = document.querySelector('.footer>input');    // 2. 获取到输入框内容    var taskContent = input.value;    // 3. 根据内容新建一个元素节点    var row = document.createElement('div');    row.className = 'row';    var checkbox = document.createElement('input');    checkbox.type = 'checkbox';    var space = document.createTextNode(' '); // 需要创建一个文本节点, 表示空格    var span = document.createElement('span');    span.innerHTML = taskContent;    row.appendChild(checkbox);    row.appendChild(space);    row.appendChild(span);    // 4. 把新节点插入到 todo 中    var todo = document.querySelector('.todo');    todo.appendChild(row);}

d) 实现完成任务

把复选框勾选中的元素移动到已完成部分.

在 addTaskButton.onclick 中设置 checkbox 的点击事件.

注意, 这个代码必须放到 addTaskButton.onclick 的回调函数内部.

 // 给 checkbox 注册点击事件checkbox.onclick = function () {    var row = this.parentNode;    // 注意! 是先触发 checked 为 true, 然后再调用 onclick 函数    if (this.checked) {        var target = document.querySelector('.done');    } else {        var target = document.querySelector('.todo');    }    target.appendChild(row);}

注意:

目前我们学习了两种创建节点的方式:

  1. createElement

  2. element.innerHTML

这两种方式都很常用.

还有一种方式为 document.write

这种方式会导致整个页面全部重绘(页面原有内容丢失). 只在特定场景下才会使用.

删除节点

使用 removeChild 删除子节点

 oldChild = element.removeChild(child);
  • child 为待删除节点

  • element 为 child 的父节点

  • 返回值为该被删除节点

  • 被删除节点只是从 dom 树被删除了, 但是仍然在内存中, 可以随时加入到 dom 树的其他位置.

  • 如果上例中的child节点不是element节点的子节点,则该方法会抛出异常.

代码示例: 待办事项2

在上面的案例中, 新增删除任务功能

24121af751108c42460396ac8e4bdca1.png

给每一个任务中增加删除按钮

 <div class="row">    <input type="checkbox">    <span>吃饭span>    <button>删除button>div>

设置按钮样式

 .row {    /* 受行高影响, row 的实际高度并不是 50px, 需要显式设定 */    height: 50px;}.row button {    /* 让按钮显式在右侧 */    float: right;    padding: 0 10px;    height: 50px;}

在新建任务时创建删除按钮

 // addTaskButton.onclick = function () { 中var button = document.createElement('button');button.innerHTML = '删除';...row.appendChild(button);

注册删除事件

 // 给删除按钮注册点击事件button.onclick = function () {    var row = this.parentNode;    var grandParent = row.parentNode;    grandParent.removeChild(row);}

拷贝节点

使用 node.cloneNode() 拷贝节点

 var dupNode = node.cloneNode(deep);
  • node: 将要被克隆的节点

  • dupNode: 克隆生成的副本节点

  • deep: 可选. 是否采用深度克隆,如果为true, 则该节点的所有后代节点也都会被克隆,如果为 false,则只克隆该节点本身.

注意1: 在 DOM4 规范中(实现于Gecko 13.0(Firefox 13.0 / Thunderbird 13.0 / SeaMonkey 2.10)),deep是一个可选参数。如果省略的话,参数的默认值为 true,也就是说默认是深度克隆。如果想使用浅克隆, 你需要将该参数设置为 false。

在最新的规范里,该方法的行为已经改变了,其默认值变成了 false。虽然该参数仍旧是可选的,但是你必须要为该方法设置 deep 参数,无论是为了向前还是向后兼容考虑。假如开发者没设置参数的话,Gecko 28.0 (Firefox 28 / Thunderbird 28 / SeaMonkey 2.25 / Firefox OS 1.3)) 版本的控制台会发出警告。从 Gecko 29.0 (Firefox 29 / Thunderbird 29 / SeaMonkey 2.26)) 开始该方法默认为浅复制而不是深度复制。

注意2: 克隆一个元素节点会拷贝它所有的属性以及属性值,当然也就包括了属性上绑定的事件(比如onclick="alert(1)"),但不会拷贝那些使用addEventListener()方法或者node.onclick = fn这种用JavaScript动态绑定的事件.

 <div class="container">    <button>我是一个按钮button>div><script>    var button = document.querySelector('button');    button.onclick = function () {        alert('haha');    }    // var newButton = button.cloneNode();    var newButton = button.cloneNode(true);    var container = document.querySelector('.container');    container.appendChild(newButton);script>

当 cloneNode 参数为空时, 复制的 newButton 不包含文本内容.

当 cloneNode 参数为 true 的时候, 复制的 newButton 才包含文本内容.

另外, 通过 onclick 方式注册的点击事件不会被拷贝.


版权声明:本文为weixin_39611506原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。