写在前面:本文为我的在平常工做和学习中的一些总结,便于后来查漏补缺,非权威性资料,请带着本身的思考^-^。
前文连接:了解一下Vue - [Vue是怎么实现响应式的(一)]
了解一下Vue - [Vue是怎么实现响应式的(二)]
前面对于响应式有了一些些了解,这里尝试本身写一遍(抄一遍)
设对象data为要被定义的响应式对象,key为data中的属性html
let uid = 0; class Dep { constructor() { this.id = ++uid; this.subs = []; } addSub(target) { // 将target添加进观察者列表 this.subs.push(target); } depend() { // if (Dep.target) { Dep.target.addDep(this); } } removeSub(target) { this.subs.splice(this.subs.findIndex(_ => _.id == target.id), 1); } notify() { const subs = this.subs.slice(); for (let i = 0; i < subs.length; i++) { subs[i].update(); } } } Dep.target = null; // 当前处于激活状态的watcher const targetStack = []; // 存放watcher的栈 const pushTarget = target => { // 更新当前激活的Dep.target if (Dep.target) { targetStack.push(target); // 将target(watcher)压入栈中 } Dep.target = target; }; const popTarget = () => { // 从watcher栈中弹出顶部watcher Dep.target = targetStack.pop(); };
class Watcher { constructor(getter, options = {}) { this.deps = []; // 依赖列表 this.newDeps = []; // 最近一次添加的依赖列表 this.depIds = new Set(); // 依赖ids列表 this.newDepIds = new Set(); // 最近一次添加的依赖ids列表 this.getter = getter; // this.lazy = !!options.lazy; // 懒依赖,在首次实例化的时候不执行getter this.dirty = this.lazy; // 脏值标识,主要用在computed计算时; this.lazy ? undefined : this.get(); } get() { pushTarget(this); // 将当前watcher做为激活的watcher对象,并推入targetStack栈中 const value = this.getter(); popTarget(); // 将当前watcher置为栈中上一个watcher this.cleanupDeps(); // 依赖整理,主要用来整理this.deps、this.depIds return value; } addDep(dep) { if (!this.newDepIds.has(dep.id)) { this.newDepIds.add(dep.id); this.newDeps.push(dep); if (!this.depIds.has(dep.id)) { dep.addSub(this); } } } cleanupDeps() { let i = this.deps.length; while (i--) { const dep = this.deps[i]; if (!this.newDepIds.has(dep.id)) { // 若是新的依赖列表中再也不包含以前的依赖项,则调用dep.removeSub方法,将当前watcher从dep.subs列表中移除 dep.removeSub(this); } } [this.deps, this.newDeps] = [this.newDeps, this.deps]; this.newDeps.length = 0; [this.depIds, this.newDepIds] = [this.newDepIds, this.depIds]; this.newDepIds.clear(); } evaluate() { this.value = this.get(); this.dirty = false; } update() { if (this.lazy) { this.dirty = true; } else { new Promise((resolve) => { resolve(); }).then(() => { this.get(); }); } } depend() { // 将当前watcher的依赖添加到当前Dep.target的依赖列表中 const deps = this.deps; for (let i = 0; i < deps.length; i++) { deps[i].depend(); } } }
用来为data[key]定义getter/settersegmentfault
const defineReactive = (target, key, val) => { const dep = new Dep(); // 这里实例化dep对象,用于在getter/setter触发的时候访问该对象进行依赖收集等操做,本质上来讲当前实例化的dep和当前的data[key]一一对应了 Object.defineProperty(target, key, { enumerable: true, configurable: true, get() { if (Dep.target) { dep.depend(); // 依赖添加 } return val; }, set(newVal) { if (val === newVal) return; val = newVal; dep.notify(); }, }); };
const initData = data => { const keys = Object.keys(data); for (let i = 0; i < keys.length; i++) { defineReactive(vm, keys[i], data[keys[i]]); } }; const initComputed = computed => { const keys = Object.keys(computed); for (let i = 0; i < keys.length; i++) { const userDef = computed[keys[i]].bind(vm); const watcher = new Watcher(userDef, { lazy: true }); Object.defineProperty(vm, keys[i], { enumerable: true, configurable: true, get() { if (watcher) { if (watcher.dirty) { watcher.evaluate(); } if (Dep.target) { watcher.depend(); } return watcher.value; } }, }); } };
<!-- html --> <body> <div id="app"></div> <button id="btn">更新</button> </body>
const data = initData(vm.data); const computed = initComputed(vm.computed); const updateComponent = () => { const app = document.querySelector('#app'); var a = vm.current; // 引用vm.current var b = vm.computedCurrent; // 应用computed app.innerHTML = `data: <i>${a}</i> computed: <strong>${b}</strong>`; }; defineReactive(vm, 'current', vm.current); // 为vm.current定义getter/setter const watcher = new Watcher(updateComponent);
能够看到点击按钮,更新vm中的current属性,页面成功进行了更新。。。app
初始化data属性getter/setter --> 实例化watcher,带有update方法、addDep方法 --> watcher.get方法执行进行依赖收集(该方法中被引用到的data属性视为当前watcher的依赖) --> watcher依赖收集的过程当中被依赖的data属性也会进行观察者收集 --> data属性更新 --> 通知watcher,update方法调用 --> 新一轮的依赖收集,新旧依赖比较,新依赖相对旧依赖缺失的从依赖列表中删除,新增的加入依赖列表同时将观察者watcher添加进该依赖的subs观察者列表中 --> 执行业务代码(视图更新);函数
对于computed来讲,它既是观察者,也是依赖;视图更新watcher依赖computed,computed依赖data;学习
对于watch来讲,它是观察者,依赖要watch的data或者computed,依赖更新时会notify(通知)它,执行相应的方法;ui
原理大抵如此,细节还有不少this
THE ENDlua