vue@2.0源碼學習---從hello world學習vue的內部作了什麼

原文地址vue

源碼版本爲2.0.0node

前文react

前文講到下面五個函數擴展了Vue的原型git

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
複製代碼

我畫了一個圖,是執行這幾個mixin以後,Vue原型掛載的方法github

image

一個簡單的例子

window.app = new Vue({
    data: {
        msg: 'hello world',
    },
    render (h) { 
      return h('p', this.msg)
    }
}).$mount('#root')

setTimeout(() => {
    app.msg = 'hi world'
}, 2000)

複製代碼

毫無疑問屏幕上會先渲染hello world,隔兩秒後變爲hi world。
本文將從這個簡單的例子追本溯源,看看Vue究竟作了什麼。express

init

咱們沿着執行順序一步一步的看,上文已經找到了Vue的構造函數以下:數組

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}
複製代碼

因此執行new Vue()的時候,實例(vm)會首先執行初始化方法vm._init(),_init方法以下:bash

Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    vm._uid = uid++
    // a flag to avoid this being observed
    vm._isVue = true
    // merge options
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options)
    } else {
      // console.log(resolveConstructorOptions(vm))
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    callHook(vm, 'beforeCreate')
    initState(vm)
    callHook(vm, 'created')
    initRender(vm)
  }
複製代碼

因爲本文是初步探索Vue,因此並無涉及到組件這個概念,可是我拷貝過來的代碼中會常常出現與組件邏輯相關的代碼,直接略過便可。閉包

執行初始化操做首先給實例添加了幾個私有屬性,而後merge了options,vm.$options最終變爲這樣app

vm.$options = {
	components: [..],
	directives: [],
	filters: [],
	vm: vm,
	data: {},
	render: function() {}
}
複製代碼

真正重要的操做是下面的幾個init函數

initLifecycle(vm)    初始化生命週期
initEvents(vm)    初始化事件系統(這裏面作的是父子組件通訊的工做,因此這篇文章暫時略過)
callHook(vm, 'beforeCreate')    執行beforeCreate鉤子
initState(vm)    初始化狀態(包括data、computed、methods、watch)
callHook(vm, 'created')    執行created鉤子
initRender(vm)    渲染頁面	   
複製代碼

從上面能夠看到,created鉤子執行的時機是在數據被observe以後(此時數據尚未收集依賴)。看一下callHook函數:

export function callHook (vm: Component, hook: string) {
  const handlers = vm.$options[hook]
  if (handlers) {
    for (let i = 0, j = handlers.length; i < j; i++) {
      handlers[i].call(vm)
    }
  }
  vm.$emit('hook:' + hook)
}
複製代碼

handle中的this綁定了vm

下面依次分析幾個初始化函數作的工做

initLifecycle

export function initLifecycle (vm: Component) {
  const options = vm.$options
  // locate first non-abstract parent
  let parent = options.parent
  if (parent && !options.abstract) {
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent
    }
    parent.$children.push(vm)
  }

  vm.$parent = parent
  vm.$root = parent ? parent.$root : vm

  vm.$children = []
  vm.$refs = {}

  vm._watcher = null
  vm._inactive = false
  vm._isMounted = false
  vm._isDestroyed = false
  vm._isBeingDestroyed = false
}
複製代碼

這裏沒什麼好說的,vm._watcher和vm._isMounted後面會用到

initEvents

這裏作的是父子組件通訊的相關工做,不在本篇的討論範圍內。

initState

export function initState (vm: Component) {
  vm._watchers = []
  initProps(vm)
  initData(vm)
  initComputed(vm)
  initMethods(vm)
  initWatch(vm)
}
複製代碼

initProps也是組件相關,所以剩下四個是咱們關心的。核心initData完成了數據的observe

1) initData

function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? data.call(vm)
    : data || {}
  if (!isPlainObject(data)) {
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object.',
      vm
    )
  }
  // proxy data on instance
  const keys = Object.keys(data)
  const props = vm.$options.props
  let i = keys.length
  while (i--) {
	// data中的字段不能和props中的重複
    if (props && hasOwn(props, keys[i])) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${keys[i]}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else {
	  // 代理
      proxy(vm, keys[i])
    }
  }
  // observe data
  observe(data)
  data.__ob__ && data.__ob__.vmCount++
}
複製代碼

首先代理data裏面的字段:

在vue中一般這樣訪問一個值

this.msg

而不是

this._data.msg
複製代碼

正是由於proxy(vm, keys[i])已經對key值作了代理,以下:

function proxy (vm: Component, key: string) {
  if (!isReserved(key)) {
    Object.defineProperty(vm, key, {
      configurable: true,
      enumerable: true,
      get: function proxyGetter () {
		// 訪問vm[key]返回的事實上是vm._data[key]
        return vm._data[key]
      },
      set: function proxySetter (val) {
		// 設置vm[key]事實上給vm._data[key]賦值
        vm._data[key] = val
      }
    })
  }
}
複製代碼

接下來就是對數據observe(本文暫不考慮數組),數據的observe能夠說是Vue的核心,網上不少文章已經介紹的十分詳細,這裏我把observe簡化一下以下:

export function observe (value) {
  if (!isObject(value)) {
    return
  }
  let ob = new Observer(value)
  return ob
}

export class Observer {
  constructor (value) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)

    this.walk(value)
  }

  walk (obj) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i], obj[keys[i]])
    }
  }
}

export function defineReactive (obj, key, val) {
  const dep = new Dep()
  let childOb = observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
	// 取值時給數據添加依賴
    get: function reactiveGetter () {
      const value = val
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
        }
      }
      return value
    },
	// 賦值時通知數據依賴更新
    set: function reactiveSetter (newVal) {
      const value = val
      if (newVal === value) {
        return
      }
	  val = newVal
      childOb = observe(newVal)
      dep.notify()
    }
  })
}
複製代碼

整個響應式系統的核心在於defineReactive這個函數,利用了一個閉包把數據的依賴收集起來,下文咱們會看到Dep.target事實上是一個個watcher。

這裏有個須要注意的地方:

if (childOb) {
   childOb.dep.depend()
}
複製代碼

爲何閉包裏的dep已經收集過了依賴,這裏還要加上這句代碼?先看一個例子

data: {
	name: {
		first: 'zhang'
	}
}
複製代碼

假如數據是這樣,咱們這樣改變數據

this.name.last = 'san'
複製代碼

想一下這樣會出發依賴更新嗎?事實上是不會的,由於last並無被監聽。Vue給咱們指明瞭正確的姿式是:

this.$set('name', 'last', 'san')
複製代碼

來看一下set的源碼(爲方便,我已把數組相關的代碼刪掉)

export function set (obj: Array<any> | Object, key: any, val: any) {
  if (hasOwn(obj, key)) {
    obj[key] = val
    return
  }
  const ob = obj.__ob__
  if (!ob) {
    obj[key] = val
    return
  }
  // 對新增的屬性進行監聽
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
}
複製代碼

想一下,this.name變化時講道理是應該通知閉包內name的依賴更新,可是因爲新增屬性並不會觸發defineReactive,而this.name.__ob__的依賴和name屬性的依賴是相同的,因此this.name.ob.notify()可達到相同的效果,這也是上面childOb.dep.depend()的緣由。同理del也是如此:

export function del (obj: Object, key: string) {
  const ob = obj.__ob__
  if (!hasOwn(obj, key)) {
    return
  }
  delete obj[key]
  if (!ob) {
    return
  }
  ob.dep.notify()
}
複製代碼

2)initWatch

function initWatch (vm: Component) {
  const watch = vm.$options.watch
  if (watch) {
    for (const key in watch) {
      const handler = watch[key]
      if (Array.isArray(handler)) {
        for (let i = 0; i < handler.length; i++) {
          createWatcher(vm, key, handler[i])
        }
      } else {
        createWatcher(vm, key, handler)
      }
    }
  }
}

function createWatcher (vm: Component, key: string, handler: any) {
  let options
  if (isPlainObject(handler)) {
    options = handler
    handler = handler.handler
  }
  if (typeof handler === 'string') {
    handler = vm[handler]
  }
  vm.$watch(key, handler, options)
}
複製代碼

能夠看出來initWatch最終調用的是$watch

Vue.prototype.$watch = function (
  expOrFn: string | Function,
  cb: Function,
  options?: Object
): Function {
  const vm: Component = this
  options = options || {}
  options.user = true
  const watcher = new Watcher(vm, expOrFn, cb, options)
  if (options.immediate) {
    cb.call(vm, watcher.value)
  }
  return function unwatchFn () {
    watcher.teardown()
  }
}
複製代碼

最終實例化了一個Watcher,watcher能夠分爲兩種,一種是用戶定義的(咱們在實例化Vue時傳入的watch選項),一種是Vue內部本身實例化的,後文會看到。

watcher的代碼以下:

export default class Watcher {
  constructor (vm, expOrFn, cb, options) {
    this.vm = vm
    vm._watchers.push(this)
    // options
    this.deep = !!options.deep
    this.user = !!options.user
    this.lazy = !!options.lazy
    this.sync = !!options.sync
    this.expression = expOrFn.toString()
    this.cb = cb
    this.id = ++uid // uid for batching
    this.active = true
    this.dirty = this.lazy // for lazy watchers
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    // parse expression for getter
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = function () {}
        process.env.NODE_ENV !== 'production' && warn(
          `Failed watching path: "${expOrFn}" ` +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        )
      }
    }
    this.value = this.lazy
      ? undefined
      : this.get()
  }

  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  get () {
    pushTarget(this)
    const value = this.getter.call(this.vm, this.vm)
    // "touch" every property so they are all tracked as
    // dependencies for deep watching
    if (this.deep) {
      traverse(value)
    }
    popTarget()
    this.cleanupDeps()
    return value
  }

  /**
   * Add a dependency to this directive.
   */
  addDep (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)
      }
    }
  }

  /**
   * Clean up for dependency collection.
   */
  cleanupDeps () {
    let i = this.deps.length
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)
      }
    }
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }

  /**
   * Subscriber interface.
   * Will be called when a dependency changes.
   */
  update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }

  /**
   * Scheduler job interface.
   * Will be called by the scheduler.
   */
  run () {
    if (this.active) {
      const value = this.get()
      if (
        value !== this.value ||
        // Deep watchers and watchers on Object/Arrays should fire even
        // when the value is the same, because the value may
        // have mutated.
        isObject(value) ||
        this.deep
      ) {
        // set new value
        const oldValue = this.value
        this.value = value
        if (this.user) {
          try {
            this.cb.call(this.vm, value, oldValue)
          } catch (e) {
            process.env.NODE_ENV !== 'production' && warn(
              `Error in watcher "${this.expression}"`,
              this.vm
            )
            /* istanbul ignore else */
            if (config.errorHandler) {
              config.errorHandler.call(null, e, this.vm)
            } else {
              throw e
            }
          }
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }

  /**
   * Evaluate the value of the watcher.
   * This only gets called for lazy watchers.
   */
  evaluate () {
    this.value = this.get()
    this.dirty = false
  }

  /**
   * Depend on all deps collected by this watcher.
   */
  depend () {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    }
  }

  /**
   * Remove self from all dependencies' subcriber list. */ teardown () { if (this.active) { // remove self from vm's watcher list
      // this is a somewhat expensive operation so we skip it
      // if the vm is being destroyed or is performing a v-for
      // re-render (the watcher list is then filtered by v-for).
      if (!this.vm._isBeingDestroyed && !this.vm._vForRemoving) {
        remove(this.vm._watchers, this)
      }
      let i = this.deps.length
      while (i--) {
        this.deps[i].removeSub(this)
      }
      this.active = false
    }
  }
}
複製代碼

代碼蠻長的,慢慢看

watcher實例有一個getter方法,咱們上文提到過watcher有兩種,當watcher是用戶建立時,此時的expOrFn就是一個expression,例如name或者name.first,此時它會被parsePath格式化爲一個取值函數

const bailRE = /[^\w\.\$]/
export function parsePath (path: string): any {
  if (bailRE.test(path)) {
    return
  } else {
    const segments = path.split('.')
	// obj爲vue實例時   輸出的即是
    return function (obj) {
      for (let i = 0; i < segments.length; i++) {
        if (!obj) return
        obj = obj[segments[i]]
      }
      return obj
    }
  }
}
複製代碼

格式化完getter函數以後緊接着執行get方法,數據的依賴正是在watcher的get方法執行時收集的,能夠說get是鏈接observer和watcher的橋樑

get () {
  pushTarget(this)
  const value = this.getter.call(this.vm, this.vm)
  // "touch" every property so they are all tracked as
  // dependencies for deep watching
  if (this.deep) {
    traverse(value)
  }
  popTarget()
  this.cleanupDeps()
  return value
}
複製代碼

get方法裏面執行了getter,前面已經說過getter是一個取值函數,這不由令咱們聯想到了數據的監聽,當取值時假如Dep.target存在那麼就能夠收集依賴了,想一想就激動。既然這樣,pushTargetpopTarget必然是定義Dep.target

Dep.target = null
const targetStack = []

export function pushTarget (_target: Watcher) {
  if (Dep.target) targetStack.push(Dep.target)
  Dep.target = _target
}

export function popTarget () {
  Dep.target = targetStack.pop()
}
複製代碼

如咱們所想,pushTarget和popTarget定義了全局惟一的Dep.target(即調用get的watcher)。這裏是須要思考的,源碼的寫法顯然代表當getter函數調用時可能會觸發其餘watcher的get方法,事實上當咱們watch一個計算屬性或者渲染一個計算屬性時便會出現這種狀況,咱們本篇暫不討論。

getter執行後,data相應閉包中的dep會執行dep.depend(),最終watcher會被添加到dep的訂閱subs中,但data中的數據改變時,相應閉包中dep會notify它的subs(即watcher)依次update,最終調用watcher的run方法實現更新,看一下run方法:

run () {
    if (this.active) {
      const value = this.get()
      if (
        value !== this.value ||
        // Deep watchers and watchers on Object/Arrays should fire even
        // when the value is the same, because the value may
        // have mutated.
        isObject(value) ||
        this.deep
      ) {
        // set new value
        const oldValue = this.value
        this.value = value
        if (this.user) {
          try {
            this.cb.call(this.vm, value, oldValue)
          } catch (e) {
            process.env.NODE_ENV !== 'production' && warn(
              `Error in watcher "${this.expression}"`,
              this.vm
            )
            /* istanbul ignore else */
            if (config.errorHandler) {
              config.errorHandler.call(null, e, this.vm)
            } else {
              throw e
            }
          }
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }
複製代碼

run方法執行的時候會首先執行get方法,而後比較新的value的舊的value,若是不相同就執行watcher.cb。至此Vue的響應式雛形基本完成。

3)initComputed

先看代碼(簡化了)

function initComputed (vm) {
  const computed = vm.$options.computed
  if (computed) {
    for (const key in computed) {
      const userDef = computed[key]
      computedSharedDefinition.get = makeComputedGetter(userDef, vm)
      Object.defineProperty(vm, key, computedSharedDefinition)
    }
  }
}

function makeComputedGetter (getter, owner) {
  const watcher = new Watcher(owner, getter, noop, {
    lazy: true
  })
  return function computedGetter () {
    if (watcher.dirty) {
      watcher.evaluate()
    }
    if (Dep.target) {
      watcher.depend()
    }
    return watcher.value
  }
}
複製代碼

從代碼能夠看到,計算屬性的值就是與之相關watcher的value。注意這裏options的lazy爲true,這代表建立watcher(稱爲a)的時候並不會執行get方法,也就是不會收集依賴。只有當咱們取計算屬性的值的時候纔會收集依賴,那麼何時會取計算屬性的值呢?好比watch計算屬性或者把計算屬性寫進render函數中。由於此get是惰性的,因此依賴於其餘watcher(稱爲b)的喚醒,當執行完watcher.evaluate()以後,會把a添加到計算屬性依賴數據dep的subs中,當執行完watcher.depend()以後,會把這個b添加到計算屬性依賴數據dep的subs中。當依賴數據變化時,a和b(至少有這兩個)watcher均會update,而且a的update是靠前的,由於其id在前面,因此當b進行update時獲取到的計算屬性爲更新後的。

這裏比較繞,多想一想吧。

initMethods

function initMethods (vm: Component) {
  const methods = vm.$options.methods
  if (methods) {
    for (const key in methods) {
      if (methods[key] != null) {
        vm[key] = bind(methods[key], vm)
      } else if (process.env.NODE_ENV !== 'production') {
        warn(`Method "${key}" is undefined in options.`, vm)
      }
    }
  }
}
複製代碼

這個沒什麼好說的,就是將方法掛載到實例上。

initRender

initRender裏面執行了首次渲染。

在進行下面的內容以前咱們先說明一下實例的_render方法,這個方法是根據render函數返回虛擬dom,什麼是所謂的虛擬dom,看下Vue文檔的解釋:

它所包含的信息會告訴 Vue 頁面上須要渲染什麼樣的節點,及其子節點。咱們把這樣的節點描述爲「虛擬節點 (Virtual Node)」,也常簡寫它爲「VNode」。「虛擬 DOM」是咱們對由 Vue 組件樹創建起來的整個 VNode 樹的稱呼。

至於vnode的生成原理不在本文的討論範圍。

進入正題,看下initRender的代碼:

export function initRender (vm: Component) {
  // 對於組件適用   其在父樹的佔位
  vm.$vnode = null // the placeholder node in parent tree
  // 虛擬dom
  vm._vnode = null // the root of the child tree
  vm._staticTrees = null
  vm._renderContext = vm.$options._parentVnode && vm.$options._parentVnode.context
  vm.$slots = resolveSlots(vm.$options._renderChildren, vm._renderContext)
  // bind the public createElement fn to this instance
  // so that we get proper render context inside it.
  // 這就是render函數裏面咱們傳遞的那個參數   
  // 它的做用是生成vnode(虛擬dom)
  vm.$createElement = bind(createElement, vm)
  if (vm.$options.el) {
    vm.$mount(vm.$options.el)
  }
}
複製代碼

initRender執行了實例的$mount,而$mount其實是調用的內部方法_mount,如今來看_mount(簡化了)

Vue.prototype._mount = function (el, hydrating) {
    const vm = this
    vm.$el = el
    callHook(vm, 'beforeMount')
    vm._watcher = new Watcher(vm, () => {
      vm._update(vm._render(), hydrating)
    }, noop)
    hydrating = false
    // root instance, call mounted on self
    // mounted is called for child components in its inserted hook
	// 假如vm是根實例  那麼其$root屬性就是其自身
    if (vm.$root === vm) {
      vm._isMounted = true
      callHook(vm, 'mounted')
    }
    return vm
  }
複製代碼

_mount給咱們提供了beforeMountmounted兩個鉤子,可想而知實例化watcher的時候已經生成了虛擬dom,而且根據虛擬dom建立了真實dom並掛載到了頁面上。

上文咱們已經講過watcher的建立過程,因此可知vm._watcher的getter函數即爲

() => {
      vm._update(vm._render(), hydrating)
 }
複製代碼

而且此watcher的get並不是爲惰性get,因此watcher實例化以後便會當即執行get方法,事實上是執行vm._render(),並將得到的vnode做爲參數傳給vm._update執行。思考一下_render()函數執行時會發生什麼,顯然會獲取data的值,此時便會觸發get攔截器,從而將vm._watcher添加至對應dep的subs中。

vm._update代碼以下(簡化了):

Vue.prototype._update = function (vnode, hydrating) {
    const vm = this
    if (vm._isMounted) {
      callHook(vm, 'beforeUpdate')
    }
    const prevVnode = vm._vnode
    vm._vnode = vnode
    if (!prevVnode) {
      // Vue.prototype.__patch__ is injected in entry points
      // based on the rendering backend used.
	  //  若是以前的虛擬dom不存在  說明是首次掛載
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating)
    } else {
	  // 以前的虛擬dom存在  須要先對新舊虛擬dom對比  而後差別化更新
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    if (vm._isMounted) {
      callHook(vm, 'updated')
    }
  }
複製代碼

能夠看到_update的主要做用就是根據vnode造成真實dom節點。當data數據改變時,對應的dep會通知subs即vm._watcher進行update,update方法中會再次執行vm._watcher.get(),從而調用vm._update進行試圖的更新。

這裏有個地方值得咱們思考,更新後的視圖可能再也不依賴於上次的數據了,什麼意思呢

更新前 <p>{{this.a}}</p>

更新後 <p>{{this.b}}</p>
複製代碼

也就是說須要清除掉a數據中watcher的依賴。看下Vue中的實現

dep.depend並無咱們想的那麼簡單,以下

depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  addSub (sub: Watcher) {
    this.subs.push(sub)
  }
複製代碼

相應的watcher的addDep以下,他會把本次更新依賴的dep的id存起來,若是更新前的id列表不存在新的dep的id,說明視圖更新後依賴於這個dep,因而將vm._watcher添加到此dep的subs中

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
        dep.addSub(this)
      }
    }
  }
複製代碼

假如以前dep的id列表存在存在某些id,這些id不存在與更新後dep的id列表,代表更新後的視圖不在依賴於這些id對應的dep,那麼須要將vm._watcher從這些dep中移除,這部分工做是在cleanupDeps中完成的,以下:

cleanupDeps () {
    let i = this.deps.length
    // console.log(i)
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)
      }
    }
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }
複製代碼

結語

這篇文章只是對Vue內部實現機制的簡單探索,不少地方沒有涉及到,好比組件機制、模板的編譯、虛擬dom樹的建立等等,但願這些能在之後慢慢搞清楚。

相關文章
相關標籤/搜索