【前端面试】Vue2与Vue3的响应式原理差异以及基本实现【上】(Vue源码相关)

213 阅读3分钟

Hi,我是JustHappy,目前来看,如果想冲击大厂,或者在体量较大的公司想要转正(前端技术栈为Vue),Vue源码是必须要学习掌握的。加之在面试中也被问到过这个问题,故作此篇。虽然Vue2已经停止更新了,但是目前来看依旧很多项目在使用Vue2,当然我认为学框架源码这些东西本身就是提升自己的内功,框架的设计本身也是一个不断取舍的过程,多了解些也未尝不好

本文基于Vue2和Vue3的官方文档,如有需要请点击以下链接

深入响应式原理 — Vue.js (vuejs.org)

深入响应式系统 | Vue.js (vuejs.org)

什么是响应式系统?

想象一下,你奶奶做饭时,如果水的温度到了100度,水就会沸腾,她就知道水开了,可以下面条了。Vue就像一个智能温度计,它能感觉到数据的变化,一旦数据变了,就像水温升高一样,Vue就会告诉网页:"嘿,数据变了,快更新一下显示的内容吧!"这样,网页上的内容就会自动更新,就像面条放进开水里一样自然。

为什么要构造响应式系统?

需求产生供给,学过几门语言的朋友都知道,几乎没有语言是原生支持数据的自动更新的,我想这和程序的顺序执行有关,但是在我们写网页或者App的时候我们又时常需要动态的数据更新。JavaScript也是如此,在没有响应式系统的时候我们需要反复的去获取到某个值以至于我们需要在使用这个值之前都要调用一个方法,那为什么不把这个方法和值结合起来呢?

/**
 * 就像这样,程序顺序执行,它不会回头去检查哪些值会变化,它是一步一步执行
 */

let book = {
  price: 10,
  count: 80,
}

let total = book.price * book.count

console.log(total) // 输出 800

book.price = 10.5

console.log(total) // 输出 800

/**
 * 我们希望在book.price发生变化的时候,total也跟着变化就得再执行一次获取到total最新的值
 */

total = book.price * book.count

console.log(total) // 输出 850

Vue2是基于什么实现的响应式?有什么缺陷?

Vue2是基于Object.defineProperty()这个方法实现的响应式系统,但是由于JavaScript 的限制,Vue2不能检测数组和对象的变化(即无法监听到对象属性的增加以及数组元素的变化)。尽管如此我们还是有一些办法来回避这些限制并保证它们的响应性。

我们先来回顾一下在Vue2中我们是如何使用响应式数据的(以下是一个简单的示例)

<template>
    <div id="app">
        <h1>单价:{{ price }}</h1>
        <h1>数量:{{ count }}</h1>
        <h1>总价格:{{ count * price }}</h1>
        <button @click="add">加一本</button>
        <button @click="del">减一本</button>
    </div>
</template>

<script>

export default {
name: "App",
    data() {
        return {
            price: 10,
            count: 20,
        };
    },
    methods: {
        add() {
            this.count++;
        },
        del() {
            this.count--;
        },
    },
};
</script>

实现效果如下

77758-ncpn9.gif

然而当变成数组或对象的时候(偷个懒,只写一个示例)

<template>
  <div id="app">
    <h1 v-for="(val, key, index) in book">{{ key }} : {{ val }}</h1>
    <button @click="newOne">新属性</button>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      book: {
        price: 10,
        count: 20,
      },
    };
  },
  methods: {
    newOne() {
      this.book.page = 100;
      console.log(this.book);
    },
  },
};
</script>

情况如下

m1104-igquv.gif

这里我使用的是一个在线的支持Vue2的练习场Vue 2 - CodeSandbox ,很适合编写样例(Vue官方首推的Vue SFC Playground (vuejs.org)我没找到配置Vue2的地方)

那么该如何解决这个问题呢?

无非就是这么几种情况,对象属性的添加、对象属性的删除、数组元素的添加、数组元素的删除,Vue2的文档中都有提供,我再次复现一下。

1. 对于对象或数组我们可以使用$set和$delete来解决

对于对象和数组 <======================== 点此跳转官方文档

<template>
  <div id="app">
    <h1 v-for="(val, key, index) in book">{{ key }} : {{ val }}</h1>
    <button @click="newOne">新属性</button>
    <button @click="delOne">删属性</button>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      book: {
        price: 10,
        count: 20,
      },
    };
  },
  methods: {
    newOne() {
      this.$set(this.book, "page", 100);
      console.log(this.book);
    },
    delOne() {
      this.$delete(this.book, "page");
      console.log(this.book);
    },
  },
};
</script>

实现效果如下

bac0m-0g9q5.gif

Vue3是基于什么实现响应式的?reactive和ref的区别是?ref为什么快?

请看下一节......