vue2源码解析--如何收集依赖、异步更新

79 阅读2分钟

依赖收集

注:为了简化代码,我把源码中的一些错误处理及判断全部删掉,只保留核心代码,以便较好的理解代码执行流程。 示例html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Vue 源码解读</title>
</head>
<body>
<div id="app" @click="handleClick">
  <div id="msg">
    {{ msg }}
  </div>
  {{branches}}{{currentBranch}}
  {{text}}
</div>
<script src="../dist/vue.js"></script>
<script>
  new Vue({
    provide: {
      foo: [1,2,3],
      fl: 'car'
    },
    el: '#app',
    data: {
      text: 'text111',
      msg: 'xx',
      branches: ['master', {
        else: 'test'
      }],
      currentBranch: 'master',
      commits: null,
      user: {
        id: 2,
        nickname: '亚克力'
      }
    },
    created: function () {
      this.fetchData()
    },
    watch: {
      currentBranch () {
        this.msg = 'ewriw'
      }
    },
    computed: {
      username() {
        return this.currentBranch + 2
      }
    },
    filters: {
      truncate: function (v) {
        var newline = v.indexOf('\n')
        return newline > 0 ? v.slice(0, newline) : v
      },
      formatDate: function (v) {
        return v.replace(/T|Z/g, ' ')
      }
    },
    methods: {
      handleClick() {
        this.msg = '你噶干嘛'
        this.text = 'dsfa'
        console.log(document.getElementById('msg'))
        this.$nextTick(() => {
          console.log(111)
          this.branches = 'happy new year'
        })
        this.$nextTick(() => {
          console.log(222)
          this.branches = 'happy new year'
        })
      },
      fetchData: function () {
      }
    }
  })
</script>
</body>
</html>

初始化data

export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

最后会调用observe方法

function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  // proxy data on instance
  proxy(vm, `_data`, key)
  // observe data
  observe(data, true /* asRootData */)
}

核心:最后会为每个属性执行observe(value),observe()最终会返回一个Observe()实例,可以打印出new Observe()看一下( console.log(ob)) 最终会执行walk()方法

export function observe (value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
 
  ob = new Observer(value)
  if (asRootData && ob) {
    ob.vmCount++
  }
  console.log(ob)
  return ob
}

Observe实例

export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data
  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    if (Array.isArray(value)) { 
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }

  /**
   * Walk through all properties and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

  /**
   * Observe a list of Array items.
   */
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

然后是defineReactive方法

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep()
  const property = Object.getOwnPropertyDescriptor(obj, key)

  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
  })
}

核心代码来了: Object.defineProperty,为数据定义get及set 先看get, if(Dep.target) {dep.depend()} 如何理解呢? 比如: computed: { username () { return this.currentBranch + 2 } } 这里就要看Watch了,我先说结论,再看代码,以上面代码为例

  • 1、在模板渲染、computed、watch时或new Watcher(),可以从源码中全局搜索到。
  • 2、new Watcher()最终会执行一个方法,this.get(),这个方法会把Dep.target置为当前的watcher,然后执行getter方法拿到值。
  • 3、执行function () {
  •     return this.currentBranch + 2
    
  • },此时出发this.currentBranch 的get,再看上面,Dep.target = 当前watcher,最终会走dep.depend()
    
  • 4、Dep.target = null
depend () {
  if (Dep.target) {
    Dep.target.addDep(this)
  }
}
addDep (dep: Dep) {
  const id = dep.id
  if (!this.newDepIds.has(id)) {
    this.newDepIds.add(id)
    this.newDeps.push(dep)
    if (!this.depIds.has(id)) {
      dep.addSub(this)
    }
  }
}
addSub (sub: Watcher) {
  this.subs.push(sub)
}

这里就完成了依赖的收集 如下图 Observe实例中,dep实例的subs记录了所有依赖当前是属性的watcher,同时watcher中也记录了deps,即所有依赖的属性信息。 watcher:

image.png Observe: image.png 可以看到,最终生成了4个Observer 1、整个data对象是一个 2、branches数组是一个 3、user对象是一个 4、branchs中的{else: test}是一个 { dep: {}, value: '' } 了结了依赖收集的流程,很多事情都会很清晰了。 比如响应式原理,watcher、computd区别

异步更新

依赖收集完成后,如何进行数据更新,继续往西看 Object.defineProperty中set方法最终会走到dep.notify(),notify()最后会执行所有watcher的update方法 先上代码,慢慢看:

notify () {
  // stabilize the subscriber list first
  const subs = this.subs.slice()
  for (let i = 0, l = subs.length; i < l; i++) {
    subs[i].update()
  }
}

update

update () {
  /* istanbul ignore else */
  if (this.lazy) {
    this.dirty = true
  } else if (this.sync) {
    this.run()
  } else {
    queueWatcher(this)
  }
}

queueWatcher

export function queueWatcher (watcher: Watcher) {
  const id = watcher.id
  if (has[id] == null) {
    has[id] = true
    if (!flushing) {
      queue.push(watcher)
    } else {
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1, 0, watcher)
    }
    // queue the flush
    if (!waiting) {
      waiting = true

      if (process.env.NODE_ENV !== 'production' && !config.async) {
        flushSchedulerQueue()
        return
      }
      nextTick(flushSchedulerQueue)
    }
  }
}

nextTick

export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  console.log(pending)
  if (!pending) {
    pending = true
    timerFunc()
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}
function flushSchedulerQueue () {
  currentFlushTimestamp = getNow()
  flushing = true
  let watcher, id
  queue.sort((a, b) => a.id - b.id)
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    if (watcher.before) {
      watcher.before()
    }
    id = watcher.id
    has[id] = null
    watcher.run()
    // in dev build, check and stop circular updates.
    if (process.env.NODE_ENV !== 'production' && has[id] != null) {
      circular[id] = (circular[id] || 0) + 1
      if (circular[id] > MAX_UPDATE_COUNT) {
        warn(
          'You may have an infinite update loop ' + (
            watcher.user
              ? `in watcher with expression "${watcher.expression}"`
              : `in a component render function.`
          ),
          watcher.vm
        )
        break
      }
    }
  }


  resetSchedulerState()


  // devtool hook
  /* istanbul ignore if */
  if (devtools && config.devtools) {
    devtools.emit('flush')
  }
}

比如上例中

handleClick() {
  this.msg = '你噶干嘛'
  this.text = 'dsfa'
  this.$nextTick(() => {
    this.branches = 'happy new year'
  })
},

当handleClick触发时,发送了什么

  1. flushSchedulerQueue是什么?是一个回调函数,这个函数执行后,会遍历当前队列中的watcher,执行他们的run()方法,执行完后设置当前wating = false。
  2. nextTick(fn),在callbacks里面push一个回调函数,如果!pending,执行timerFunc()
  3. timerFunc是什么,可以简单理解为() => { Promise.resolve().then() }

总结一下:

  1. this.msg = 'xx', 触发msg的set => 调用所有依赖msg的watcher的update方法,当前只有一个模板渲染的watcher。
  2. 走到queueWatcher,queue.push(watcher),此时队列中加入了页面渲染的watcher,此时wating默认是false,接下来,wating = true, nextTick(flushSchedulerQueue),清空queue
  3. 在全局的callbacks里面push一个flushSchedulerQueue,如果pedding = false,执行
  4. pending = true
  5. timerFunc()
  6. pedding初始值默认为false,所以就进来了,执行timerFunc(),此时pedding为true。
  7. timerfunc(),把当前flushSchedulerQueue方法放到异步队列中个,代码执行完成后,再执行异步代码。
  8. 继续执行同步代码。
  9. this.text = 'xxx',又开始从1开始,触发update,queue.push(watcher),这里有个去重,因为msg和text触发的是同一个watcher,所以这里就直接return,而且当前wating = true,也不会进到nextTick(flushSchedulerQueue)。
  10. 继续执行同步代码:nextTick
  11. 同步代码执行完成,开始执行异步代码,这里立马把pedding = false。执行flushSchedulerQueue,更新页面。
  12. this.nextTick()直接触发nexttick方法,在callbacks里面push一个当前的回调函数,此时pedding = false,执行timerFunc(),也就是 () => this.branches = 'happy new year' 15、这里又进入了第一步,更新页面等等。 简单来说 this.msg = '你噶干嘛' 想浏览器的异步队列中加入了一个callback1, 同步代码执行完后,执行callback1() 此时,nextTIck()又向异步队列中加入了一个callback2, 然后执行callback2