数据劫持
数据劫持(Data Observation)是 Vue.js 中实现响应式的核心机制之一。它通过
Object.defineProperty()
方法来实现对数据对象的属性进行劫持,从而监听属性的读取和写入操作,实现了对数据的观测。
// 定义 Observer 类
function Observer(data) {
// 遍历数据对象的所有属性
for (var key in data) {
if (data.hasOwnProperty(key)) {
defineReactive(data, key, data[key]); // 对每个属性进行劫持
}
}
}
// 定义 defineReactive 函数,用来对属性进行劫持
function defineReactive(obj, key, val) {
// 为每个属性创建一个 Dep 对象
var dep = new Dep();
// 递归地对属性值进行劫持,使其也变成响应式
var childOb = observe(val);
// 使用 Object.defineProperty() 方法劫持属性
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function() {
if (Dep.target) {
dep.depend(); // 收集依赖
if (childOb) {
childOb.dep.depend(); // 收集子对象的依赖
}
}
return val; // 返回属性值
},
set: function(newVal) {
if (val === newVal) {
return; // 如果新值与旧值相同,则不进行更新
}
val = newVal; // 更新属性值
dep.notify(); // 通知所有订阅者进行更新
}
});
}
// 定义 observe 函数,用来递归地对属性值进行劫持
function observe(value) {
if (!value || typeof value !== 'object') {
return; // 如果值为空或者不是对象,则不进行劫持
}
return new Observer(value);
}
// 定义 Dep 类,用来收集和管理依赖
function Dep() {
this.subs = []; // 存储所有订阅者
}
// Dep 类的原型方法,用来添加订阅者
Dep.prototype.addSub = function(sub) {
this.subs.push(sub);
};
// Dep 类的原型方法,用来移除订阅者
Dep.prototype.removeSub = function(sub) {
var index = this.subs.indexOf(sub);
if (index !== -1) {
this.subs.splice(index, 1);
}
};
// Dep 类的原型方法,用来通知所有订阅者进行更新
Dep.prototype.notify = function() {
this.subs.forEach(function(sub) {
sub.update();
});
};
// 定义 Watcher 类,用来表示订阅者
function Watcher(vm, expOrFn, cb) {
this.vm = vm;
this.expOrFn = expOrFn;
this.cb = cb;
this.value = this.get(); // 获取当前属性值
}
// Watcher 类的原型方法,用来获取当前属性值
Watcher.prototype.get = function() {
Dep.target = this; // 将当前订阅者指定为全局的 target
var value = this.vm[this.expOrFn]; // 获取当前属性值,从而触发 getter
Dep.target = null; // 清除全局的 target
return value;
};
// Watcher 类的原型方法,用来在属性值发生变化时更新视图
Watcher.prototype.update = function() {
var value = this.get(); // 获取最新的属性值
if (value !== this.value) {
this.value = value;
this.cb.call(this.vm, value); // 执行回调函数,更新视图
}
};
// 示例数据
var data = {
message: 'Hello, Vue!'
};
// 创建 Observer 实例,对数据进行劫持
observe(data);
// 创建 Watcher 实例,监视数据的变化
new Watcher(data, 'message', function(value) {
console.log('Message changed: ' + value); // 当数据变化时,输出新值
});
// 修改数据,触发数据劫持和响应式更新
data.message = 'Hello, World!';
case2:
// 发布者
class Dep{
constructor() {
this.subs = [];
}
addSubs(sub) {
if (this.subs.indexOf(sub) < 0) {
// 12.1添加当前watcher实例
this.subs.push(sub);
}
}
notify() {
// 17.通知 watcher 进行更新
this.subs.forEach(item => item.update())
}
}
Dep.target = null;
// 订阅者
class Watcher {
constructor(obj, key, updateCb) {
// 待观察对象
this.data = obj;
// 待观察对象的 key
this.key = key;
// 回调函数
this.updateCb = updateCb;
this.value = null;
// 8. 触发一次 get
this.get();
}
get() {
// 9.将 watcher 实例对象设置为 Dep.target
// 此时 watcher 实例对象包含: {data: {a: 1},key: 'a', updateCb: Function, value: null}
Dep.target = this;
// 10.this.data[this.key] 触发 getter 事件
this.value = this.data[this.key];
// 13.清空当前 watcher 实例
Dep.target = null;
return this.value;
}
update() {
// 18.获取旧的对象属性值
const oldValue = this.value;
// 19.触发一次 get
// 重复步骤8
const newValue = this.get();
// 20.执行回调函数
this.updateCb(newValue, oldValue);
}
}
// observer类 劫持数据
class Observer {
constructor(obj) {
this.data = obj;
// 3.判断是否为对象
if (this.data == null || typeof this.data !== "object") {
return;
}
// 4.判断是否为数组类型
if (Array.isArray(this.data)) {
this.observeArray();
} else {
// 5.对象类型
this.walk();
}
}
walk() {
for (let i in this.data) {
// 6.为每一个key进行响应化设置
// 复写getter/setter方法
this.defineReactive(this.data, i);
}
}
observeArray() {
for (let i = 0; i < this.data.length; i++) {
// 4.1 遍历数组值,为每一个值进行响应式处理
// 重复步骤1
observe(this.data[i]);
}
}
defineReactive(obj, key) {
let val = obj[key];
// 对属性值进行响应式处理
// 非对象类型在 步骤3 直接 return
observe(val);
// 创建一个发布者对象
const dep = new Dep();
Object.defineProperty(obj, key, {
get() {
// 11.Dep.target 为当前 watcher 实例对象
if (Dep.target) {
// 12.加入当前 watcher 实例对象,收集依赖
dep.addSubs(Dep.target)
}
return val;
},
set(newVal) {
if (newVal === val) {
return;
}
val = newVal;
// 15.为新值进行响应化处理
observe(val);
// 16.发布通知数据更新
dep.notify();
}
})
}
}
// 数据监测方法
function observe(data) {
// 2.创建一个Observer对象
new Observer(data);
}
// 写一个最简单的例子
const obj = {
a: 1
};
// 1.将对象设置为响应式
observe(obj);
// 7.创建观察者对象
// 并观察该对象属性值的变化
new Watcher(obj, "a", (newVal, oldVal) => {
// 21.函数回调
console.log("newVal", newVal);
console.log("oldVal", oldVal + '\n');
});
// 14.obj.a 重新赋值
obj.a = 2;
obj.a = 3;