Vue源碼分析系列三:render

回顧

在上一系列中,我麼已經瞭解到$mount是如何工做的,最後經過調用 updateComponent方法來執行vm._update(vm._render(), hydrating);,接下來咱們來分析一下 vm._render()方法是如何運行的。html


手寫 render 方法

首先來看一下使用template的形式,node

new Vue({
  el: '#app',
  template: '<h1>hello world</h1>'
})
// 或者這種方式
new Vue({
  el: '#app',
  components: { App },
  template: '<App/>'
})
複製代碼

那麼,咱們如何手寫 render 函數呢?仔細觀察下面這段代碼,試想一下這裏的 createElement 參數是什麼 。瀏覽器

new Vue({
    el: '#app',
    render(createElement) {
        return createElement('div', {
            attrs: {
                id: 'app1' //注意這裏的id 是 app1
            }
        }, this.value)
    },
    data() {
        return {
            value: 'render function'
        }
    }
})
複製代碼

來看頁面效果:bash

請注意上面紅框框的 id, 是咱們剛剛定義的 'app1'。因此咱們爲何不能將元素綁定在body/html這些元素上就知道了吧,由於它會替換掉頁面上的元素。app

好了,進入正題,開始分析源碼。函數

_render

Vue 的 _render 方法是實例的一個私有方法,它用來把實例渲染成一個虛擬 Node。它的定義在 src/core/instance/render.js 文件中:工具

注意,在分析源碼的時候必定要走主線,不能一次性將某一個方法全看完或看透徹,由於它可能依賴了不少其它變量或者方法,若是都去分析,可能到後面看着看着就什麼都看不懂了。在這裏咱們只分析 render 方法,像下面的 _parentVnode、$slots、$vnode 等等在後面的系列章節中再分析。ui

Flow 是 facebook 出品的 JavaScript 靜態類型檢查工具。Vue.js 的源碼利用了 Flow 作了靜態類型檢查,function (): VNode表示這個方法的返回值是一個 vnode。this

在這段代碼中,主要研究這一段代碼 render.call(vm._renderProxy, vm.$createElement)spa

Vue.prototype._render = function (): VNode {
    const vm: Component = this
    const { render, _parentVnode } = vm.$options

    // 下面這兩個 if 先不用看
    // reset _rendered flag on slots for duplicate slot check
    if (process.env.NODE_ENV !== 'production') {
      for (const key in vm.$slots) {
        // $flow-disable-line
        vm.$slots[key]._rendered = false
      }
    }

    if (_parentVnode) {
      vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject
    }

    // set parent vnode. this allows render functions to have access
    // to the data on the placeholder node.
    vm.$vnode = _parentVnode
    // render self
    let vnode
    try {
      // 分析主線
      // 從這裏咱們能夠看出,手寫render方法中的createElement參數就是 vm.$createElement方法
      vnode = render.call(vm._renderProxy, vm.$createElement)
      
      // 這個render 是 vm.$options.render
      // vm._renderProxy 若是在生產環境下,其實就是 vm 
      // 若是在開發環境下,就是 Proxy 對象(ES6中的API,不瞭解的話能夠去看看)
      // vm.$createElement 定義在 initRender 函數中,初始化的時候定義的
      // 手寫 render 函數建立 vnode 的方法
      // vm.$createElement = function (a, b, c, d) { 
      //    return createElement(vm, a, b, c, d, true);
      // };
      
      // 若是是編譯生成的render函數,建立vnode的方法則是下面這個方法
      // vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };
      
    } catch (e) {
      handleError(e, vm, `render`)
      // return error render result,
      // or previous vnode to prevent render error causing blank component
      /* istanbul ignore else */
      if (process.env.NODE_ENV !== 'production') {
        if (vm.$options.renderError) {
          try {
            vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
          } catch (e) {
            handleError(e, vm, `renderError`)
            vnode = vm._vnode
          }
        } else {
          vnode = vm._vnode
        }
      } else {
        vnode = vm._vnode
      }
    }
    // return empty vnode in case the render function errored out
    if (!(vnode instanceof VNode)) {
      if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
        warn(
          'Multiple root nodes returned from render function. Render function ' +
          'should return a single root node.',
          vm
        )
      }
      vnode = createEmptyVNode()
    }
    // set parent
    vnode.parent = _parentVnode
    return vnode
  }
}
複製代碼

_renderProxy 是什麼 ?

這個方法在初始化的時候就定義好了,在 initMixin中的 Vue.prototype._init() 方法中,咱們來看一下是如何定義的:

if (process.env.NODE_ENV !== 'production') {
  // 若是是開發環境,就調用 initProxy 方法
  initProxy(vm);
} else {
  // 不然就是 vm 實例
  vm._renderProxy = vm;
}
複製代碼

Proxy對象

描述: Proxy 能夠理解成,在目標對象以前架設一層「攔截」,外界對該對象的訪問,都必須先經過這層攔截,所以提供了一種機制,能夠對外界的訪問進行過濾和改寫。Proxy 這個詞的原意是代理,用在這裏表示由它來「代理」某些操做,能夠譯爲「代理器」。

initProxy 方法

路徑: src/core/instance/proxy.js

// proxy.js中還有一些其它方法,這裏咱們只看 initProxy方法
let initProxy
if (process.env.NODE_ENV !== 'production') {

  // 判斷咱們的瀏覽器支不支持proxy方法
  const hasProxy =
    typeof Proxy !== 'undefined' && isNative(Proxy)

    if (hasProxy) {
        const isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta,exact')
        config.keyCodes = new Proxy(config.keyCodes, {
          set (target, key, value) {
            if (isBuiltInModifier(key)) {
              warn(`Avoid overwriting built-in modifier in config.keyCodes: .${key}`)
              return false
            } else {
              target[key] = value
              return true
            }
          }
        })
    }
    var hasHandler = {
        has: function has (target, key) {
          var has = key in target;
          var isAllowed = allowedGlobals(key) || key.charAt(0) === '_';
          if (!has && !isAllowed) {
            // 這個警告就是當你使用了沒有定義的變量或者方法時調用的方法
            // 方法就不列出來了。
            warnNonPresent(target, key);
          }
          return has || !isAllowed
        }
    };

    initProxy = function initProxy (vm) {
        if (hasProxy) {
          // determine which proxy handler to use
          const options = vm.$options
          const handlers = options.render && options.render._withStripped
            ? getHandler
            : hasHandler
          // options.render上沒有_withStripped,因此handlers 就是hasHandler
          vm._renderProxy = new Proxy(vm, handlers)
        } else {
          vm._renderProxy = vm
        }
    }
}
export { initProxy }
複製代碼

最後

當執行完了 _initProxy 以後,再回到 render 方法中看以下這段代碼

if (!(vnode instanceof VNode)) {
      if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
        // render function 返回了多個 vnode 根節點
        // 應當返回單一 vnode 根節點
        warn(
          'Multiple root nodes returned from render function. Render function ' +
          'should return a single root node.',
          vm
        );
      }
      vnode = createEmptyVNode();
    }
    // set parent
    vnode.parent = _parentVnode;
    
    // 最後 return 這個 vnode
    return vnode
複製代碼

總結

vm._render 最終是經過執行 createElement 方法並返回的是 vnode,那這個 createElement 是如何建立 vnode 的呢 ? 在後面的兩個系列中,我會先介紹 virtual DOM 的概念,而後再分析 createElement 的實現。

相關文章
相關標籤/搜索