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 开发之旅。

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

相关推荐
hqxstudying13 分钟前
J2EE模式---前端控制器模式
java·前端·设计模式·java-ee·状态模式·代码规范·前端控制器模式
开开心心就好1 小时前
Excel数据合并工具:零门槛快速整理
运维·服务器·前端·智能手机·pdf·bash·excel
im_AMBER2 小时前
Web开发 05
前端·javascript·react.js
Au_ust2 小时前
HTML整理
前端·javascript·html
安心不心安2 小时前
npm全局安装后,依然不是内部或外部命令,也不是可运行的程序或批处理文件
前端·npm·node.js
迷曳3 小时前
28、鸿蒙Harmony Next开发:不依赖UI组件的全局气泡提示 (openPopup)和不依赖UI组件的全局菜单 (openMenu)、Toast
前端·ui·harmonyos·鸿蒙
爱分享的程序员4 小时前
前端面试专栏-工程化:29.微前端架构设计与实践
前端·javascript·面试
上单带刀不带妹4 小时前
Vue3递归组件详解:构建动态树形结构的终极方案
前端·javascript·vue.js·前端框架
-半.4 小时前
Collection接口的详细介绍以及底层原理——包括数据结构红黑树、二叉树等,从0到彻底掌握Collection只需这篇文章
前端·html
90后的晨仔4 小时前
📦 Vue CLI 项目结构超详细注释版解析
前端·vue.js