初始
1s 后, 修改 obj.text
的值,document.body.innerText
上的内容随之发生更改
使用如下
<html></html>
<script src="./index.js"></script>
<script>
let obj = {
text: "hello world"
}
obj = createReactiveObject(obj)
effect(() => {
document.body.innerText = obj.text
})
setTimeout(() => {
obj.text = "hello vue"
}, 1000)
</script>
大致逻辑,对传入 createReactiveObject 的对象
进行代理,当 getter/setter 对象内的值时,会触发对应函数(如下面的 Set 内的函数,Set 为所有使用到 text 的地方)
传入 effect 内的函数,会先执行一遍,使得可以触发对应 getter/setter 函数
track 函数用于收集所有使用到响应式数据的地方
trigger 函数用于触发 track 所收集的
targetMap 的内容,如
targetMap = {
{text: 'hello vue'}: {
'text': Set(() => { document.body.innerText = obj.text })
}
}
// 对应结构
targetMap = {
Object: Map
}
Object = target // Proxy 中的 target,即整个 obj 对象
Map = {
key: Set(fn);
}
实现代码
let targetMap = new WeakMap()
let activeEffect
class ReactiveEffect {
constructor(fn) {
this.fn = fn
}
run() {
activeEffect = this
this.fn()
}
}
function effect(fn) {
const _effect = new ReactiveEffect(fn)
_effect.run()
}
function createGetter() {
return function get(target, key) {
const res = target[key]
track(target, key)
return res
}
}
function createSetter() {
return function set(target, key, newValue) {
target[key] = newValue
trigger(target, key)
return true // 严格模式, set 不返回 true 会报错
}
}
function createReactiveObject(data) {
const get = createGetter()
const set = createSetter()
const handler = {
get,
set
}
return new Proxy(data, handler)
}
function track(target, key) {
if (!activeEffect) return
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
trackEffects(dep)
}
function trackEffects(dep) {
dep.add(activeEffect)
}
function trigger(target, key) {
let depsMap = targetMap.get(target)
if (!depsMap) return
let dep = depsMap.get(key)
triggerEffects(dep)
}
function triggerEffects(dep) {
dep && dep.forEach(effect => {
effect.run()
});
}
在 ts
中, 当constructor
的参数为 public
时, 无需赋值便可使用
class Demo {
constructor(public foo: string, public fn: () => void) {
}
run() {
console.log(this.foo)
console.log(this.fn)
}
}
const demo = new Demo('haha', () => console.log('haha'))
demo.run()
// haha
// [Function (anonymous)]
public
默认指代 this
, 编译成 js
代码如下
"use strict";
var Demo = /** @class */ (function () {
function Demo(foo, fn) {
this.foo = foo;
this.fn = fn;
}
Demo.prototype.run = function () {
console.log(this.foo);
console.log(this.fn);
};
return Demo;
}());
var demo = new Demo('haha', function () { return console.log('haha'); });
demo.run();
1 依赖清除
document.body.innerText = obj.ok ? obj.text : 'not'
: body
显示的内容与 obj.ok
有关, 1秒后将 obj.ok
设置为 false
, 此时系统内已经和 obj.text
没什么关系了, 无论 obj.text
怎么改变, 都不应该执行effect 函数
, 当前的代码还做不到, 2秒后修改 obj.text
的值, 依然执行effect函数
<html></html>
<script src="./index.js"></script>
<script>
let obj = {
ok: true,
text: "hello world"
}
obj = createReactiveObject(obj)
effect(() => {
document.body.innerText = obj.ok ? obj.text : 'not'
console.log(`执行了 effect 函数`)
})
setTimeout(() => {
obj.ok = false
console.log(targetMap)
}, 1000)
setTimeout(() => {
obj.text = 'hello vue'
}, 2000)
</script>
解决思路, 每次effect函数
执行的时候, 将所有相关联的依赖从集合中删除
实现方式, trackEffects
时给 deps
添加 dep
, 当 cleanupEffect
时, 可通过 effect.deps
拿到所有的dep
class ReactiveEffect {
deps = [] // <----- new code
constructor(fn) {
this.fn = fn
}
run() {
cleanupEffect(this) // <----- new code
activeEffect = this
this.fn()
}
}
function cleanupEffect(effect) { // <----- new code
for (let i = 0; i < effect.deps.length; i++) {
const dep = effect.deps[i]
dep.delete(effect)
}
effect.deps.length = 0
}
// ...
function trackEffects(dep) {
dep.add(activeEffect)
activeEffect.deps.push(dep) // <----- new code
}
执行当前函数, 会发生死循环, 原因如下
setTimeout(() => {
obj.ok = false // <----- trigger
console.log(targetMap)
}, 1000)
trigger(target, key) // <----- triggerEffects
// ---------------- 从这里开始无线套娃 -----------------------
function triggerEffects(dep) {
dep && dep.forEach(effect => {
effect.run() // <----- run
});
}
run() {
cleanupEffect(this) // <----- 清除 dep 内的 effect
activeEffect = this
this.fn() // <----- 执行 effect fn
}
effect(() => {
document.body.innerText = obj.ok ? obj.text : 'not' // <----- 执行 trackEffects 添加依赖
})
function trackEffects(dep) { // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 添加重复的依赖
dep.add(activeEffect) // <----- 重新给 dep 添加依赖, 问题出在这里, 之后又会返回到 triggerEffects
activeEffect.deps.push(dep)
}
function triggerEffects(dep) {
dep && dep.forEach(effect => {
effect.run() // <----- 执行在 trackEffects 添加的 dep
});
}
因为 trigger
时会遍历 dep
, 执行 dep
内所有的 effect 函数
, effect 函数
会cleanupEffect
删除依赖, 再执行 track
添加依赖, 再 set 中, 套娃了, 如下
遍历 set 集合时, 如果一个已经访问过的值
, 从集合中删除
并又重新添加
, 如果遍历未结束, 该值会被重新访问
const set = new Set([1])
set.forEach(item => {
set.delete(1)
set.add(1)
console.log('死循环中......')
})
因此, 解决如下
const set = new Set([1])
const newSet = new Set(set)
newSet.forEach(item => {
set.delete(1)
set.add(1)
console.log('执行一次')
})
对应代码如下
function triggerEffects(dep) {
dep = new Set(dep)
dep.forEach(effect => {
effect.run()
});
}
上面是<vue设计与实现>中实现的代码, 而在 vue3 源码中是这样实现的, 将 dep 扩展成数组, 然后遍历那个数组, 也能解决死循环的问题
function triggerEffects(dep) {
for (const effect of Array.isArray(dep) ? dep : [...dep]) {
effect.run()
}
}
2 effect嵌套
<html></html>
<script src="./index.js"></script>
<script>
let obj = {
foo: 'foo',
bar: "bar"
}
obj = createReactiveObject(obj)
let temp1, temp2
effect(function effectFn1() {
console.log('effectFn1 执行')
effect(function effectFn2() { // <----- 将会覆盖 activeEffect
console.log('effectFn2 执行')
temp2 = obj.bar
})
temp1 = obj.foo
})
setTimeout(() => {
obj.foo = false // <----- effectFn2 执行
}, 1000);
</script>
输出和预期不符, 因为 effectFn2
覆盖了activeEffect
, 导致effectFn1
添加的是effectFn2
内的实例
所以我们要在修改activeEffect
那块代码那做出调整
const effectStack = [] // <----- new code
class ReactiveEffect {
deps = []
constructor(fn) {
this.fn = fn
}
run() {
cleanupEffect(this)
activeEffect = this
effectStack.push(this) // <----- new code
this.fn()
effectStack.pop() // <----- new code
activeEffect = effectStack[effectStack.length - 1] // <----- new code
}
}
3 无限循环
<html></html>
<script src="./index.js"></script>
<script>
let obj = {
foo: 'foo'
}
obj = createReactiveObject(obj)
effect(() => {
obj.foo = obj.foo + 1
})
</script>
原因如下
run() {
// ......
this.fn()
// ......
}
() => {
obj.foo = obj.foo + 1 // <----- track // <----- trigger
}
function trackEffects(dep) {
dep.add(activeEffect) // <----- 添加依赖
activeEffect.deps.push(dep)
}
function triggerEffects(dep) {
dep = new Set(dep)
dep.forEach(effect => { // <----- 遍历依赖
effect.run()
});
}
run() { // <----- 没想到吧, 我又回来了
// ......
this.fn()
// ......
}
解决办法: 如果 trigger
触发的effect 函数
与当前正在执行的effect 函数
是同一个函数, 则不执行
function triggerEffects(dep) {
dep = new Set(dep)
dep.forEach(effect => {
if (effect !== activeEffect) { // <----- new code
effect.run()
} // <----- new code
});
}
解构版本
function triggerEffects(dep) {
for (const effect of Array.isArray(dep) ? dep : [...dep]) {
if (effect !== activeEffect) { // <----- new code
effect.run()
} // <----- new code
}
}
当前所有代码
<html></html>
<script src="./index.js"></script>
<script>
// ------------依赖无关----------------
// 执行了 effect 函数 2 次
let obj = {
ok: true,
text: "hello world"
}
obj = createReactiveObject(obj)
effect(() => {
document.body.innerText = obj.ok ? obj.text : 'not'
console.log(`执行了 effect 函数`)
})
setTimeout(() => {
obj.ok = false
}, 1000)
setTimeout(() => { // <----- 清除依赖
obj.text = 'hello vue'
}, 2000)
// ------------effect嵌套----------------
// effectFn1 执行 effectFn2 执行 各执行2次
let obj2 = {
foo: 'foo',
bar: "bar"
}
obj2 = createReactiveObject(obj2)
let temp1, temp2
effect(function effectFn1() {
console.log('effectFn1 执行')
effect(function effectFn2() { // <----- 将会覆盖 activeEffect
console.log('effectFn2 执行')
temp2 = obj2.bar
})
temp1 = obj2.foo
})
setTimeout(() => {
obj2.foo = false // <----- effectFn2 执行
}, 1000);
// ------------无限循环----------------
// 不会卡死
effect(() => {
obj.foo = obj.foo + 1
})
</script>
执行了 effect 函数
effectFn1 执行
effectFn2 执行
执行了 effect 函数
effectFn1 执行
effectFn2 执行
let targetMap = new WeakMap()
const effectStack = [] // <----- 2 new code
let activeEffect
class ReactiveEffect {
deps = [] // <----- 1 new code
constructor(fn) {
this.fn = fn
}
run() {
cleanupEffect(this) // <----- 1 new code
activeEffect = this
effectStack.push(this) // <----- 2 new code
this.fn()
effectStack.pop() // <----- 2 new code
activeEffect = effectStack[effectStack.length - 1] // <----- 2 new code
}
}
function cleanupEffect(effect) { // <----- 1 new code
for (let i = 0; i < effect.deps.length; i++) {
const dep = effect.deps[i]
dep.delete(effect)
}
effect.deps.length = 0
}
function effect(fn) {
const _effect = new ReactiveEffect(fn)
_effect.run()
}
function createGetter() {
return function get(target, key) {
const res = target[key]
track(target, key)
return res
}
}
function createSetter() {
return function set(target, key, newValue) {
target[key] = newValue
trigger(target, key)
return true
}
}
function createReactiveObject(data) {
const get = createGetter()
const set = createSetter()
const handler = {
get,
set
}
return new Proxy(data, handler)
}
function track(target, key) {
if (!activeEffect) return
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
trackEffects(dep)
}
function trackEffects(dep) {
dep.add(activeEffect)
activeEffect.deps.push(dep) // <----- 1 new code
}
function trigger(target, key) {
let depsMap = targetMap.get(target)
if (!depsMap) return
let dep = depsMap.get(key)
triggerEffects(dep)
}
function triggerEffects(dep) {
for (const effect of Array.isArray(dep) ? dep : [...dep]) { // <----- 1 new code set套娃问题
if (effect !== activeEffect) { // <----- 3 new code
effect.run()
} // <----- 3 new code
}
}
4 调度执行
当触发trigger
时,可以决定effect 函数
执行的时机⏲与次数
<html></html>
<script src="./index.js"></script>
<script>
let obj = {
foo: 1
}
obj = createReactiveObject(obj)
effect(() => console.log(obj.foo))
obj.foo++
console.log('end')
</script>
1
2
end
在不调整代码位置的前提下, 如何使end在2前面执行
改变执行时机
使用如下
<html></html>
<script src="./index.js"></script>
<script>
let obj = {
foo: 1
}
obj = createReactiveObject(obj)
const fn = () => console.log(obj.foo) // <----- diff code
effect(fn, {
scheduler: () => queueJob(fn)
}) // <----- diff code
obj.foo++
console.log('end')
</script>
新添加代码
class ReactiveEffect {
deps = []
constructor(fn, scheduler = null) { // <----- diff code
this.fn = fn
this.scheduler = scheduler // <----- new code
}
}
function effect(fn, options) { // <----- diff code
const _effect = new ReactiveEffect(fn, options.scheduler) // <----- diff code
_effect.run()
}
function triggerEffects(dep) {
for (const effect of Array.isArray(dep) ? dep : [...dep]) {
if (effect !== activeEffect) {
if (effect.scheduler) { // <----- new code
effect.scheduler() // <----- new code
} else { // <----- new code
effect.run()
} // <----- new code
}
}
}
// <----- the bottom is new code
const queue = []
let flushIndex = 0
const resolvedPromise = Promise.resolve()
function findInsertionIndex(id) {
let start = flushIndex + 1
let end = queue.length
while (start < end) {
const middle = (start + end) >>> 1
const middleJobId = getId(queue[middle])
middleJobId < id ? (start = middle + 1) : (end = middle)
}
return start
}
function queueJob(job) {
if (job.id == null) {
queue.push(job)
} else {
queue.splice(findInsertionIndex(job.id), 0, job)
}
queueFlush()
}
function queueFlush() {
resolvedPromise.then(flushJobs)
}
const getId = (job) => job.id == null ? Infinity : job.id
function flushJobs() {
queue.sort((a, b) => getId(a) - getId(b))
try {
for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
const job = queue[flushIndex]
if (job) {
job()
}
}
} finally {
flushIndex = 0
queue.length = 0
}
}
可以看到, 如果有 scheduler
, 就执行 scheduler
而 scheduler
中的代码是个匿名函数, 其会调用queueJob
, 它会将传入queueJob
的函数压入一个栈中, 而栈会在微任务
中执
改变执行次数-自定义刷新队列
不要过度状态, 只要最终状态
自己通过 promise 实现个调度器
<html></html>
<script src="./index.js"></script>
<script>
let obj = {
foo: 1
}
obj = createReactiveObject(obj)
const fn = () => console.log(obj.foo)
effect(fn, {
scheduler: () => flushJob(fn) // <----- diff code (queueJob -> flushJob)
})
const p = Promise.resolve() // <----- new code
let isFlushing = false
function flushJob(fn) {
if (isFlushing) return
isFlushing = true
p.then(() => {
fn()
}).finally(() => {
isFlushing = false
})
} // <----- new code
obj.foo++
obj.foo++ // <----- new code
obj.foo++
obj.foo++
obj.foo++ // <----- new code
console.log('end')
</script>
1
end
6
在 vue 中使用的方式
<script src="https://unpkg.com/vue@3"></script>
<script>
const { reactive, effect } = Vue
let obj = {
foo: 1
}
obj = reactive(obj)
const fn = () => console.log(obj.foo)
effect(fn, {
scheduler: () => flushJob(fn)
})
const p = Promise.resolve()
let isFlushing = false
function flushJob(fn) {
if (isFlushing) return
isFlushing = true
p.then(() => {
fn()
}).finally(() => {
isFlushing = false
})
}
obj.foo++
obj.foo++
obj.foo++
obj.foo++
obj.foo++
console.log('end')
</script>
5 Lazy
class ReactiveEffect {
run() {
// ......
const res = this.fn() // <----- diff code
// ......
return res // <----- new code
}
}
function effect(fn, options) { // <----- 4 diff code
const _effect = new ReactiveEffect(fn, options.scheduler) // <----- 4 diff code
if (!options.lazy) { // <----- new code
_effect.run()
} // <----- new code
const runner = _effect.run.bind(_effect) // <----- new code
return runner // <----- new code
}
<html></html>
<script src="./index.js"></script>
<script>
let obj = {
foo: 1
}
obj = createReactiveObject(obj)
const fn = () => console.log(obj.foo)
const foo = effect(fn, {
lazy: true
})
foo()
</script>
实际使用用处不大, 可如果将 fn 看成一个 getter 函数呢?
<html></html>
<script src="./index.js"></script>
<script>
let obj = {
foo: 1
}
obj = createReactiveObject(obj)
const fn = () => obj.foo + obj.foo // <----- diff code
const foo = effect(fn, {
lazy: true
})
console.log(foo()) // <----- diff code
obj.foo = 123 // <----- new code
console.log(foo()) // <----- new code
</script>
computed
无缓存使用
<html></html>
<script src="./index.js"></script>
<script>
let obj = {
foo: 1
}
obj = createReactiveObject(obj)
const fn = () => obj.foo + obj.foo
const foo = computed(fn) // <----- diff code
console.log(foo.value) // <----- diff code
</script>
function computed(getterOrOptions) {
let getter
let setter
const onlyGetter = typeof getterOrOptions === 'function'
if (onlyGetter) {
getter = getterOrOptions
setter = () => { }
} else {
getter = getterOrOptions.get
setter = getterOrOptions.set
}
return new ComputedRefImpl(getter, setter)
}
class ComputedRefImpl {
constructor(getter, setter) {
this._setter = setter
this.effect = new ReactiveEffect(getter)
}
get value() {
return this.effect.run()
}
set value(newValue) {
this._setter(newValue)
}
}
以上代码多次访问foo.value
时, 会进行多次计算
解决: 对值进行缓存的功能
有缓存使用
<html></html>
<script src="./index.js"></script>
<script>
let obj = {
foo: 1
}
obj = createReactiveObject(obj)
const fn = () => {
console.log('computing......') // <----- diff code
return obj.foo + obj.foo // <----- diff code
}
const foo = computed(fn) // <----- diff code
console.log(foo.value) // <----- diff code
console.log(foo.value) // <----- diff code
obj.foo = 123123
console.log(foo.value) // <----- diff code
</script>
class ComputedRefImpl {
_value // <----- new code
_dirty = true // <----- new code
constructor(getter, setter) {
this._setter = setter
this.effect = new ReactiveEffect(getter, () => { // <----- diff code
if (!this._dirty) {
this._dirty = true
}
})
}
get value() {
if (this._dirty) { // <----- new code
this._dirty = false // <----- new code
this._value = this.effect.run() // <----- new code
} // <----- new code
return this._value // <----- diff code
}
set value(newValue) {
this._setter(newValue)
}
}
effect 中不会触发
<html></html>
<script src="./index.js"></script>
<script>
let obj = {
foo: 1
}
obj = createReactiveObject(obj)
const fn = () => {
console.log('computing......')
return obj.foo + obj.foo
}
const foo = computed(fn)
effect(() => { // <----- new code
console.log(foo.value)
}, {})
obj.foo++ // <----- new code 没有执行副作用函数
</script>
class ComputedRefImpl {
constructor(getter, setter) {
this._setter = setter
this.effect = new ReactiveEffect(getter, () => {
if (!this._dirty) {
this._dirty = true
triggerEffects(this.dep) // <----- new code 触发
}
})
this.dep = new Set() // <----- new code 设置
}
get value() {
trackEffects(this.dep) // <----- new code 收集 在 effect 函数中已经设置 activeEffect 了
// ......
}
}
当前所有代码
let targetMap = new WeakMap()
const effectStack = [] // <----- 2 new code
let activeEffect
class ReactiveEffect {
deps = [] // <----- 1 new code
constructor(fn, scheduler = null) { // <----- 4 diff code
this.fn = fn
this.scheduler = scheduler // <----- 4 new code
}
run() {
cleanupEffect(this) // <----- 1 new code
activeEffect = this
effectStack.push(this) // <----- 2 new code
const res = this.fn() // <----- 5 diff code
effectStack.pop() // <----- 2 new code
activeEffect = effectStack[effectStack.length - 1] // <----- 2 new code
return res // <----- 5 new code
}
}
function cleanupEffect(effect) { // <----- 1 new code
for (let i = 0; i < effect.deps.length; i++) {
const dep = effect.deps[i]
dep.delete(effect)
}
effect.deps.length = 0
}
function effect(fn, options) { // <----- 4 diff code
const _effect = new ReactiveEffect(fn, options.scheduler) // <----- 4 diff code
if (!options.lazy) { // <----- 5 new code
_effect.run()
} // <----- 5 new code
const runner = _effect.run.bind(_effect) // <----- 5 new code
return runner // <----- 5 new code
}
function createGetter() {
return function get(target, key) {
const res = target[key]
track(target, key)
return res
}
}
function createSetter() {
return function set(target, key, newValue) {
target[key] = newValue
trigger(target, key)
return true
}
}
function createReactiveObject(data) {
const get = createGetter()
const set = createSetter()
const handler = {
get,
set
}
return new Proxy(data, handler)
}
function track(target, key) {
if (!activeEffect) return
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
trackEffects(dep)
}
function trackEffects(dep) {
dep.add(activeEffect)
activeEffect.deps.push(dep) // <----- 1 new code
}
function trigger(target, key) {
let depsMap = targetMap.get(target)
if (!depsMap) return
let dep = depsMap.get(key)
triggerEffects(dep)
}
function triggerEffects(dep) {
for (const effect of Array.isArray(dep) ? dep : [...dep]) { // <----- 1 new code
if (effect !== activeEffect) { // <----- 3 new code
if (effect.scheduler) { // <----- 4 new code
effect.scheduler() // <----- 4 new code
} else { // <----- 4 new code
effect.run()
} // <----- 4 new code
} // <----- 3 new code
}
}
// <----- 4 the bottom is new code
const queue = []
let flushIndex = 0
const resolvedPromise = Promise.resolve()
function findInsertionIndex(id) {
let start = flushIndex + 1
let end = queue.length
while (start < end) {
const middle = (start + end) >>> 1
const middleJobId = getId(queue[middle])
middleJobId < id ? (start = middle + 1) : (end = middle)
}
return start
}
function queueJob(job) {
if (job.id == null) {
queue.push(job)
} else {
queue.splice(findInsertionIndex(job.id), 0, job)
}
queueFlush()
}
function queueFlush() {
resolvedPromise.then(flushJobs)
}
const getId = (job) => job.id == null ? Infinity : job.id
function flushJobs() {
queue.sort((a, b) => getId(a) - getId(b))
try {
for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
const job = queue[flushIndex]
if (job) {
job()
}
}
} finally {
flushIndex = 0
queue.length = 0
}
}
// <----- computed code
function computed(getterOrOptions) {
let getter
let setter
const onlyGetter = typeof getterOrOptions === 'function'
if (onlyGetter) {
getter = getterOrOptions
setter = () => { }
} else {
getter = getterOrOptions.get
setter = getterOrOptions.set
}
return new ComputedRefImpl(getter, setter)
}
class ComputedRefImpl {
_value // <----- new code
_dirty = true // <----- new code
constructor(getter, setter) {
this._setter = setter
this.effect = new ReactiveEffect(getter, () => { // <----- diff code
if (!this._dirty) {
this._dirty = true
triggerEffects(this.dep) // <----- new code 触发
}
})
this.dep = new Set() // <----- new code 设置
}
get value() {
trackEffects(this.dep) // <----- new code 收集 在 effect 函数中已经设置 activeEffect 了
if (this._dirty) { // <----- new code
this._dirty = false // <----- new code
this._value = this.effect.run() // <----- new code
} // <----- new code
return this._value // <----- diff code
}
set value(newValue) {
this._setter(newValue)
}
}
watch
<html></html>
<script src="./index.js"></script>
<script>
let obj = {
foo: 1
}
obj = createReactiveObject(obj)
watch(obj,() => {
console.log('watching ....')
})
obj.foo++
</script>
function watch(source, cb, options) {
return doWatch(source, cb, options)
}
function doWatch(source, cb, options) {
let getter
if (typeof source === 'function') {
getter = source
} else {
getter = () => traverse(source)
}
let scheduler = () => cb()
const effect = new ReactiveEffect(getter, scheduler)
effect.run()
}
function traverse(value, seen = new Set()) {
if (typeof value !== 'object' || value === null || seen.has(value)) return value
seen.add(value)
for (const k in value) {
traverse(value[k], seen)
}
return value
}
新旧值的使用
<html></html>
<script src="./index.js"></script>
<script>
let obj = {
foo: 1
}
obj = createReactiveObject(obj)
watch(() => obj.foo,(newValue, oldValue) => {
console.log(newValue)
console.log(oldValue)
console.log('watching ....')
})
obj.foo++
</script>
function watch(source, cb, options) {
return doWatch(source, cb, options)
}
function doWatch(source, cb, options) {
let getter
if (typeof source === 'function') {
getter = source
} else {
getter = () => traverse(source)
}
let oldValue // <----- new code
const job = () => { // <----- new code
const newValue = effect.run() // <----- new code
cb(newValue, oldValue) // <----- new code
oldValue = newValue // <----- new code
} // <----- new code
let scheduler = job // <----- diff code
const effect = new ReactiveEffect(getter, scheduler)
oldValue = effect.run() // <----- diff code
}
function traverse(value, seen = new Set()) {
if (typeof value !== 'object' || value === null || seen.has(value)) return value
seen.add(value)
for (const k in value) {
traverse(value[k], seen)
}
return value
}
立即执行
watch(() => obj.foo,(newValue, oldValue) => {
console.log(newValue)
console.log(oldValue)
console.log('watching ....')
},{
immediate: true
})
function doWatch(source, cb, options) {
// ......
if(options.immediate) { // <----- new code
job() // <----- new code
} else { // <----- new code
oldValue = effect.run()
} // <----- new code
}