基于vue3源码及《vue设计与实现》―手写 vue 响应式系统

初始

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的函数压入一个栈中, 而栈会在微任务中执

七千字深度剖析 Vue3 的调度系统

改变执行次数-自定义刷新队列

不要过度状态, 只要最终状态

自己通过 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
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值