vue中修改数组内容视图没有发生更新问题源码解析

改变数组长度无法触发视图更新

在vue中,我们可能会有这种情况,修改了数组的长度,但是视图没有发生更新

<template>
  <div id="app">
      <div v-for="(item,index) in arr" :key="index">
        {{index}}:{{item}}
      </div>
      <button @click="click">btn</button>
  </div>
</template>
export default {
  name: 'app',
  data(){
    return{
      arr:[1,2,3,4,5]
    }
  },
  methods:{
    click:function(){
      this.arr.length=6
      console.log('this.arr: ', this.arr)
    }
  }
}

在这里插入图片描述
可以看到数组的长度已经变成了6,最后一个为empty,但是却没触发视图的更新
在这里插入图片描述

而当我们在点击方法后面添加一个push操作,就会触发视图的更新

export default {
  name: 'app',
  data(){
    return{
      arr:[1,2,3,4,5]
    }
  },
  methods:{
    click:function(){
      this.arr.length=6
      console.log('this.arr: ', this.arr)
      this.arr.push(7)
      console.log('this.arr: ', this.arr)
    }
  }
}

在这里插入图片描述

可以看到,视图发生了更新,this.arr.length=6触发的数组变化

我们试着不用push,直接给arr上某个成员变量重新赋值

export default {
  name: 'app',
  data(){
    return{
      arr:[1,2,3,4,5]
    }
  },
  methods:{
    click:function(){
      this.arr.length=6
      console.log('this.arr: ', this.arr)
      this.arr[0] = 2
      console.log('this.arr: ', this.arr)
    }
  }
}

点击发现视图也是没有发现变化

为什么使用push,数组会发生变化,但是改变长度和数组成员就无效呢,我们可以从源码去进行解析

源码解析

我们知道,在vue里面,其核心双向绑定的实现,是通过Object.defineProperty()(在vue3中使用了proxy)来劫持对象的,但Object.defineProperty()无法劫持数组成员的变化,而实际上,我们在使用vue的时候,当我们通过push,splice,pop等方法去修改数组的时候,是会触发响应式更新的,那么vue是怎么做到的呢

实际上,vue重写了这些方法

vue/src/core/observer/array.js

/*
 * not type checking this file because flow doesn't play well with
 * dynamically accessing methods on Array prototype
 */

import { def } from '../util/index'

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [ // 将要重写的方法放在一个数组中
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function (method) {
  // cache original method
  const original = arrayProto[method] // 得到原来的方法
  def(arrayMethods, method, function mutator (...args) { // 将重写的方法定义到arrayMethods上,mutator就是重写的方法
    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
  })
})

将这些方法重写到arrayMethods上之后,我们还需要将data上的数组对应的方法改成我们定义的方法,所以需要在Ovserver中对数组的监听做额外的处理
vue/src/core/observer/index.js

import { arrayMethods } from './array' // 引入在array中我们重定义的方法
export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data

  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
    if (Array.isArray(value)) { // 判断为数组
      if (hasProto) { // 如果有__prop__,则将数组的__prop__指向arrayMethods
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys) // 否则直接把重写的方法写在数组上
      }
      this.observeArray(value) // 监听数组里的每个成员
    } else {
      this.walk(value)
    }
  }
  // ...
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}
// ...
function protoAugment (target, src: Object) {
  /* eslint-disable no-proto */
  target.__proto__ = src
  /* eslint-enable no-proto */
}

function copyAugment (target: Object, src: Object, keys: Array<string>) {
  for (let i = 0, l = keys.length; i < l; i++) {
    const key = keys[i]
    def(target, key, src[key])
  }
}

回到问题

现在我们可以知道,为什么用push可以,而直接修改length和数组成员不行了

vue对于数组的处理,是将push,pop,splice等多个数组操作的方法重写,在调用这些方法的时候就去触发视图的更新

所以就会出现这样的情况
当我们调用这个方法的时候,视图没有更新

click:function(){
    this.arr.length=6
    console.log('this.arr: ', this.arr)
}

但是当我们调用push,即使我们没有push进任何东西,依然会引起视图的更新

click:function(){
    this.arr.length=6
    console.log('this.arr: ', this.arr)
    this.arr.push()
    console.log('this.arr: ', this.arr)
}

在这里插入图片描述
所以如果你修改数组后,视图没有发生更新,在处理的函数后面加入一行

this.arr.push()

如果视图成功发生更新,那就说明你是直接去修改了数组的成员或者操作它的属性了

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值