Skip to content

元素节点的添加/删除/替换/克隆与document 对象(Day13 3/3) ​

本页对应 Day13_DOM,共 5 段视频,约 50 分钟。

学习目标 ​

  1. 能使用 document.createElement 创建指定标签名的新元素,并为其设置文本或 HTML 内容。
  2. 能使用 appendChild 将新元素追加为父元素的最后一个子节点,并使用 insertBefore 将新元素插入到指定子节点之前。
  3. 能使用 removeChild 删除指定子元素,使用 replaceChild 用新元素替换指定旧子元素。
  4. 能使用 cloneNode(true/false) 克隆元素,并说明参数对是否克隆子内容的影响,能解释同一元素在 DOM 中移动而非复制。
  5. 能使用 document.title 读写网页标题,并结合 setInterval 与字符串截取、拼接实现标题栏滚动效果。
  6. 能列举并运用 document 对象常用属性与方法,如 documentElement、body、head、title、createElement、querySelector 等完成页面元素操作。

知识点 ​

先看页面结构:为什么需要元素节点增删改 ​

待办清单的本质是:一个 ul 里放多个 li。用户点“添加”,我们希望动态多一个任务;点“删除”,删掉一个任务;点“替换”,把旧任务换成新任务;点“插入”,把新任务放到指定位置。为什么需要这些方法:只改 innerHTML 容易把已有结构和事件覆盖掉,而用 DOM 节点方法能精确操作某一个子节点。怎么想:先拿到父元素 ul、输入框和各个按钮,再创建/查找节点,最后由父元素调用添加、删除、替换方法。怎么做:准备如下结构,ID 按转写稿整理为 todoContent、input、addBtn、insertBtn、deleteBtn、replaceBtn。易错点:创建出来的元素只是“游离”在内存里,不调用添加方法不会出现在页面上。

html
<ul id="todoContent">
  <li>吃饭</li>
  <li>看电影</li>
</ul>
<input id="input">
<button id="addBtn">添加</button>
<button id="insertBtn">插入</button>
<button id="deleteBtn">删除</button>
<button id="replaceBtn">替换</button>
js
const todoContent = document.querySelector('#todoContent');
const input = document.querySelector('#input');
const addBtn = document.querySelector('#addBtn');
const insertBtn = document.querySelector('#insertBtn');
const deleteBtn = document.querySelector('#deleteBtn');
const replaceBtn = document.querySelector('#replaceBtn');

创建与追加:createElement + appendChild ​

为什么需要:不能每次都在 HTML 里手写新 li,要用 JS 动态创建。怎么想:document.createElement('li') 的参数是标签名字符串,返回值是新元素;新元素同样可以设置 innerHTML、样式和属性。怎么做:创建 li,把输入框的 value 写进去,再用父元素 todoContent.appendChild(newLi) 追加到末尾。易错点:转写稿中第一次点添加“没反应”,不是创建失败,而是新元素还没被加入文档;另外输入框内容用 input.value 获取,不是 innerHTML。

js
addBtn.onclick = function () {
  const newLi = document.createElement('li');
  newLi.innerHTML = input.value;
  todoContent.appendChild(newLi);
  input.value = '';
};

插入到指定位置:insertBefore ​

为什么需要:appendChild 只能追加到最后,但有时要把任务放到最前面。怎么想:仍然由父元素调用,insertBefore(新节点, 旧节点) 表示把新节点放到旧节点前面。怎么做:创建新 li 后,用 todoContent.insertBefore(newLi, todoContent.firstElementChild) 插到第一个子元素前。易错点:参数顺序是“新元素在前、旧元素在后”;方法名里没有 child,但它操作的仍是子节点。若再次把同一个 newLi 插入别处,它是“移动”,不是复制。

js
insertBtn.onclick = function () {
  const newLi = document.createElement('li');
  newLi.innerHTML = input.value;
  todoContent.insertBefore(newLi, todoContent.firstElementChild);
  input.value = '';
};

删除与替换:removeChild / replaceChild ​

为什么需要:清单需要删除最后一项,或把最后一项换成新内容。怎么想:删除和替换都由父元素调用;先找到要操作的那个子元素。怎么做:删除用 todoContent.removeChild(todoContent.lastElementChild);替换用 replaceChild(新元素, 旧元素)。易错点:删到没有子元素时,lastElementChild 为 null,再删会报错,实际项目应先判断;replaceChild 参数顺序不能反。

js
deleteBtn.onclick = function () {
  const last = todoContent.lastElementChild;
  if (last) todoContent.removeChild(last);
};

replaceBtn.onclick = function () {
  const newLi = document.createElement('li');
  newLi.innerHTML = input.value;
  const last = todoContent.lastElementChild;
  if (last) todoContent.replaceChild(newLi, last);
  input.value = '';
};

克隆:cloneNode 与本尊/复制品 ​

为什么需要:如果反复 appendChild(newLi),同一个节点只会被移动,页面上始终只有一个。怎么想:cloneNode(true) 返回克隆后的元素,true 表示连内部文本一起克隆;不传或传 false 只克隆元素本身。怎么做:添加克隆版,而不是本尊。易错点:不写 true 时,新 li 里没有文字;克隆出来的是新节点,可以多次添加,本尊不动。

js
addBtn.onclick = function () {
  const newLi = document.createElement('li');
  newLi.innerHTML = '我是新元素' + Math.random();
  todoContent.appendChild(newLi.cloneNode(true));
};

补充:document.title 与标题滚动 ​

为什么需要:标题栏空间有限,长标题可做滚动提示。怎么想:标题不能用 scrollLeft,但可以不断改 document.title 的字符串顺序。怎么做:每次取第一个字符拼到最后,定时器重复执行。易错点:变量名或 slice 拼错时,控制台会出现 undefined 相关报错,先看控制台再改代码。

js
document.title = '你有一条新消息:澳门最大的线上赌场上线了';
setInterval(function () {
  const t = document.title;
  document.title = t.slice(1) + t[0];
}, 300);

document 是什么 ​

document 表示整个文档,是 window 的一个属性;它也是 HTML 元素的父节点。页面里的元素、标题、cookie 等,都可以通过 document 来读写。

为什么需要:JS 要操作页面,先要有入口。document 就是这个入口。
怎么想:把 document 看成“整个文档对象”,先通过它拿到元素,再操作元素。
怎么做:直接使用 document.xxx 或 document.方法()。
易错点:document 不是某个具体标签,而是文档对象;documentElement 才是 HTML 根元素。

笔记中列出的属性和方法要保留:

js
documentElement        // 获取 html 根元素
body                   // 获取 body 元素
head                   // 获取 head 元素
all                    // 获取所有元素组成的集合
title                  // 读写标题栏标题
cookie                 // 读写 cookie 信息

createElement()
getElementById()
getElementsByTagName()
getElementsByClassName()
getElementsByName()
querySelector()
querySelectorAll()

获取元素与创建元素 ​

为什么需要:要做“待办清单”的添加、插入、删除、替换,先要拿到按钮、输入框、列表等元素;要新增任务,还要创建新的 li。
怎么想:先获取已有元素,再用 document.createElement('li') 创建新元素。创建出来的元素和查询得到的元素本质一样,都可以设置样式、属性、内容。
怎么做:

js
const todoContent = document.querySelector('选择UL的选择器');
const addBtn = document.querySelector('选择添加按钮的选择器');
const inputBox = document.querySelector('选择输入框的选择器');

addBtn.onclick = function () {
  const newLi = document.createElement('li');
  newLi.innerHTML = inputBox.value;
  todoContent.appendChild(newLi);
  inputBox.value = '';
};

易错点:只 createElement 并设置内容,元素还“游离”在页面结构之外,不会显示;必须用 appendChild 等方法加入文档。转写稿里第一次点添加没反应,原因就是新 li 还没被添加到 ul 中。

添加与插入子节点 ​

为什么需要:新任务默认追加到最后;有时又要插到最前面或指定元素前面。
怎么想:添加子节点由父元素调用。追加用 appendChild;插入到某个旧元素前面用 insertBefore(新元素, 旧元素)。
怎么做:

js
todoContent.appendChild(newLi);

todoContent.insertBefore(newLi, todoContent.firstElementChild);

insertBefore 虽然名字里没有 append,但功能也是添加子节点,参数顺序是“新元素在前,旧元素在后”。
易错点:不要写成子元素调用;插入位置要传父元素下已存在的子元素。转写稿中把新元素插到“吃饭”前面,就是用第一个子元素作为参照。

删除与替换子节点 ​

为什么需要:任务会完成,也会写错,需要删除或替换。
怎么想:删除、替换都由父元素调用。删除用 removeChild(要删的元素);替换用 replaceChild(新元素, 旧元素)。
怎么做:

js
todoContent.removeChild(todoContent.lastElementChild);

const newLi = document.createElement('li');
newLi.innerHTML = inputBox.value;
todoContent.replaceChild(newLi, todoContent.lastElementChild);

易错点:删除前要先找到要删的元素;列表删空后继续删最后一个会报错。转写稿演示中删到没有最后一项就报错,实际开发可先判断是否存在子元素。

克隆元素 ​

为什么需要:同一个新元素反复添加时,如果直接添加本尊,它会在页面中“挪位置”,不是复制。
怎么想:要保留原元素,就克隆一份再添加。cloneNode(true) 返回克隆后的元素,参数 true 表示连同内部内容一起克隆。
怎么做:

js
const copy = newLi.cloneNode(true);
todoContent.appendChild(copy);

易错点:不传 true 时只克隆元素本身,不克隆里面的文字内容。转写稿中反复点添加、插入、替换,发现总是同一个元素在移动,因为变量始终指向同一个新元素;添加克隆版后,本尊不动,复制品才会不断增加。

title 标题滚动案例 ​

为什么需要:标题栏空间有限,长标题显示不全,可以用 document.title 做滚动提示。
怎么想:标题是字符串。每次取出第一个字符拼到末尾,再写回 document.title,定时器不断执行,就像向左滚动。
怎么做:

js
document.title = '你有一条新消息:澳门最大的线上赌场上线了';

setInterval(function () {
  const first = document.title.charAt(0);
  const rest = document.title.slice(1);
  document.title = rest + first;
}, 300);

易错点:charAt 拼错会得到 undefined,再调用 slice 就报错;scrollLeft 不能直接让标题栏滚动。转写稿调试时正是 charAt 写错导致控制台报错,改正后标题就能滚动。

本节说明 ​

本节笔记的作业是:

  1. 随机点名器
  2. 选项卡效果
  3. 优化图片延迟加载
  4. 实现图片无缝滚动

课堂转写稿没有展开这四个作业的具体实现,而是围绕 to-do list 示例讲了 DOM 元素节点的创建、添加、删除、替换、克隆,以及用 document.title 做标题栏滚动。下面按转写稿整理本节核心知识;作业四项需要综合运用这些节点操作完成,不能凭空编造课堂未讲的内容。

创建新元素并追加:为什么需要 → 怎么想 → 怎么做 → 易错点 ​

为什么需要:页面上原来的任务列表是写死的,用户点“添加”时,应该动态生成新的 li,而不是手写 HTML。
怎么想:先拿到父元素 ul、按钮和输入框;点击按钮时创建 li,把输入框内容写进去,再追加到 ul 末尾。
怎么做:

js
const toDoContent = document.querySelector('#toDoContent');
const addBtn = document.querySelector('#addBtn');
const inputBox = document.querySelector('#input');

addBtn.onclick = function () {
  const newLi = document.createElement('li');
  newLi.innerHTML = inputBox.value;
  toDoContent.appendChild(newLi);
  inputBox.value = '';
};

document.createElement('li') 的参数是标签名字符串,返回值是新元素。新元素刚创建时“游离在三界之外”,还没有进入页面结构;必须用父元素调用 appendChild(newLi) 追加到最后。
易错点:只创建不追加,页面不会变化;appendChild 是父元素调用;输入框内容用 inputBox.value 读取;追加后可清空输入框,转写稿也提醒空值可自行加判断。

插入到指定位置:为什么需要 → 怎么想 → 怎么做 → 易错点 ​

为什么需要:appendChild 只能追加到最后,但有时要把新任务插到最前面。
怎么想:仍然先创建 li,再用 insertBefore(newNode, oldNode),把新节点放到某个已有子节点前面。
怎么做:

js
insertBtn.onclick = function () {
  const newLi = document.createElement('li');
  newLi.innerHTML = inputBox.value;
  toDoContent.insertBefore(newLi, toDoContent.firstElementChild);
  inputBox.value = '';
};

易错点:insertBefore 也是父元素调用;第二个参数必须是该父元素已有的子元素;方法名里没有 child,但它同样是添加子节点。转写稿中强调:想插到“吃饭”前面,就把“吃饭”作为第二个参数。

删除与替换:为什么需要 → 怎么想 → 怎么做 → 易错点 ​

为什么需要:任务清单要能删掉一项,也要能把某一项换成新内容。
怎么想:删除用父元素 removeChild(要删的元素);替换用父元素 replaceChild(新元素, 旧元素)。
怎么做:

js
deleteBtn.onclick = function () {
  toDoContent.removeChild(toDoContent.lastElementChild);
};

replaceBtn.onclick = function () {
  const newLi = document.createElement('li');
  newLi.innerHTML = inputBox.value;
  toDoContent.replaceChild(newLi, toDoContent.lastElementChild);
};

易错点:删除前要先找到要删的元素;没有子元素时继续删会报错,转写稿说可自行加判断;replaceChild 参数顺序是“新在前、旧在后”。

克隆节点:为什么需要 → 怎么想 → 怎么做 → 易错点 ​

为什么需要:同一个新元素如果反复 appendChild、insertBefore、replaceChild,它不是复制,而是“搬家”,旧位置就没有了。转写稿演示:代码写在外面只创建一次,变量始终指向同一个元素,所以点添加、插入、替换时感觉“怎么还是它”。
怎么想:要保留本尊不动,就克隆一份再添加。
怎么做:

js
const copy = newLi.cloneNode(true);
toDoContent.appendChild(copy);

cloneNode(true) 表示元素和里面内容一起克隆;不写参数或写 false 只克隆元素本身,不带内容。
易错点:克隆后仍要追加;不加 true 会丢内容;本尊和复制品是两个节点。

document.title 与标题滚动:为什么需要 → 怎么想 → 怎么做 → 易错点 ​

为什么需要:标题栏空间有限,长标题显示不全,可以做成滚动效果。标题栏不能用 scrollLeft,因为它是特殊区域。
怎么想:把标题字符串的第一个字符取出来拼到最后,定时器反复执行,视觉上就像向左滚动。
怎么做:

js
setInterval(function () {
  const title = document.title;
  document.title = title.slice(1) + title.charAt(0);
}, 300);

易错点:slice 拼错会得到 undefined,再调用方法就报错;charAt(0) 取首字符;速度用时间间隔调整。转写稿调试时就是 slice 拼写错误导致后台报错,改正后滚动正常。

作业说明 ​

作业四项——随机点名器、选项卡效果、图片延迟加载、图片无缝滚动——需要在本节 DOM 节点操作基础上完成。转写稿没有给出这四项的具体代码,因此这里只保留作业清单,不编造实现步骤。

课堂画面 ​

课堂画面 1

课堂画面 2

课堂画面 3

课堂画面 4

课堂画面 5

代码示例 ​

一、To Do List:元素节点的添加 / 插入 / 删除 / 替换 / 克隆(完整可运行) ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>To Do List</title>
<style>
  body { font-family: "Microsoft YaHei", sans-serif; padding: 20px; }
  input  { width: 200px; height: 28px; padding: 0 8px; }
  button { height: 32px; margin-right: 4px; cursor: pointer; }
  ul { margin-top: 16px; }
  li { line-height: 2; }
</style>
</head>
<body>
  <h2>我今天计划做的事情</h2>
  <input type="text" id="input" placeholder="请输入任务">
  <button id="addBtn">添加</button>
  <button id="insertBtn">插入到最前</button>
  <button id="deleteBtn">删除最后一项</button>
  <button id="replaceBtn">替换最后一项</button>
  <button id="cloneBtn">克隆第一项</button>

  <ul id="todoContent">
    <li>喝茶</li>
    <li>钓鱼</li>
    <li>看电影</li>
    <li>吃晚饭</li>
  </ul>

<script>
  // 1. 先获取相关元素
  var todoContent = document.querySelector('#todoContent');
  var inputBox    = document.querySelector('#input');
  var addBtn      = document.querySelector('#addBtn');
  var insertBtn   = document.querySelector('#insertBtn');
  var deleteBtn   = document.querySelector('#deleteBtn');
  var replaceBtn  = document.querySelector('#replaceBtn');
  var cloneBtn    = document.querySelector('#cloneBtn');

  // 2. 添加:创建新元素 → 设置内容 → 追加到 UL 最后
  addBtn.onclick = function () {
    if (inputBox.value.trim() === '') return;      // 空值不执行
    var newLi = document.createElement('li');      // 参数是标签名字符串
    newLi.innerHTML = inputBox.value;              // 新元素同样有 innerHTML
    todoContent.appendChild(newLi);                // 必须是父元素调用
    inputBox.value = '';                           // 清空输入框
  };

  // 3. 插入:insertBefore(新元素, 旧元素) —— 插到旧元素前面
  insertBtn.onclick = function () {
    if (inputBox.value.trim() === '') return;
    var newLi = document.createElement('li');
    newLi.innerHTML = inputBox.value;
    todoContent.insertBefore(newLi, todoContent.firstElementChild);
    inputBox.value = '';
  };

  // 4. 删除:父元素.removeChild(要删的子元素)
  deleteBtn.onclick = function () {
    if (todoContent.lastElementChild) {            // 删空了要判断,否则报错
      todoContent.removeChild(todoContent.lastElementChild);
    }
  };

  // 5. 替换:父元素.replaceChild(新元素, 旧元素)
  replaceBtn.onclick = function () {
    var newLi = document.createElement('li');
    newLi.innerHTML = '我是替换出来的新任务 ' + Math.random().toFixed(2);
    if (todoContent.lastElementChild) {
      todoContent.replaceChild(newLi, todoContent.lastElementChild);
    }
  };

  // 6. 克隆:cloneNode(true) 连内容一起克隆,本尊岿然不动
  cloneBtn.onclick = function () {
    var first = todoContent.firstElementChild;
    if (!first) return;
    var copy = first.cloneNode(true);              // 不加 true 只有空标签
    todoContent.appendChild(copy);
  };
</script>
</body>
</html>

关键行与易错点

  • document.createElement('li'):参数是标签名字符串,不要写成 '<li>';创建出来的元素和 querySelector 拿到的元素完全一样,样式、属性、innerHTML 都能操作。
  • appendChild / insertBefore / removeChild / replaceChild 都由父元素调用,参数是子元素。insertBefore 名字里没有 "child",但它同样是添加子节点。
  • insertBefore(newLi, oldLi):第二个参数是已存在的子元素,新元素会插到它前面;想插到最前面就传 firstElementChild。
  • 用 lastElementChild / firstElementChild 而不是 lastChild / firstChild,后者会把换行文本节点也算进去。
  • cloneNode(true) 的 true 表示连子内容一起克隆;不传只有空标签。克隆出来的才是"复制品",直接 appendChild 本尊是把本尊挪走,原位置就没有了。
  • 输入框为空时先 return,否则会添加空 li;删除、替换前也要判断元素是否存在,否则 removeChild(null) 直接报错。

二、document.title 实现标题栏滚动(完整可运行) ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>标题滚动</title>
</head>
<body>
  <h2>请看浏览器标签页上的标题,它一直在滚动</h2>

<script>
  // document.title 是读写标题栏的快捷方式
  document.title = '你有一条新消息:澳门最大的线上赌场上线了,性感荷官在线发牌';

  setInterval(function () {
    var t = document.title;              // 拿到当前标题字符串
    document.title = t.slice(1) + t.charAt(0);  // 第一个字符挪到最后
  }, 300);
</script>
</body>
</html>

关键行与易错点

  • 标题栏在 <head> 里,不能用 scrollLeft 让它滚动,只能不停改写 document.title 的文字内容,靠"截头补尾"制造向左滚动的错觉。
  • t.slice(1) 取从第 2 个字符开始的剩余部分,t.charAt(0) 取第一个字符,两者拼接后赋值回 document.title。
  • 方法名拼写必须准确:写成 sclie 之类,t 就是 undefined,undefined.slice 会直接抛错,定时器里刷屏报错。
  • 间隔时间(这里是 300ms)越小滚得越快,100ms 会明显一卡一卡;不要忘记定时器会一直执行,循环滚动永不停止。
  • 没有 document.title 这种快捷方式时,得先 document.querySelector('title') 再改 innerHTML / innerText。

三、document 对象的常用属性与方法(完整可运行) ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>document 对象</title>
</head>
<body>
  <ul id="list">
    <li class="item">喝茶</li>
    <li class="item">钓鱼</li>
  </ul>
  <p id="out"></p>

<script>
  // 属性
  console.log(document.documentElement);        // <html> 根元素
  console.log(document.head);                   // <head>
  console.log(document.body);                   // <body>
  console.log(document.all.length);             // 所有元素组成的集合
  console.log(document.title);                  // 读标题
  document.title = '标题被我改了';              // 写标题

  // 方法
  console.log(document.getElementById('list'));
  console.log(document.getElementsByTagName('li'));
  console.log(document.getElementsByClassName('item'));
  console.log(document.querySelector('#list'));
  console.log(document.querySelectorAll('li'));

  // 演示:document 是 html 元素的父节点,window.document 就是它
  document.getElementById('out').innerHTML =
    '根元素:' + document.documentElement.tagName +
    ',body 子元素个数:' + document.body.children.length;
</script>
</body>
</html>

关键行与易错点

  • document 表示整个文档,是 html 元素的父节点,同时是 window 的一个属性,所以 window.document === document。
  • documentElement 拿的是 <html>,body / head 直接拿对应元素,比 querySelector 更短。
  • document.all 是所有元素组成的集合,document.title 是标题的读写快捷方式;cookie 用来读写登录等信息,要到前后端交互阶段才用得上。
  • getElementsByTagName / getElementsByClassName / getElementsByName 返回的是动态集合,querySelectorAll 返回的是静态 NodeList,后者更常用。
  • 取 URL、域名这类信息推荐用 location,不要用 document.URL / document.domain 这类基本用不上的属性。

常见错误 ​

1. 创建了元素,页面却毫无反应

  • 现象:document.createElement('li') 执行成功,控制台能看到对象,但页面上什么都没多出来。
  • 原因:创建只是让节点存在于内存中(游离节点),从未挂到文档树上;漏写了 appendChild / insertBefore / replaceChild。
  • 修法:把“创建 + 设置内容 + 插入”当成一个固定三步动作,写完 createElement 立刻补上插入语句,并在插入后刷新验证。

2. 回调函数里创建的元素写在回调外面,反复点击只出现一次

  • 现象:第一次点“添加”有效,后面再点按钮,节点只是从原位置“跑”到新位置,数量始终是 1。
  • 原因:createElement 写在了 onclick 外面,全程只执行一次,变量始终指向同一个节点;DOM 中同一节点只能存在于一个位置,再次插入等于移动。
  • 修法:需要多次生成就把创建语句放进事件回调内部;需要在多个位置复用同一份内容就用 cloneNode(true)。

3. cloneNode() 没带参数,克隆出来的是空壳

  • 现象:克隆出的 li 里文字、子元素全没了,只剩标签本身。
  • 原因:cloneNode() 默认浅克隆,只复制元素节点本身,不复制子节点和文本。
  • 修法:需要连内容一起复制就写 cloneNode(true);只有确实想要空容器时才省略参数。

4. replaceChild / insertBefore 的参数顺序写反

  • 现象:控制台报 NotFoundError: Failed to execute 'replaceChild' on 'Node',或替换后原节点还在。
  • 原因:两个方法都是“新节点在前、参照/被替换的旧节点在后”,习惯性按中文语序写成“旧→新”就会错;insertBefore 的第二个参数还必须是父元素的直接子节点,传 null 或不相关的节点也会报错。
  • 修法:记成“新替旧、新插旧前”,第二个参数统一用 xxxElementChild 这类从父元素上取到的真实子节点。

5. 删到最后一个就报错

  • 现象:连点删除,删完后控制台抛 TypeError: Cannot read properties of null。
  • 原因:lastElementChild 在子元素清空后返回 null,直接把它传给 removeChild 就会崩。
  • 修法:删除前判断 if (父元素.lastElementChild) { ... },或改用 lastElementChild?.remove()。

6. 用 lastChild 代替 lastElementChild,删除/替换行为诡异

  • 现象:有时删掉的是空白,有时一次删两个,replaceChild 报错。
  • 原因:lastChild / childNodes 会把标签之间的换行、空格当成文本节点,取到的不是你以为的那个元素。
  • 修法:只要操作元素,一律用 children / firstElementChild / lastElementChild。

7. 标题滚动案例里字符串方法写错

  • 现象:定时器一启动就报 ... is not a function,标题不动。
  • 原因:把 slice 写成 splice、concat 拼错成 conect,导致取到 undefined 再调用方法。
  • 修法:报错先看行号定位到具体调用;拼接优先用模板字符串 `${rest}${first}` 代替 +,能少踩一半拼写坑。

练习 ​

1. 待办清单完整版 基于课堂的 to-do list,补齐四个按钮:追加到末尾、插入到第一项之前、删除最后一项、把最后一项替换成新输入内容。要求:输入为空时点击任何按钮都不产生空条目;每次操作后清空输入框并自动聚焦。

2. 用克隆做“再来一份” 页面上有一个带图标、带子 span 的复杂卡片。点击按钮时,在列表末尾追加一份完全一样的卡片,并给克隆出来的卡片标题加上“副本 2、副本 3”的序号。要求原卡片始终留在原位不被移动。

3. 复制已有节点 vs 移动已有节点 页面上放两个 ul,第一个 ul 里已有 3 个 li。做两个按钮:一个把第一个 ul 的第一个 li 挪到第二个 ul,另一个把它的副本放过去。观察并口头解释两者在数量上的差别。

4. 标题栏滚动增强版 实现 document.title 滚动,并增加控制能力:点击“暂停/继续”切换滚动状态,点击“重置”恢复初始文本。注意 clearInterval 后变量要置空,避免重复启动产生多个定时器。

5. 安全删除封装 写一个 removeLast(parent) 函数,父元素没有子元素时不做任何事也不报错,有子元素时删掉最后一个并返回被删掉的节点。再用它连续删 5 次做验证。

原视频对照 ​

#视频时长
1创建新元素和添加子元素17:18
2删除子元素03:02
3替换子元素03:38
4克隆元素14:44
5标题栏滚动案例11:46

小结 ​

  1. DOM 操作永远是“两步走”:先在内存里造/找节点,再显式挂到树上;只做第一步,页面一定不动。
  2. 节点是对象引用,不是值拷贝——同一个节点在文档中永远只有一份,重复插入等于搬家,不会变多。想变多只有 cloneNode(true)。
  3. 创建语句放在回调里还是回调外,决定了“点一次加一个”还是“点一百次也就一个”,这是初学者最常踩的坑,写完先连点三次验证。
  4. 增、删、替三个方法都由父元素调用,参数里出现的一定是子节点;insertBefore 和 replaceChild 的第二个参数负责“定位/被替换”,写错顺序或传入非子节点必报错。
  5. 取子元素一律用带 Element 的那一套(children / firstElementChild / lastElementChild),避免被空白文本节点干扰。
  6. 所有删除、替换、插入都要考虑边界:空列表、只剩一个、目标不存在。加上一行判空,代码就从不稳定变成稳定。
  7. document 上的大部分属性(URL、domain、lastModified 等)实际项目里用 location 等更专门的 API 代替;真正高频的是 createElement 系列方法和 title、cookie。
  8. 标题栏是特殊区域,无法用 CSS 滚动,只能靠定时器改写字符串顺序来“伪造”滚动效果——这类“换思路绕过限制”的做法,比死磕某个 API 更有价值。