Vue2和Vue3响应式原理实现的核心

2023-10-14 09:01:06 浏览数 (2)

Vue简介

Vue.js 是一个开源的渐进式 JavaScript 前端框架,主要用于构建用户界面和单页应用程序(SPA)。Vue.js 可以轻松地与其他库或现有项目集成使用,并被认为是开发响应式数据驱动的现代 Web 应用的一种有效方式。

Vue.js 的核心特点:

  1. 响应式数据绑定:Vue.js 可以通过对数据进行双向绑定来响应用户输入和页面变化。
  2. 组件化:Vue.js 使得开发者可以将单个组件变成一个独立的模块,组件性能优良且可以重复利用。
  3. 基于模板的语法:Vue.js 提供了一套模板语法,使得开发者可以简单地编写 HTML 模板,并将其与 Vue.js 组件绑定。

Vue2的响应式原理

Vue2 的响应式原理建立在 ES5 的 Object.defineProperty() 上,该方法可以定义对象的属性,并对其进行劫持,当属性值发生变化时,Vue 可以检测到该变化并重新渲染相应的页面内容。

具体实现原理如下:

  1. 在 Vue 的初始化阶段,Vue 会对传入的 data 对象进行递归劫持,将 data 对象的所有属性都转换成 getter/setter 形式。
  2. 当页面中使用数据时,Vue 会通过访问属性的方式触发 getter 函数,从而将当前的 Watcher (观察者)对象加入到当前属性的依赖中。
  3. 当数据发生变化时,Vue 会通过监听器检测到变化,并触发对应属性的 setter 函数,从而通知该属性下所有的依赖 Watcher 更新;
  4. Watcher 对象被通知后,会向对应的组件发送消息通知需要重新渲染视图,从而实现整个页面的更新。

需要注意,Vue2 只能监听对象属性的变化,并不能监听到添加/删除对象属性、数组方法的变化,因此我们可以使用 Vue.set() 或者 Vue.delete() 方法来更新对象属性,但是只能使用原生 JavaScript 数组的 push()pop()splice()shift()unshift() 方法等来操作数组。

Vue2数据劫持的示例代码如下:

代码语言:javascript复制
const data = { msg: 'Hello Vue' }

Object.defineProperty(data, 'msg', {
  get() {
    console.log('get');
    return val;
  },
  set(newValue) {
    console.log('set', newValue);
    val = newValue;
  }
})

Object.defineProperty()详解

Object.defineProperty() 是在 ES5 中新增的一个方法,用于为对象定义新的属性或修改对象的属性,其语法如下:

代码语言:javascript复制
Object.defineProperty(obj, prop, descriptor)

其中的参数含义:

  • obj:要定义属性的对象。
  • prop:要定义或修改的属性的名称。
  • descriptor:需要定义或修改的属性描述符对象。

属性描述符对象中包含以下可选属性:

  • value:属性的值,默认为 undefined。
  • writable:如果为 true,则该属性的值可以被赋值运算符改变,默认为 false。
  • enumerable:如果为 true,则该属性可以在枚举对象属性时被枚举,默认为 false。
  • configurable:如果为 true,则可以使用 Object.defineProperty() 方法修改该属性的描述符,默认为 false。
  • get:属性读取方法。
  • set:属性赋值方法。

Object.defineProperty()的缺点

  • 无法监听数组的变化 Vue2 把会修改原来数组的方法定义为变异方法。 变异方法例如 push、pop、shift、unshift、splice、sort、reverse等,是无法触发 set 的。 非变异方法,例如 filter,concat,slice 等,它们都不会修改原始数组,而会返回一个新的数组。 Vue2 的做法是把这些变异方法重写来实现监听数组变化。
  • 必须遍历对象的每个属性 使用 Object.defineProperty 多数情况下要配合 Object.keys 和遍历,于是就多了一层嵌套。 并且由于遍历的原因,假如对象上的某个属性并不需要“劫持”,但此时依然会对其添加“劫持”。
  • 必须深层遍历嵌套的对象 当一个对象为深层嵌套的时候,必须进行逐层遍历,直到把每个对象的每个属性都调用 Object.defineProperty() 为止。

Vue3的响应式原理

Vue3 的响应式原理主要使用了 ES6 的 Proxy 代替了 Vue2 中的 Object.defineProperty(),从而实现了更加高效和强大的数据劫持和响应式。

Proxy 对象可以通过对访问和修改数据的拦截来实现数据劫持。而 Reflect 对象则提供了更加灵活和易用的数据操作方法,比如可以使用 Reflect.has() 来检查对象是否有某个属性,使用 Reflect.defineProperty() 来代替 Object.defineProperty()。

具体实现原理如下:

  1. 在 Vue3 的初始化阶段,Vue3 会对传入的 data 对象通过使用 Proxy 对象进行代理,即使用 new Proxy(target, handler),其中 target 是被代理的对象,handler 是一个对象,用来定义代理 target 中的操作。
  2. 当页面中使用数据时,Vue3 会触发 get 操作,代理对象 handler.get() 会被调用,进而让 handler 捕获该操作,并将当前的 Watcher (观察者)对象加入到当前属性的依赖中。
  3. 当数据发生变化时,Vue3 会通过监听器检测到变化,并触发对应数据的 set 操作,代理对象 handler.set() 会被调用,从而通知该属性下所有依赖的 ‘Watcher’ 对象更新;
  4. Watcher 对象被通知后,会向对应的组件发送消息通知需要重新渲染视图,从而实现整个页面的更新。

Vue3 中使用 Proxy 对象实现数据响应式的代码如下:

代码语言:javascript复制
const data = { msg: 'Hello Vue' }

const reactiveData = new Proxy(data, {
  get(target, key) {
    console.log('get');
    return target[key];
  },
  set(target, key, value) {
    console.log('set');
    target[key] = value;
    return true;
  }
})

上述代码中,data 对象通过 Proxy 对象 reactiveData 进行代理,当访问 reactiveData 对象的属性时,Proxy 对象内部的 get() 函数被调用;当设置属性时,Proxy 对象内部的 set() 函数被调用。在 get() 和 set() 函数中,可以对属性的读取和赋值进行拦截,从而实现数据的响应式。

Proxy详解

Proxy 是在 ES6 中新增的一个对象,用于代理另一个对象并拦截该对象的读取、赋值、属性定义等一系列操作,其语法如下:

代码语言:javascript复制
new Proxy(target, handler)

Proxy中的参数:

  • target:被代理的目标对象。
  • handler:一个对象,其属性是钩子函数(trap),用于拦截代理对象的操作。

handler 包含以下可选钩子函数(trap):

  • get(target, prop):用于拦截对象的读取操作。
  • set(target, prop, value):用于拦截对象的赋值操作。
  • has(target, prop):用于拦截 in 操作。
  • deleteProperty(target, prop):用于拦截 delete 操作。
  • apply(target, thisArg, args):用于拦截函数调用。
  • construct(target, args):用于拦截 new 操作。

Proxy的优点

  • Proxy可以直接监听对象而非属性;
  • Proxy可以直接监听数组的变化;
  • Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty只能遍历对象属性直接修改;

0 人点赞