// vue属性监测变更原理
// 概述: 在使用vue中我们在data中定义的属性,能够在改变后,页面能够监听这些变化,下面演示属性监测变更的一些原理。
// 1、定义一个对象,用get、set改写属性, 即在get和set可以定义扩展的功能,而vue属性监测变更主要是通过同一个Dep对象
// 实现get,set的变更监听。即get时(如页面使用属性时),包装使用这些者属性为一个(Watch对象),添加到Dep中,而set时
//,调用这些所有, 添加的监听。
var obj = {
id:1,
name:'name',
price:3000,
};
var value = obj.price;
class DepDemo {
depend () {
// 如果调用get访问器,那么就是使用这个属性的,就添加到依赖
}
notify() {
// 如果set访问器执行,那么就通知所有添加的依赖对象。
}
};
var depDemo = new DepDemo();
Object.defineProperty(obj, 'price', {
enumerable: true,
configurable: true,
get() {
console.log('price属性被读取了')
depDemo.depend();
return value
},
set(newVal) {
console.log('price属性被修改了')
value = newVal;
depDemo.notify();
}
});
obj.price = 3000; // price属性被读取了
var price = obj.price; // price属性被读取了
// ...............................................................................
// 2、完整案例
// 类组成
// Observer :观察者对象,将一个对象的所有属性变成get和set访问器模式。
// Dep : 通过一个subs数组维护依赖对象,主要有depend添加依赖,notify通知所有依赖变更。
// Watcher :监听对象,主要有get方法获取属性,添加自身到dep中,update方法为notify通知的变更
// 备注:一个Observer对象对应一个子属性,并且对应了一个Dep对象,而Dep可以添加多个Watcher对象
// 主要流程:当一个Watcher对象使用get方法获取属性,把自身设置成全局对象,最终调用的是Observer的get方法
// 此时Observer会把Watcher添加到Dep对象中,而这个属性变更,又会由Observer的set到Watcher的update变更。
// Observer
class Observer {
constructor(value) {
this.value = value;
if(Array.isArray(value)) {
}else {
this.walk(value)
}
}
walk(obj) {
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
defineReactive(obj,keys[i]);
}
}
}
function defineReactive(obj, key, val) {
if (arguments.length === 2) {
val = obj[key];
}
if(typeof val === 'object') {
new Observer(val); // 递归操作
}
const dep = new Dep()
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
dep.depend();
return val;
},
set(newVal) {
if(val===newVal) {
return
}
val = newVal;
dep.notify();
}
});
}
// Dep依赖收集
class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
removeSub(sub) {
remove(this.subs, sub)
}
// --当依赖调用者获取属性时,会把自身设置到window.target属性
depend () {
if (window.target) {
this.addSub(window.target)
}
}
notify() {
console.log('更新操作')
const subs = this.subs;
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
function remove (arr, item) {
if (arr.length) {
const index = arr.indexOf(item)
if (index > -1) {
return arr.splice(index, 1)
}
}
}
// Watcher对象
class Watcher {
constructor (vm, expOrFn, cb) {
this.vm = vm;
this.cb = cb;
this.getter = parsePath(expOrFn)
this.value = this.get()
}
get () {
window.target = this;
const vm = this.vm
let value = this.getter.call(vm, vm)
window.target = undefined;
return value
}
update () {
const oldValue = this.value
this.value = this.get()
this.cb.call(this.vm, this.value, oldValue);
}
}
const bailRE = /[^\w.$]/
function parsePath (path) {
if (bailRE.test(path)) {
return
}
const segments = path.split('.');
return function (obj) {
for (let i = 0; i < segments.length; i++) {
if (!obj) return
obj = obj[segments[i]]
}
return obj
}
}
// 定义window来全局临时挂载target属性。
var window = {}
var vue = {
id:'id',
name:'name',
}
new Observer(vue);
new Watcher(vue, 'id', (val, oldValue)=>{
console.log(val, oldValue);
});
vue.id = 2;