链表节点复用
系列文章目录
第一章 vue 简单的effect实现
第二章 链表运用到响应式中
第三章 effect问题处理
第四章 effect的参数和返回值
第五章 链表节点的复用
第六章 分支切换和依赖清理
前言
上节我们说到了分支切换和依赖清理,这章我们整点简单的。
一、代码整改
首先,我们把所有的代码整理一下。
sys.js
function propagate(subs) {// 依次执行let link = subs; //保存头节点let queuedEffects = []; //保存需要执行的effectwhile (link) {// 注意:这里不能直接执行effect,因为effect可能会再次触发set,导致死循环queuedEffects.push(link.sub); //将effect保存到数组中link = link.nextSub; //指向下一个节点}// 依次执行queuedEffects.forEach((effect) => {effect?.notify(); //执行effect 2});
}
// dep:ref sub: effect
const link = function (dep, sub) {const currentDep = sub.depsTail; //先拿到尾节点/*** 分两种情况* 1.如果头节点有,尾节点没有,复用头节点* 2.如果尾节点还有nextDep,复用尾节点的nextDep*/const nextDep = currentDep === undefined ? sub.deps : currentDep.nextDep; //保存下一个节点// // 3.6 复用未成立 说明当前nextDep不要被复用if (nextDep?.dep === dep) {//nextDep?.dep之前已经收集的ref 和dep:当前要收集的ref 全都是指 ref()// 如果头节点有,尾节点没有,说明之前已经收集过了,不需要再次收集 // 尝试复用// 复用完成后,将尾节点指向新节点 sub就是effectsub.depsTail = nextDep; //将尾节点指向nextDep// console.log("当前的effect的依赖项已经收集过了,不需要再次收集", dep);return; //如果当前的effect的依赖项已经收集过了,就不需要再次收集}const newLink = {sub,nextSub: null,prevSub: null,dep,// nextDep: null,nextDep, //3.6};// 尾插法 链表节点和dep关联关系if (dep.subsTail) {dep.subsTail.nextSub = newLink;newLink.prevSub = dep.subsTail;dep.subsTail = newLink;} else {// 没有的话头尾一样dep.subs = newLink;dep.subsTail = newLink;}// 单向链表 链表节点和sub关联关系if (sub.depsTail) {sub.depsTail.nextDep = newLink;sub.depsTail = newLink;} else {// 没有的话头尾一样sub.deps = newLink;sub.depsTail = newLink;}
};
function clearTrack(link) {while (link) {const { nextDep, nextSub, dep, prevSub } = link;if (prevSub) {prevSub.nextSub = nextDep;link.nextSub = undefined;} else {dep.subs = nextDep;}/*** 下一个*/if (nextSub) {nextSub.prevSub = nextSub;link.prevSub = undefined;} else {// 尾节点dep.subsTail = prevSub;}link.dep = undefined;link.sub = undefined;link.nextDep = undefined;link = nextDep;}
}
function endTrack(sub) {const depsTail = sub.depsTail;/*** 如果有depsTail且depsTail.nextDep,需要清理*/if (depsTail) {if (depsTail.nextDep) {// 移出clearTrack(depsTail.nextDep);depsTail.nextDep = undefined;}} else if (sub.deps) {console.log("从头开始");clearTrack(sub.deps);sub.deps = undefined;}
}
export { propagate, link, clearTrack, endTrack };
effect 函数
import { propagate, link, endTrack } from "../common/sys.js";
class RefImpl {_v_isRef = true;/*** 订阅者链表头节点*/subs;/*** 订阅者链表尾节点*/subsTail;constructor(value) {this.value = value;}get value() {if (activeSub) {trackRef(this);}return this._value;}set value(newVal) {// 触发更新this._value = newVal;// this.subs?.();triggerRef(this);}
}
function ref(val) {return new RefImpl(val);
}
let activeSub = null;// interface Link {
// // effect(订阅者)
// sub: Function;
// nextSub: Link;
// prevSub: Link;
// dep: Dep; //依赖项
// }function trackRef(dep) {// 收集依赖/**TODO: 嵌套会导致外部的无法被收集 对应的index2.html* class ReactiveEffect {* constructor(fn) {* this.fn = fn;* }* run() {* activeSub = this;* try {* return this.fn();* } finally {* activeSub = undefined;* }* }* }*/if (activeSub) {link(dep, activeSub);}
}function triggerRef(dep) {if (dep.subs) {propagate(dep.subs); //触发更新}
}class ReactiveEffect {deps;depsTail;constructor(fn) {this.fn = fn;}run() {const prevSub = activeSub; //保存当前的effect,处理嵌套逻辑activeSub = this;// 标记 如果头节点有,表示是已经收集过的// 如果都没有,表示第一次收集this.depsTail = undefined;try {return this.fn();} finally {if (this.depsTail.nextDep) {console.log("依赖清理", this.depsTail.nextDep);endTrack(this);}// 执行完一次恢复到之前的activeSub = prevSub;}}// 通知更新调用这个函数notify() {this.scheduler();}scheduler() {this.run();}
}
function effect(fn, options) {const e = new ReactiveEffect(fn);// schedulerObject.assign(e, options);e.run();const run = e.run.bind(e);run.effect = e;return run;
}export { effect, ref };
其实就是把clearTrack和endTrack迁移到sys文件里面
二、复用
1.为什么
在clearTrack这个函数里面,我们将节点全部设置为null或者undefined
但是呢在创建节点的时候又需要
这就导致一边创建节点,一边删除节点 有些许性能问题
2.如何复用
直接上代码 sys.js
function propagate(subs) {// 依次执行let link = subs; //保存头节点let queuedEffects = []; //保存需要执行的effectwhile (link) {// 注意:这里不能直接执行effect,因为effect可能会再次触发set,导致死循环queuedEffects.push(link.sub); //将effect保存到数组中link = link.nextSub; //指向下一个节点}// 依次执行queuedEffects.forEach((effect) => {effect?.notify(); //执行effect 2});
}
let linkPool ;
// dep:ref sub: effect
const link = function (dep, sub) {const currentDep = sub.depsTail; //先拿到尾节点/*** 分两种情况* 1.如果头节点有,尾节点没有,复用头节点* 2.如果尾节点还有nextDep,复用尾节点的nextDep*/const nextDep = currentDep === undefined ? sub.deps : currentDep.nextDep; //保存下一个节点// // 3.6 复用未成立 说明当前nextDep不要被复用if (nextDep?.dep === dep) {//nextDep?.dep之前已经收集的ref 和dep:当前要收集的ref 全都是指 ref()// 如果头节点有,尾节点没有,说明之前已经收集过了,不需要再次收集 // 尝试复用// 复用完成后,将尾节点指向新节点 sub就是effectsub.depsTail = nextDep; //将尾节点指向nextDep// console.log("当前的effect的依赖项已经收集过了,不需要再次收集", dep);return; //如果当前的effect的依赖项已经收集过了,就不需要再次收集}// #region let newLink;if (linkPool) {newLink = linkPool;linkPool = linkPool.nextDep;newLink.nextDep = nextDep;newLink.dep = dep;newLink.sub = sub;// #endregion} else {newLink = {sub,nextSub: null,prevSub: null,dep,// nextDep: null,nextDep, //3.6};}// 尾插法 链表节点和dep关联关系if (dep.subsTail) {dep.subsTail.nextSub = newLink;newLink.prevSub = dep.subsTail;dep.subsTail = newLink;} else {// 没有的话头尾一样dep.subs = newLink;dep.subsTail = newLink;}// 单向链表 链表节点和sub关联关系if (sub.depsTail) {sub.depsTail.nextDep = newLink;sub.depsTail = newLink;} else {// 没有的话头尾一样sub.deps = newLink;sub.depsTail = newLink;}
};
function clearTrack(link) {while (link) {const { nextDep, nextSub, dep, prevSub } = link;if (prevSub) {prevSub.nextSub = nextDep;link.nextSub = undefined;} else {dep.subs = nextDep;}/*** 下一个*/if (nextSub) {nextSub.prevSub = nextSub;link.prevSub = undefined;} else {// 尾节点dep.subsTail = prevSub;}link.dep = undefined;link.sub = undefined;// 加入到链表池link.nextDep = linkPool;linkPool = link;link = nextDep;}
}
function endTrack(sub) {const depsTail = sub.depsTail;/*** 如果有depsTail且depsTail.nextDep,需要清理*/if (depsTail) {if (depsTail.nextDep) {// 移出clearTrack(depsTail.nextDep);depsTail.nextDep = undefined;}} else if (sub.deps) {console.log("从头开始");clearTrack(sub.deps);sub.deps = undefined;}
}
export { propagate, link, clearTrack, endTrack };
下面这个代码if和else里面的逻辑其实是一样的
if (linkPool) {newLink = linkPool;linkPool = linkPool.nextDep;newLink.nextDep = nextDep;newLink.dep = dep;newLink.sub = sub;// #endregion} else {newLink = {sub,nextSub: null,prevSub: null,dep,// nextDep: null,nextDep, //3.6};}
总结
这一节比较简单,只要理解了之前的链表节点,就非常好理解。
