上篇博客,我们做了一些准备工作,接下来,我现在就想要一个东西:
我希望这个 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 要快,虽然它肯定是吃内存的,但是它吃的不多。