JavaScript进阶(二十三):实现一个循环需要注意的问题

这篇博客,我们准备拿 DOM 的东西来做点事。

实际上来说,几乎在所有的框架当中,它们普遍都有一个操作,就是循环。在 vue 里面叫做 v-for,在 react 里面叫做 map。

那么接下来,我希望的就是,我要如何来实现一个这样的功能,根据数据来做循环。

 

比方说现在我们这有个数组,我希望的是,让这个数组里的东西,能够自动的被渲染到 UI 里面去:如果数组里有 4 个东西,那我页面上就有 4 个 div,如果数组里面删了一个,那页面上对应的,也就变成 3 个 div。

这不就是我们前几篇博客说的可响应吗?所以,如果我要完成一个这样的工作,首先我们要做的,就是如何让一个数组可响应。

 

我们先弄一个大概的样子:

然后,我们如果什么东西都像以前一样,都是 innerHTML 直接往里堆,那肯定不行。

所以我们先放一个模板的元素在这:

那么现在这时候,基本的样子就有了。

 

首先,我想让一个数组,变成可响应的状态,那我们应该怎么做?

当然是用我们前面说到的 proxy:

然后对这个东西来说,我们主要监听的就是两个东西,一个 get,一个 set。

当然在这,我们主要关注的是 set,因为当它对我进行了一些操作的时候,我需要能够响应它的操作,比方说,去做一个渲染。

那么有人 set 了,我们就先 console 一下,以及对这个 arr 进行一下相应的操作:

然后我们来试一试:

你可以看到这个 set 就已经被触发了。

 

然后我们在 js 进阶十六的时候,有个东西只说了结论,并没有深入往下扯,那就是 push。

比如,我现在想要给它 push 一个值:

是不是给我报错了,那么这个错到底是怎么回事?其实它是 2 个 set。

原因是这样的,其实这个 push,它会映射为两个数组操作:

第一个操作,它要给数组增加一个值。

第二个操作,它要去改变 length。

 

所以在这个时候,我需要 return 一个 true:

什么意思呢?意思就是说,我这个操作继续。

 

那这个 return 是怎么回事呢?

简单来说,大概的意思就是,这个 set,它有一个作用,就是说,你在设置的时候,你要校验一下,到底这个 set 是不是合法的,如果不合法,我就不再继续往下运行了,是干这个用的。

所以在这我们 return 一个 true,它的作用也很简单,就是告诉这个 proxy 对象,没问题,这事可以,我们接着往下走。

所以这也是为什么,我们不 return 的时候,它给我报错,我们仔细看看这个报错:

trap,指的就是 set 函数,也就是所谓的陷阱函数,怎么叫都行。

它说这个东西,return 了一个假的值,所以这时候它就会给你报错了。

而如果你真正,去 return 一个 true 的话,你会发现,就没这问题了:

然后你会发现,它触发了 2 次 set,我相信大家一定和我一样奇怪,是哪 2 次 set 啊?

我们把这个 name 和 val 给打印出来看看:

你可以看到,第一次,是它设置了一个新的 3,你本来有 0,1,2,然后 push,是不是要在 3 号位加个值啊?这很正常。

然后第二次,它给了你一个 length,让你把 length 变成 4,不奇怪吧?

那么在这种情况下,我们的这个数组,你可以看到,它就正常的更新完了。

所以这个 set,会触发 2 次,然后你需要 return 一个 true,表示这个操作可以被继续下去,就完了。

 

说白了,现在这个 arr = [12, 5, 8],如果我 push 个 99。

首先,它肯定会变成 [12, 5, 8, 99],那么这个工作,它其实会变成 2 个:

第一,我要给第三个的位置加个 99,也就是 arr[3] = 99。

第二,我这个数组的 length 还要变成 4,也就是 arr.length = 4。

所以说,push 其实会被拆解为 2 个操作,这也是为什么它会触发 2 次 set 的原因。

 

然后,这个也同样适用于别的,比方说我现在不去 return,然后我去做一个 pop。

大家想象一下,pop 是不是也一样啊?我是不是既要设置值,又要设置 length,所以如果我不 return 的话,它也会报错:

而如果我 return 一个 true,那就代表这个操作要继续下去:

所以这个其实没啥可说的,就是说你需要做一个 return,就完了,这是 proxy 的一个要求。

 

那么接下来呢,别的我们不管,至少现在我们已经可以做到这个数组是可响应的了。

然后我们需要做一个小操作,响应完不是赋个值就完了,我们还需要一个 render 来帮助我们渲染东西,比方说现在这个 render 我就单纯的 console 一下:

然后我们加上去,先更新完数据,然后 render:

那么接下来,我们实验一下:

可以看到,当我们去添加或者修改的时候,都能触发 render,但是,用 push 的时候,触发了 2 次。

为什么触发 2 次?因为我们做了 2 次 set,原因上面也详细说过了。

那么这个时候,其实我们就有一个特别巨大的问题,什么问题呢?

就是说,我们要如何才能只 render 一次。

 

首先肯定一点,我就 render 两次了,怎么滴?

其实也没事,但是性能上会有很大的负担。

而且说句特别实在的话,你现在做一个 push,它是 render 两次,如果我们这个数组多弄几个:

我不知道大家有没有想过一个问题,如果我去做一个 shift 呢?

shift 是什么操作? shift 是从头上删掉一个。

什么叫从头上删掉一个?简单来说,就是原来的 1 号元素,要来到 0 的位置、第 2 号要来到 1 的位置,第 3 号要来到 2 的位置,等等,以及 length 还要变。

所以当你去做 shift 的时候:

你会发现,它足足给我 render 了 7 次。

而且,这还是在我这个数组东西比较少的前提下,如果我这东西多,比如几百上千的,也是有可能的,那这时候就 happy 了:

你可以看到,413 次 render,这个感觉一定很爽。

啥样的系统能经得起你这么搞呀?肯定不行。

所以我们必须得想办法把这个重复的渲染给它干掉。

 

当然,办法是有的,也很多,但是你要干掉的,仅仅是这个 proxy 自身给你带来的重复渲染吗?并不是。

用户操作数据,有没有可能同时操作了很多个东西?

比方说,他给这个数组 push 了一个东西,他又给那个 title 改了一下,他又给一个 user 删了一个东西等等,这个用户同时操作很多个数据,这都是有可能的。

 

所以,现在的问题就是,怎么样能够把这些渲染操作,给它合并为只有一个。

其实挺简单的,我们有很多正统的方法,但是比较麻烦,这里我们直接搞个野路子,怎么做呢?

首先,我这会弄个定时器,但不是立刻就去做渲染,而是要经过一个小小的延时,也不用太大,0 毫秒就可以了:

然后,我还要做一个事。

因为我们这个定时器有可能被多次设置,如果就只是这么写的话,没有任何效果。

所以我们要提前去 clearTimeout,把这个 timer 先给它清了:

什么意思呢?

就是说,如果在极快的速度之下,多次的执行 render,其实我就变成了一个,先把上一次的取消,在开个新的,然后我又被取消,又来一个新的,它剩的是不是最后一次啊?

我们直接来看看效果,给这个数组 push 和 shift:

可以看到,就一次 render,多了没有。

其实我们在这,是把这个同步操作,化为了一个异步操作。

首先很明确的说,定时器,它是一个异步操作,并且,我们 js 里面的定时器都有一个特点,就是说,它先要等当前的那个代码执行完,然后等到都完事了,它才有机会去检查定时器,哪怕到时候一检查,发现是个 0 毫秒,它赶紧执行,无所谓,它也达到我的目的变成异步的了。

所以说白了,它会把定时器里面的代码,优先级往后放,别人都执行完了在走你(异步执行如果不太懂,可以看下前面的 js 基础)

 

所以到目前为止,我们就利用了一种野路子的方式,来帮助我们把这个 render 操作,都给它归成了一个,就执行最后一次,多了没有。

那么现在我们做了两件事:

第一,我们把这个数组变成可响应的了。

第二,我们做了一个 render,并且完成了一个最重要的工作,就是把这些额外的、无效的、没有意义的 render 给它取消掉了。

 

然后接下来我们要做的就是,我要按照数组的值,来 render 出东西来。

并且最开始我们也说了,不像以前一样,直接拿 innerHTML 直接堆,那个太乱了。

所以现在我要做一件事,我提前把作为模版的这个元素,给它存起来,怎么存呢?

注意,如果到这为止,什么都不做,你会发现,页面上那个元素消失了:

你可以看到,控制台也没报错,那么元素去哪了呢?

我们上一篇博客是不是刚好说过,当我们去把一个元素添加一个新的父级下的时候,它会自动的跟原来那个父级解除关系,并且自动的进入到新的父级里边,那原来的父级也就没这个东西了。

所以当我把 box 拉到 frag 里面来的时候,那页面上自然就没有了。

 

然后在 render 里面,我们就不去做 console 了,我要拿着数组,来做一个循环,每一次我都需要拿着这个 frag,来 cloneNode:

为什么?这样的话,就完全跟它自己那个保持一致,它页面里面怎么写,我这就是怎么写,就这么简单。

然后这时候我就得到了一个新的元素 el,那么接下来最后一步,我们需要干什么?

只需要把 el 插到页面里面去就行了,并且我们也知道,在渲染的过程当中,我们一般是先把它原来的那些东西全都给它清空:

那么现在这时候,你可以看到,页面上还是没有:

因为我们现在这个 render 什么时候执行?只有当数据变化的时候才执行。

那换句话说,一开始是空的就不奇怪了,所以我们一开始就要做一次初始渲染。

那么现在你可以看到,页面就有东西了。

当然我们还有一个小小的要求,就是它上面显示的都是写死的 sss,我希望它这里面能够放我数组里面的值:

那么我们就加上。

el.children[0] 就是我们里面一个个 class = box 的 div,我要它里面的 h2,它的 innerHTML 就变成我们数组里的元素。

这时候你可以看到,就变了:

以及,我能不能给它稍微的加点什么:

所以说在这种情况下,你就发现了,非常的简单。

我们现在就做到了,让这个数组变化的同时,UI 界面直接去响应它的这种变化。

 

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