|
| 1 | +"use strict"; |
| 2 | +// 用一个全局变量存储当前激活的 effect 函数 |
| 3 | +let activeEffect; |
| 4 | +// effect 栈 |
| 5 | +const effectStack = []; // 新增 |
| 6 | +function effect(fn, options) { |
| 7 | + const effectFn = () => { |
| 8 | + cleanup(effectFn); |
| 9 | + // 当调用 effect 注册副作用函数时,将副作用函数赋值给 activeEffect |
| 10 | + activeEffect = effectFn; |
| 11 | + // 在调用副作用函数之前将当前副作用函数压栈 |
| 12 | + effectStack.push(effectFn); |
| 13 | + // 将 fn 的执行结果存储到 res 中 |
| 14 | + const res = fn(); // 新增 |
| 15 | + // 在当前副作用函数执行完毕后,将当前副作用函数弹出栈,并把 activeEffect 还原为之前的值 |
| 16 | + effectStack.pop(); |
| 17 | + activeEffect = effectStack[effectStack.length - 1]; |
| 18 | + return res; |
| 19 | + }; |
| 20 | + // 将 options 挂载到 effectFn 上 |
| 21 | + effectFn.options = options; // 新增 |
| 22 | + // activeEffect.deps 用来存储所有与该副作用函数相关的依赖集合 |
| 23 | + effectFn.deps = []; |
| 24 | + // 只有非lazy的时候,才执行 |
| 25 | + if (options && !options.lazy) { |
| 26 | + // 执行副作用函数 |
| 27 | + effectFn(); |
| 28 | + } |
| 29 | + return effectFn; |
| 30 | +} |
| 31 | +function cleanup(effectFn) { |
| 32 | + // 遍历 effectFn.deps 数组 |
| 33 | + for (let i = 0; i < effectFn.deps.length; i++) { |
| 34 | + // deps 是依赖集合 |
| 35 | + const deps = effectFn.deps[i]; |
| 36 | + // 将 effectFn 从依赖集合中移除 |
| 37 | + deps.delete(effectFn); |
| 38 | + } |
| 39 | + // 最后需要重置 effectFn.deps 数组 |
| 40 | + effectFn.deps.length = 0; |
| 41 | +} |
| 42 | +const bucket = new WeakMap(); |
| 43 | +function track(target, key) { |
| 44 | + // 没有 activeEffect,直接 return |
| 45 | + if (!activeEffect) |
| 46 | + return; |
| 47 | + let depsMap = bucket.get(target); |
| 48 | + if (!depsMap) { |
| 49 | + bucket.set(target, (depsMap = new Map())); |
| 50 | + } |
| 51 | + let deps = depsMap.get(key); |
| 52 | + if (!deps) { |
| 53 | + depsMap.set(key, (deps = new Set())); |
| 54 | + } |
| 55 | + // 把当前激活的副作用函数添加到依赖集合 deps 中 |
| 56 | + deps.add(activeEffect); |
| 57 | + // deps 就是一个与当前副作用函数存在联系的依赖集合 |
| 58 | + // 将其添加到 activeEffect.deps 数组中 |
| 59 | + activeEffect.deps.push(deps); // 新增 |
| 60 | +} |
| 61 | +// 在 set 拦截函数内调用 trigger 函数触发变化 |
| 62 | +function trigger(target, key, type) { |
| 63 | + const depsMap = bucket.get(target); |
| 64 | + if (!depsMap) |
| 65 | + return; |
| 66 | + const effects = depsMap.get(key); |
| 67 | + const effectsToRun = new Set(); |
| 68 | + effects && effects.forEach((effectFn) => { |
| 69 | + if (effectFn !== activeEffect) { |
| 70 | + effectsToRun.add(effectFn); |
| 71 | + } |
| 72 | + }); |
| 73 | + effectsToRun.forEach((effectFn) => { |
| 74 | + var _a; |
| 75 | + // 如果一个副作用函数存在调度器,则调用该调度器,并将副作用函数作为参数传递 |
| 76 | + if ((_a = effectFn.options) === null || _a === void 0 ? void 0 : _a.scheduler) { |
| 77 | + effectFn.options.scheduler(effectFn); |
| 78 | + } |
| 79 | + else { |
| 80 | + // 否则直接执行副作用函数(之前的默认行为) |
| 81 | + effectFn(); |
| 82 | + } |
| 83 | + }); |
| 84 | + if (type === 'ADD') { |
| 85 | + const iterateEffects = depsMap.get(ITERATE_KEY); |
| 86 | + iterateEffects && iterateEffects.forEach((effectFn) => { |
| 87 | + if (effectFn !== activeEffect) { |
| 88 | + effectsToRun.add(effectFn); |
| 89 | + } |
| 90 | + }); |
| 91 | + effectsToRun.forEach((effectFn) => { |
| 92 | + var _a; |
| 93 | + if ((_a = effectFn === null || effectFn === void 0 ? void 0 : effectFn.options) === null || _a === void 0 ? void 0 : _a.scheduler) { |
| 94 | + effectFn.options.scheduler(effectFn); |
| 95 | + } |
| 96 | + else { |
| 97 | + effectFn(); |
| 98 | + } |
| 99 | + }); |
| 100 | + } |
| 101 | +} |
| 102 | +function computed(getter) { |
| 103 | + // value 用来缓存上一次计算的值 |
| 104 | + let value; |
| 105 | + // dirty 标志,用来标识是否需要重新计算值 |
| 106 | + let dirty = true; |
| 107 | + // 把 getter 作为副作用函数,创建一个 lazy 的 effect |
| 108 | + const effectFn = effect(getter, { |
| 109 | + lazy: true, |
| 110 | + scheduler() { |
| 111 | + if (!dirty) { |
| 112 | + dirty = true; |
| 113 | + // 当计算属性依赖的响应式数据变化时,手动调用 trigger 函数触发响应 |
| 114 | + trigger(obj, 'value'); |
| 115 | + } |
| 116 | + } |
| 117 | + }); |
| 118 | + const obj = { |
| 119 | + get value() { |
| 120 | + if (dirty) { |
| 121 | + value = effectFn(); |
| 122 | + dirty = false; |
| 123 | + } |
| 124 | + track(obj, 'value'); |
| 125 | + return value; |
| 126 | + } |
| 127 | + }; |
| 128 | + return obj; |
| 129 | +} |
| 130 | +function traverse(value, seen = new Set()) { |
| 131 | + // 如果要读取的数据是原始值,或者已经被读取过了,那么什么都不做 |
| 132 | + if (typeof value !== 'object' || value === null || seen.has(value)) |
| 133 | + return; |
| 134 | + // 将数据添加到 seen 中,代表遍历地读取过了,避免循环引用引起的死循环 |
| 135 | + seen.add(value); |
| 136 | + // 暂时不考虑数组等其他结构 |
| 137 | + // 假设 value 就是一个对象,使用 for...in 读取对象的每一个值,并递归地调用 traverse 进行处理 |
| 138 | + for (const k in value) { |
| 139 | + traverse(value[k], seen); |
| 140 | + } |
| 141 | + return value; |
| 142 | +} |
| 143 | +function watch(source, cb, options) { |
| 144 | + let getter; |
| 145 | + if (typeof source === 'function') { |
| 146 | + getter = source; |
| 147 | + } |
| 148 | + else { |
| 149 | + getter = () => traverse(source); |
| 150 | + } |
| 151 | + let oldValue, newValue; |
| 152 | + // cleanup 用来存储用户注册的过期回调 |
| 153 | + let cleanup; |
| 154 | + // 定义 onInvalidate 函数 |
| 155 | + function onInvalidate(fn) { |
| 156 | + // 将过期回调存储到 cleanup 中 |
| 157 | + cleanup = fn; |
| 158 | + } |
| 159 | + const job = () => { |
| 160 | + newValue = effectFn(); |
| 161 | + // 在调用回调函数 cb 之前,先调用过期回调 |
| 162 | + if (cleanup) { |
| 163 | + cleanup(); |
| 164 | + } |
| 165 | + // 将 onInvalidate 作为回调函数的第三个参数,以便用户使用 |
| 166 | + cb(newValue, oldValue, onInvalidate); |
| 167 | + oldValue = newValue; |
| 168 | + }; |
| 169 | + const effectFn = effect( |
| 170 | + // 执行 getter |
| 171 | + () => getter(), { |
| 172 | + lazy: true, |
| 173 | + scheduler: () => { |
| 174 | + if (options.flush === 'post') { |
| 175 | + const p = Promise.resolve(); |
| 176 | + p.then(job); |
| 177 | + } |
| 178 | + else { |
| 179 | + job(); |
| 180 | + } |
| 181 | + } |
| 182 | + }); |
| 183 | + if (options.immediate) { |
| 184 | + job(); |
| 185 | + } |
| 186 | + else { |
| 187 | + oldValue = effectFn(); |
| 188 | + } |
| 189 | +} |
| 190 | +const obj = { |
| 191 | + foo: 1, |
| 192 | + get bar() { |
| 193 | + // 现在这里的 this 为代理对象 p |
| 194 | + return this.foo; |
| 195 | + } |
| 196 | +}; |
| 197 | +const ITERATE_KEY = Symbol(); |
| 198 | +// 代理对象 |
| 199 | +const p = new Proxy(obj, { |
| 200 | + // 拦截读取操作,接收第三个参数 receiver |
| 201 | + get(target, key, receiver) { |
| 202 | + track(target, key); |
| 203 | + // 使用 Reflect.get 返回读取到的属性值 |
| 204 | + return Reflect.get(target, key, receiver); |
| 205 | + }, |
| 206 | + // 拦截设置操作 |
| 207 | + set(target, key, newVal, receiver) { |
| 208 | + // 如果属性不存在,则说明是在添加新属性,否则是设置已有属性 |
| 209 | + const type = Object.prototype.hasOwnProperty.call(target, key) ? 'SET' : 'ADD'; |
| 210 | + // 设置属性值 |
| 211 | + const res = Reflect.set(target, key, newVal, receiver); |
| 212 | + // 把副作用函数从桶里取出并执行 |
| 213 | + trigger(target, key, type); |
| 214 | + return res; |
| 215 | + }, |
| 216 | + // 拦截 in 操作 |
| 217 | + has(target, key) { |
| 218 | + track(target, key); |
| 219 | + return Reflect.has(target, key); |
| 220 | + }, |
| 221 | + // 拦截 for...in 操作 |
| 222 | + ownKeys(target) { |
| 223 | + // 使用ITERATE_KEY 代替 key,forin迭代操作针对对象,使用symbol作为唯一标识 |
| 224 | + track(target, ITERATE_KEY); |
| 225 | + return Reflect.ownKeys(target); |
| 226 | + }, |
| 227 | + // 拦截 delete 操作 |
| 228 | + deleteProperty(target, key) { |
| 229 | + // 删除属性 |
| 230 | + const res = Reflect.deleteProperty(target, key); |
| 231 | + // 触发删除操作 |
| 232 | + trigger(target, key, 'DELETE'); |
| 233 | + return res; |
| 234 | + }, |
| 235 | +}); |
| 236 | +effect(() => { |
| 237 | + // obj 是原始数据,不是代理对象,这样的访问不能够建立响应联系 |
| 238 | + for (const key in p) { |
| 239 | + console.log(key); |
| 240 | + } |
| 241 | +}); |
0 commit comments