Vue响应式系统基本原理
Object.defineProperty
Object.defineProperty(obj, prop, descriptor)
这是实现响应式的基础,通过对象属性描述对象来控制obj
的prop
{
value: 'static'
writable: true,
enumerable: true
configurable: true
get: function(){}
set: function(){}
}
上面六个对象属性的描述属性,可以看作是控制属性的属性
实现Observer
首先定义一个更新函数来模拟视图更新
function updata (val) {
console.log("视图更新了哈~");
}
然后定义一个defineReact
函数通过Object.defineProperty(obj, prop, descriptor)
,来进行数据劫持,实现对象的响应化。
当一个对象的属性一旦定义了取值函数get
(或存值函数set
),就不能将writable
属性设为true
,或者同时定义value
属性,否则会报错。
经过defineReact
函数处理后,对象读取属性时会触发get,修改属性值时会触发set
function defineReact (obj, key, value) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactGetter () {
return value;
},
set: function reactSetter (newValue) {
if (newValue === value) return;
updata(newValue);
}
});
通过递归遍历将一个obj的属性都转化为由存取器控制,为方便理解去掉递归的过程
function observer (targetObj) {
if (!targetObj || (typeof targetObj !== 'object')) {
return;
}
Object.keys(targetObj).forEach((key) => {
defineReact(targetObj, key, targetObj[key]);
});
}
最后来看看在Vue中的实现
class Vue {
//Vue的构造函数
constructor(options) {
this._data = options.data;
observer(this._data);
}
}
我们在使用Vue生成实例的时候,就会将data中的数据实现Observe,响应化
const obj = new Vue({
data: {
text: 'hello'
}
})
总结,一旦触发setter函数,Vue将会自动判断是否要触发视图更新函数,来更新页面,实现响应式。
响应式系统的依赖收集
依赖收集会让 data中的某个数据知道有多少个地方依赖我的数据,在我变化的时候需要通知它们。
创建一个发布订阅模式GatherWatcher来接收依赖数据的更新,它的主要作用就是将在Vue实例中依赖data中某个数据的所有地方(watcher
)收集起来,统一管理。
class GatherWatcher {
constructor () {
/* 用来存放Watcher对象的数组 */
this.subs = [];
}
/* 在subs中添加一个Watcher对象 */
addSub (sub) {
this.subs.push(sub);
}
/* 通知所有Watcher对象更新视图 */
notify () {
this.subs.forEach((sub) => {
sub.update();
})
}
}
接下来我们修改一下 defineReact
以及 Vue 的构造函数,来完成依赖收集。
function defineReactive (obj, key, val) {
//一个watchers对象,来收集对某个数据的的watcher
const watchers = new GatherWatcher();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactGetter () {
//在get的时候将当前的Watcher对象存入watchera的subs中
watchers.addSub(watcher);
return val;
},
set: function reactiveSetter (newVal) {
if (newVal === val) return;
// 在set的时候触发watchers的notify来通知所有的watcher对象更新视图
watchers.notify();
}
});
}
class Vue {
constructor(options) {
this._data = options.data;
observer(this._data);
/* 新建一个Watcher观察者对象,这时候wacther会指向这个Watcher对象 */
new Watcher();
console.log('render', this._data.test);//模拟render
}
}
总结一下
1. 通过`Object.defineProperty` 把这些属性全部转为 `getter/setter`,进行数据劫持
2. 在某个数据`getter`时,通过订阅,收集对这个数据的依赖者们(watcher)
3. 在数据setter时,发布更新`Notify`,通知这个数据的订阅者们进行更新,重新渲染。