前言
Vue 3 的核心部分可以分为三个主要模块:Compiler、Reactivity 和 Runtime。响应式的处理逻辑在 Reactivity 部分。
Compiler(编译器):Template
=> 渲染函数
将 Vue 的模板(Template)转换成 JavaScript 渲染函数。
在 Vue 3 中,编译器通过 @vue/compiler-sfc
提供的工具将 .vue 文件中的模板、脚本和样式拆分开,并编译为 JavaScript 代码。模板会被转换成渲染函数(render function),这个渲染函数会在 Runtime
中执行来生成虚拟 DOM。
Runtime(运行时)
负责执行渲染函数、更新虚拟 DOM、协调更新视图、处理生命周期钩子等。运行时并不涉及模板的编译,它负责 Vue 应用的整体生命周期和视图更新。
虚拟 DOM:渲染函数生成的虚拟 DOM 会通过 diff 算法与现有虚拟 DOM 对比,然后更新到真实 DOM。
组件生命周期:通过运行时机制控制组件的生命周期钩子(如 mounted, updated 等)。
事件处理:事件绑定、事件触发等操作也由 runtime 负责。
Reactivity(响应式系统)
实现 Vue 3 的响应式数据机制,即在数据发生变化时自动更新视图。Vue 3
引入了 Proxy
作为响应式的核心实现方式,替代了 Vue 2
中的 Object.defineProperty
。
关键点:
- reactive:将对象变成响应式对象。
- ref:将基本数据类型(如字符串、数字)包装成响应式对象。
- effect:自动追踪依赖,当数据变化时自动更新视图。
- computed:用于创建依赖其他响应式数据的计算属性。
Vue2对于响应式的处理
Vue2 使用 Object.defineProperty,缺点如下:
- 针对对象的某个属性,劫持整个对象则需要遍历,性能不如Proxy,具体可看本人另一篇文章:为什么Proxy性能优于Object.defineProperty
- 无法劫持对象 新增删除属性,需要单独重写原型方法
Vue3对于响应式的处理
Vue3:通过 Proxy和Reflect 对整个对象进行劫持,主要代码如下:
get(target,key,receiver){
const result = Reflect.get(target,key,receiver)
track(traget,key) // 追踪
return isObject(result) ? reactive(result):result
},
set(target,key,value,receiver){
const oldValue = target[key]
const result = Reflect.set(target,key,value,receiver)
if(oldValue !== value){
trigger(target,key) // 更新
}
return result
}
track 响应式数据依赖追踪
trigger 触发更新
使用 Reflect
是为了解决 this 带来的问题,通过如下示例说明:
const obj = {
a: 1,
b: 2,
get c() {
console.log(this)
return this.a + this.b
},
}
const handler = new Proxy(obj, {
get(target, key, receiver) {
console.log("get", key)
return target[key]
},
})
handler.c
这个示例中,明明访问器成员c也依赖了a和b,但是却无法正常劫持,这是因为 Proxy 只拦截 直接的属性访问,而不会拦截通过 访问器 方法(getter)调用的属性。
所以需要通过 Reflect.get 使用对象的原始方法 [[get]],确保访问器方法依赖属性的
const obj = {
a: 1,
b: 2,
get c() {
console.log(this)
return this.a + this.b
},
}
const handler = new Proxy(obj, {
get(target, key, receiver) {
console.log("get", key)
return Reflect.get(target, key, receiver)
},
})
handler.c