重读 Vue 文档 --- 数组更新

2,514 阅读2分钟

引言

看过多遍 vue.js 文档,但是对一些知识点始终似懂非懂。于是便有了重读 Vue.js 文档的计划。此篇对 Vue 中数组更新进行剖析并结合源码,希望对您有所帮助。

变异方法(mutation method)

顾名思义,变异方法,即会改变调用了这些方法的原始数组。但由于数组是引用类型,存于栈内存中的地址是不会改变的,改变的是真实的数组值。包括:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()
const arr = arr1 = [1, 2, 3];
arr === arr1; // true  arr === arr1 == [1, 2, 3];
arr.push(4);
arr === arr1; // true arr === arr1 == [1, 2, 3, 4]; 数组值改变了,但两数组依然指向同一地址

非变异方法(non-mutation method)

即不会改变调用这些方法的数组,而总是返回一个新数组。例如:concat、filter、map、slice 等等。但我们使用非变异方法,可以用新数组替换旧数组。

new Vue({
    data () {
        return {
            imgs: ['https://juejin.im/1', 'https://juejin.im/2', 'https://juejin.im/3', 'https://juejin.im/4', 'https://juejin.im/5']
        }
    },
    methods: {
        transform () {
            this.imgs = this.imgs.map(img => img + '.jpg'); // 用新数组替换旧数组
        }
    }
})

数组更新

无论是变异数组或是非变异数组,当数组值改变的时候,地址指针都不会改变。而 vue 是一个以数据驱动的框架,当数组值变化的时候,自然希望依赖于变化数据的视图也触发更新。用过 vue 的小伙伴都知道,当数组改变的时候,视图也更新了。那 vue 帮我们做了什么事情呢,就让我们深入源码一探究竟吧。

// vue 2.6  
// src/core/observer/array.js
const arrayProto = Array.prototype; // 获取数组原型对象
const arrayMethods = Object.create(arrayProto); // 生成一个新对象,且__proto__ 指向 数组原型对象
const methods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];

/**
 * Intercept mutating methods and emit events
 */
methods.forEach(method => {
    const original = arrayProto[method]; 
    let val = function mutator (...args) {
        const result = original.apply(this, args); //缓存原始方法
        let inserted = null;
        switch (method) {
            case 'push':
                inserted = args;
                break;
            case 'unshift':
                inserted = args;
                break;
            case 'splice':
                inserted = args.slice(2); // array.splice(start[, deleteCount[, item1[, item2[, ...]]]]) [item])
                break;
        }
        if (inserted) observeArray(inserted); // 获取插入的值,并设置响应式监听
        notify(); // 通知依赖像更新
        return result;
    }
    
    Object.defineProperty(arrayMethods, method, {
        value: val,
        writable: true,
        configurable: true,
        enumerable: true
    })
})

const observeArray = function observeArray (inserted) { // 简写,[具体实现](https://github.com/vuejs/vue/blob/2.6/src/core/observer/index.js)
    console.log(inserted); //新增的元素形成的数组
    console.log('响应式监听数组');
}

const notify = function notify () {
    console.log('视图更新了');
}

注意事项

由于 JavaScript 的限制,Vue 不能检测以下数组的变动:

  1. 利用索引直接设置一个数组项,例:vm.imgs[index] = newValue
  2. 修改数组的长度, 例:vm.imgs.length = newLength

为解决第一类问题:

// Vue.set
Vue.set(vm.imgs, index, newValue);

// Array.prototype.splice
vm.imgs.splice(index, 1, newValue);

为解决第二类问题:

vm.imgs.splice(newLength);

总结

以上是个人对vue 数组的一些理解,希望能对大家有所帮助。如果有错误或不严谨的地方,欢迎批评指正,如果喜欢,欢迎点赞。

参考