Web 基础知识:使用 JavaScript 操作 DOM

本文是系列文章的一部分:Web 基础知识

DOM 代表网页的结构,使开发人员能够实时修改内容、样式和元素。在本文中,我们将探讨 DOM 操作的基础知识,并为您提供增强 Web 开发项目所需的基本技术和示例。

我们将在本文中学习什么

  • 操作 DOM 元素

    • 创建元素
    • 选择元素
    • 添加和删除stylesesclass和内容
    • 监听事件
  • 数据加载


document

document是你与 DOM 及其中加载的所有内容进行交互的方式。我们使用document.前缀来操作构成我们正在查看的网页的所有元素。

理解 DOM

如果你还没读过上一章,现在是时候读一读了。我们将直接深入探讨如何操作 DOM,但理解浏览器显示内容背后的背景也很重要。

📝点击此处阅读文章

创建元素

我们可以使用该方法创建 HTML 元素createElement('tag')。让我们创建一个由<p>标签指定的新段落。

js 复制代码
const newParagraph = document.createElement('p');
newParagraph.textContent = "This is a new paragraph.";

但这只是在const我们创建的文档中存储了一个新元素。为了显示内容,我们必须将一个元素附加到文档中。

我们将新段落附加到<body>标签中。

js 复制代码
const newParagraph = document.createElement('p');
newParagraph.textContent = "This is a new paragraph.";
document.body.appendChild(newParagraph);

如果我们查看我们的文档,我们现在会看到我们的段落是正文的一部分。

我们可以使用createElement('tag')HTML 来创建任何支持的标签。在第一章 HTML 中,我们讲解了语义元素。记得回顾一下!


选择元素

在上面的演示中,我们已经利用了 JavaScript 的选择方法来使我们的演示具有交互性。但这里列出了我们可以从 DOM 获取元素的方法。

方法 JavaScript 返回
选择依据id document.getElementById("myElement") 唯一的 HTMLElement
选择依据class document.getElementsByClassName("myClass") HTMLCollection(实时)
选择依据<tag> document.querySelectAll("p") 节点列表(静态)

操作元素

现在我们了解了如何选择元素并将它们绑定到变量,我们可以开始以任何我们想要的方式操作它们。

我们现在将使用在 Web 基础知识中学到的所有内容 --- --- 变量、样式、类、继承,并通过 JavaScript 应用它们!

添加和删除样式

CSS 属性被称为styles,我们可以通过 JavaScript 访问它们。

在 CSS 中,属性显示如下:

css 复制代码
body {  background-color: lightblue;}

在 JavaScript 中访问属性时,我们使用不同的语法,即驼峰式命名法。

使用document,我们可以访问和应用我们想要的任何样式。

ini 复制代码
document.body.style.backgroundColor = "lightblue";

然而,也有一些限制。JavaScript 与 CSS 不同,没有简写。我们以padding为例。

css 复制代码
body {  padding: 24px 48px;  }

然而,在 JavaScript 中,我们不能同时应用这些属性。在 JavaScript 中,我们需要分别应用每个属性。

js 复制代码
document.body.style.paddingTop = "24px";
document.body.style.paddingBottom = "24px";
document.body.style.paddingLeft = "48px";
document.body.style.paddingRight = "48px";

添加和删除类

在 HTML 中,我们可以根据需要将任意数量的类应用于元素。

html 复制代码
<button class="btn-emphasized btn-large btn-icon">Button label</button>

在 CSS 中,这些被声明为单独的类。

css 复制代码
.btn-emphasized { };.btn-large { };.btn-icon { };

在 JavaScript 中,我们可以使用后缀访问元素的类.classList

我们可以使用以下命令添加一个类。

js 复制代码
const element = document.getElementById("#element");element.classList.add("myClass");

我们可以使用以下命令删除一个类:

js 复制代码
const element = document.getElementById("#element");
element.classList.remove("myClass");

我们还可以使用来切换类 .toggle

js 复制代码
const element = document.getElementById("#element");
element.classList.toggle("myClass");

在下面的演示中,我们在卡片上切换两个类:.small.no-image。当两者同时应用时,我们也会执行样式更改!

css 复制代码
.small {  width: 240px;}
.no-image > #illustration {  display: none;}
.pfp-card.small.no-image {  border-radius: 24px;}

添加和删除事件

在本系列的所有演示中,我们都将事件附加到元素上,使其具有交互性。现在我们将正式讨论它们。

要添加事件,我们必须使用 将其附加到元素addEventListener(type, listener)

js 复制代码
const toggleButton = document.getElementById("myButton");
toggleButton.addEventListener('click', myFunction);
function myFunction() = { ... };

在上面的例子中,我们addEventListener()正在检测的交互类型'click',并在触发时执行该myFunction功能。

事件类型

并非所有事件都生而平等。不仅特定元素可以触发特定事件,不同的交互源也可以触发特定事件。


获取数据

现在让我们将数据添加到布局中!到目前为止,我们一直在处理静态数据,即手动输入显示给用户的每条内容。但是,如果我们想从 API 获取数据呢?

为此,我们使用一个fetch请求,或者一个GET请求。假设我们的 API 提供了两个密钥对:一个标题和一个图片 URL。

为了获取这些数据,我们需要编写GET如下请求:

js 复制代码
fetch("https://example.com/api/random-image")
.then(response => response.json())   
.then(data => {const title = data.title; const imageUrl = data.url;  })  
.catch(error => console.error('Error:', error)); 

使用asyncawait

我们可以优化代码,使其更具可读性。使用async functionawait,我们可以从代码中删除.then()命令,使其更易于理解和解析。

js 复制代码
async function fetchRandomImage() {  
    try {    
        const response = await fetch("https://example.com/api/random-image");    
        const data = await response.json();  
        const title = data.title;        
        const imageUrl = data.url;     
    } catch (error) {    console.error('Error:', error);      }}

注意,我们已经完全替换了.then(data => {})块。有了async,那些就不再需要了。

了解有关 await 和 async 的更多信息

如果您想了解有关数据获取的更多信息,我们有一篇文章适合您!

📝解释 JavaScript 中的 Promises、Async 和 Await


填充布局

现在让我们修改代码,以便它填充整个项目网格!

在下面的演示中,我们将对函数进行多次迭代fetch,并创建一个包含所有项目的数组。

数组完成后,我们为每个项目构建一张新卡片appendChild,然后将该卡片添加到网格中!


处理错误

处理 API 调用时,可能会出现一些问题。目前为止,我们看到的所有示例中都存在一个catch()命令,但它实际上很少处理 API 调用中可能出现的错误。

js 复制代码
catch (error) {  console.error('Error:', error);  }

那么我们该如何解决这个问题呢?

处理网络错误

response.status命令专门用于检查 HTTP 请求是否成功。这意味着它不会验证响应本身的任何数据。它让我们具体了解在特定情况下会给出哪些错误代码,如下所示:

js 复制代码
if (response.status === 404) throw new Error('Not found');
if (response.status === 500) throw new Error('Internal server error');

这使得开发人员可以根据错误情况向用户显示不同的消息和操作。例如,当出现Not found错误时,可以为用户提供返回上一页的快捷方式;同样,当服务器发生内部错误(这种情况可能不会再次发生)时,也可以提供刷新按钮。

这是可以返回的 HTTP 代码范围。

代码 类别
100-199 信息响应
200-299 成功的响应
300-399 重定向消息
400-499 客户端错误响应
500-599 服务器错误响应
查看所有回复 查看 MDN 文档以了解完整详情。

处理数据错误

现在假设,尽管期待某种类型的数据,但却没有返回。

我们简要查看的代码并未涵盖这些情况,为了捕获这些错误,我们必须自己验证它们。

让我们以前面的例子为基础来验证我们的数据。

js 复制代码
async function fetchRandomImage() {  
    try {    
        const response = await fetch("https://example.com/api/random-image");
        if (!response.ok) {      throw new Error(`HTTP error! Status: ${response.status}`);    }    
        const data = await response.json();  
        const title = data.title;             
        const imageUrl = data.url;          
    } catch (error) {    console.error('Error:', error); }}

默认情况下,某些数据错误会被捕获catch()。例如,如果我们data返回一个格式错误的 JSON,则在尝试使用 解析它时会自动捕获该错误response.json()并抛出错误。

然而,在上面的例子中,我们无法确保返回的数据是有效的。我们也需要能够处理这些数据错误!

自定义错误

我们的代码需要一个title和一个URL,但我们无法确保它们存在,也无法确保响应是否有效!如果其中任何一个失败,catch()它本身不会执行任何操作,这就是为什么我们必须自己验证它们。

让我们来看看之前的代码,完全没有改变:

js 复制代码
async function fetchRandomImage() { 
    try {    
    const response = await fetch("https://example.com/api/random-image");    
    const data = await response.json();  // Parse the response as JSON    
    if (!response.ok) {      throw new Error(`HTTP error! Status: ${response.status}`);    }    
    switch (true) {      
        case !data.title && !data.url:        
            throw new Error('Both title and URL are missing.');            
        case !data.title:        
            throw new Error('Title is missing.');            
        case !data.url:        
            throw new Error('URL is missing.');                 
        default:            
            const title = data.title;                
            const imageUrl = data.url;               
    }      
    } catch (error) {    console.error('Error:', error);  }}

在上面的例子中,我们处理了请求中提供给我们的数据可能存在问题的所有可能情况fetch()

但是,您可能会注意到所有这些都会抛出一个新的Error。如果您想在这些失败时提供默认响应,以免破坏应用程序,该怎么办?我们可以简单地修改 switch 语句,将这些响应作为值提供。

js 复制代码
const title = '';const imageUrl = '';switch (true) {  case !data.title && !data.url:    title = 'Title is missing.';    url = "URL is missing.";  case !data.title && data.url:    title = 'Title is missing.';    url = data.url;    case data.title && !data.url:    title = data.title;    url = "URL is missing.";   }

但对于这个用例来说,这太冗长了。我们可以为 和 的值定义默认值titleimageUrl以进一步优化代码。

js 复制代码
const title = data.title || 'Title is missing.';const url = data.url || 'URL is missing.';

如果您需要处理默认值并且不想Error在任何数据丢失或无效时抛出异常,这是迄今为止最优化的选项!


处理装载

处理大型数据集或图形时,可能需要一些时间才能将内容显示给用户。我们在网格示例中就遇到过这种情况;我们的组件需要一段时间才能加载,但我们不会向用户显示任何正在获取数据的警告。

让我们改变它!

我们将重构代码,使其不再逐个填充网格。相反,我们将:

  1. 等待所有图像加载完毕。
  2. 将它们添加到数组中。
  3. 使用数组的内容填充列表

这样,它们就会同时更新。

见面onload

我们可以通过监听onload事件来检测某些内容是否已加载。根据**MDN 文档**,我们甚至可以将其设置为属性来选择 HTML 元素。

html 复制代码
<body onload="functionName()"></body><img src="image.png" onload="functionName()" /><iframe src="index.html" onload="functionName()"></iframe><link rel="stylesheet" href="stylesheet.css" onload="functionName()" /><script src="main.js" onload="functionName()"></script>

onload="functionName()"属性调用匹配的 JavaScript 函数并在对象完全加载后自动运行它。

了解了这一点,我们可以使用以下代码片段重构我们的代码:

js 复制代码
function fetchDogs() {  // Declare the array for all cards  const dogCardArray = [];  const requests = [];  for (let i = 0; i < maxItems; i++) {    const request = fetch("https://dog.ceo/api/breeds/image/random")    .then(response => response.json());    requests.push(request);  }  Promise.all(requests)    .then(results => {    results.forEach(data => {      /*       Unchanged      */      // Check when the dogImage for each card is loaded      dogImage.onload = () => {        // Build the dogCard components        dogCard.appendChild(dogImage);        dogCard.appendChild(dogName);        dogCardArray.push(dogCard);        /* Check if the dogArray has reached the maximum        amount of items prior to displaying them */        if (dogCardArray.length === maxItems) {          /* Clear the grid as the items are ready to be rerendered. */          dogGrid.innerHTML = '';          /* Populate the grid */          dogCardArray.forEach(card => {            dogGrid.appendChild(card);          })        }      }    });  })    .catch(error => console.error('Error:', error));}

在这个演示中,有一些添加的调整。

现在,加载时,所有卡片都略微透明,以显示其内容正在被替换并且处于非活动状态。

然而,这还不够好。 我们希望为用户提供更直观、更易用的指标。

为此,让我们实际显示一个加载微调器,并更改标签以指示正在发生的事情!首先,我们必须稍微重构一下代码。

  1. 将加载微调器添加到按钮。
  2. 将按钮标签包裹在<p>标签中,以便我们的innerText更改不会在此过程中删除加载指示器。
  3. 根据我们的需要切换加载指示器的位置和可见性。
js 复制代码
const refreshLabel = document.getElementById("refresh-btn-label");const loadingSpinner = document.getElementById("loading-spinner");/* Initial state */loadingSpinner.style.visibility = 'hidden';loadingSpinner.style.position = 'absolute';
js 复制代码
function fetchDogs() {   loadingSpinner.style.visibility = 'visible';  loadingSpinner.style.position = 'unset';  refreshLabel.innerText = "Fetching new dogs..."  dogGrid.style.opacity = 0.5; // Fade the cards  ...
js 复制代码
if (dogCardArray.length === maxItems) {  dogGrid.innerHTML = '';  dogCardArray.forEach(card => {    refreshLabel.innerText = "Fetch new dogs";    dogGrid.appendChild(card);  })  /* Hide spinner once everything is populated */  loadingSpinner.style.visibility = 'hidden';  loadingSpinner.style.position = 'absolute';  dogGrid.style.opacity = 1;}

结论

至此,我们已经了解了 Web 基础知识。

我要感谢每一位阅读过本系列文章、提供过反馈以及认为它有帮助的人!

如果您想查看本系列的更多文章,探讨我们遗漏的主题,请在 Discord 中告诉我们! 我们一直在关注博客文章的请求,并希望帮助越来越多的人开启他们的 Web 开发之旅。

下次再见。保重,祝开发顺利!

相关推荐
WeiXiao_Hyy22 分钟前
成为 Top 1% 的工程师
java·开发语言·javascript·经验分享·后端
吃杠碰小鸡39 分钟前
高中数学-数列-导数证明
前端·数学·算法
kingwebo'sZone1 小时前
C#使用Aspose.Words把 word转成图片
前端·c#·word
xjt_09011 小时前
基于 Vue 3 构建企业级 Web Components 组件库
前端·javascript·vue.js
我是伪码农1 小时前
Vue 2.3
前端·javascript·vue.js
夜郎king2 小时前
HTML5 SVG 实现日出日落动画与实时天气可视化
前端·html5·svg 日出日落
辰风沐阳2 小时前
JavaScript 的宏任务和微任务
javascript
夏幻灵3 小时前
HTML5里最常用的十大标签
前端·html·html5
冰暮流星3 小时前
javascript之二重循环练习
开发语言·javascript·数据库
Mr Xu_3 小时前
Vue 3 中 watch 的使用详解:监听响应式数据变化的利器
前端·javascript·vue.js