奶一波浏览器DOM操作

一、简述

​ 关于浏览器DOM的操作可以说是一名合格的前端工程师的必备技能,在我个人看来,无论你是使用jquery到炉火纯青(本人有看过部分源码),还是使用各种css框架到6得飞起,或者正在使用主流的前端框架,我都觉得个人都要通过DOM这一关,这是十分重要的,万层高楼基层起,熟练掌握这种技能十分重要。 ​ 这是其一,另一方面就是,DOM这部分是浏览器模型的一部分,相信有听说过DOM树,就是关于浏览器如何解析HTML语言并且展示出来,(当然需要合成渲染树才能整体呈现给用户)。这也是作为一名合格的前端工程师应该去了解的关于浏览器模型的知识点,( 🗝是不是又点亮了技能树?)

二、DOM是什么?

​ DOM是什么?文档对象模型(Document Object Model),个人觉得是一种对于在浏览器运行的HTML文件的解析出来的一个API文档,(可以结合上次讲到js的运行机制)DOM树的构建则是浏览器在呈现一个页面中一个重要的步骤,这就意味着每一个浏览器中必然有着DOM,但是由于浏览器种类很多,不同浏览器对DOM的执行是不一样的,尤其像IE浏览器这种非主流的存在(有一段时间不遵循W3c,可以了解一下IE浏览器的历史)。

DOM重要的功能?

​ 其实就是构建DOM树,就是构建本节点、父节点和兄弟节点以及它们之间的关系,从而形成DOM树,它其实本身是一个API文档,方便调用其来达到修改文档内容结构和样式的作用;

如何构建DOM树?

​ 看一下图就会明白了(图来自网络)

这是浏览器里内部的一个处理机制。
主要步骤有: ​

  • 1、Bytes to Characters :把二进制转为html字符串,即是传统说的从汇编语言到高级语言(虽然html并不算是高级语言,只是一个标记语言【SGML】) ​
  • 2、 Characters to Tokens :把html字符串按照标记算法转化成 Tokens(标记),存有自己闭合标签里的文本信息; ​
  • 3、Tokens to Nodes :把标记转为一个个节点(node),然后文本信息添加完了,接着肯定是要添加节点的属性以及属性访问器; ​
  • 4、Nodes to DOM :最后就是构建DOM树了,把兄弟节点父节点连起来,构建它们之间的关系网络,形成DOM树。 ​

Parse HTML

​ 其中在浏览器的开发者工具可以看到关于DOM树的构建,即Parse HTML,可以看到构建的时间和性能,这里我选取的Chrome的chrome dev。(旧版本是Timeline标签,新版本是Performance标签)。

这一部分可以跳过不看,没大的影响,只是可以普及一下知识,高手可以跳过不看,想快速上手操作DOM对象的也可以跳过这一部分。

三、JS操作DOM

​ 其实在这里可以从一道面试题衍生出去:

1、DOM结构 —— 两个节点之间可能存在哪些关系以及如何在节点之间任意移动。

2、DOM操作 —— 如何添加、移除、移动、复制、创建和查找节点等。

部分demo图

DOM的结构

​ DOM结构,我个人理解是分:

本节点、其兄弟节点、其父节点、其子节点。
​每个节点里内含属性以及文本信息。

DOM节点之间的任意移动

​ 其实就是访问相关的node节点,包括自身的节点、父子节点、兄弟节点。

访问自身的节点
1
2
3
4
5
6
7
8
//常规性操作;
let node = document.getElementById('main');
let node = document.getElementsByClassName('mainClass')\[0\]
let node = document.getElementsByTagName('div')\[0\]

//通过选择器访问,此外还有css选择器访问的,这里没有写,可以参考MDN的
let node = document.querySelector('#main');
let node = document.querySelectorAll('#main')\[0\]

访问其父子节点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//访问node的父节点
let node_parentNode = node.parentNode;
let node_parentElemnt = node.parentElement;

//访问node的子节点

//访问node所有子节点
let node_childNodes = node.childNodes;

//访问第一个子节点
let node_firstChild = node.firstChild;

//访问最后一个子节点
let node_lastChild = node.lastChild;

访问其兄弟节点
1
2
3
4
5
//访问当前节点的同属上一个节点
let node_previousSibling = node.previousSibling

//访问当前节点的同属下一个节点
let node_nextSibling = node.nextSibling

⚠️值得注意的是,如果想要访问上一个div节点,就得链式得调用 previousSibling 或者 nextSibling ,不然只会得到 text 类型的空节点;
如图:

拓展:

​ 刚才可以在我在vscode上编写的代码图片可以看到,em….就是下面这张;
​ ​
被我用红线框起来的这部分代码,可以说我做了一个对比,是关于node和element的。
这里其实可以参考MDN里的关于node的一个介绍,大概是说大部分的dom都是node这个为基类去拓展自身的属性,而且它也给出了关于互相的继承的图示(图1),以及可以通过node的 nodeType 属性得到不同继承node基类所形成的实类的类型的值(图2)。 ​
图1:这是在关于在讲述element页面摘下的图。


在图1可以看到, Element 继承了 node ,而 node 则继承了 EventTarget。 ​
图2:这是node的属性 nodeType 得出不同类型的值。 ​

简单来说在常见的 element text comment document 都是继承node的实例,分别对应着 ELEMENT_NODE ,值为1;TEXT_NODE 值为3; COMMENT_NODE 值为8;DOCUMENT_NODE 值为9。

如何证明真的存在这些实例呢?

​ 我编写了一个简单的text.js来证明这一理论:
相关代码图示:

运行在浏览器的结果:

可见,它们和NodeType的值是一一对应的; 然后,再来看一下它们的节点内涵的文本值:

中间有一些地方时是空的,说明节点之间是存有一个空的节点,也就是说如果这些空的节点填入实际的文本信息的话,它会显示在浏览器,你再一次log它的时候,是可以看到有值得,例如 “注释“ 会显示出来。
上个图说明一下:

这是可以说明理论是对的, element text comment 是特殊的 node 的实例,是继承于 Node ; 也可以通过在控制台可以看见其子节点,亦可以说明这一点。

DOM的操作

​关于DOM的操作,有创建、添加、移除、移动、复制和查找节点。 这里贴出代码图,方便参考一下:

DOM的查找

​ 上面其实已经说过了,就是如何访问相关的DOM节点。 ​

DOM的创建

​ 上文有说过,一个节点的结构,包含了当前节点的 Element 文本信息 属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 创建当前节点
let newNode = document.createElement('div');

// 添加当前节点的属性;
// 方法1:
let newAtrr = document.createAttribute('new');
newAtrr.value = 'opop';
newNode.setAttributeNode(newAtrr);

// 方法2:
newNode.setAttribute('new','opop');


// 添加当前节点的文本
let newText = document.createTextNode("这是新的文本信息");
newNode.appendChild(newText);

创建仅仅是这样,但是新创建的元素必须要基于现在已经渲染出来的节点去添加,才会先显示在文档流中,

⚠️这里需要注意的是:

如果你是使用js文件去创建新节点,你不能把js脚本放在head的里面,否则的话浏览器会渲染不出来,你会发现自己创建的新节点会消失掉。

里面的原理是,和浏览器的渲染过程有关系,如果把js脚本放在head里面,浏览器会先去渲染heade标签里面的js标签,再去渲染已有的dom文档,js先于dom节点执行,结果是无法找到你要添加的基点节点,因此想要创建的节点就无法显示,所以这样的做法是无法渲染出来的。这里面也有涉及到js脚本的阻塞问题。

解决方法:把js脚本放在body的底部,或者使用 <body onload='js函数'>,在dom完成之后,才开始去创建新的节点。

DOM的添加

这里是和DOM的创建是联系的,上文也已经说到了。

1
2
3
4
5
6
7
8
9
10
11
// 添加文本到当前节点上
newNode.appendChild(newText);

// 添加当前节点到另一个节点里面;很明显child嘛,就是添加子节点;
document.body.appendChild(newNode);

// body作为为父节点,把newNode添加到 id=main 节点的前面;
// 父节点.insertBefore(新节点,要被插入到前面的节点)
document.body.insertBefore(newNode,document.getElementById('main'));


DOM的替换节点

父节点.replaceChild(新节点,要被替换的节点)

document.body.replaceChild(newNode,document.getElementById('main'))

DOM的移除节点

父节点.removeChild(要被移除的子节点)

document.body.removeChild(document.getElementById('main'))

DOM的复制节点

要复制的节点.cloneNode(ture/false) true意味着要把自身和所有后代节点的信息都复制,包括事件等等;

false意味着只复制自身,当然也包括事件等等;

document.getElementById('main').cloneNode(false)

四、最后

这里附上相关的dom思维导图,来自网上,也想自己做一份,感觉网上已经比较齐全了;

其实也是自己对所学的DOM基本操作的一个总结,除了以上所说的,还有其他DOM API可以参考,同时最重要的需要考虑不同浏览器的不同DOM操作的兼容性。

好了,现到这里,觉得不错的可以点个赞哈~。