记一次比较Vue2和Vue3响应式原理和性能差异

3,171 阅读3分钟

写在前面

Vue响应式原理是数据驱动视图的核心,它能够自动帮助开发者监听数据的变化,最终触发视图更新。它是Vue的渲染更新过程的核心。那么了解这一部分的原理至关重要,这篇文章就结合vue2vue3响应式来做一个对比,既明白了响应式原理的实现,又对比了二者在性能上的差异~

需要了解的api

这篇文章不是零基础,需要对一些基础api有一定的了解,那么这里给出这些api的官方文档,供大家参考学习:

Reflect

准备一个原始对象数据

为了做这个实验,我使用同一个数据对象来分别做vue2vue3的示例:注意这个data数据从最外层花括弧到最里层总共8个层级,你可以数一下。

const data = {
    username: 'tom',
    profile: {
        city: 'beijing',
        a: {
            b: 12,
            c: {
                d: 23
            }
        }
    }
}

接下来我分别写点代码对这个数据做响应式处理:

  • vue2
const data = {
    username: 'tom',
    profile: {
        city: 'beijing',
        a: {
            b: 12,
            c: {
                d: 23
            }
        }
    }
}
function observe(data) {
    console.log(1);
    if (typeof data !== 'object' || data == null) {
        return data
    }
    for (const key in data) {
        defineReactive(data, key, data[key])
    }
}

function defineReactive(target, key, val) {
    observe(val)
    Object.defineProperty(target, key, {
        get() {
            console.log(`get ${key}`);
            return val
        },
        set(newValue) {
            console.log(`set ${key}`);
            if (newValue !== val) {
                observe(newValue)
                val = newValue
            }
        }
    })
}
observe(data);
console.log(data);

打印的结果如下: 从图中可以清晰的看到总共打印了8次1,也就是说observe一共执行了8次;由此可见vue2是一次递归到底的来实现响应式的,好接下来看看vue3:

  • vue3
function observe(data) {
    console.log(1);
    if (typeof data !== 'object' || data == null) {
        return data
    }
    const p = new Proxy(data, {
        get(target, key, receiver) {
            console.log(`get ${key}`)
            const result = Reflect.get(data, key, receiver)
            // 当获取值的是时候 再设置响应式
            return observe(result)
        },
        set(target, key, val, receiver) {
            console.log(`set ${key}`)
            const result = Reflect.set(target, key, val, receiver)
            return result
        }
    })
    return p
}

const data = {
    username: 'tom',
    profile: {
        city: 'beijing',
        a: {
            b: 12,
            c: {
                d: 23
            }
        }
    }
}

const p1 = observe(data)
console.log(p1)

打印结果如图: 只打印了一次1,也就是说observe只执行了一次,从代码可以看出,将来执行get的时候,observe又会再次执行。什么意思呢?也就是说Vue3对于数据的深层监听只有当获取值的时候才会响应式处理,这样在性能上肯定会提升很多。

争议

读到这里,抛开其他细节来讲,可能你会来句卧槽,那Vue2也可以在get的时候observe啊,确实如此,但是想一下是不是会有点问题呢?问题就在于每次执行get的时候,不管是获取哪个属性,都需要执行一下observe,并且它还是会向下遍历,这样增加了时间复杂度,可想而之,其性能是多么浪费的,还不如一次性深度监听到底呢?而proxy则不一样,它只会响应式处理你get的那个层级,它不会深度遍历,这样解释你明白了吗?

总结

接下来总结一下二者响应式的差异

  • Object.defineProperty
    • 深度监听,性能问题
    • 新增删除属性问题,这个可以参考我前面写的一个文章总结一下Vue那些常用必会面试必考的API,这里说明了vue2为什么需要set&delete两个api
    • 数组问题,需要重写数组原型的几个方法
  • Proxy
    • 性能提升了一大截
    • 解决了vue2的那些问题
    • 兼容性有待处理

当然示例代码都没做优化,实际上源码肯定是对各种情况还做了处理,这里就不做介绍了,可以自己去看源码哈~如果你对本文有什么看法或者觉得不对的地方,请在评论区赐教!共勉进步~~

参考

vue2响应式原理

vue3响应式原理