业务说明

现在公司有这么一项业务需要:一个表单有多个输入框,每个输入框可以设置公式,由别的输入框的值求得。

思路:每个输入框绑定watch事件。

实现

  1. 我们能从表单数据中拿到每个输入框里面的信息,包括name,数据,公式,涉及到的输入框。

  2. 通过遍历当前输入框的涉及到的输入框,生成一个mapkey是涉及到的输入框的name,如果该key存在,则将当前输入框的name push进去;如果不存在,则新建键值对。

    // relyMap是要生成的map,sname是当前输入框的name,map是当前输入框涉及的输入框
    let relyMap={},sname
    for (let j in Map) {
      if (relyMap[j]) {
        relyMap[j].push(sname)
      } else {
        relyMap[j] = [sname]
      }
    }
  3. 遍历生成的relymap,对于每个键都用watch绑定,当被绑定监听的对象值发生改变时,遍历上方生成的数组,将每个键对应的公式以及对应的数据结构传给专门负责计算的函数,将返回值通过set将值绑定

    this.$watch('name.' + i, function (newVal, oldVal){
      relymap[i].foreach(j => {
        // 求和
        // set赋值
      })
    })

算法

很简单是不是,但是如果a=b+c 的情况下,对于bc在新加公式b=a+cc=a+b后,这就成了一个环,我们修改a的话就会触发b和c的改变,bc改变后又会触发ac,ab的改变,然后又会相互触发,不上锁的话会在瞬间吃爆内存,同时这些都是在watch内部发生的,我们没办法直接查看修改watch里面的数据。

因此设计了一个bfs和dfs合并的保护算法,逻辑为:

  • 当被绑定监听的值被修改后,如果广搜的数组为空,代表时用户触发了监听,此时直接将该键关联的数据直接推入到广搜的数组中,如果不为空代表是关联触发的,不用管。
  • 遍历该键关联的全部字段,对于每个字段:
    • 到这一步代表已经触发监听了,监听数量的变量+1
    • 判断dfs数组里面是否有当前字段,如果有代表已经操作过了,直接return
    • 将当前字段推入深搜数组里面
    • 将该字段所关联的字段推入广搜数组
    • 通过函数计算值
    • 通过set赋值
  • 判断当前监听变量是否与bfs长度相等,如果相等代表我触发的watch和watch队列的里面是一样的,清空对应的值
let dfs=[]
let bfs=[]
let watch=0
for(let i in relymap) {
  this.$watch('name.' + i, function (newVal, oldVal){
    relymap[i].foreach(j => {
      if (bfs.length === 0) bfs.push(...relymap[i])
      relymap[i].forEach(j => {
        watch++
        if (dfs.indexOf(j) !== -1) return
        bfs.push(...(relyMap[j] || []))
        dfs.push(j)
        // 求和
        // set赋值
      })
      if (watch === bfs.length) {
        dfs = []
        bfs = []
        watch = 0
      }
    })
  })
}

总结

因为watch不能直接操作,所以实现了一种在监听外面贴了一层算法,通过对算法的值进行计算来判断是否真的结束了全部的监听