vue响应式原理是Vue的核心特性之一,数据驱动视图,我们修改数据视图随之响应更新。注意响应式原理和数据的双向绑定原理是两回事,数据的双向绑定是指v-model,是vue的一个语法糖,不要混淆!
vue响应式原理
Vue 的响应式原理是核心是通过 ES5 的 Object.defindeProperty
(3.x是es6的Proxy)中的访问器属性中的 get 和 set 方法,data 中声明的属性都被添加了访问器属性,当读取 data 中的数据时自动调用 get 方法,当修改 data 中的数据时,自动调用 set 方法,检测到数据的变化,会通知观察者 Wacher,观察者 Wacher自动触发重新render 当前组件(子组件不会重新渲染),生成新的虚拟 DOM 树,Vue 框架会遍历并对比新虚拟 DOM 树和旧虚拟 DOM 树中每个节点的差别,并记录下来,最后,加载操作,将所有记录的不同点,局部修改到真实 DOM树上。
Observer
遍历数据对象,给所有属性加上setter
和getter
,监听数据的变化compile
解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
Watcher
订阅者是Observer
和Compile
之间通信的桥梁,主要做的事情
- 在自身实例化时往属性订阅器 (
dep
) 里面添加自己 - 待属性变动
dep.notice()
通知时,调用自身的update()
方法,并触发Compile
中绑定的回调。
vue2.x 数据劫持的实现
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
//拦截get,当我们访问data.key时会被这个方法拦截到
get: function getter () {
//我们在这里收集依赖
return obj[key];
},
//拦截set,当我们为data.key赋值时会被这个方法拦截到
set: function setter (newVal) {
//当数据变更时,通知依赖项变更UI
}
})
通过Object.defineProperty为对象obj添加属性,设置对象属性的getter和setter函数,每次在获取对象属性时会触发getter函数,每次在修改属性值时,触发setter函数,在该函数中会通知订阅者,从而触发视图更新。
Vue2.x对数组方法的重写
在2.x中,vue可以检测对象属性的修改,但无法监听数组的所有变动及对象的新增和删除,只能使用数组变异方法及$set
方法,我们看下源码的实现
/*
* not type checking this file because flow doesn't play well with
* dynamically accessing methods on Array prototype
*/
import { def } from '../util/index'
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// notify change
ob.dep.notify()
return result
})
})
可以看到,arrayMethods
首先继承了 Array
,然后对数组中所有能改变数组自身的方法,如 push
、pop
等这些方法进行重写。重写后的方法会先执行它们本身原有的逻辑,并对能增加数组长度的 3 个方法 push
、unshift
、splice
方法做了判断,获取到插入的值,然后把新添加的值变成一个响应式对象,并且再调用 ob.dep.notify()
手动触发依赖通知。
Vue3.x数据劫持的实现
3.x和2.x的核心思想一致,只不过数据劫持的方法使用的是Proxy,因为Proxy在处理数组和新增属性的响应式处理更加方便。
let nObj=new Proxy(obj,{
//拦截get,当我们访问nObj.key时会被这个方法拦截到
get: function (target, propKey, receiver) {
console.log(`getting ${propKey}!`);
return Reflect.get(target, propKey, receiver);
},
//拦截set,当我们为nObj.key赋值时会被这个方法拦截到
set: function (target, propKey, value, receiver) {
console.log(`setting ${propKey}!`);
return Reflect.set(target, propKey, value, receiver);
}
})