API 回顾 Object.defineProperty
js
var obj = {
id: 101,
name: "tom",
};
// 定义getter/setter
function defineGetterSetter(obj, key) {
var val = obj[key];
Object.defineProperty(obj, {
configurable: false,
enumerable: true,
get() {
// 获取值
console.log("## getter:", val);
return val;
},
set(newVal) {
// 设置值
console.log("## setter:", newVal);
val = newVal;
},
});
}
发布订阅模式
js
// 发布者
class Publisher {
constructor() {
this.subs = [];
}
// 添加订阅者
addSubscriber(sub) {
this.subs.push(sub);
}
// 通知所有定订阅者: 执行 update 方法
// 反过来说, 订阅者必须有 update 方法
notify(){
this.subs.forEach(item => item.update());
}
}
// 订阅者
class Subscriber{
constructor() {
// 第一次执行做一些操作
}
update() {
// Publisher 的通知后,执行更新操作
}
}
vue 响应式实现原理流程
- new Vue 时, 用 Object.defineProperty 给 data 中所有的数据定义 getter/setter
- 利用发布/订阅模式, 在 getter 中收集依赖, 在 setter 通知依赖的订阅者更新, 走到setter中证明数据更新了
vue 如何监听数组变化
有些改变数组本身的方法无法被 Object.defineProperty 监听到: sort,reverse,push,pop,shift,unshift,splice
- 重写数组上的改变数组本身的方法, 类似代理模式, 在自己实现的方法中收集依赖
js
// 代理数组方法: 因为这些方法会改变原数组,
// 而且无法被 Object.defineProperty 监听到
function proxyArrayMethods(arr) {
const methods = ["pop", "push", "shift", "unshift", "splice", "sort", "reverse"];
const arrayProto = Array.prototype;
const middleman = Object.create(arrayProto);
for (const m of methods) {
const original = arrayProto[m];
middleman[m] = function proxyMethod(...args) {
// 执行原来的数组方法
const result = original.apply(this, args);
// 如果调用的新增元素的方法, 需要获取新增的值, 然后观察它
// 让新增的这个元素也具备响应式的功能
let insertedElements;
switch (m) {
case "push":
case "unshift":
insertedElements = args;
break;
case "splice":
insertedElements = args.slice(2);
break;
}
insertedElements && observe(insertedElements);
return result;
};
}
// 给数组设置原型
Object.setPrototypeOf(arr, middleman);
return arr;
}