(目录)


什么是 DOM?

文档对象模型 (DOM) 是 HTML 和 XML 文档的编程接口。它提供了对文档的结构化的表述,并定义了一种方式可以使从程序中对该结构进行访问,从而改变文档的结构,样式和内容。DOM 将文档解析为一个由节点和对象(包含属性和方法的对象)组成的结构集合。简言之,它会将 web 页面和脚本或程序语言连接起来。

一个 web 页面是一个文档。这个文档可以在浏览器窗口或作为 HTML 源码显示出来。但上述两个情况中都是同一份文档。文档对象模型(DOM)提供了对同一份文档的另一种表现,存储和操作的方式。 DOM 是 web 页面的完全的面向对象表述,它能够使用如 JavaScript 等脚本语言进行修改。


什么是 DOM 树?

一个页面的结构是一个树形结构,称为 DOM 树

image-20220629151422609

  • 文档: 一个页面就是一个 文档,使用 document 表示.
  • 元素:页面中所有的标签都称为 元素,使用 element 表示.
  • 节点:网页中所有的内容都可以称为 节点,(标签节点,注释节点,文本节点,属性节点等),使用 node 表示

这些文档等概念在 JS 代码中就对应一个个的对象

所以才叫 “文档对象模型”


DOM API

Web api文档:https://developer.mozilla.org/zh-CN/docs/Web/API/Document_Object_Model/Introduction

JS 分成三个大的部分:

  1. ECMAScript: 基础语法部分
  2. DOM API: 操作页面结构
  3. BOM API: 操作浏览器

学习了上述 JS 语法,能做什么?   要想写实际的程序,光会语言是不够的,还需要掌握相关的“生态”。

生态:配套的库/'框架。


获取元素

要想操作页面上的元素,就需要先拿到对应的 JS 对象

DOM 中提供了一组 API 能够获取到网页的元素,最重要的两个:

  1. querySelector
  2. querySelectorAll

1、querySelector

是一个 document 这样的对象的属性

页面中的全局对象,一个页面加载好了,就会自动生成一个全局变量,就叫做 document,这里面就有

一些属性和方法,让我们来操作页面的内容

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div class="one">
        hello
    </div>
    
    <div id="two">
        hello two
    </div>
    
    <ul>
        <li> aaa </li>
    </ul>
    
    <script>
        // querySelector 参数就是一个 CSS 的选择器
        let div = document.querySelector('.one');
        console.log(div);
    
        // id 选择器
        let obj = document.querySelector('#two');
        console.log(obj);
    

        // 复合选择器
        let obj2 = document.querySelectorAll('ul li');
        console.log(obj2); 
    </script>
    

</body>
</html>

image-20220629152621616


2、querySelectorAll

但这里有一个问题,如果我们无序列表中列表选项不止一个,会怎么样?

image-20220629152911427

使用querySelectorAll

let obj2 = document.querySelectorAll('ul li');

image-20220629153042638

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div class="one">
        hello
    </div>
    
    <div id="two">
        hello two
    </div>
    
    <ul>
        <li> aaa </li>
        <li> bbb </li>
        <li> ccc </li>
        <li> ddd </li>
        <li> eee </li>
    </ul>
    
    <script>
        // querySelector 参数就是一个 CSS 的选择器
        let div = document.querySelector('.one');
        console.log(div);
    
        // id 选择器
        let obj = document.querySelector('#two');
        console.log(obj);
    
        // 复合选择器
        let obj2 = document.querySelectorAll('ul li');
        console.log(obj2); 
    </script>
    

</body>
</html>

事件

1、什么是事件

  • JS 要构建动态页面,就需要感知到用户的行为
  • 用户对于页面的一些操作(点击,选择,修改等),操作都会在浏览器中产生一个个事件,被 JS 获取到,从而进行更复杂的交互操作

JS 中的很多代码,都是通过 “事件” 来触发的

事件就是浏览器对于用户的操作行为进行了一个 “统称” (准确的说,事件也不一定全是用户操作产生的,但是大部分是的)

例如,鼠标在页面上移动,就会产生一个鼠标移动事件 再例如,鼠标在页面某个位置点击,就会产生一个鼠标点击事件。再例如,鼠标滚轮,来滚动页面,就会产生一组滚动事件。再例如,用户按下键盘的某个按键,也会产生一个键盘事件。再例如,用户修改浏览器窗口大小,也会产生一个窗口大小改变事件

JS 干的一个主要工作,就是在不同的事件中,进行不同的处理


2、事件三要素

  • 事件源 :哪个HTML元素产生的事件.
  • 事件类型 :鼠标移动 / 鼠标点击 / 键盘事件 / 窗口大小改变事件…
  • 事件的处理程序 :当事件产生之后,执行什么样的 JS 代码,进一步如何处理,往往是一个回调函数

3、点击事件

<!-- 事件源:button -->
<button>这是一个按钮</button>

<script>
    let button = document.querySelector('button');
    // 事件类型:onclick   事件处理函数:function
    button.onclick = function() {
        alert('hello');
    }
</script>

称此函数为回调函数:这个函数不会立即调用,而是在合适的实际,被 库 / 框架 自动的调用

另一种写法:这个写法,就把页面 (HTML) 写的更乱,我们期望 结构,样式,行为,能够分离

<button onclick="f()">这是一个按钮</button>

<script>
    function f() {
        alert('hello');
    }
</script>

image-20220629154244528


操作元素

操作 == 获取 + 修改

  1. 操作元素内容
  2. 操作元素的属性
  3. 操作元素的样式

1、操作元素内容

通过 对象 里面的一个属性 innerHTML 来实现

innerHTML:通过 这个方法,就能获取到 元素的内容。 进一步,就能修改元素的内容。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="screen">hello world</div>

    <button id="btn">这是一个按钮</button>

    <script>
        let btn = document.querySelector('#btn');

        btn.onclick = function() {
            let screen = document.querySelector('#screen');
            console.log(screen.innerHTML);
        }
    </script>

</body>
</html>

image-20220629154917884

上面虽然获得了元素的内容,但是只是一个文本内容。

那么如果不是文本内容呢?会有什么效果?

一组标签

image-20220629155508208


下面,我们在修改对象内容,该怎么做。

screen.innerHTML='<h1>修改<h1>'

image-20220629160152097

总之,无论是获取对象内容,还是修改对象内容,这两个操作的核心都是 innerHTML。


实现点击数字自增

div 显示整数,一个按钮,每次点击这个按钮,就让里面的整数+1

注意:

innerHTML 得到的是 string 类型,要想进行数字相加,就需要把字符串转成整数

let val = screen.innerHTML;
console.log(typeof(val)); // string

==由于 JS 是动态类型,转换成数字之后,仍然可以赋值给 valval,就从 string => number 类型==

<div id="screen">
    0
</div>

<button id="plus">+</button>

<script>
    let plusBtn = document.querySelector('#plus');
    plusBtn.onclick = function() {
        // 1、获取 screen 的值
        let screen = document.querySelector('#screen');
        let val = screen.innerHTML;
        val = parseInt(val);

        // 2、将这个值 + 1
        val = val + 1;
        
        // 3、把新值写回去
        screen.innerHTML = val;
    }
</script>

image-20220629160748632


2、操作元素的属性

实现点击切换图片

通过 DOM 对象. 属性名 就可以进行操作了。

<img src="picture1.jpg" alt="">

<script>
    let img = document.querySelector('img');
    img.onclick = function() {
        console.log(img.src); // 打印 src 属性的内容
        
        if (img.src.indexOf('picture1.jpg') >= 0) {
            img.src = 'picture2.jpg';
        } else if (img.src.indexOf('picture2.jpg') >= 0) {
            img.src = 'picture1.jpg';
        }
    }
</script>

在这里插入图片描述


一个 HTML 标签里,能写哪些属性,就同样可以通过 JS 中的 DOM 对象来获取到一样的属性,

可以通过 console.dir 这个方法,打印出一个 dom 对象的全部属性和值

console.dir(img);

image-20220629161557114


3、获取/修改表单元素属性

表单 (主要是指 input 标签) 的以下属性都可以通过 DOM 来修改

  • value : input 的值.
  • disabled : 禁用
  • checked : 复选框会使用
  • selected : 下拉框会使用
  • type : input 的类型(文本, 密码, 按钮, 文件等)

这些属性,都属于表单元素专有的属性


点击计数

使用一个输入框输入初始值(整数). 每次点击按钮, 值 + 1

<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>
  • input 具有一个重要的属性 value, 这个 value 决定了表单元素的内容
  • 如果是输入框, value 表示输入框的内容, 修改这个值会影响到界面显式; 在界面上修改这个值也会影响到代码中的属性
  • 如果是按钮, value 表示按钮的内容. 可以通过这个来实现按钮中文本的替换

切换按钮的文本

假设这是个播放按钮, 在 “播放” - “暂停” 之间切换

<input type="button" value="播放">

<script>
    let input = document.querySelector('input');
    input.onclick = function() {
        if (input.value == '播放') {
            input.value = '暂停';
        } else if (input.value == '暂停') {
            input.value = '播放';
        }
    }
</script>

全选 / 取消全选按钮

实现一个全选效果,主要是操作 input 的 checked 属性

  1. 点击全选按钮,则选中所有选项
  2. 只要某个选项取消,则自动取消全选按钮的勾选状态
<input type="checkbox" id="all"> 全选 <br>
<input type="checkbox" class="girl"> 薛宝钗 <br>
<input type="checkbox" class="girl"> 林黛玉 <br>
<input type="checkbox" class="girl"> 王熙凤 <br>
<input type="checkbox" class="girl"> 贾探春 <br>

<script>
    // 1、获取元素
    let all = document.querySelector('#all');
    let girls = document.querySelectorAll('.girl'); // 

    // 2、给 all 注册点击事件
    all.onclick = function() {
        for (let i = 0; i < girls.length; i++) {
            // all.checked 就是 all 这个复选框的选中状态
            girls[i].checked = all.checked;
        }
    }

    // 3、针对每个 girl 注册点击事件,实现对于 all 的取消操作
    for (let i = 0; i < girls.length; i++) {
        girls[i].onclick = function() {
            all.checked = checkGirls(girls);
        }
    }

    function checkGirls(girls) {
        // 判断是不是所有的 girl 都被选中了
        for (let i = 0; i < girls.length; i++) {
            if (!girls[i].checked) {
                // 只要有一个是未选中,all 就是未选中状态
                return '';
            }
        }
        // 遍历完,所有都是选中状态,就让 all 也是选中状态
        return 'checked';
    }
</script>

动画


4、操作元素样式

本质上也是操作元素属性

  1. style 对应行内样式 (直接把样式写到 style 里面)
  2. className / classList 对应 内部样式 / 外部样式,应用了一个 / 一组 CSS 类名

4.1 点击加字体大小

let div = document.querySelector('div');
div.onclick = function() {
    // 1、获取当前的字体大小
    console.log(div.style.fontSize);   
}

注意:

1、CSS 不区分大小 写,一般不用驼峰。JS 不能支持 作为变量名,不能使用脊柱。所有的 CSS 属性都是同样的规则映射过去的

2、当前这里得到 fontSize 是一个字符串,要想相加,就得转成整数 ‘20px’ => parselnt => 20 parselnt 转换的时候,会从头往后去转换,遇到非数字字符 ‘px’ ,就转换停止了

3、当修改CSS属性值的时候,一定要注意单位 ! ! 如果单位不合适 / 遗漏,就会失效

<div style="font-size: 20px;">这是一个文本</div>

<script>
    let div = document.querySelector('div');
    div.onclick = function() {
        // 1、获取当前的字体大小
        console.log(parseInt(div.style.fontSize));
        let fontSize = parseInt(div.style.fontSize);

        // 2、在当前字体大小的基础上,多增加 5px
        fontSize += 5;
        div.style.fontSize = fontSize + 'px';
    }
</script>

4.2 夜间模式

在 HTML 中,表示类名的属性,就是 class

但是在 JS 里,属性名变成了 className / classList ,为什么不直接使用 class 这个名字?

class 在 JS 中也是一个关键字 ( JS ES6 版本以上,也引入了类这个概念)

如果要修改的样式比较多,通过 style 来修改就麻烦了,可以直接借助 CSS 类来修改

<style>
    .light {
        background-color: #fff;
        color: #000;
    }

    .dark {
        background-color: #000;
        color: #fff;
    }
</style>


<div class="light" style="height: 300px;">这是一段话</div>
<button>关灯</button>

<script>
    let div = document.querySelector('div');
    let button = document.querySelector('button');
    button.onclick = function() {
        if (div.className == 'light') {
            div.className = 'dark';
            button.innnerHTML = '开灯';
        } else if (div.className == 'dark') {
            div.className = 'light';
            button.innerHTML = '关灯';
        }
     }
</script>


4.3 切换颜色

image-20220630141404494

    <style>
        div {
            width: 500px;
            height: 300px;
            font-size: 20px;
            font-weight: bold;
            text-align: center;
            line-height: 300px;
            
            background-image: url(f:/Photos/520.png);
            background-size: 100%, 100%;
        }

        .one {
            color: orange;
        }

        .two {
            color: green;
        }

        button {
            width: 150px;
            height: 50px;
            background-color: rgb(0, 128, 128);
            border-radius: 10px;
            border: none;
            outline: none;
        }

        button:active {
            background-color: #666;
        }
    </style>

    <div class="one">have a good day!</div><br>
    <button style="font-size: 17px;">orange</button>

    <script>
        let div = document.querySelector('div');
        let button = document.querySelector('button');
        button.onclick = function() {
            if (div.className == 'one') {
                div.className = 'two';
                button.innerHTML = 'green';
            } else if (div.className == 'two') {
                div.className = 'one';
                button.innerHTML = 'orange';
            }
        }
    </script>


5、操作节点

针对元素操作,其实是操作元素的属性 (元素本身没有发生改变)

针对节点操作,其实是新增节点 / 删除节点


5.1 新增节点

分成两个步骤:

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

==创建新节点:==

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

<script>
    let newDiv = document.createElement('div'); // 创建标签
    newDiv.id = 'newDiv';
    newDiv.className = 'one';
    newDiv.innerHTML = 'hello';
    console.log(newDiv);
</script>

image-20220630141907952

此处创建的节点,并没有被挂在 dom 树上,因此浏览器页面中,是显示不出来的

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

  • createTextNode 创建文本节点
  • createComment 创建注释节点
  • createAttribute 创建属性节点

==把节点挂在dom树上:==

使用 appendChild 把节点插入到某个节点插入到指定节点的最后一个孩子之后

<div class="container"></div>

<script>
    let newDiv = document.createElement('div'); // 创建标签
    newDiv.id = 'newDiv';
    newDiv.className = 'one';
    newDiv.innerHTML = 'hello';
    console.log(newDiv);

    let container = document.querySelector('.container');
    container.appendChild(newDiv);
</script>

image-20220630142109909


还可以使用 insertBefore 将节点插入到指定节点前

let insertedNode = parentNode.insertBefore(newNode, referenceNode);
  • insertedNode 被插入节点(newNode)
  • parentNode 新插入节点的父节点
  • newNode 用于插入的节点
  • referenceNode newNode 将要插在这个节点之前

如果 referenceNode 为 null 则 newNode 将被插入到子节点的末尾.

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

<div class="container">
	<div>11</div>
	<div>22</div>
	<div>33</div>
	<div>44</div>
</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>

image-20220630142341510

注意1:

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

<div class="container">
	<div>11</div>
	<div>22</div>
	<div>33</div>
	<div>44</div>
</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>

image-20220630142621150

注意2:

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

<div class="container">
	<div>11</div>
	<div>22</div>
	<div>33</div>
	<div>44</div>
</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>

image-20220630142635552


5.2 删除节点

删除节点,removeChild 方法来实现

得先拿到父节点,然后再拿到待删除的子节点,通过 removeChild 就能删除了

oldChild = element.removeChild(child);
  • child 为待删除节点
  • element 为 child 的父节点
  • 返回值为该被删除节点
  • 被删除节点只是从 dom 树被删除了, 但是仍然在内存中, 可以随时加入到 dom 树的其他位置.
  • 如果上例中的 child节点 不是 element 节点的子节点,则该方法会抛出异常
<div class="container"></div>

<button>删除 div</button>

<script>
    let newDiv = document.createElement('div'); // 创建标签
    newDiv.id = 'newDiv';
    newDiv.className = 'one';
    newDiv.innerHTML = 'hello';
    console.log(newDiv);

    let container = document.querySelector('.container');
    container.appendChild(newDiv);

    let button = document.querySelector('button');
    button.onclick = function() {
        container.removeChild(newDiv); // 前面已经获取了
    }
</script>

image-20220630142859081


五、代码案例

1、猜数字

image-20220630142937259

Math.random 得到的 [0,1) 随机的 小数 ,如何生成 1 - 100 呢?

先 *100,得到 [0,100) 之间的小数。然后向下取整 Math.floor ,也就是直接舍弃小数部分

<button id="resetBtn">重新开始一局游戏</button> <br>
<span>要猜的数字:</span>
<input type="text">
<button id="guessBtn">猜</button> <br>
<span>结果:</span> <span id="result"></span> <br>
<span>已经猜的次数:</span> <span id="guessCount">0</span>
    
<script>
    // 1、需要用到的元素
    let resetBtn = document.querySelector('#resetBtn');
    let input = document.querySelector('input');
    let guessBtn = document.querySelector('#guessBtn');
    let resultSpan = document.querySelector('#result');
    let guessCountSpan = document.querySelector('#guessCount');
    
    // 2、生成一个 1 - 100 的随机数
    let toGuess = Math.floor(Math.random() * 100) + 1;
    console.log(toGuess);

    // 3、实现点击 猜 按钮的逻辑
    guessBtn.onclick = function() {
        // 1) 读取 input 输入的内容,转成整数
        if (input.value == '') {
            return;
        }
        let curNum = parseInt(input.value);
        // 2) 判断大小 给出提示
        if (curNum < toGuess) {
            resultSpan.innerHTML = '低了';
            resultSpan.style.color = 'red';
        } else if (curNum > toGuess) {
            resultSpan.innerHTML = '高了';
            resultSpan.style.color = 'red';
        } else {
            resultSpan.innerHTML = '猜对了';
            resultSpan.style.color = 'green'               ;
        }
        // 3) 更新猜的次数
        let guessCount = parseInt(guessCountSpan.innerHTML);
        guessCountSpan.innerHTML = guessCount + 1;
    }

    // 4、实现 reset 操作,开始游戏
    resetBtn.onclick = function() {
        // 让页面刷新即可
        // location 是和 document 并列关系的对象,用来控制页面的链接/地址,通过 reload 操作就可以刷新页面
        location.reload();
    }
</script>


2、表白墙

image-20220630143413130

<style>
    * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
    }

    .container {
        width: 100%;
    }

    h3 {
        text-align: center;
        padding: 30px 0; /* 上下内边距 20,左右为 0 */
        font-size: 24px;
    }

    p {
        text-align: center;
        color: #999;
        padding: 10px 0;
    }

    .row {
        width: 400px;
        height: 50px;
        margin: 0 auto;

        display: flex;
        justify-content: center;
        align-items: center;
    }

    .row span {
        width: 60px;
        font-size: 20px;
    }

    .row input {
        width: 300px;
        height: 40px;
        line-height: 40px;
        font-size: 20px;
        text-indent: 0.5em;
        /* 去掉输入框的轮廓线 */
        outline: none;
    }

    .row #submit {
        width: 200px;
        height: 40px;
        font-size: 20px;
        line-height: 40px;
        margin: 0 auto;
        color: white;
        background-color: orange;
        /* 去掉边框 */
        border: none;
        border-radius: 10px;
    }

    /* 按下的效果 */
    .row #submit:active {
        background-color: grey;
    }
</style>

<div class="container">
    <h3>表白墙</h3>
    <p>输入后点击提示,会将信息显示在表格中</p>
    <div class="row">
        <span>谁:</span>
        <input type="text">
    </div>
    <div class="row">
        <span>对谁:</span>
        <input type="text">
    </div>
    <div class="row">
        <span>说:</span>
        <input type="text">
    </div>
    <div class="row">
        <button id="submit">提交</button>        
    </div>
</div>

<script>
    // 当用户点击 submit,就会获取 input 中的内容,把内容构造成一个 div,插入页面末尾
    let submitBtn = document.querySelector('#submit');
    submitBtn.onclick = function() {
        // 1、获取 2 个 input
        let inputs = document.querySelectorAll('input');
        let from = inputs[0].value;
        let to = inputs[1].value;
        let msg = inputs[2].value;
        if (from == '' || to == '' || msg == '') { // 用户还未填写完毕
            return;
        }
        // 2、生成一个新的 div,内容就是 input 中的内容,新的 div 加到页面中
        let div = document.createElement('div');
        div.innerHTML = from + ' 对 ' + to + ' 说 ' + msg;
        div.className = 'row'; // 应用 row 的样式
        let container = document.querySelector('.container');
        container.appendChild(div);
        // 3、清空之前输入框的内容
        for (let i = 0; i < inputs.length; i++) {
            inputs[i].value = '';
        }
    }
</script>


刚才咱们写的表白墙程序,是通过一些 div.row 来保存咱们提交的消息,这些 div.row,是挂在 DOM树上,就是在内存中的,容易失去的 一旦页面刷新 / 关闭了,此时,之前内存中保存的数据,就没了

为了解决上述的数据容易丢失问题,有以下解决方案:

  1. 可以把提交的数据,保存在浏览器本地 ( 浏览器提供了 localStorage / indexDB 这样的机制,能够实现本地存储),本质上,是通过浏览器,把你要存的数据,存到当前电脑的磁盘上 问题:只有我在自己的电脑上能看到,别人看不到
  2. 可以把提交的数据,通过网络通信,传输给服务器,由服务器进行保存
  • 服务器保存在内存里
  • 服务器保存在文件中
  • 服务器保存在数据库里

3、待办事项

image-20220630143810735

<style>
    * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
    }
    .container {
        width: 800px;
        margin: 0 auto;
        display: flex;
    }
    .todo,
    .done {
        width: 50%;
        height: 100%;
    }
    .container h3 {
        height: 50px;
        text-align: center;
        line-height: 50px;
        background-color: #333;
        color: #fff;
    }
    .nav {
        width: 800px;
        height: 100px;
        margin: 0 auto;
        display: flex;
        align-items: center;
    }
    .nav input {
        width: 600px;
        height: 50px;
    }
    .nav button {
        width: 200px;
        height: 50px;
        border: none;
        background-color: orange;
        color: #fff;
    }
    .row {
        height: 50px;
        display: flex;
        align-items: center;
    }
    .row input {
        margin: 0 10px;
    }
    .row span {
        width: 300px;
    }
    .row button {
        width: 50px;
        height: 40px;
    }
</style>

<div class="nav">
    <input type="text">
    <button>新建任务</button>
</div>
<div class="container">
    <div class="todo">
        <h3>未完成</h3>
        <div class="row">
            <input type="checkbox">
            <span>任务</span>
            <button>删除</button>
        </div>
    </div>
    <div class="done">
        <h3>已完成</h3>
    </div>
</div>

<script>
    // 一、实现新增任务
    let addTaskButton = document.querySelector('.nav button');
    addTaskButton.onclick = function () {
        // 1. 获取到任务内容的输入框
        let input = document.querySelector('.nav input');
        // 2. 获取到输入框内容
        let taskContent = input.value;
        // 3. 根据内容新建一个元素节点
        let row = document.createElement('div');
        row.className = 'row';
        let checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        let span = document.createElement('span');
        span.innerHTML = taskContent;
        let button = document.createElement('button');
        button.innerHTML = '删除';
        row.appendChild(checkbox);
        row.appendChild(span);
        row.appendChild(button);
        // 4. 把新节点插入到 todo 中
        let todo = document.querySelector('.todo');
        todo.appendChild(row);

        // 二、点击复选框后将元素放到 "已完成"
        //    在事件回调函数中使用 this 能够获取到当前处理事件的元素.
        //    通过 this.parentNode 属性能够获取到当前元素的父元素.
        //    点击 checkbox 时, 会先修改 value , 再触发点击事件
        
        // 修改 addTaskButton.onclick
        // 5. 给 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);
        }

        // 三、点击删除按钮删除该任务
        // 6. 给删除按钮注册点击事件
        button.onclick = function () {
            let row = this.parentNode;
            let grandParent = row.parentNode;
            grandParent.removeChild(row);
        }
    }
</script>