JavaScript进阶(三十一):如何把真实的 DOM 抽象成虚拟 DOM 树(一)

上篇博客,我们做了一些准备工作,接下来,我现在就想要一个东西:

我希望这个 VNode,可以用它来描述我现在的这个 div#root

那么在这时候,我们就要考虑一个事:我的 VNode,需不需要某种参数?需要。

因为所有的虚拟节点,你不论它虚了个什么,反正它只要是,就必然会有一个东西,它需要对应于一个真实节点,这是肯定的,不然它就废了。

 

那么这里问大家一个问题,一个元素需要对应一个真实节点吗?

当然需要,一个 div 对应一个虚拟 div。

然后反过来,一个文本需要对应一个真实节点吗?其实你想完了就发现,也需要。为什么呢?

比方说,我虚拟的 VText,它里面的字变了,那我这里面要不要跟着变?必须跟着变。

所以在这时候,你可以想到,我们的 VElement 和 VText,它们都需要一个真实的 DOM 作为参数,要不然,我哪知道对应谁啊。

 

那既然它们都需要,我们为什么不放在父级呢?

所以这时候,我们就决定了, 把这个真实的 DOM 元素,放到我们的父级 VNode 里面:

这个父级 VNode 其实它更适合的,是被抽象成为一个抽象类,这个其实是更合适的,当然我们的 js 并不支持这个功能。

我只是想告诉大家,它本身其实没什么功能可言,所以它最多就是可以帮我们把这个 DOM 给存起来。

并且,我们也不太希望它是一个谁都可以访问的状态,要不然万一,别人把它给改了,我们这就乱了。

所以,我们给它一个 _dom:

以及,我们前面的博客里面也说过,一个程序,想要让它保证健壮性的话,就一定要检查好你所有的参数。

所以说在这种情况下,我们还得有一个 common.js,现在我们就需要一个 assert,并且以后还会填充一些其他的东西:

那么现在有了 assert 之后,就需要来看一看,你这个 dom 到底给了没,你要没给,我就报错。

以及,我还需要来检验一下,你这个 dom,到底是不是一个真正的元素,如果不是,也不行:

当然这时候,我们稍微的来验证一下,写一点就验证一点,否则的话就完蛋了:

那么现在这时候,你可以看到,是一个 false,所以幸好验证了一下,不然就废了,所以千万别一次写太多在测。

那么在这时候,我们就明确的知道了一件事,就是 HTMLElement,它其实表述的是元素那种东西,但并不能表述文本 text。

其实在我们的 HTML 里面,它有一个东西叫做 Node:

你可以看到,这个时候就是 true 了。

所以,我们的代码验证也需要跟着改:

那么现在呢,我就想创建一个东西:

那么这时候你可以明确的看到,它是一个 VElement,并且它里面有个 _dom,指向的,就是那个最外层的 div#root。

 

那么我们接下来,就要仔细的来思考一下了。

一个抽象出来的元素,它应该具备什么?我们关心什么?

首先,我们所有的东西都关心吗?并不是的,我们只关心我们要的东西。

 

那么我们来考虑一下,一个元素它都有什么?首先,它有没有属性?

元素上有属性很奇怪吗?当然正常,然后你比方说,我这还可以有事件 onclick:

事件也是属性!

那么你仔细想想就会发现,其实一个标签,它什么都没有,就只是属性构成的,事实上也就如此。

当然,除了属性以外,还有另一个我们需要关注的东西,就是,它是什么标签,它是 div,还是 span?

尽管我们没有去抽象那个专用的类,但是如果我有一个标识,用来表明这是一个什么东西,其实还是有用的,这样的话,对处理起来是很方便的。

 

所以我们主要关心这几件事:

第一,它是什么东西,div 还是 span?

第二,我关注它上面都有哪些属性。

第三,我还要关注一下,它有哪些子节点,这个也是很需要的。

因为比方说,假设我现在要把这个 div#root 给移除掉,那么我是不是也要把这些子元素全都移除掉?所以说也是需要的。

 

那么我们在加这些东西之前,先考虑下,我是加在 VElement 这级合适,还是加在父级 VNode 合适?

首先,我们需要一个标签,比如我叫它 _type:

然后我们是不是还需要属性?我们就叫它 _attrs:

然后我还需要一个东西,就是我的子节点们,我们可以叫它 _children:

那么现在我们就来考虑下,这几个东西,需不需要往父级提?或者说的更明白一点,这几个东西,VText 需不需要有?

我个人觉得不用,比方说 _attrs,文本节点有属性吗?没有。

然后 _type 呢?不用,文本节点就一种类型,文本。

以及 _children,文本节点就没有孩子一说,它就是一堆字。

所以说这 3 个,就是 VElement 独有的。

 

那么接下来,这个 this._type = 'div',我们是写死吗?不可能。

我们是不是可以直接从 dom 上面直接去取它的 tagName 就行了?

说白了,标签是啥类型的,由标签名决定。

而且这时候还没完,注意,tagName 是全大写的,所以在这个时候,你就可以看到,它的 _type 有了,但是是大写的:

就我个人来说,大写看着挺难受的,所以这里就用 toLowerCase 来变成小写:

以及接下来,这个 _attrs,我们写出来一定是空的吗?不一定,是不是得根据真正的 DOM 属性来走。

那这时候我们怎么样能够把它上面的哪些东西,给拿过来呢?

其实非常的简单,我们可以用 attributes 来循环,把每个属性的 name 和 value 给 _attrs 加上:

那么,我们先看看 _attrs 有没有正确的加上:

目前看起来都是对的,并且你会发现,它有个 :value,并且把冒号也给带上了。

因为 attributes,它本身就能够去帮你把那些自定义的属性给加进去,而且你甭管多怪都可以,包括有加号的 +click。

所以现在这时候,我们这个 _attrs 工作的非常好,什么属性都能进来。

 

那么在接下来,我们还有一些 children,这时候有个问题来了。

请问一个虚拟节点 VElement,它的子节点是什么?

是真实 DOM?不可能,它的子节点,是不是也是虚拟 DOM 啊。

 

所以在这种情况下,我们的这个 _children,就需要稍微的做点小事了。

我需要的,是把我这边真实的 DOM,它的 childNodes,给循环一遍,那么我们就得到了一个又一个的 child:

然后我们把它 push 进去,push 一个 new VElement:

这样是不是就说白了,我的子级也依然还是一个虚拟节点,这就对了。

 

这时候有人肯定不乐意了,我所有的子级一定都是 VElement 吗?不一定。

比如,这个就不是,它是文本:

所以说,我们在这里就要做个判断:

以及,我们考虑到万一有什么节点被我漏掉了,我们可以 console 一下, 看看是什么节点:

那么现在这时候,有了这个之后,我们再来看看:

然后你会发现,一棵树就已经构建出来了。

所以这个东西到现在为止,先不管活干没干好,我们已经能够把真实的 DOM 树,抽象成为一颗虚拟 DOM 树了。

 

当然,现在有很多问题,比方说我们的 VText 还没开始干活,这个肯定不行。

然后以我个人的习惯,一般空文本节点,我是会忽略掉它的,为什么呢?

因为空文本节点,你处理它干啥?东西越少,你肯定跑的越快,就这么简单,既然我知道它没用,那我就把它删掉。

所以下篇博客,我们就给它做点事。

 

当然在最后,可能有人说,你上面的递归创建有问题,要是页面大的话,不就炸了吗?

其实很简单的一件事,如果我们不创建虚拟 DOM,直接操作真实 DOM,会怎么样?

我们都知道,性能会很低,虚拟 DOM 无论如何,它的性能,一定是优于真实的 DOM。

 

所以说句很实在的话,首先在我们创建的过程当中,页面如果大的话,比方说有几十万个元素,那这时候,我们的虚拟 DOM 树也会很大,没错,这个一点不假。

但是,我们操作这个虚拟 DOM 树的性能,速度一定是比不干这套事,要快得多。

 

这个虚拟 DOM 树说白了,最多就是占点内存,它在性能上有极大的优势,你页面越大,反而我这边的工作才越有价值,反而这边的性能才越高。

所以你不用担心,越大的页面,不光不会影响性能,反而会提高性能,这是第一

 

然后第二,就是我们在创建虚拟 DOM 的时候,会占掉一定的内存,没办法,任何东西都要占内存,那么到底有多大?

其实这个很好估计,你就估算一下,它有什么属性之类的,就按一个虚拟节点 500 个字节算,这其实算比较大的了。

那么假设你有一万个节点,那就要占 10000 * 500个字节,满打满算都不到 5 M,对现在的电脑来说根本不算什么。

 

所以你会发现,就算你真的有一万个节点,也就只占 5 M不到的内存空间,其实并不算大。

而且说句特别直白的话,如果你这页面真有一万个节点,先不管虚不虚拟,首先你这页面估计都跑不起来,那页面都没了,其他的你还有啥好担心的。

所以,真实 DOM 没炸,虚拟 DOM 一般也不会,虚拟 DOM 它一定是比真实的 DOM 要快,虽然它肯定是吃内存的,但是它吃的不多。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章