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

700 阅读5分钟

了解Web API的DOM和BOM

这是我参与更文挑战的第9天,活动详情查看:更文挑战

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

本文将讲解 JSWeb APIDOMBOM 操作。

一、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 标签等等。引用百度来做一个解释:

DOM的本质

大家可以看到,上面一层一层递进的关系,把整个 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

控制台打印结果如下:

property

从上面的结果中可以看到,通过修改 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'));

控制台打印结果如下:

attribute

从上面的结果中可以看到,通过修改 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>

此时执行状态如下。

新增子节点前

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

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

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

新增子节点

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

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

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

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

移动节点

(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);

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

获取子元素和父元素

(3)删除子元素

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

// 移除元素
div1.removeChild(div1ChildNodesP[0]);

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

移除节点

以上就是对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);
}

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

DOM性能优化1

按照预想的,呈现出了我们想要的结果,且看着好像也没什么问题。但是呢,这就违反了我们所说的一次性操作 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);

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

DOM性能优化2

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

5、回顾

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

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

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

(2)DOM操作的常用API

DOM操作的常用API

(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);

此时浏览器打印如下。

浏览器内核信息

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

2、screen

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

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

此时浏览器打印如下。

screen

从上图中可以看到,通过 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); //获取哈希值,即#号后面的值

此时浏览器打印如下。

location

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

4、history

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

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

此时浏览器打印如下。

history

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

三、结束语

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

关于 DOMBOM 的操作就讲到这里啦!如有疑问欢迎评论区评论或私信我交流~

  • 关注公众号 星期一研究室 ,不定期分享学习干货,学习路上不迷路~
  • 如果这篇文章对你有帮助,记得点个赞加个关注再走哦~