vue 源碼解析 --虛擬Dom-render

instance/index.js

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)
}
renderMixin(Vue)

初始化先執行了 renderMixin 方法, Vue 實例化執行this._init, 執行this.init方法中有initRender()node

renderMixin

installRenderHelpers( 將一些渲染的工具函數放在Vue 原型上)數組

Vue.prototype.$nextTick = function (fn: Function) {
    return nextTick(fn, this)
  }

仔細看這個函數, 在Vue中的官方文檔上這樣解釋promise

Vue 異步執行 DOM 更新。只要觀察到數據變化,Vue 將開啓一個隊列,並緩衝在同一事件循環中發生的全部數據改變。若是同一個 watcher 被屢次觸發,只會被推入到隊列中一次。這種在緩衝時去除重複數據對於避免沒必要要的計算和 DOM 操做上很是重要。而後,在下一個的事件循環「tick」中,Vue 刷新隊列並執行實際 (已去重的) 工做。Vue 在內部嘗試對異步隊列使用原生的 Promise.then 和MessageChannel,若是執行環境不支持,會採用 setTimeout(fn, 0)代替。app

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)
    }
  })
  if (!pending) {
    pending = true
    timerFunc()
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

Vue.nextTick用於延遲執行一段代碼,它接受2個參數(回調函數和執行回調函數的上下文環境),若是沒有提供回調函數,那麼將返回promise對象。異步

function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

這個flushCallbacks 是執行callbacks裏存儲的全部回調函數。
timerFunc 用來觸發執行回調函數函數

  1. 先判斷是否原生支持promise,若是支持,則利用promise來觸發執行回調函數;
  2. 不然,若是支持MutationObserver,則實例化一個觀察者對象,觀察文本節點發生變化時,觸發執行
    全部回調函數。
  3. 若是都不支持,則利用setTimeout設置延時爲0。
const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true

MutationObserver是一個構造器,接受一個callback參數,用來處理節點變化的回調函數,observe方法中options參數characterData:設置true,表示觀察目標數據的改變工具

_render函數

經過執行 createElement 方法並返回的是 vnode,它是一個虛擬的 Node。優化

vnode = render.call(vm._renderProxy, vm.$createElement)

createElement.js

兼容不傳data的狀況 以及判斷傳入的alwaysNormalize是否爲true
再調用_createElement函數,能夠看到,createElement是對參數作了一些處理之後,將其傳給_createElement函數。this

if (Array.isArray(data) || isPrimitive(data)) {
    normalizationType = children
    children = data
    data = undefined
  }
  
  if (isTrue(alwaysNormalize)) {
    normalizationType = ALWAYS_NORMALIZE
  }

建立虛擬節點 _createElement

若是data未定義(undefined或者null)或者是data的__ob__已經被observed,上面綁定了Oberver對象 就建立一個空節點prototype

if (isDef(data) && isDef((data: any).__ob__)) {
    process.env.NODE_ENV !== 'production' && warn(
      `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
      'Always create fresh vnode data objects in each render!',
      context
    )
    return createEmptyVNode()
  }

主要完成的功能是判斷children中的元素是否是數組,若是是的話,就遞歸調用數組,並將每一個元素保存在數組中返回。

export function normalizeChildren (children: any): ?Array<VNode> {
  return isPrimitive(children)
    ? [createTextVNode(children)]
    : Array.isArray(children)
      ? normalizeArrayChildren(children)
      : undefined
}

normalizeArrayChildren 核心代碼
先判斷children中的元素是否是數組,是的話遞歸調用函數。若是第一個和最後一個都是文本節點的話,將其合併,優化。判斷該元素是否是基本類型。若是是,在判斷最後一個結點是否是文本節點,是的話將其與該元素合併爲一個文本節點。不然,把這個基本類型轉換爲文本節點(VNode)最後一種狀況,該元素是一個VNode,先一樣進行優化(合併第一個和最後一個節點),而後判斷該節點的屬性,最後將該節點加入到結果中。

if (Array.isArray(c)) {
      if (c.length > 0) {
        c = normalizeArrayChildren(c, `${nestedIndex || ''}_${i}`)
        // merge adjacent text nodes
        if (isTextNode(c[0]) && isTextNode(last)) {
          res[lastIndex] = createTextVNode(last.text + (c[0]: any).text)
          c.shift()
        }
        res.push.apply(res, c)
      }
     
    } else if (isPrimitive(c)) {
      if (isTextNode(last)) {
        // merge adjacent text nodes
        // this is necessary for SSR hydration because text nodes are
        // essentially merged when rendered to HTML strings
        res[lastIndex] = createTextVNode(last.text + c)
      } else if (c !== '') {
        // convert primitive to vnode
        res.push(createTextVNode(c))
      }
    } else {
      if (isTextNode(c) && isTextNode(last)) {
        // merge adjacent text nodes
        res[lastIndex] = createTextVNode(last.text + c.text)
      } else {
        // default key for nested array children (likely generated by v-for)
        if (isTrue(children._isVList) &&
          isDef(c.tag) &&
          isUndef(c.key) &&
          isDef(nestedIndex)) {
          c.key = `__vlist${nestedIndex}_${i}__`
        }
        res.push(c)
      }
    }
相關文章
相關標籤/搜索