// 一、 vue 属性检测原理之基本案例
var obj = {
id : 1,
name : 'zhangsan',
price : 20,
}
// var value = obj.price ; // 常规操作属性方式
// 如何在获取或设置属性时能够做其他的操作呢? 此时我们引入一个叫Depend对象,它有二个方法 depend notify
class Depend {
constructor() {}
depend () {
console.log('添加依赖'); // 如果调用get访问器,那么就是使用这个属性的,就添加到依赖
}
notify() {
console.log('通知所有依赖变更') // 如果set访问器执行,那么就通知所有添加的依赖对象。
}
}
// 我们以price属性为例,通过defineProperty设置price的属性访问器模式,如果把所有属性都改成属性器那么就是Observer对象
var depend = new Depend();
var val = obj.price;
Object.defineProperty(obj,'price',{
configurable:true,
enumerable:true,
get() {
depend.depend();
return val;
},
set( newval) {
depend.notify();
val = newval;
}
});
console.log(obj.price); // 添加依赖 20
obj.price = 20; // 通知所有依赖变更
// 二、vue实现原理
// 通过上面我们知道将一个对象所有属性全部变成get/set访问器模式,那么获取值的时候可以添加依赖,而如果值产生
// 变更的时候就会通知所有依赖变更。我们把所有对象都变成set/get的Observer对象,并且实现了Depend依赖管理对象
// ,但是我们还缺少一个监听器Watch对象
// Watch对象就是监听某个对象的属性,通过访问器get获取值的时候,会自动添加依赖到Depend对象中,如果这个属性发生变更
// 即Observer的set方法触发,就会触发depend的notify方法,notify方法实际调用的是Watch对象的update方法。
// 1、Watch 对象最简单演示
var temp = null; // 临时
var depend = null; // 代替depend
class Watch {
constructor(obj,param,callback) {
this.obj = obj;
this.param = param;
this.callback = callback;
temp = this;
this.value = obj[this.param];
temp = null;
}
update () {
const oldValue = this.value
temp = this;
this.value = this.obj[this.param];
temp = null;
this.callback.call(this.obj, this.value, oldValue);
}
}
Object.defineProperty(obj,'price',{
configurable:true,
enumerable:true,
get() { // depend.depend(); // 此处不使用Depend对象
depend = temp;
return val;
},
set( newval) { // depend.notify();
val = newval;
depend.update();
}
});
var watch = new Watch(obj,'price' ,function(value,old) {
console.log(value,old);
});
obj.price = 40; // 40 , 20
// 缺点,我们只用了一个depend变量来建立watch和observer的关系,显然是不行的。
// 我们需要建立一个Depend类来管理所有的依赖关系,并且depend实例对应一个属性。
// 即一个observer对应一个响应式对象,一个depend对应响应式对象一个属性,多个watch对象公用一个depend依赖管理
// 2、完整案例
// 类组成
// Observer :观察者对象,将一个对象的所有属性变成get和set访问器模式。
// Dep: 通过一个subs数组维护依赖对象,主要有depend添加依赖,notify通知所有依赖变更。
// Watcher :监听对象,主要有get方法获取属性,添加自身到dep中,update方法为notify通知的变更
//Observer类
var Observer = function(obj) {
this.obj = obj;
walk(obj);
function 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(); // get方法添加到依赖管理
return val;
},
set(newVal) { // set方法触发所有已经添加的依赖
val = newVal;
dep.notify();
}
});
}
}
let uid = 0; // 计数器
class Dep {
constructor (){
this.id = uid++; // 优化代码
this.subs = [];
}
remove(sub) {
this.subs = this.subs.filter(ele => {
return ele != sub;
});
}
addSub(sub) {
this.subs.push(sub);
}
depend( ) {
if (Dep.target) {
// this.addSub(Dep.target); // 优化代码删除
Dep.target.addDep(this); // 优化代码
}
}
notify() {
var subs = this.subs.slice();
for (let i = 0; i < subs.length; i++) {
subs[i].update();
}
}
}
// Dep类
Dep.target = null;
function pushTarget (target) {
Dep.target = target
}
function popTarget () {
Dep.target = null;
}
// Watcher类
class Watcher {
constructor (vm, expOrFn, callback) {
this.depIds = new Set(); // 优化代码
this.vm = vm;
this.callback = callback;
this.getter = parsePath(expOrFn)
this.value = this.get()
}
get () {
console.log('执行Watcher的get方法()');
Dep.target = this;
const vm = this.vm
let value = this.getter.call(vm, vm)
Dep.target = undefined;
return value;
}
update () {
const oldValue = this.value
this.value = this.get()
this.callback.call(this.vm, this.value, oldValue); // 触发回调函数
}
addDep(dep) { // 优化代码
const id = dep.id;
if (!this.depIds.has(id)) {
this.depIds.add(id);
dep.addSub(this);
}
}
}
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
}
}
var vue = {
id:'1',
name:'zhangsan',
}
new Observer(vue);
new Watcher(vue, 'id', (val, oldValue)=>{
console.log(val, oldValue); // 2 , id
});
vue.id = 2;
console.log('......');
vue.id = 3;
console.log('......');
vue.id = 4;
// 3、优化,从打印结果我们可以看出,我们多次执行了Watcher的get方法()
// 实际上我们只需要注册一次,即可,不需要多次执行。
// 解决办法: 通过Dep定义一个唯一的id,每一个Watcher只能添加一个依赖到同一dep解决。
// 3.1 Dep 添加代码
// let uid = 0; // 计数器
// class Dep {
// constructor (){
// this.id = uid++; // 优化代码
// this.subs = [];
// }
// depend( ) {
// if (Dep.target) {
// // this.addSub(Dep.target); // 优化代码删除
// Dep.target.addDep(this); // 优化代码
// }
// }
// 3.2 watcher 优化代码
// constructor (vm, expOrFn, callback) {
// this.depIds = new Set(); // 优化代码
// addDep(dep) { // 优化代码
// const id = dep.id;
// if (!this.depIds.has(id)) {
// this.depIds.add(id);
// dep.addSub(this);
// }
// }
vue基础扩展9-属性监测变更原理
最新推荐文章于 2023-12-21 09:23:24 发布