看视频瞎写的! 参考了各自各样的信息, 所以有点四不像 :(
订阅者模式
let activeEffect;
class Dep {
constructor(val) {
this._value = val;
this.subscribers = new Set(); // 避免收集相同的依赖
}
get value() {
this.depend(); // 收集当前依赖
return this._value;
}
set value(newValue) {
this._value = newValue;
this.notify(); // 发布消息
}
depend() {
if (activeEffect) {
this.subscribers.add(activeEffect);
}
}
notify() {
this.subscribers.forEach((effect) => {
effect();
});
}
}
function watchEffect(effect) {
activeEffect = effect;
effect();
activeEffect = null;
}
// 使用
const dep = new Dep(10);
let b;
watchEffect(() => {
// 这里依赖 dep 的 value
// 因此调用 Dep 的 set 收集这个依赖(activeEffect)
// 由于都是同步的, 所以依赖会一个一个收集
b = dep.value + 1;
});
watchEffect(() => {
// 这里什么都不依赖
console.log("不被收集");
});
dep.value = 11;
console.log(b); // 12
dep.value = 12;
console.log(b); // 13
defineProperty
let activeEffect;
class Dep {
subscribers = new Set();
depend() {
if (activeEffect) {
this.subscribers.add(activeEffect);
}
}
notify() {
this.subscribers.forEach((effect) => {
effect();
});
}
}
function reactive(raw) {
Object.keys(raw).forEach((key) => {
const dep = new Dep(); // 每个属性都有各自的 dep
let value = raw[key];
// 给 raw 的每个属性绑定 getter 和 setter
Object.defineProperty(raw, key, {
get() {
dep.depend();
return value;
},
set(newValue) {
value = newValue;
dep.notify();
},
});
});
return raw;
}
function watchEffect(effect) {
activeEffect = effect;
effect();
activeEffect = null;
}
// 使用
const a = reactive({
age: 10,
});
let b;
watchEffect(() => {
b = a.age + 1;
});
a.age = 11;
console.log(b);
a.age = 12;
console.log(b);
Proxy
和 defineProperty 不太一样, getter 和 setter 根据 target 和 key 判断, 因此需要有一个地方保存每个属性的 dep
const reactiveHandler = {
get(target, key) {},
set(target, key, value) {},
};
function reactive(raw) {
return new Proxy(raw, reactiveHandler);
}
let activeEffect;
class Dep {
subscribers = new Set();
depend() {
if (activeEffect) {
this.subscribers.add(activeEffect);
}
}
notify() {
this.subscribers.forEach((effect) => {
effect();
});
}
}
const targetMap = new WeakMap();
function getDep(target, key) {
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
let dep = depsMap.get(key);
if (!dep) {
dep = new Dep();
// 将响应式对象存储在 map 中
depsMap.set(key, dep);
}
return dep;
}
const reactiveHandler = {
get(target, key, receiver) {
const dep = getDep(target, key);
dep.depend();
// 类似于 target[key].call(receiver), 为什么后面讲
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const dep = getDep(target, key);
// 先设置新值再通知
const res = Reflect.set(target, key, value, receiver);
dep.notify();
return res;
},
};
function reactive(raw) {
return new Proxy(raw, reactiveHandler);
}
function watchEffect(effect) {
activeEffect = effect;
effect();
activeEffect = null; // 用过一次后就清掉
}
// 使用
const a = reactive({
age: 10,
});
let b;
watchEffect(() => {
b = a.age + 1;
});
a.age = 11;
console.log(b);
a.age = 12;
console.log(b);
console.log(a);
上面写法在 effect 中有 setter 又有 getter 时会造成栈溢出
watchEffect(() => {
a.age = 10;
b = a.age + 1;
});
a.age = 11;
因为 setter 时会执行所有相关的 effect 里的函数, 而执行所有 effect 中函数又会调用 setter, 如此反复...
解决办法在 响应式 effect
Proxy 搭配 Reflect 使 this 总是指向 proxy
const obj = {
name: "obj",
get getName() {
console.log(this === obj); // true
return this.name;
},
};
const proxy = new Proxy(obj, {
get(target, key) {
return target[key];
},
set(target, key, value) {
target[key] = value;
},
});
console.log(proxy.getName);
// proxy.getName 走了一次代理
// this.name 不走代理, 因为 this == obj, 不等于 proxy
const obj = {
name: "obj",
get getName() {
console.log(this === proxy); // true
return this.name;
},
};
const proxy = new Proxy(obj, {
get(target, key, receiver) {
console.log("走了代理");
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
return Reflect.set(target, key, value, receiver);
},
});
console.log(proxy.getName);
// proxy.getName 走了一次代理
// this.name 又走了一次代理
// result:
// 走了代理
// true
// 走了代理
// obj
深度代理
const reactiveHandler = {
get(target, key, receiver) {
// ...
const res = Reflect.get(target, key, receiver);
// 只有 get 的时候才会代理
if (typeof res === 'object' && res !== null) {
return reactive(res); // 是对象, 把对象做成响应式
}
return res;
},
};
判断是否是响应式对象
在第一次创建代理对象时, 给 target 绑定上特定标识
const enum ReactiveFlags {
IS_REACTIVE = "_v_isReactive", // 响应式标识
}
const targetMap = new WeakMap();
function isReactive(target) {
return !!target[ReactiveFlags.IS_REACTIVE];
}
function reactive(raw) {
// 代理过了直接返回
if (isReactive(raw) || !!targetMap.get(raw)) return raw;
return new Proxy(raw, {
get(target, key /* */) {
// 只有代理对象上这个 key 是 true
if (key === ReactiveFlags.IS_REACTIVE) return true;
// 其他逻辑...
},
});
}
响应式 effect
export class Dep {
subscribers = new Set();
depend() {
if (activeEffect) {
this.subscribers.add(activeEffect);
}
}
notify() {
this.subscribers.forEach((effect) => {
// 判断当前正在执行的 activeEffect 是否为 effect
// 避免因为 setter 而反复执行这个 effect
if (activeEffect !== effect) {
effect.run();
}
});
}
}
export let activeEffect = null;
export class ReactiveEffect {
constructor(fn) {
this.active = true;
this.fn = fn;
}
run() {
if (!this.active) {
// 此时 activeEffect == null
// 所以只执行 fn 而不会收集依赖
return this.fn();
}
// 默认 active 为 true, 要收集依赖
activeEffect = this; // 将全局副作用函数变为 this
// 此时 activeEffect 不是 null 了, 就会被收集到
const result = this.fn();
activeEffect = null; // 用过一次后就清空
return result;
}
}
export function effect(fn) {
const _effect = new ReactiveEffect(fn);
_effect.run(); // 默认执行一次
}
上面将 activeEffect 清空会出现问题
这样不支持嵌套操作
effect(() => {
b = a.age + 1;
effect(() => {
c = a.month + 2;
}); // 由于这个结束后 activeEffect 变成 null 了
d = a.year + 3; // 这个依赖就不会被收集了
});
因此使用一个 parent 节点保存上一次的 activeEffect
run() {
// ...
this.parent = activeEffect; // 保存前一次依赖
activeEffect = this;
const result = this.fn();
activeEffect = this.parent; // 恢复前一次依赖
// ...
}
依赖收集优化
const a = reactive({
age: 10,
count: 1,
flag: true,
});
let b;
effect(() => {
console.log("执行");
// flag 为 true 时只收集 flag 和 age 依赖
// flag 为 false 时只收集 flag 和 count 依赖
b = a.flag ? a.age + 1 : a.count + 1;
});
console.log(b);
a.flag = false;
console.log(b);
setTimeout(() => {
a.age = 1;
}, 1000);
// result:
// 执行
// 11
// 执行
// 2
// (1s 后)
// 执行 => 这说明 age 的 effect 还会执行:(
使用数组保存依赖对应的响应式属性
export class ReactiveEffect {
constructor(fn) {
this.active = true;
this.fn = fn;
this.deps = []; // 保存该副作用函数涉及到的所有响应式属性
}
// ...
}
这样在进行依赖追踪的时候保存一下
// getter
const dep = getDep(target, key);
dep.depend(); // 响应式属性收集依赖
activeEffect.deps.push(dep.subscribers); // 对应依赖保存响应式属性
在执行依赖收集前清空之前的依赖(双向清理)
// 这里也可以写成独立于 ReactiveEffect 的函数
export class ReactiveEffect {
// ...
cleanupEffect() {
this.deps.forEach((dep) => {
dep.delete(this); // dep 是一个 Set
});
effect.deps.length = 0; // 把 deps 清空
}
}
export class ReactiveEffect {
run() {
// 其他逻辑...
this.cleanupEffect(); // 收集前清空之前的
const result = this.fn();
}
}
但是这需要改变通知函数
export class Dep {
// ...
notify() {
// 使用临时的 temp 执行通知
// 因为 cleanupEffect 会清掉 subscribers 里的某个 effect
// 但是再调用 this.fn() 后又会重新收集到 subscribers 里
// 删一个又加一个, 就会导致无限循环
// 所以删除前保存一个快照
const temp = new Set(this.subscribers);
temp.forEach((effect) => {
if (activeEffect !== effect) {
effect.run();
}
});
}
}
现在在在 setTimeout 里改 a.age 就不会再执行 effect 了
调度器
export function effect(fn) {
const _effect = new ReactiveEffect(fn);
_effect.run(); // 默认执行一次
const runner = _effect.run.bind(_effect);
runner.effect = _effect;
return runner;
}
let b = 0;
const runner = effect(() => {
b = b + 1;
});
console.log(b); // 1
runner.effect.stop(); // 停止依赖收集
setTimeout(() => {
runner(); // 手动决定执行时机
console.log(b); // 2
}, 1000);
使用调度器 scheduler 优化
// 新增 options 选项
export function effect(fn, options = {}) {
const _effect = new ReactiveEffect(fn, options.scheduler);
// ...
}
export class ReactiveEffect {
constructor(fn, scheduler) {
// 新增 scheduler
this.scheduler = scheduler;
}
}
notify() {
const temp = new Set(this.subscribers);
temp.forEach((effect) => {
if (activeEffect !== effect) {
// 如果有调度函数, 则执行调度函数
if (effect.scheduler) {
effect.scheduler();
} else {
effect.run();
}
}
});
}
// 使用
const runner = effect(
() => {
console.log(a.age);
},
{
scheduler() {
setTimeout(() => {
runner();
}, 1000);
},
}
);
a.age = 100;
setTimeout(() => {
a.age = 111; // 异步操作
}, 1000);
计算属性
export class ComputedRefImpl {
constructor(getter, setter) {
this._dirty = true; // 默认脏数据, 取值时计算
this.setter = setter;
// 使用 effect 收集依赖
this.effect = new ReactiveEffect(getter, (/* 调度器 */) => {
// 这个调度器是 computed 所用到的属性调用的
// 也就是如果有某个响应式属性执行了其 setter, 就执行这个调度器
// 将 computed 的数据变成脏数据
// 当 computed 的属性被 getter 时, 就重新 run 获取新值, 实现了缓存
if (!this._dirty) {
this._dirty = true;
}
});
}
get value() {
// 说明数据是脏的, 需要重新 run 获取新值
if (this._dirty) {
this._dirty = false;
this._value = this.effect.run(); // 执行用户提供的 getter
}
return this._value;
}
set value(n) {
this.setter(n);
}
}
export function computed(options) {
if (typeof options === "function") {
// 只有 getter
return new ComputedRefImpl(options, () => {});
} else {
// 对象
return new ComputedRefImpl(options.get, options.set);
}
}
// 使用
const name = reactive({
first: "xiao",
last: "ming",
});
const full = computed(() => {
return name.first + " " + name.last;
});
console.log(full.value); // xiao ming
name.last = "hong";
console.log(full.value); // xiao hong
watch
export function watch(source, cb) {
let getter;
if (isReactive(source)) {
// 如果是响应式数据
// 递归遍历各个属性, 调用各属性的 getter 触发依赖收集
getter = () => traversal(source);
} else if (typeof source === "function") {
// 如果是函数, 直接作为 getter
getter = source;
}
let oldValue;
const job = (/* 调度器 */) => {
// 当被监控的数据调用 setter 后执行获取新值
const newValue = effect.run();
cb(newValue, oldValue); // 执行回调函数
oldValue = newValue;
};
const effect = new ReactiveEffect(getter, job);
oldValue = effect.run();
}
function traversal(value, set = new Set()) {
// 不是对象, 直接返回
if (typeof value !== "object") return value;
// 已经访问过了, 直接返回
if (set.has(value)) return value;
set.add(value);
for (let key in value) {
traversal(value[key], set);
}
return value;
}
// 使用
const name = reactive({
first: "xiao",
last: "ming",
});
watch(
() => name.last,
(n, o) => {
console.log(n, o);
}
);
setTimeout(() => {
name.last = "hong";
}, 1000);
// hong ming
ref
将一个值变成响应式
function convert(value) {
// 如果是对象则使用 reactive 处理一下
return typeof value === "object" ? reactive(value) : value;
}
// effect.js
// 依赖收集
export function trackEffects(dep) {
// 依赖是否收集过, 收集过就不要再收集
if (!!activeEffect && !dep.has(activeEffect)) {
dep.add(activeEffect);
activeEffect.deps.push(dep);
}
}
// effect.js
// 触发依赖
export function triggerEffects(dep) {
const temp = new Set(dep);
for (const effect of temp) {
if (effect !== activeEffect) {
if (effect.scheduler) {
effect.scheduler();
} else {
effect.run();
}
}
}
}
function convert(value) {
return typeof value === "object" ? reactive(value) : value;
}
export class RefImpl {
constructor(raw) {
this._rawValue = raw;
this._value = convert(raw); // 如果是对象, 转换成响应式
this.deps = new Set();
}
get value() {
trackEffects(this.deps); // 收集依赖
return this._value;
}
set value(newValue) {
if (newValue !== this._rawValue) {
this._value = convert(newValue);
this._rawValue = newValue;
triggerEffects(this.deps); // 触发依赖
}
}
}
export function ref(value) {
return new RefImpl(value);
}
// 使用
const name = ref(false);
effect(() => {
// 会触发 ref 的 getter 并收集依赖
console.log(name.value);
});
name.value = true;
是否为 ref
export class RefImpl {
__v_isRef = true; // 只要是 ref 对象就有这个静态属性
}
export function isRef(value) {
return !!value.__v_isRef;
}
去 ref
export function unRef(ref) {
return isRef(ref) ? ref.value : ref;
}
toRefs
class ObjectRefImpl {
constructor(obj, key) {
this.obj = obj;
this.key = key;
}
get value() {
return this.obj[this.key];
}
set value(newValue) {
this.obj[this.key] = newValue;
}
}
function toRef(object, key) {
return new ObjectRefImpl(object, key);
}
export function toRefs(object) {
let res;
if (typeof object === "array") {
res = new Array(object.length);
} else {
res = {};
}
for (let key in object) {
// 把对象或数组里的每一个属性取出来
// 封装成有 getter 和 setter 的对象
// 其中的 getter 和 setter 直接调用属性的 getter 和 setter
// 不能直接 res[key] = object[key]
// 因为属性是一个 proxy, 没有 value 属性, 不是 ref
res[key] = toRef(object, key);
}
return res;
}