Object.defineProperty
通过Object.defineProperty方法进行数据代理, 用vm对象的属性来代理data对象的属性
方法案例
/*
此方法用于定义或修改对象属性的方法。它允许你精确地控制属性的行为,
包括属性的值、可枚举性、可配置性和可写性。
接受三个参数:1 要定义属性的对象、2属性名以及一个3描述符对象。
描述符对象包含了你想要定义的属性的特性。描述符对象的属性包括:
value: 属性的值,默认为 undefined。
writable: 属性是否可写,默认为 false。
enumerable: 属性是否可枚举,默认为 false。
configurable: 属性是否可配置,默认为 false。*/
let person = {} // 定义一个空对象
Object.defineProperty(person, 'name', {
value: 'ZhangSan',
writable: false,
enumerable: true,
configurable: false,
})
console.log(person.name);
person.name = "Bob" // 这行代码不会修改 'name' 属性的值,因为 'writable' 属性被设置为 false
for (let key in person) {
console.log(key + ': ' + person[key]); // 只会输出 'name: John',因为 'enumerable' 属性被设置为 true
}
delete person.name; // 这行代码不会删除 'name' 属性,因为 'configurable' 属性默认为 false
console.log('name' in person); // 输出 true,'name' 属性仍然存在于对象中
观察者Observer 订阅者Watcher模式
在Observer类中递归遍历data对象,对data对象中的每个属性都进行数据劫持,都指定一个getter、setter。
例外的,对于数组,不能通过object.defineProperty()进行数据代理,因为监听的数组下标变化时会出现数据错乱问题,所以数组是调用数组重写的原生方法来实现响应式。
当通过vm对象修改data对象中的属性时,会触发data属性的setter方法,然后触发它Dep实例的notify方法进行依赖分发,通知所有依赖的Watcher实例执行内部回调函数。
最后会触发renderWatcher回调,会重新执行render函数,重新对比新旧虚拟DOM,重新渲染页面。
模拟Observer类和 watcher 工作原理:
//简单的示例,展示了如何使用观察者模式实现数据劫持:
// 观察者类
class Observer {
constructor(data) {
this.data = data;
this.observe(data)
}
// 监听
observe(obj) {
// 判断是否是一个对象类型
if (!obj || typeof obj !== 'object') {
return
}
Object.keys(obj).forEach(key => {
// 拿到对象中的每一个属性
console.log(obj, key, obj[key], '拿到对象中的每一个属性--------');
this.defineReactive(obj, key, obj[key])
// 是否有多层嵌套。
this.observe(obj[key])
})
}
// 定义对象的响应式属性的方法
defineReactive(obj, key, value) {
let _this = this;
let dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
// 收集依赖
if (Dep.target) {
dep.addSub(Dep.target);
}
return value;
},
set(newValue) {
if (newValue === value) {
return;
}
value = newValue;
// 通知所有订阅者
dep.notify();
}
});
}
}
// 代表一个依赖(Dependency)管理器 用于管理和通知订阅者
class Dep {
constructor() {
this.subs = [];
}
// 添加订阅者
addSub(sub) {
console.log(sub, '添加订阅者-----------');
this.subs.push(sub);
}
// 通知订阅者
notify() {
this.subs.forEach(sub => {
sub.update();
});
}
}
// 订阅者
class Watcher {
constructor(vm, key, changeFunc) {
this.vm = vm;
this.key = key;
this.changeFunc = changeFunc;
Dep.target = this;
this.vm[this.key]; // 触发 getter,收集依赖 没有此代码 订阅者不会被添加 也不会调用update方法
Dep.target = null;
}
update() {
console.log(this.vm, this.vm[this.key], '更新中---------');
// call 是函数对象的一个方法,它允许你调用一个函数并指定函数内部 this 的值,以及传递其他参数。
this.changeFunc.call(this.vm, this.vm[this.key]);
}
}
// 用法示例
let data = {
name: 'Alice',
age: 30
};
// 创建观察者类 观察data中数据发生变化
let observer = new Observer(data);
// 创建 Watcher 实例 订阅者订阅data中name属性是否发生改变 并且改变的时候执行什么操作
let watcher = new Watcher(data, 'name', function (value) {
console.log('Name changed:', this, value);
});
// 修改数据,触发通知
console.log(data.name, '更改前----------');
data.name = 'Bob'; // 输出: Name changed: Bob