- 教程
- 文档
【免费下载链接】thejsway
The JavaScript Way book
本篇对应《The JavaScript Way》第 15 章(Create interactive web pages 系列第 4 篇)。浏览器加载完 HTML 后,JavaScript 依然可以通过 DOM API 继续"改造"页面:修改已有元素的内容与样式、动态创建并插入新节点、替换或删除旧节点。读完本文,你将掌握
innerHTML、textContent、classList、setAttribute()、createElement()、appendChild()、insertBefore()、insertAdjacentHTML()、replaceChild()、removeChild()、style与getComputedStyle()等一整套 DOM 修改 API,并能在真实页面中写出结构清晰、性能友好的动态更新代码。
本章导读与前置知识
在开始"修改"之前,先回顾《The JavaScript Way》的 DOM 学习路线,这对理解本篇文章的位置很有帮助:
- 第 13 章 介绍了 DOM 树的基本概念:页面是一棵由元素节点与文本节点构成的树,
document变量指向树根,childNodes、parentNode、nodeType等属性负责逐节点遍历; - 第 14 章 解决了"怎么快速找到要操作的元素"的问题:
getElementById()、getElementsByTagName()、getElementsByClassName()、querySelector()、querySelectorAll()等选择方法(其中 querySelector/querySelectorAll 使用 CSS 选择器); - 本章则聚焦**"找到之后如何改"**:无论是修改已有元素的内容、属性、类名与样式,还是凭空创建一个新元素插入页面,都属于 DOM 修改(Modify page structure)的范畴;
- 后续 第 16 章 将在此基础上引入事件监听(如
addEventListener()),让页面能响应用户点击等操作,动态修改的技术基础正是本章内容。
示例页面:贯穿全章的编程语言列表
本章绝大部分示例都基于下面这段 HTML。它包含一个标题和一个<ul>无序列表,列表里有 C++、Java、C#、PHP 四种语言:
<h3 class="beginning">Some languages</h3> <div id="content"> <ul id="languages"> <li id="cpp">C++</li> <li id="java">Java</li> <li id="csharp">C#</li> <li id="php">PHP</li> </ul> </div>你可以把这段代码保存为 HTML 文件,在浏览器中打开并打开开发者工具的控制台(Console),逐条运行后面的 JavaScript 示例观察页面变化——注意示例之间可能存在叠加效果,建议按顺序执行或刷新页面后再试。
修改已有元素
上一章学到的 DOM 遍历与选择属性/方法,同样可以用来更新页面中的元素。
用 innerHTML 修改 HTML 内容
innerHTML属性可以读取或改写元素内部的 HTML 内容。向列表追加一个新条目,可以用+=运算符拼接 HTML 字符串——下面这行代码先通过getElementById("languages")选中<ul>,再在列表末尾追加一个<li>:
// Modifying an HTML element: adding an <li> document.getElementById("languages").innerHTML += '<li id="c">C</li>';innerHTML最常见的另一个用途是清空内容——把它赋值为空字符串即可删除元素内部所有子节点:
// Delete the HTML content of the list, replacing it with nothing document.getElementById("languages").innerHTML = "";在继续之前,请把这行代码从你的程序中移除,否则列表就空了,后续示例将无处施展!
!!! tip "建议"
使用 `innerHTML` 时,HTML 内容是以**字符串**形式写入的。为了保持代码可读、避免拼接出错,建议只在改动**较小**的内容时使用 `innerHTML`。下面你会看到更灵活、更安全的解决方案。值得一提的是,上一章已经介绍过innerHTML的读取能力——第 14 章用它输出元素内部的 HTML 源码;本章则演示了它的写入能力,读写两端合起来才算完整掌握该属性。另外需要留意:innerHTML不是 W3C DOM 规范的一部分(由微软提出),但如今所有主流浏览器都支持它。
用 textContent 修改文本内容
与innerHTML处理 HTML 字符串不同,textContent属性用于修改元素的纯文本内容。下面的代码选中页面第一个<h3>标题,在其现有文本后追加一段文字:
// Modify the title's text content document.querySelector("h3").textContent += " for programming";执行后标题从 "Some languages" 变为 "Some languages for programming"。注意textContent会把内容当作纯文本处理,即使用户输入包含<、>之类的字符也不会被解析成 HTML,因此在处理用户输入、避免注入风险时,textContent往往比innerHTML更安全、更合适。
修改元素属性:setAttribute() 与属性直接赋值
setAttribute()方法可以为元素设置任意属性的值,参数依次是属性名和属性值:
// Define the id attribute of the first title document.querySelector("h3").setAttribute("id", "title");如第 14 章所见,部分常用属性(如id、href、value)在 DOM 中同时以对象属性的形式存在,可以直接赋值,效果与setAttribute()相同:
// Define the id attribute of the first title document.querySelector("h3").id = "title";两种写法等价。对于id这类常见属性,直接赋属性值更简洁;setAttribute()则胜在通用——它可以处理任意属性,包括data-*自定义属性等。
用 classList 增删类名
一个元素可以同时拥有多个 class。classList属性返回该元素的类名集合,并提供了add()/remove()等方法。下面代码把第一个<h3>的beginning类移除、再加上title类,并把元素打印到控制台:
const titleElement = document.querySelector("h3"); // Grab the first h3 titleElement.classList.remove("beginning"); // Remove the class "beginning" titleElement.classList.add("title"); // Add a class called "title" console.log(titleElement);上一章还介绍过classList的查询能力(contains()判断是否包含某类、length与下标访问遍历类名);本章的add()/remove()则是它的写入能力。此外classList还内置toggle()等方法,可结合 第 14 章 的内容一并掌握。
添加新元素:三步走
修改已有元素之外,更常见的是凭空创建一个新元素并插入页面。整个过程可以拆成三步:
- 创建新元素;
- 设置元素属性(ID、类、文本内容等);
- 把新元素插入DOM 树。
例如,给语言列表添加一门 "Python":
const pythonElement = document.createElement("li"); // Create an "li" element pythonElement.id = "python"; // Define element ID pythonElement.textContent = "Python"; // Define its text content document.getElementById("languages").appendChild(pythonElement); // Insert the new element into the DOM下面逐一步骤拆解。
第 1 步:createElement() 创建元素
createElement()方法用于创建元素节点。它在document对象上调用,参数是新元素的标签名,返回值是创建好的元素对象(DOM 中它尚未挂载到任何位置,只是一个游离节点):
const pythonElement = document.createElement("li"); // Create an li element第 2 步:设置元素属性
元素创建后,就可以利用前面学的 DOM 属性给它"填充细节"——ID、class、文本内容等等。示例中把新元素的 ID 设为"python"、文本内容设为"Python":
// ... pythonElement.id = "python"; // Define element ID pythonElement.textContent = "Python"; // Define its text content这里也可以改用setAttribute()或classList.add(),取决于你想设置的属性类型。
第 3 步:appendChild() 插入 DOM
把新节点挂到 DOM 树上的技术有多种,最常用的是appendChild():在未来父元素上调用它,新节点会被追加为该父元素最后一个子节点:
// ... document.getElementById("languages").appendChild(pythonElement); // Insert the new element into the DOM在我们的例子中,pythonElement成为<ul id="languages">的新子节点,排在 C++、Java、C#、PHP 之后。
添加元素的更多变体
appendChild()是"追加到末尾"的标准方案,但实际需求往往更复杂:有时只想加纯文本、有时要插到指定位置。本章提供了三种补充方案。
createTextNode():添加纯文本节点
不想用textContent属性,也可以先创建文本节点再挂载。createTextNode()创建文本节点,再用appendChild()把它挂到元素上。下面的代码用这种方式在列表末尾插入 Ruby:
const rubyElement = document.createElement("li"); // Create an "li" element rubyElement.id = "ruby"; // Define element ID rubyElement.appendChild(document.createTextNode("Ruby")); // Define its text content document.getElementById("languages").appendChild(rubyElement); // Insert the new element into the DOM注意这里的双重appendChild():第一个把文本节点挂进<li>,第二个把<li>挂进<ul>。回想第 13 章提到的"文本节点是 DOM 树中与元素节点平级的一类节点"(见 chapter13.md),createTextNode()正是程序化生成这类节点的方法。
insertBefore():插到指定节点之前
当"追加到末尾"不满足需求时,可以用insertBefore()。在父元素上调用,参数依次是新节点和参照节点,新节点会被插入到参照节点之前。例如把 Perl 插到 PHP 前面:
const perlElement = document.createElement("li"); // Create an "li" element perlElement.id = "perl"; // Define element ID perlElement.textContent = "Perl"; // Define its text content // Insert the new element before the "PHP" node document.getElementById("languages").insertBefore(perlElement, document.getElementById("php"));insertBefore(newNode, referenceNode)与appendChild(newNode)的参数顺序相反,使用时务必区分:前者把节点放在参照节点之前,后者把节点放在父元素子节点列表的末尾。
insertAdjacentHTML():精确控制插入位置
insertAdjacentHTML()可以更精确地指定插入位置。调用方式是在已有元素上调用,传入两个参数:位置和一段HTML 字符串。位置取值共有四种:
| 位置 | 含义 |
|---|---|
beforebegin | 插入到该元素之前(作为其兄弟节点) |
afterbegin | 插入到该元素内部、其第一个子节点之前 |
beforeend | 插入到该元素内部、其最后一个子节点之后 |
afterend | 插入到该元素之后(作为其兄弟节点) |
以现有<p>标签为例,四个位置对应关系如下:
<!-- beforebegin --> <p> <!-- afterbegin --> foo <!-- beforeend --> </p> <!-- afterend -->实际使用中,位置参数不区分大小写(文档示例用的是"afterBegin")。下面的代码把 JavaScript 插入到语言列表的顶部(即<ul>第一个子节点之前):
// Add an element to the beginning of a list document.getElementById('languages').insertAdjacentHTML("afterBegin", '<li id="javascript">JavaScript</li>');与createElement()+appendChild()相比,insertAdjacentHTML()直接接收 HTML 字符串、一步完成插入,代码更简短;代价与innerHTML类似——字符串拼接过多会降低可读性,且需要留意 HTML 解析带来的开销与安全边界。
替换与删除节点
replaceChild():替换节点
replaceChild()用新节点替换父元素的某个子节点,参数顺序是(新节点, 被替换节点)。下面的代码把 Perl 替换成 Lisp:
const lispElement = document.createElement("li"); // Create an li element lispElement.id = "lisp"; // Define its ID lispElement.textContent = "Lisp"; // Define its text content // Replace the element identified by "perl" with the new element document.getElementById("languages").replaceChild(lispElement, document.getElementById("perl"));参数顺序容易记混:新节点在前、旧节点在后,与insertBefore(newNode, referenceNode)的"新节点在前"风格一致。
removeChild():删除节点
删除节点使用removeChild(),参数是要被移除的节点(通常先用选择方法拿到它):
// Remove the element with the "lisp" id document.getElementById("languages").removeChild(document.getElementById("lisp"));执行后列表恢复为 C++、Java、C#、PHP、C、Ruby、Perl(已被替换为 Lisp 又被删除)、JavaScript 的中间状态——这正是各示例顺序执行时页面呈现的效果。
修改元素样式
JavaScript 不仅能改页面结构,还能改元素的视觉样式。下面是样式部分的示例 HTML 与配套 CSS。
示例 HTML(三个段落,第二个通过内联style属性设为绿色):
<p>First</p> <p style="color: green;">Second</p> <p id="para">Third</p>配套 CSS 样式表(#para的文本为蓝色且倾斜):
#para { font-style: italic; color: blue; }style 属性:修改内联样式
每个 DOM 元素都有一个style属性,它返回一个代表元素内联style属性的对象;对象上的各属性与 CSS 属性一一对应。给这些属性赋值,就等于直接修改元素的样式。例如选中第一个段落并修改其文字颜色与外边距:
const paragraphElement = document.querySelector("p"); paragraphElement.style.color = "red"; paragraphElement.style.margin = "50px";复合 CSS 属性的 camelCase 命名
部分 CSS 属性由多个单词组成(如background-color)。在 JavaScript 中访问这类属性时,需要去掉连字符并把后续单词首字母大写(camelCase 驼峰命名法)。下面的代码把同一个段落的font-family与background-color分别设为 Arial 和黑色:
// ... paragraphElement.style.fontFamily = "Arial"; paragraphElement.style.backgroundColor = "black";这种命名约定在本书此前章节中已经出现过:CSS 的background-color对应 JS 的backgroundColor,font-family对应fontFamily,规律完全一致。各 CSS 属性对应的 JavaScript 属性名,可查阅 Mozilla Developer Network 的 CSS Properties Reference 文档。
style 属性的局限:读不到样式表里的样式
style属性只反映元素内联style属性中声明的样式,无法读取定义在 CSS 样式表中的样式。试着读取三个段落的文字颜色:
const paragraphElements = document.getElementsByTagName("p"); console.log(paragraphElements[0].style.color); // "red" console.log(paragraphElements[1].style.color); // "green" console.log(paragraphElements[2].style.color); // Show an empty string前两个段落分别返回"red"和"green"——因为它们的内联样式被 JavaScript 或 HTML 显式设置了;第三个段落的蓝色来自外部 CSS 样式表(#para规则),style属性对它一无所知,返回空字符串。
getComputedStyle():读取最终计算样式
要读取元素最终生效的样式(包括样式表规则、继承结果),应使用getComputedStyle()。它以 DOM 节点为参数,返回一个代表该元素计算样式(computed style)的对象,之后即可读取各 CSS 属性:
const paragraphStyle = getComputedStyle(document.getElementById("para")); console.log(paragraphStyle.fontStyle); // "italic" console.log(paragraphStyle.color); // color blue in RGB values这里的蓝色以 RGB 形式呈现:红、绿、蓝三个通道的取值都在 0 到 255 之间。
需要强调的是:getComputedStyle()返回的对象是只读的——它用于"读取"元素当前实际样式;要"修改"样式,依然靠style属性或classList操作类名(让 CSS 规则生效)。
DOM 操作与性能
每次通过 JavaScript 更新 DOM,浏览器都需要重新计算页面的显示效果(包括重新布局与重绘)。频繁的 DOM 操作会导致页面卡顿、性能下降,因此应尽可能减少 DOM 的访问与更新次数。
一个典型的性能优化习惯是:先把新元素的属性设置好,再一次性插入 DOM。对比下面两种写法:
// Bad: DOM is updated multiple times const newNode = document.createElement(...); // Create new element parentNode.appendChild(newNode); // Add it to the DOM newNode.id = ...; // Set some element properties newNode.textContent = "..."; // ... // Better: DOM is updated only once const newNode = document.createElement(...); // Create new element newNode.id = ...; // Set some element properties newNode.textContent = "..."; // ... parentNode.appendChild(newNode); // Add it to the DOM左侧写法先插入、后设置属性,浏览器被迫在元素插入后再次为属性变化重新计算渲染;右侧写法先在"游离状态"下把所有属性配齐,再一次性挂载,DOM 只被更新一次。这也正是本章"添加新元素三步走"(先创建、再设置、后插入)的顺序如此安排的深层原因——它不仅是逻辑清晰,更是性能友好的实践。
快速回顾(TL;DR)
本章知识要点一览:
- 修改一个 DOM 元素的信息,可以使用
innerHTML、textContent、classList等属性,以及setAttribute()方法; - 创建新的 DOM 节点,用
createTextNode()(文本节点)与createElement()(元素节点); appendChild()把新节点插入为某元素的最后一个子节点;insertBefore()与insertAdjacentHTML()是插入内容的另外两种方式,分别支持"插到指定节点之前"与"四种精确位置";replaceChild()替换已有节点,removeChild()删除节点;- JavaScript 的
style属性代表 DOM 节点的内联style属性,可用于修改样式; - 多单词 CSS 属性在 JavaScript 中写成camelCase,例如
font-family→fontFamily; style属性不适合用来读取元素样式,读取请使用getComputedStyle();- DOM 操作应克制进行,避免拖慢页面性能。
实战练习
以下是本章配套练习,建议在浏览器控制台或独立 HTML 页面中完成,逐一验证输出结果。
练习 1:添加一个段落
在"语言列表"示例的基础上,向页面添加一个段落(<p>),段落内包含一个指向编程语言列表页面(https://en.wikipedia.org/wiki/List_of_programming_languages)的超链接(<a>)。提示:用createElement()分别创建<p>与<a>,设置a.href与文本内容,再通过appendChild()依次挂载。
练习 2:报纸列表
给定如下 HTML 页面:
<h3>Some newspapers</h3> <div id="content"></div>编写程序,把下面数组中定义的报纸以可点击链接的形式显示在页面上:
// Newspaper list const newspapers = ["https://www.nytimes.com", "https://www.washingtonpost.com", "http://www.economist.com"];思路:遍历数组,为每项创建<a>元素(textContent用报纸名、href用 URL),再插入#content。注意观察用innerHTML拼接与用createElement()+appendChild()两种写法的差异。
练习 3:迷你词典
给定如下 HTML 页面:
<h3>A mini-dictionary</h3> <div id="content"></div>编写程序,把下面数组中的词条以"术语 + 定义"列表的形式显示在页面上:
const words = [{ term: "Procrastination", definition: "Avoidance of doing a task that needs to be accomplished" }, { term: "Tautology", definition: "logical argument constructed in such a way that it is logically irrefutable" }, { term: "Oxymoron", definition: "figure of speech that juxtaposes elements that appear to be contradictory" }];要求使用 HTML 的<dl>(描述列表)标签组织内容(可参考 MDN 上关于<dl>标签的文档),并且每个术语用<strong>标签加粗强调。<dl>内通常包含<dt>(术语)与<dd>(定义)两类子元素,注意createElement()只能逐个创建标签,需要你为每个词条创建两个子元素。
练习 4:更新颜色
给定包含三个标题与三个正文段落的 HTML 页面(每对<h1>+<div>构成一个小节)。编写程序:
- 首先通过
prompt()询问用户新的文字颜色; - 再通过
prompt()询问新的背景颜色; - 页面中所有段落随之更新。
思路:用querySelectorAll()或getElementsByTagName()选中所有<div>,遍历并为每个元素设置style.color与style.backgroundColor——注意background-color在 JavaScript 中要写成backgroundColor。
练习 5:获取元素的信息
给定如下 HTML 与 CSS:
<div id="content">ABC <br>Easy as <br>One, two, three </div> <div id="infos"></div>#content { float: right; margin-top: 100px; margin-right: 50px; }编写程序,在#infos中显示#content元素的高度与宽度。这里无法用style属性读取(尺寸由 CSS 样式表与内容共同决定),需要借助getComputedStyle()或元素的offsetWidth/offsetHeight等布局属性来获取实际渲染尺寸。
完成以上练习后,你就完整走通了"选择元素 → 修改内容/属性/类名 → 创建与插入新节点 → 替换与删除 → 修改样式 → 读取计算样式"这条 DOM 动态化主链路。接下来的 第 16 章 将把这一切与事件监听结合,让页面真正响应用户交互,进入"事件驱动"的交互式 Web 开发阶段。
- 教程
- 文档
【免费下载链接】thejsway
The JavaScript Way book
相关推荐
The JavaScript Way 实战项目:用 DOM 与事件构建 PubLink 社交新闻网页
The JavaScript Way 实战项目:用 DOM 与事件构建 PubLink 社交新闻网页 本篇文章是《The JavaScript Way》开源书中
教程文档网页开发入门:The JavaScript Way 中的 HTML、CSS 与 JavaScript 三大语言指南
网页开发入门:The JavaScript Way 中的 HTML、CSS 与 JavaScript 三大语言指南 本篇文章以开源书籍 The JavaScri
教程文档The JavaScript Way 第八章精讲:JavaScript 字符串处理的完整实战指南
The JavaScript Way 第八章精讲:JavaScript 字符串处理的完整实战指南 本文基于开源书籍《The JavaScript Way》的 m
教程文档
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考