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

上篇博客中,我们的 VText 还没开始,这里我们继续。

那么实际上来说,对于一个 VText,你觉得我关心的是啥?我关心的是字。

所以在这个时候,我身上的属性就一个东西 _text,那么它的值就是对应文本节点的 data:

那么这时候,我们就来看一下:

你可以看到,虽然 _text 有了,但是有些奇怪,所以这时候,我们有几个问题。

首先,空文本节点,其实我不想留着,我想给它删了,因为没有意义,我们不需要去处理。

所以我们就直接来,加个 trim:

可以看到,去完空格之后,_text 的值就比较正常了,第一个和最后一个就是空的了,中间那个也只剩下字。

那么到这为止,我们已经算是有了一定的成效了。

 

但是有一个小问题,就是我不想让空的文本节点存在。

那么我们就可以在 VElement 里面做个判断:创建完文本之后,我并不会直接的就把它塞进去,而是做一个判断,如果这个 node,它里面的 _text,不是空的,换句话说,就是有值的,我才把它 push 进去:

当然这时候可能有人说,你前面的博客不是说过,尽量不要处理别人的私有成员吗,那你怎么还用 node._text?

其实是这样的,首先 VNode、VElement、VText 这几个玩意,其实是同一套库内部的东西,所以这时候动,其实没事的,因为本质上,我其实是把一段大代码,分成了好几份来写,作者都是我一个人,所以这个倒问题不大。

然后,反过来,你就说不行,不能操作私有成员。没问题,那么这时候,我们可以在 VText 里实现一个方法 isEmpty,在这来判断一下,它是不是空的:

然后我们这边是不是就可以用实例上的 isEmpty 来判断了呀:

可以看到,这样也是可以的。

但是说实话,这个库因为都是我们自己写的,所以就懒得去搞了,反正别人又不用,我们自己用问题不大。

 

那么这时候,我们再重新来看看现在的结构:

你可以看到,我只有 3 个子节点了,没问题,挺好的。

所以到目前为止,我们就算是完成了,让真实的 DOM,映射为一个虚拟 DOM,这样的一个工作。

不过接下来,我们还有很多很多事要做,这只是第一步。

 

我们接下来要做什么呢?

首先,就是我现在希望让这些属性有所区别:

比如,不论是 vue 还是 react、angular,它所带的那些自定义的属性,也就是指令,其实我是需要把这些指令给识别出来的。

也就是说,这种指令类的东西,我一定是要单独的提出来,因为我要处理它。

以及,事件类的东西我要不要提出来?要,我也要处理它。

所以在这种情况下,我们不能只有 _attrs,还得有一些特殊的属性,指令或事件。

 

所以,我们不光有 _attrs,我们还有指令 _directives:

那么这时候有一个问题了,同样是属性,你怎么知道它是正常属性,还是指令 _directives?我们都知道,有标志的。

比方说,但凡是以 v- 开头的,我就认为是指令 _directives。

而冒号 : 它其实是个简称,它全程叫做 v-bind:,它其实也是以 v- 开头的,这就是标志,比如:

所以这个指令 _directives,它就是所有以 v- 开头的。

当然,我们就不以别人的标准来做事,这里我用 z- 开头,然后 @ 我们就用 + 号来代替。

因为你想选什么作为标志,都是自己定的,无所谓,只要有个标志就行。

 

然后再接下来,我还需要一些东西 _listeners,就是它上面各种各样的事件:

这些事件标志非常的简单,就是以 + 号开头的,或者以 z-on 开头的。

其实就是一个简写和非简写的关系,比如:

 

那么接下来,首先,我们就想要来识别出所有的指令 _directives。

注意,这个 _directives,我们是不用从零开始识别的,是不是从 _attrs 来就行?

说白了,这个 _attrs,我希望它里面放的是大杂烩,也就是,所有的属性在它里面都有。

而 _directives 呢,它就比较的专用,是专门用来放指令的,而 _listeners,就是专门放事件的。

 

那么在这时候,有一个问题,如果我把所有的逻辑,都写到构造函数 constructor 里面,那几乎是一场灾难:

我们都知道一件事,就是,对于一个类来说,它的构造函数应该极度的精简,为什么?

这样做有一个巨大的好处,就是别人,包括你自己,在看这个类的时候,一目了然。

如果你这写了很大一堆,别人一眼看过去,根本就不知道是啥,你自己维护,也要找半天。

所以说,我们需要把它稍微的抽一下,精简一下。

 

那么我们就直接来,首先,我们来一个命名空间,也就是用匿名函数自执行来形成闭包:

那么这时候就有一个问题了,外面的 VElement 就用不了了。

所以,我们可以挂在全局 window 上:

那么我们为什么要这么做?

原因很简单,就比方说,创建子元素这个事,我希望用一个函数来搞定它,比如我就叫它 createChildren:

当然这时候可能也有人奇怪,你为什么非要把它放到外面,搞成个函数?

直接放到 class 里面当个方法不就完了吗?就像这样:

其实很简单。

首先,我这个方法 createChildren ,希望用我这个类的人来调用吗?绝对不希望。

所以我们一般情况下的做法,都是在名字前面加个下划线,来表示它是私有的,你别用这个方法。

但这个方法有几个问题。

首先,你把这些都放在类上,类的体积是不是会变大,那这时候性能就会有所下降。

因为一个东西越大,它就越慢,这是理所当然的道理,也是事实。

比如,有兴趣可以试试,一个 json,里面就一个东西,跟它里面有 100W 个东西,从它里面取一个元素出来,时间是不一样的。

所以,这时候我都放到类里面,首先就会降低性能。

而且说句实话,你这么做了,别人就一定调不了了吗?这就防君子不防小人。

所以,如果我想把它隐藏的及其彻底,那你作为我的一个局部函数存在,别人就无论如何也访问不了了,因为是局部的,所以这种更安全。

 

那么现在这个时候,我们就把这一块,全部的照搬过来:

当然,里面的细节也要修改一下。

我们需要把 dom 当参数传进来,并且返回处理好的 children:

所以我们这样做,整个构造函数 constructor 是不是就稍微干净了一些。

以及呢,我们这个 _attrs,其实也最好拿走,我们就叫它 createAttrs:

那么现在,我的这个构造函数 constructor 就及其的精简了:

这样的话,别人去看你的代码,可读性会非常的强,他可以一瞬间就明白你大概做了哪些事。

而他再关心某个具体问题的时候,比方说 _children 怎么来的,他就可以单独的去看你某一个东西。

所以这样做是非常好的,能够增强代码的可读性。

 

然后继续往下努力,我们还需要一个 createDirectives 和 createListeners:

然后这时候,我们的这个 constructor 其实已经做完了,剩下的只是填补这些东西而已。

其实你会发现,虚拟 DOM 本身并不难,我们就是在用一个对象,来表述一个真实的 DOM。

当然,我们要做到几件事:

首先,我们得精确,别人家有,我们没有,这不行。

然后,你要抽取你需要的那些信息,你不要的,你也不需要。

以及,我们现在所做的这些事,其实还有一个非常大的工作需要去做,就是我需要去更新它,比方说有一天,数据变了,那么我需要同步的去更新它。

这些工作怎么做,都是我们要考虑的,那么我们就一步一步的来。

 

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