提升对前端的认知,不得不了解Web API的DOM和BOM

简介: 在现代的开发中,vue和react都是很流行的开发框架,框架虽好用,但是框架的原理还是基于 DOM 操作去实现。如果一个前端工程师只会框架,不会 DOM ,那基本上是很容易被淘汰的。因为框架的存活时间我们谁也说不准,且技术更新迭代也特别快,说不定三五年就会被淘汰了都有可能。所以,扎实的学会 js 的基础原理,不要被框架和一些外部事件所迷惑,对自己会有一个更好的竞争力提升。本文将讲解 JS 中 Web API 的 DOM 和 BOM 操作。

一、DOM操作



DOM操作,即Document Object Model文档对象模型。下面通过几个知识点来分析DOM本质节点操作结构操作以及 DOM性能


1、DOM的本质


DOM的本质就是一棵 ,是树结构。

我们从早起xml说起,xml是一种可扩展性的标准语言,早期基本是用xml来对DOM进行编写。具体形式如下:

<?xml version = "1.0" encoding = "UTF-8"?>
<note>
    <to>Tony</to>
    <from>Abby</from>
    <heading>London</heading>
    <body>Have a nice day!</body>
    <other>
        <a></a>
        <b></b>
    </other>
</note>
复制代码

我们可以看到,一层一层的下来,层层递进,很像一棵树。


回到现在,我们基本上用的是 html 来进行编写。 html 也是一种特定的 xml ,只不过它是有自己的一套规范,比如说我们常见的 p 标签, span 标签, li 标签等等。引用百度来做一个解释:

1.png

大家可以看到,上面一层一层递进的关系,把整个 html  渲染出来形成我们看到的页面,这一层层递进的关系,其实就是 DOM 树,所以也可以说, DOM 是从 html 文件解析出来的一棵树。


2、DOM节点操作


DOM节点主要有两种操作,一种是property操作,另一种是attribute操作。下面让我们来看看这两种操作。


(1)property形式


html代码:

<div id="div1" class="container">
    <p>文段 1</p>
    <p>文段 2</p>
    <p>文段 3</p>
</div>
<div id="div2">
    <img src="https://dss1.baidu.com/6ONXsjip0QIZ8tyhnq/it/u=2144980717,2336175712&fm=58" alt="">
</div>
复制代码

JS代码:

/**
 * property形式
 */
 const pList = document.querySelectorAll('p');
 const p1 = pList[0];
 p1.style.width = '100px'; //修改样式
 console.log(p1.style.width); //获取样式
 p1.className = 'red'; //修改class名称
 console.log(p1.className); //获取class名称
//  获取nodeName和nodeType
 console.log(p1.nodeName);  //打印节点名称,p
 console.log(p1.nodeType); //打印节点类型,普通的DOM节点元素为1,文本类型是3
复制代码

控制台打印结果如下:

2.png

从上面的结果中可以看到,通过修改 DOMJS 变量,从而操作 DOM ,最终得到我们想要的结果。


(2)attribute形式


html代码:

<div id="div1" class="container">
    <p>文段 1</p>
    <p>文段 2</p>
    <p>文段 3</p>
</div>
<div id="div2">
    <img src="https://dss1.baidu.com/6ONXsjip0QIZ8tyhnq/it/u=2144980717,2336175712&fm=58" alt="">
</div>
复制代码

JS代码:

/**
 * attribute形式
 */
 const pList = document.querySelectorAll('p');
 const p1 = pList[0];
 p1.setAttribute('data-name', 'immoc');
 console.log(p1.getAttribute('data-name'));
 p1.setAttribute('style', 'font-size:50px;');
 console.log(p1.getAttribute('style'));
复制代码

控制台打印结果如下:

3.png

从上面的结果中可以看到,通过修改 DOM 结构的节点属性,最终得到我们想要的结果。

综上所述, porpertyattribute 这两种操作类型,主要有以下区别:

  • property:修改对象属性,不会体现到 html 结构中;
  • attribute:修改 html 属性,会改变 html 结构,即标签结构;
  • 两者都有可能引起DOM重新渲染,但 attribute 引起 DOM 重新渲染的可能性更大,因为它会改动 html 的结构。所以在实际开发中,可以选择的话尽量渲染 property 去操作 DOM


3、DOM结构操作


通过上面的了解,我们都明白了DOM是一种树结构。那既然是树结构,就应该可以对节点进行增删改的操作。

因此,DOM结构操作主要有以下三种类型:

  • 新增/插入节点;
  • 获取子元素列表,获取父元素;
  • 删除子元素。

接下来对这三种类型进行讲解。


(1)新增/插入节点


先附上html代码。

<!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="div1" class="container">
        <p id = "p1">文段 1</p>
        <p>文段 2</p>
        <p>文段 3</p>
    </div>
    <div id="div2"> 
        <img src="https://dss1.baidu.com/6ONXsjip0QIZ8tyhnq/it/u=2144980717,2336175712&fm=58" alt="">
    </div>
    <script src="./你的文件路径.js"></script>
</body>
</html>
复制代码

此时执行状态如下。

4.png

如果此时要给 div1 新增一个子节点 p ,代码如下。

// 新建节点
const newP = document.createElement('p');
newP.innerHTML = 'this is newP';
// 插入节点
div1.appendChild(newP);
复制代码

此时浏览器执行状态如下。

5.png

大家可以看到, div1 成功新增了一个节点,这就是关于新增节点操作的具体流程。

那如果在此基础上,要移动一个节点呢?具体操作如下。

// 移动节点 -> 把p1移动到div2中来
const p1 = document.getElementById('p1');
div2.appendChild(p1);
复制代码

此时浏览器执行状态如下。

6.png


(2)获取子元素列表,获取父元素


在上面的基础上,我们来看看获取父元素和子元素列表的流程。

// 获取父元素
console.log(p1.parentNode);
// 获取子元素列表
const div1ChildNodes = div1.childNodes;
console.log(div1ChildNodes); //此处获取的包含p标签以及p标签下面的文本,因此需要一层过滤
const div1ChildNodesP = Array.prototype.slice.call(div1.childNodes).filter(child => {
    if(child.nodeType === 1){
        return true;
    }else{
        return false;
    }
});
console.log('div1ChildNodesP', div1ChildNodesP);
复制代码

此时浏览器执行状态如下。

7.png


(3)删除子元素


在上面操作的基础上,我们现在对 div1 中的第一个 p 节点进行移除。

// 移除元素
div1.removeChild(div1ChildNodesP[0]);
复制代码

此时浏览器执行状态如下。

8.png

以上就是对DOM结构的新增、移动、获取子父元素以及删除子元素的一个操作,相信大家对DOM结构的增删改有了一个新的了解。接下来我们讲解DOM性能。


4、DOM性能


为什么要对DOM做性能优化呢,原因在于 DOM 操作是非常“昂贵”的,每一次操作都很有可能引发浏览器的重排和重绘,因此要避免频繁的 DOM 操作。那如何做到避免频繁的 DOM 操作,给 DOM 进行性能优化呢?主要有以下两个方面给 DOM 操作进行性能优化:

  • DOM 查询做缓存
  • 将频繁操作改为一次性操作

接下来将对这两种方法进行讲解。


(1)对DOM查询做缓存


// 不缓存 DOM 查询结果
for(let i = 0; i < document.getElementsByTagName('p').length; i++){
    // 每次循环,都会计算length,频繁进行 DOM 查询
}
复制代码
// 缓存 DOM 查询结果
const pList = document.getElementsByTagName('p');
const length = pList.length;
for(let i = 0; i < length; i++){
    // 缓存length,只进行一次 DOM 查询
}
复制代码

分析以上两段代码,在第一段当中,每次循环的时候,都会计算 length ,频繁的对 DOM 进行查询,如此频繁的操作,可想对程序都不是不太好的。

因此,通过优化,我们写出第二段代码。在第二段代码中,把 length 放在外部进行缓存,等到每次循环的时候,只需要进行一次 DOM 查询,而不用像第一段一样频繁操作,极大提高了性能。


(2)将频繁操作改为一次性操作


我们来看一个例子。比如说,我们先在要通过操作 DOM 来一次性插入10个列表。

在正常情况下我们想象的操作如下:

html代码

<!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="div1" class="container">
        <p id = "p1">文段 1</p>
        <p>文段 2</p>
        <p>文段 3</p>
    </div>
    <div id="div2"> 
        <img src="https://dss1.baidu.com/6ONXsjip0QIZ8tyhnq/it/u=2144980717,2336175712&fm=58" alt="">
    </div>
    <ul id="list">
    </ul>
    <script src="./你的路径.js"></script>
</body>
</html>
复制代码

js代码

const list = document.getElementById('list');
for(let i = 0; i < 10; i++){
    const li = document.createElement('li');
    li.innerHTML = `List item ${i}`;
    list.appendChild(li);
}
复制代码

此时浏览器执行状态如下。

9.png

按照预想的,呈现出了我们想要的结果,且看着好像也没什么问题。但是呢,这就违反了我们所说的一次性操作 DOM 的原则,因为它在频繁的操作 DOM ,一直在频繁操作中修改。因此,我们可以通过以下方法来对性能做一个优化:

/**
 * 频繁操作改为一次性操作
 */
const list = document.getElementById('list');
// 创建一个文档片段,此时还没有插入到 DOM 结构中
const frag = document.createDocumentFragment();
for(let i = 0; i < 15; i++){
    const li = document.createElement('li');
    li.innerHTML = `List item ${i}`;
    //先插入文档片段
    frag.appendChild(li);
}
// 都完成之后,再统一插入到 DOM 结构中
list.appendChild(frag);
复制代码

执行后,浏览器也同样效果呈现出来了。

10.png

通过代码我们可以发现,通过创建一个文档片段,来对节点进行一个缓存,等到全部节点操作都完成以后,再统一插入到 DOM 结构中。这种方法,也极大提高了程序中的性能。


5、回顾


最后,我们用几个题目来回顾DOM的知识点。


(1)DOM是哪一种数据结构?

DOM是一种的数据结构,DOM也常被称为DOM树。


(2)DOM操作的常用API

11.png


(3)attribute和property的区别

两者主要有以下区别:

  • property:修改对象属性,不会体现到 html 结构中;
  • attribute:修改 html 属性,会改变 html 结构,即标签结构;
  • 两者都有可能引起DOM重新渲染,但 attribute 引起 DOM 重新渲染的可能性更大,因为它会改动 html 的结构。所以在实际开发中,可以选择的话尽量渲染 property 去操作 DOM


(4)一次性插入多个DOM节点,需考虑性能,怎么操作?


可以通过创建一个Fragment的文档片段,来对节点进行一个缓存,等到 全部节点操作 都完成以后,再统一插入到 DOM 结构中,从频繁执行改为一次执行。


二、BOM操作



BOM,即Brouse Object Model浏览器对象模型。下面通过几个知识点来了解浏览器的BOM操作。


1、navigator


navigator 主要用到 userAgent 属性, navigator.userAgent 表示获取浏览器的用户代理字符串如以下代码操作:

//navigator
const ua = navigator.userAgent;
console.log(ua);
const isChrome = ua.indexOf('Chrome');
console.log(isChrome);
复制代码

此时浏览器打印如下。

12.png

从上图中可以看到,通过 userAgent 可以获取到当前所使用浏览器的内核信息


2、screen


screen 主要用到width和height属性,screen.width表示获取当前屏幕的宽度,sceen.height表示获取当前屏幕的高度。如以下代码操作:

// screen
console.log(screen.width); //获取屏幕宽度
console.log(screen.height); //获取屏幕高度
复制代码

此时浏览器打印如下。

13.png

从上图中可以看到,通过 screen.widthscreen.height 可以获取到当前屏幕的宽度和高度。


3、location


location 主要用到 href/protocol/pathname/search/hash 属性,具体含义如下代码所示。

// location
console.log(location.href); //获取整个网址
console.log(location.protocol); //获取网址协议
console.log(location.pathname); //获取网址域名
console.log(location.search); //获取网址中的一些参数
console.log(location.hash); //获取哈希值,即#号后面的值
复制代码

此时浏览器打印如下。

14.png

相信从上图的演示之后,大家对 location 属性的应用有了有一定的了解。


4、history


history 主要用到 backforward 属性,当网页执行history.back()代码时,会将当前网页向后退一页;当网页执行history.forward()代码时,会将当前网页向前进一页。如以下代码操作:

// history
history.back(); //对网页进行后退
history.forward(); //对网页进行前进
复制代码

此时浏览器打印如下。

15.png

从上图中可以看到,通过 history.back()history.forward() 可以让当前浏览页面进行前进或者后退操作。


三、结束语



JS 的基础知识规定了 ECMA 的语法标准,而 Web API 则是网页操作的 API ,是 W3C 的标准。如果要说两者的关系,那自然是 ES 标准是 Web API 的基础。在实际应用开发中,只有两者结合才能真正做到实际应用。所以,不管是 ES 标准还是 Web API 中的 DOMBOM 操作,在实际开发中都是至关重要的内容。


相关文章
|
10天前
|
弹性计算 算法 安全
视觉智能开放平台产品使用合集之在Web应用和WinForm应用程序中调用API时,出现Web端能够成功调用而WinForm端调用失败,是什么原因
视觉智能开放平台是指提供一系列基于视觉识别技术的API和服务的平台,这些服务通常包括图像识别、人脸识别、物体检测、文字识别、场景理解等。企业或开发者可以通过调用这些API,快速将视觉智能功能集成到自己的应用或服务中,而无需从零开始研发相关算法和技术。以下是一些常见的视觉智能开放平台产品及其应用场景的概览。
|
5天前
|
JavaScript 前端开发
Web Components详解-Shadow DOM样式控制
Web Components详解-Shadow DOM样式控制
|
5天前
|
JavaScript API 开发者
Web Components详解-Shadow DOM插槽
Web Components详解-Shadow DOM插槽
|
5天前
|
JavaScript 前端开发 开发者
Web Components详解-Shadow DOM基础
Web Components详解-Shadow DOM基础
|
5天前
|
监控 JavaScript 前端开发
前端 JS 经典:Web 性能指标
前端 JS 经典:Web 性能指标
9 1
|
6天前
|
开发框架 安全 .NET
使用VB.NET构建Web服务和REST API的指南
【7月更文挑战第2天】使用VB.NET构建Web服务和REST API的指南:从Web服务基础到ASP.NET Core实践,涵盖控制器、路由、模型绑定、安全措施(如JWT、HTTPS)及测试、部署(Azure、Docker)与监控工具。了解如何利用VB.NET在现代云环境中创建高效、安全的API。开始你的VB.NET Web服务开发之旅!**
6 1
|
5天前
|
人工智能 前端开发 API
基于Web Speech API给AI语言模型加上语音功能,距离MOSS又近了一步
基于Web Speech API给AI语言模型加上语音功能,距离MOSS又近了一步
13 0
|
5天前
|
前端开发 JavaScript API
前端 JS 经典:数组新增 API
前端 JS 经典:数组新增 API
6 0
|
6天前
|
开发框架 JSON .NET
|
6天前
|
XML 前端开发 JavaScript
前端概论 web
前端概论 web
11 0