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' 属性被设置为 falsefor (let key in person) {console.log(key + ': ' + person[key]); // 只会输出 'name: John',因为 'enumerable' 属性被设置为 true}delete person.name; // 这行代码不会删除 'name' 属性,因为 'configurable' 属性默认为 falseconsole.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