zoukankan      html  css  js  c++  java
  • 浅探 Vue 为什么不增加数组下标响应式——为什么不能检测到数组元素直接赋值

    Vue 的双向数据绑定,使得修改数据后,视图就会跟着发生更新,比如对数组进行增加元素、切割等操作。然而直接通过下标修改数组内容后,视图却不发生变化。那么,在保留原有的数组响应方式下,为什么 Vue 不增加对数组下标的响应式监听呢?

    arr[index] = val 不是响应式的

    在 Vue 官网的 列表渲染 — Vue.js 中,有强调 Vue 不能 直接检测通过数组下标改变值的变化,需要通过 数组更新检测 来实现。

    <template>
      <div>
        <span v-for="i in arr">{{ i }}</span>
        <button @click="updateIndex">改变下标对应的值</button>
        <span v-for="key in Object.keys(obj)">{{ obj[key] }}</span>
        <button @click="updateKey">改变key对应的值</button>
      </div>
    </template>
    <script>
    export default {
      data() {
        return {
          arr: [ 1, 2, 3, 4 ],
          obj: { a: 1, b: 2, c: 3, d: 4 }
        }
      },
      methods: {
        updateIndex() {
          this.arr[0]++                // 对数组这样的操作不会引起视图的更新
          // this.arr.splice(0, 0)     // 需要调用数组的方法,才能使视图更新
        },
        updateKey() {
          this.obj['a']++    // 但对对象这样会引起视图更新
        }
      }
    }
    </script>

    从源码看 Vue 中数组的 Observer 实现

    在 Vue 2.6.10 中,可以看到 Observer (/src/core/observer/index.js) 的实现方式:

    export class Observer {
      // ......
      constructor (value: any) {
        this.value = value
        this.dep = new Dep()
        this.vmCount = 0
        def(value, '__ob__', this)
        if (Array.isArray(value)) {
          // 这里对数组进行单独处理
          if (hasProto) {
            protoAugment(value, arrayMethods)
          } else {
            copyAugment(value, arrayMethods, arrayKeys)
          }
          this.observeArray(value)
        } else {
          // 对对象遍历所有键值
          this.walk(value)
        }
      }
      walk (obj: Object) {
        const keys = Object.keys(obj)
        for (let i = 0; i < keys.length; i++) {
          defineReactive(obj, keys[i])
        }
      }
      observeArray (items: Array<any>) {
        for (let i = 0, l = items.length; i < l; i++) {
          observe(items[i])
        }
      }
    }

    可以看到 vue 对对象是采取 Object.keys然后 defineReactive 所有键值,而对数组并没这样做,而是只 observe 了每个元素的值,数组的下标因为没有被监听,所以直接通过下标修改值是不会更新视图的。

    而数组方法能够响应式,是因为 Vue 对数组的方法进行了 def 操作 (/src/core/observer/array.js)

    const methodsToPatch = [
      'push',
      'pop',
      'shift',
      'unshift',
      'splice',
      'sort',
      'reverse'
    ]
    methodsToPatch.forEach(function (method) {
      // cache original method
      const original = arrayProto[method]
      def(arrayMethods, method, function mutator (...args) {
        const result = original.apply(this, args)
        const ob = this.__ob__
        let inserted
        switch (method) {
          case 'push':
          case 'unshift':
            inserted = args
            break
          case 'splice':
            inserted = args.slice(2)
            break
        }
        if (inserted) ob.observeArray(inserted)
        // notify change
        ob.dep.notify()
        return result
      })
    })

    并非不能实现下标响应式

    但数组也是对象的一种,它的下标就是它的键,只是平常使用时,数组的键数量往往比对象的键数量大的多。所以原则上它也是可以使用对象的处理方式。通过修改 源码 后引入后查看效果:

    export class Observer {
      // ....
      constructor (value: any) {
        this.value = value
        this.dep = new Dep()
        this.vmCount = 0
        def(value, '__ob__', this)
        if (Array.isArray(value)) {
          if (hasProto) {
            protoAugment(value, arrayMethods)
          } else {
            copyAugment(value, arrayMethods, arrayKeys)
          }
          this.observeArray(value)
          this.walk(value)    // 保留原有的数组监听方式下,增加对下标的监听响应
        } else {
          this.walk(value)
        }
      }
      // ......
    }

    视图代码还是和上面的一样,点击按钮可以看到视图会实时更新:

    实验探测数组下标响应式对性能的影响

    通过上面的修改,可以知道 Vue 其实是可以监听数组下标的。但为什么 Vue 不采取,且说是“JavaScript的限制”呢?在 github issue#8562 中,Vue.js 作者尤雨溪解释是因为性能问题。

    为了验证数组下标响应式对性能的影响,我做了以下实验实现相同的效果,分别设置循环次数 TIMES 为 1000,10000,100000(以下只贴出关键代码部分,其他部分代码一致):

    1. 使用修改能响应下标触发页面更新的 Vue ,通过数组下标修改值 TIMES 次
    <template>
      <div>
        <span v-for="i in arr">{{ i }}</span>
        <button @click="updateIndex">改变下标对应的值</button>
      </div>
    </template>
    <script>
    // import modified vue
    export default {
      data() {
        return {
          arr: new Array(100).fill(0)
        }
      },
      methods: {
        updateIndex() {
          console.time('updateIndex')
          for (let i = 0; i < TIMES; i++) {
            this.arr[0]++
          }
          console.timeEnd('updateIndex')
        }
      }
    }
    </script>
    1. 使用原版 vue 通过数组下标修改值 TIMES 次,并通过 splice 方法触发视图更新 
    <template><!-- 和上面一样 --></template>
    <script>
    // import origin vue
    export default {
      data() { /* 和上面一样 */ },
      methods: {
        updateIndex() {
          console.time('updateIndex')
          for (let i = 0; i < TIMES; i++) {
            this.arr[0]++
          }
          this.arr.splice(0, 0)    // 通过 splice 实现视图更新
          console.timeEnd('updateIndex')
        }
      }
    }
    </script>

    每个实验不同 TIMES 都重复10次,取平均值,实验数据如下:

    增加数组下标响应式对性能会有影响

    通过上面的实验,可见在循环次数较少的时候,增加下标响应式似乎没有多大影响,但随循环次数增加,带来的性能损耗将快速增加。如果想要实现直接修改下标对应的内容来自动更新视图,对性能会有一些影响。因此对于数组的更新,最好还是通过数组更新检测来实现。

    在选择 TIMES 取值的时候,也发现需要到 10000 级别才会体现出较明显的差距。但一般情况下,我们并不会执行像上面一样庞大的操作,也许仅仅只是改变一个值而已,实现下标响应式消耗的时间和普通的方式几乎一样,或许在这方面 vue 牺牲了一点开发体验。

     转自:https://blog.csdn.net/dobility/article/details/97261478

  • 相关阅读:
    vss的ss.ini丢失或损坏导致的vss无法登录错误
    prtvu xsdabljc 视图代码
    安装Ehlib经验
    PHP连接MSSQL
    在Access中实现 case when功能
    快捷输入电大学号 delphi代码
    毕业预警的SP
    新系统班级名称规范化
    查询哪些学生没有做课程注册
    第一个PHP数据库查询应用
  • 原文地址:https://www.cnblogs.com/vickylinj/p/14370084.html
Copyright © 2011-2022 走看看