- vue2 响应式原理
Vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty()来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。主要分为以下几个步骤:
- 需要 observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter 和 getter 这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化
- compile 解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
- Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁,主要做的事情是: ① 在自身实例化时往属性订阅器(dep)里面添加自己 ② 自身必须有一个 update()方法 ③ 待属性变动 dep.notice()通知时,能调用自身的 update()方法,并触发 Compile 中绑定的回调,则功成身退。
- MVVM 作为数据绑定的入口,整合 Observer、Compile 和 Watcher 三者,通过 Observer 来监听自己的 model 数据变化,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observer 和 Compile 之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据 model 变更的双向绑定效果。
- vue3 响应式原理
Vue3 使用 Proxy 来监控数据的变化。Proxy 是 ES6 中提供的功能,其作用为:用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等).相对于 Object.defineProperty() 其有以下特点:Proxy 直接代理整个对象而非对象属性,这样只需做一层代理就可以监听同级结构下的所有属性变化,包括新增属性和删除属性。
Proxy 可以监听数组的变化。
怎么理解响应式?
总结一下:
- 任何一个 Vue Component 都有一个与之对应的 Watcher 实例。
- Vue 的
data
上的属性会被添加 getter 和 setter 属性。 - 当 Vue Component
render
函数被执行的时候,data
上会被触碰(touch)
, 即被读
,getter
方法会被调用, 此时 Vue 会去记录此 Vue component 所依赖的所有data
。(这一过程被称为依赖收集) data
被改动时(主要是用户操作), 即被写
,setter
方法会被调用, 此时 Vue 会去通知所有依赖于此data
的组件去调用他们的 render 函数进行更新
响应性是一种可以使我们声明式地处理变化的编程范式
let A0 = 1
let A1 = 2
let A2 = A0 + A1
console.log(A2) // 3
A0 = 2
console.log(A2) // 仍然是 3
分析
let A2
function update() {
A2 = A0 + A1
}
当 A0, A1 的值改变时,就去自动执行 update
然后,我们需要定义几个术语:
- 这个
update()
函数会产生一个副作用,或者就简称为作用,因为它会更改程序里的状态。 A0
和A1
被视为这个作用的依赖,因为它们的值被用来执行这个作用。因此这次作用也可以说是一个它依赖的订阅者。
我们无法直接追踪对上述示例中局部变量的读写过程,在原生 JavaScript 中没有提供这样一种机制。但是,我们是可以追踪一个对象的属性进行读和写的。
Object.defineProperty
v2 的用法
基本使用
Object.defineProperty(obj, 'a', {
set(val) {
console.log('obj.a被赋值...')
},
get() {
console.log('obj.a被访问...')
},
})
封装函数
function defineReactive(obj, key, value) {
Object.defineProperty(obj, key, {
set(val) {
console.log('obj.a被赋值...')
value = val
},
get() {
console.log('obj.a被访问...')
return value
},
})
}
let obj = { a: 1, b: 2 }
defineReactive(obj, 'a', obj['a'])
循环+递归
function defineReactive(obj, key, value) {
Object.defineProperty(obj, key, {
set(val) {
console.log(key, '被赋值...')
value = val
},
get() {
console.log(key, '被访问...')
return value
},
})
}
var obj = { a: 1, b: 2, c: { c1: 0 } }
function walk(obj) {
for (let key in obj) {
let value = obj[key]
if (typeof value === 'object') {
walk(value)
} else {
defineReactive(obj, key, value)
}
}
}
walk(obj)
v2 的问题
- 深度监听需要一次性递归;
- 无法监听对象新增|删除的属性
- 无法监听数组的元素操作
Proxy
可以有效解决上面的三个问题
let obj = { a: 1, b: 2, c: { c1: 0 } }
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
console.log(key, '被访问')
let value = target[key]
if (typeof value === 'object') {
return reactive(value)
} else {
return target[key]
}
},
set(target, key, newValue) {
target[key] = newValue
console.log(key, '被设置')
},
})
}
let newObj = reactive(obj)