这篇博客,我们就来干点 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 里面,也都是这样。