[VUE]探究vue对数组的处理

931 阅读3分钟

前言

本人在一家中小公司工作了几年,一开始只负责前端,到后来用nodejs写服务以及负责一些团队基础设施等运维工作。由于做的事情太杂,最近想重新系统地整理一下前端的知识,因此写了这个专栏。我会尽量写一些业务相关的小技巧和前端知识中的重点内容,核心思想。

问题场景

相信用过VUE开发项目的朋友一定会遇到过一个问题,就是在data中写了一个数组。然后用索引去修改这个数组或者把数组替换后,并不会更新视图。

//在data中定义了一个数组
data(){
	return{
    list:[
      {a:1},
      {a:1},
      {a:1}
    ]
  }
}

//用method修改数组
clickBtn(){
  this.list[0] = {a:2};
}

上述的例子中,触发clickBtn,视图并不会渲染。用浏览器console输出list的内容

可以看出data下的list的每一项原来都是被监听着的,但是索引为0的项,被我们修改了之后,vue并没有重新监听。也就是说,修改索引为0的项,没有触发dep的notify。

解决方案

1.项内属性修改

上述内容可以看出list中的项目原来是被监听着的,所有上面的例子如果我们直接修改项里的某一个属性仍然可以触发渲染。

clickBtn(){
  this.list[0].a=2;
}

2.vm.$set

既然vue没法自动帮我们监听数组的变化,那我们就自己告诉他,这里需要监听。

clickBtn(){
  this.$set(this.list,0,{a:2})
}

3.map()

map()、filter()、concat()、slice()等方法是vue是可以识别到的。vue会认为你不是在替换这个data,只是在修改。

clickBtn(){
  this.list = this.list.map((item, index) => {
    f (index === 0) item.a = 2;
    return item;
  });
}

官方说明

vue官方是用对数组的渲染问题作出说明的:

从源码分析

我们从vue的数据劫持原理上看,vue是不对data作修改的。那么他是怎么知道我们用了pop()这类方法的呢?下面我们开始从源码入手,了解vue对数组的处理。

/*
 * 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;// 使用arrayProto记录数据原有的原型
export const arrayMethods = Object.create(arrayProto)// 创造了一个空对象,这个空对象的__proto__指向数组的原型

// 规范了一列数组方法
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]// 获取到了数组原型上的对应的方法
  // 往arrayMethods上添加定义的方法
  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
  })
})

总结

vue的数据监听核心就是用Object.defineProperty()对数据进行劫持。这就决定了vue实例中的数据如果想要实现响应式,就必须在生命周期created之前定义好。如果在响应调节完整后修改数组,对象等内容,很可能会导致新内容没有响应。

对此官方也给出了一些解决方案,我们也通过源码,看出vue在一系列的数组方法中作了修改。在新增加的数组内容,vue会辅助加上监听。但如果是对象,新增属性,目前还是没有自动处理的。