JavaScript进阶(二十七):DOM 的编译(二)

这篇博客,我们就来干点 render 该干的事。

首先,我们现在写的这个 render,我个人更喜欢把它叫做 update:

为什么呢?

因为我喜欢把 render 单独的提出来。

render 就是 render,render 不做判断,它就直接是搞事情的。

我这么做其实有一个巨大的好处,是什么呢?

我们现在写的这个类 Damu,将来肯定是要当做父类、基类来用的。

那么我在子类里面,是不是只要单纯的去 render 东西就好了呀?只要单纯的去覆盖 render,我就可以做事了。

以及,我这里的 update,是不是可以作为一个,手动去更新的方法啊?更新就调用 update 就可以了。

 

所以,我们在 set 里面调用的就是 update:

当然,这个对我们的功能没有任何的影响:

可以看到,它也能触发 render,我们只是为了让它更干净。

 

那么到现在为止呢,我们还有一个小问题,请问我们的这个渲染,进行多少次?

只进行初始的这一次?绝对不是。

那我们要什么时候做?

是不是每当数据变化的时候,我们都要做一次渲染。

所以这个时候,其实我们每一次渲染,都需要这个原始的模板。

什么意思呢?

就是说,div#root 里的这个原始模版,如果一旦破坏掉了,比方说我把它编译成了这个:

那以后我们还找的着 {{name}} 吗?就找不着了。

所以,这个原始的模版我还得保留,不能破坏它。

我真正去渲染,得是把这个模板复制一份,然后去改那个副本。

这样,我手里永远都有原版。

 

所以,我们在 this 身上加一个属性,叫做 _template,当然,叫什么名字无所谓。

这个 _template,就直接是一个文档碎片 DocumentFragment:

那这个 _template 怎么来呢?

我们直接做一个 appendChild,我要的就是 this._root:

那么接下来,在 render 里面我要干什么呢?

注意,我不是直接就去改模板 this._template。

我会把 this._template 直接做一个 cloneNode,这样的话,它里面的东西,我也能拿来。

这个,就是我们现在的 root。

 

然后,我们肯定是要对这个 root 做各种各样一大堆的操作,具体先不去写。

等到最后的时候,我要做什么呢?

我要做的很简单,就是把我现在这个的 _root,也就是页面当中的 div#root,给它换下去:

那么 replaceChild 的参数怎么写,谁换谁?

我们是把这个新编译好的 root 给仍进去,替换掉原来的那个 div#root。

这样的话,我们是不是就实现了更新啊?

当然,我 this 身上的_root,它也就变成了新的这个。

这就跟你家里换灯泡一样,旧的拧下来扔了,新的再拧上去就行了。

 

当然,到目前为止,我们在 render 里面只是做了一个 cloneNode。

所以页面上是没什么效果的:

但是,尽管现在没做事,但我应该有东西啊?好歹你给我个没编译过的东西吧,为什么页面上啥都没有呢?原因很简单。

因为我们原来的那个根元素 this._root,已经从 HTML 里面摘掉了。(如果这里没看懂,可以看下 js 进阶二十二)

所以它既然已经摘掉了,那么它现在还有 parentNode 吗?

那么现在这个时候,你可以明确的发现,它有,是文档碎片 DocumentFragment。

完全都在 DocumentFragment 里面来搞,这肯定不行的。

 

那我们怎么办?很简单,存一个不就完了嘛。

每次我们都要去重新找一遍 parentNode,本身就没这个必要。

我就给 this 身上加个 _parent,它里面存的,就是 this._root 的 parentNode:

然后 _parent 有了之后,我们就可以直接这么去用了,它也方便很多:

但是,你可以发现这时候又不行了:

注意,这时候,我们的 _root 还在不在 _parent 里面?其实早就不在了。

为什么早就不在了?因为它被文档碎片拿走了,它现在在 this._template 身上。

那怎么办?很简单,现在我们先不摘。

如果先不摘的话,又有一个问题,那就是 _template 没了。

所以在 render 里面,我们需要先用原来的那个_root 代替:

现在我们来看看效果:

那么现在你可以看到,就能出来了。

 

但是有一个小问题,如果我再渲染一次,那么我们的 _root 就没办法用了,为什么?

因为我那个原始的 _root 其实是没保存的。

为什么呢?因为在 render 里面它已经被替换掉了,然后它就没了,最后再赋值,它就彻底被覆盖掉了。

所以说,DocumentFragment 我们可以先不用,但是,我这必然要有一个东西:

我这个 _template,等于最开始的 this._root,也就是 div#root,来给它克隆一份。

而且这个 _template,它永远都不动,我每次需要新东西,都从它身上再克隆出一份:

也就是说,this._template 里面放的,永远都是 div#root 这个东西:

我每次复制出来,都是全新的一份。

 

那么接下来的问题,就是我怎么样识别出 {{name}} 这些东西,或者说,我怎么样得到这些东西的内容?

所以,我们的第一件事,就是找到所有的模板字符串,也就是双花括号 {{ }}:

那么这个怎么做呢?

首先我先问大家一个问题,这个 {{ }},我放元素里面肯定是可以的。

但是,如果我们放在标签上面,比如说 id = {{ }},这个我们认吗?

这个我们不认。

所以,我们肯定是在文本里面去找 {{ }}。

那么,我们就把这个 root,它所有的 childNodes,给它循环一下:

注意,为什么我们现在不用 children?原因很简单,因为我找的是文本。

当然在这可能有人说,你这就一个文本节点啊,为什么要循环呢?

比如说,div#root 里面有没有可能还有一些其他的元素,把它们给切开了?

完全有可能,现在不就是 3 个节点了嘛。

所以我们考虑到这个问题,还是要做循环的。

 

那么通过循环,我们会得到一个又一个的 child,然后就做一个判断。

我看看这个 child,它的 nodeType 是不是等于 document.TEXT_NODE。

如果是的话,别的我不管,我就想先把它里面的内容,拿出来看看:

你可以明确的看到,一个姓名,一个 a+b。

 

然后注意,这玩意是个字符串吗?

其实不是的,它是一个 text,文本节点。

这时候有人可能会说,我们直接用个 innerHTML 不就拿出来了吗?

innerHTML 是给元素用的,文本节点没有。

那 innerText 呢?

也没有。

实际上来说,如果你想拿一个文本节点的内容,需要用 data:

现在你可以看到,它就是一个普通的字符串了。

 

然后我现在要做的,就是从字符串里面,找到所有的 {{ 开头,}} 结尾,以及它中间的东西。

怎么做到这一点,相信大家已经想到了,正则:

但是别忘了,这个 { 和 } 在正则里面本身就有含义,它代表的是量词。

所以我们需要对它做一个转义:

{{ 开头,}} 结尾我们搞定了,那么中间放什么?

放 [a-z] 字符串?如果我里面还有数字呢?再加上各种各样的符号?所以我们这么判断不行,因为一个个加是加不完的。

但是你别忘了,{{ }} 这里面无论出现啥,有一个东西是出现不了的,就是 }}。

如果出现 }},是不是这个表达式就提前结束了呀?所以就它不能出来。

那么在中间,我们就可以用一个特别简单的写法,排除。

除了右花括号 }},什么我都能认,就这个不行:

然后别的我们不管,现在就想看看,它给我匹配出来的东西对不对:

那么你可以看到,我们要的就是它。

然后为了方便演示,我们现在先不管加法运算,当然,后面也会做。

然后现在这时候,我们已经有它了。

那么接下来,是不是要把左右的花括号 {{ }} 给去掉,我只要中间的内容,那怎么办?

其实很简单,直接用 substring 去掉前面 2 个和末尾 2 个就行了:

那么这时候你可以看到,一个 name,一个 a。

 

事情还没完,现在这个 str 有了,我们要做什么?

很简单,我要把 {{name}} 这个东西,替换成我 data 里面的数据,所以我们要做的就是 replace。

首先肯定一点,我在这里 return 什么,它就是什么,比如我这里就 return 'aaa':

这个时候,你可以明确的发现,就都变成 aaa 了。

那么我要的是什么,我要的是,真正变成这个 str 所对应的东西。

比方说,现在这个 str 是 name,那么我要返回的,就是 data 当中的 name。

如果我们直接写 data[str]:

可以看到,报错了,因为你还没告诉我 data 是谁。

实际上我们要找的,其实是 proxy,因为数据都在它身上。

可能有人说,我知道了,可以 this[str]:

可以看到,是 undefined,为什么呀?

注意,我们上面也说过,这个 proxy,并不是 this。

this,是我当前的那个实例。

而 proxy,只是帮我这个 this 出去顶雷的一个哥们,它是对外的。

所以怎么办呢?我得把它稍微的存一下:

顺带一说,vue 里面的那个 data,就是这么来的。

 

那么,我们是不是就可以写成 this._data[str]?

出来以后的效果,大概就是这个样子:

然后,我想给 a 重新赋个值:

以及我又想给 name 换个名字:

你可以看到,就这么简单。

vue 和 react 里面,也都是这样。

 

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