Vue 响应式原理(面试必问)

115 阅读5分钟

大家好,我是格子。今天我们来聊一聊 Vue 的响应式原理,以及 Vue2 和 Vue3 响应式原理有什么区别🪲。

面试的时候,如果不问 Vue 响应式原理的公司往往可以不用去了🤣。

不管是 Vue 还是 React 框架,都具有响应式编程的特性,通过虚拟 DOM(Virtual DOM)的机制来实现响应式更新。虚拟 DOM 是一个轻量级的 JavaScript 对象,它以树形结构来模拟真实的 DOM 结构。

什么是响应式?

响应式的核心是,数据的变化能够自动地更新与之绑定的 DOM 元素。就是当一个 Vue 实例中的数据发生变化时,Vue 会自动重新渲染与之相关的模板部分,从而使页面上呈现的数据始终与数据模型保持同步(这样的数据称为响应式数据)。🍏直白的讲:我们定义绑定在标签上面的变量发生改变时,Vue 会动态的根据变量值的更新界面。响应式的设计,让我们摆脱了之前用 JavaScript 去操作真实 DOM 的复杂步骤。

Vue 实现响应式,到底主要做了哪些事情

我们可以想象一下,如果我们定义的变量在某个时候改变了,同时想让该变量绑定的 DOM 也发生改变,其实就是要知道该变量什么时候改变了就行了。当我们知道变量什么时候发生了改变,更新界面(DOM)就能实现了。但是要完成这个步骤,还有一个重要条件,就是我们得知道变量怎么变的。下面我们通过案例演示一下。

🍉比如我们定义了一个 student 对象,属性有 nameagehobby。我们在组件中渲染了这个对象。

<script setup lang="ts">
import {reactive} from "vue";

const student = reactive({
  name: '格子',
  age: 18,
  hobby: ['学习', '睡觉', "吃饭"]
})

const changeName = () => {
  student.name = "格子大暑版"
}
</script>

<template>
  <div style="margin: 50px">
    <button @click="changeName">修改姓名</button>
   <p>姓名:{{student.name}}</p>
   <p>年龄:{{student.age}}</p>
   <p>hobby:{{student.hobby}}</p>
  </div>
</template>

image.png 这是一个简单的示例,当我们通过按钮、或者定时任务去改变 name 这个字段时,Vue 要必须要知道 DOMstudent 发生了改变,变的字段是 name,变的结果是 格子大暑版。其实最终还是通过最原始的方法去获取 DOM 然后去更新。只是这些复杂的过程 Vue 帮我们做了。🍒

响应式原理🌽

那么,浏览器怎么才能知道变量什么时候发生了改变呢?是因为浏览器 提供了 Object.defineProperty() 方法和 Proxy 代理对象来监听对象的变化。其中 Vue2 用的Object.defineProperty() Vue3 用的 Proxy 。下面我们具体讨论一下他们的使用和区别,以及 Vue 是怎么操作的。

Object.defineProperty()🍇

基本使用方法

  • Object.defineProperty()方法用于在一个对象上定义一个新属性,或者修改一个已经存在的属性。它接受三个参数:要定义属性的对象、属性的名称和一个描述符对象。描述符对象包含了可以设置属性的各种特性,如value(属性的值)、writable(是否可写)、enumerable(是否可枚举)和configurable(是否可配置)。
  • 例如,定义一个简单的对象属性:
let obj = {};
Object.defineProperty(obj, 'name', {
    value: '格子',
    writable: true,
    enumerable: true,
    configurable: true
});
console.log(obj.name); // 输出: 格子

🍃实现数据劫持(响应式数据的关键应用)🍀🍀🍀

  • Vue2 利用Object.defineProperty()实现数据劫持来构建响应式系统。其核心思想是对对象的每个属性进行重新定义,通过自定义getset访问器来拦截属性的读取和写入操作。
  • 例如,创建一个简单的响应式数据对象:
let data = {
    count: 0
};
let _count = data.count;
Object.defineProperty(data, 'count', {
get: function () {
    return _count;
},
set: function (newValue) {
    _count = newValue;
    // 在这里可以添加更新视图的逻辑,比如调用更新函数
    console.log('数据发生变化,count的值为:', newValue);
}
});
data.count = 1; // 会触发set访问器,控制台会输出相关信息

局限性

  • 只能对对象已有的属性进行数据劫持。如果要对新添加的属性进行响应式处理,需要额外的方法(如 Vue 中的$set方法)。
  • 无法直接监听数组的一些方法(如pushpop等),需要对数组的方法进行重写来实现响应式监听。

Proxy🍭

基本使用方法

  • Proxy是一个构造函数,用于创建一个对象的代理。它接受两个参数:目标对象和一个处理程序对象(handler)。处理程序对象定义了代理的各种行为,例如拦截属性访问、赋值、函数调用等操作。
  • 例如,创建一个简单的代理对象:
let target = {
    name: '格子'
};
let handler = {
    get: function (target, property) {
        return target[property];
    },
    set: function (target, property, value) {
        target[property] = value;
        console.log('属性发生变化,新值为:', value);
        return true;
    }
};
let proxy = new Proxy(target, handler);
console.log(proxy.name); // 输出: 格子
proxy.name = '小明'; // 会触发set拦截器,控制台会输出相关信息

对比 Object.defineProperty() 优势🐝

  • 可以代理整个对象,包括对新添加的属性进行拦截。不需要像Object.defineProperty()那样对每个新属性单独进行处理。
  • 可以更灵活地拦截各种操作,不仅是属性的访问和赋值。例如,可以拦截in操作符、delete操作符等。
  • 对于数组的操作也能很好地拦截,因为它是对整个对象进行代理,而不是像Object.defineProperty()那样对数组属性有局限性。

完结

🪲🪲最后,希望通过本文大家能够理解 Vue 的响应式。欢迎大家在评论区讨论留言,共同进步是我们的愿望~🌹🌹🌹