请问以下写法对吗?会出现什么问题?
1、数据代理
简单的说,数据代理是一种机制,使得我们可以通过一个对象来访问另一个对象的属性,并修改其属性。在Vue2中,这种数据代理体现在Vue实例(vm)对data配置项的处理上。当创建一个Vue实例时,data配置项中的属性会被代理到vm实例上,这样我们就可以通过vm实例直接访问和修改data中的属性。
具体来说,Vue2通过Object.defineProperty方法为vm实例的每个属性添加getter和setter函数,从而实现数据的读取和修改。当访问vm实例的属性时,会触发getter函数,从而返回data中对应的属性值;当修改vm实例的属性时,会触发setter函数,从而更新data中对应的属性值。这种机制使得Vue能够实时地监听数据的变化,并在数据变化时触发相应的更新操作。
1.1数据代理的实现
基于 Object.defineProperty() 方法,具体可参考developer.mozilla.org/zh-CN/docs/…
基本语法:Object.defineProperty(obj, prop, descriptor)
其中:
- obj:要定义属性的对象(目标对象)。
- prop:要定义或修改的属性的名称(目标属性名)。
- descriptor:一个对象,它定义了要定义或修改的属性的描述符。
- 描述符对象包含了四个可选属性:value、writable、enumerable 和 configurable:
- value:属性的值。如果未定义,属性的默认值为 undefined。
- writable:如果为 true,则属性值可以被赋值运算符改变;否则它是只读的。
- enumerable:如果为 true,则属性会出现在对象的枚举属性中;否则不会。
- configurable:如果为 true,则该属性描述符的配置可以更改,同时该属性也可以从对应对象中删除;否则它是不可配置的。
简化版的模拟Vue2的数据代理
function Vue(options) {
this._data = options.data;
数据代// 实现理
for (let key in this._data) {
Object.defineProperty(this, key, {
get: function() {
return this._data[key];
},
set: function(value) {
this._data[key] = value;
}
});
}
}
const vm = new Vue({
data: {
message: "Hello, Vue!",
auther:"wangfei",
}
});
console.log(vm.message); // 通过数据代理访问属性
vm.message = "Hello, Vue 2.0!"; // 通过数据代理修改属性
console.log(vm._data.message); // 通过原始 data 访问属性(仍然可行)
console.log(vm.message); // 通过原始 data 访问属性(仍然可行)
2、数据劫持
数据劫持是指在访问或修改对象的属性时,对这些操作进行拦截和监视,以便在属性发生变化时能够触发相关的操作。Vue2 通过在数据对象的属性上使用 Object.defineProperty 来实现数据劫持,每当访问属性或修改属性时,Vue 会触发相应的 get 和 set 拦截器,从而实现对数据变化的监听。通过数据劫持,Vue 能够在属性发生变化时自动触发视图的更新,从而实现了响应式的特性。
2.1 数据劫持的实现
简化版模拟数据劫持的例子
class Dep {
constructor() {
this.subs = []; // 存储订阅者的数组
}
// 添加订阅者
addSub(sub) {
if (!this.subs.includes(sub)) {
this.subs.push(sub);
}
}
// 通知所有订阅者更新
notify() {
this.subs.forEach(sub => {
sub.update();
});
}
}
class Watcher {
constructor(expr, cb) {
this.expr = expr;
this.cb = cb;
this.value = this.get(); // 初始化时执行 get 方法,触发依赖收集
}
// 获取数据并收集依赖
get() {
Dep.target = this; // 将当前订阅者设置为全局目标
const value = this.expr(); // 假设 expr 是一个返回数据属性的函数
Dep.target = null; // 清理全局目标
return value;
}
// 更新方法,当数据变化时调用
update() {
this.value = this.get(); // 重新获取数据并触发依赖收集
this.cb(this.value); // 执行回调函数,更新视图或其他操作
}
}
// 模拟 Vue 的响应式系统
function observer(obj) {
Object.keys(obj).forEach(key => {
let dep = new Dep(); // 为每个属性创建一个依赖
let value = obj[key];
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
if (Dep.target) { // 如果存在全局目标(订阅者)
dep.addSub(Dep.target); // 收集依赖
}
return value;
},
set(newVal) {
if (newVal === value) return;
value = newVal;
dep.notify(); // 通知所有订阅者更新
}
});
});
}
debugger
// 示例用法
let data = { message: 'Hello Vue!' };
observer(data);
// 模拟一个订阅者,当 message 变化时,执行回调函数
new Watcher(() => data.message, newValue => {
console.log(`Message updated to: ${newValue}`);
});
// 修改数据,触发更新
data.message = 'Hello Vue 2!'; // 输出:Message updated to: Hello Vue 2!
上述例子中:
Dep 类用于存储订阅者并通知它们更新。
Watcher 类表示一个订阅者,它在初始化时执行 get 方法以触发依赖收集,并在数据变化时调用 update 方法进行更新。
observer 函数遍历对象的属性,并使用 Object.defineProperty 将它们转换为 getter/setter,从而在访问和修改属性时执行相应的逻辑。
当 Watcher 的 get 方法被调用时,它将 Dep.target 设置为当前实例,从而允许 Dep 收集这个订阅者。当数据属性的 getter 被触发时,如果 Dep.target 存在,则将该订阅者添加到 Dep 的 subs 数组中。
当数据属性的 setter 被触发时,Dep 通知其所有的订阅者执行 update 方法,从而更新视图或执行其他操作。
这个简化的例子展示了 Vue 2 中观察者、依赖收集和订阅者更新机制的基本工作原理。但是在真实的 Vue 2 实现中,这个过程会更加复杂,并且会涉及更多的优化和细节处理。
3、如何给data中的对象添加响应式属性
参考文档:
v2.cn.vuejs.org/v2/api/#Vue…,v2.cn.vuejs.org/v2/guide/re…
原文链接:https://juejin.cn/post/7351708519176896546 作者:王小菲