首頁 > 軟體

Vue範例初始化為渲染函數設定檢查原始碼剖析

2022-08-22 22:01:43

引言

之前的文章提到,Vue範例化時_init方法做了很多處理,其中就有這麼一段:

if (__DEV__) {
  initProxy(vm)
} else {
  vm._renderProxy = vm
}

在生產模式下,_renderProxy直接指向了Vue範例本身,而在開發環境下呼叫了initProxy方法,那麼它究竟是做什麼的呢?

_renderProxy是幹什麼的

通過對_renderProxy進行全域性搜尋,我們在srccoreinstancerender.ts檔案中找到了它:

// 原始碼檔案 srccoreinstancerender.ts
vnode = render.call(vm._renderProxy, vm.$createElement)

也就是說,_renderProxy是渲染函數render的執行上下文,在生產環境下,執行上下文就是範例本身,而在開發環境下,執行上下文則使用initProxy進行了處理,我們接下來看看它究竟做了什麼。

initProxy方法

首先判斷了當前環境下Proxy物件是否存在進行了判斷:

//原始碼檔案 srccoreinstanceproxy.ts
const hasProxy = typeof Proxy !== 'undefined' && isNative(Proxy)
//...
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
    vm._renderProxy = new Proxy(vm, handlers)
  } else {
    vm._renderProxy = vm
  }
}

如果Proxy物件不存在,就放棄治療,上下文仍為原Vue範例。

而如果Proxy物件存在,則進一步去$options裡獲取_withStripped屬性,如果_withStripped存在,則使用getHandler方法來代理Vue範例,如果不存在,就使用hasHandler來代理範例。

關於Proxy物件的用法,我在這篇文章裡提過,簡單來說,它可以為一個物件設定代理,用以攔截物件的各種方法。

那麼,我們進一步看一下,hasHandlergetHandler都做了什麼,首先是比較簡單的getHandler:

// 原始碼檔案:srccoreinstanceproxy.ts
const getHandler = {
  get(target, key) {
    if (typeof key === 'string' && !(key in target)) {
      if (key in target.$data) warnReservedPrefix(target, key)
      else warnNonPresent(target, key)
    }
    return target[key]
  }
}

這個方法攔截了Vue範例物件的getter,也就是說,當獲取範例的屬性時,就會觸發這個方法,在這個方法中,對屬性值是否在範例中以及是否在範例的$data中進行了檢查,如果不存在則發出相應的警告:

// 原始碼檔案:srccoreinstanceproxy.ts
const warnReservedPrefix = (target, key) => {
  warn(
    `Property "${key}" must be accessed with "$data.${key}" because ` +
      'properties starting with "$" or "_" are not proxied in the Vue instance to ' +
      'prevent conflicts with Vue internals. ' +
      'See: https://v2.vuejs.org/v2/api/#data',
    target
  )
}
const warnNonPresent = (target, key) => {
  warn(
    `Property or method "${key}" is not defined on the instance but ` +
      'referenced during render. Make sure that this property is reactive, ' +
      'either in the data option, or for class-based components, by ' +
      'initializing the property. ' +
      'See: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.',
    target
  )
}

hasHandler則是對範例物件的in操作符進行攔截,也就是攔截以下操作:

  • 屬性查詢:foo in proxy
  • 繼承屬性查詢:foo in Object.create(proxy)
  • with 檢查: with(proxy) { (foo); }
  • Reflect.has()

那麼做了什麼呢:

// 原始碼檔案:srccoreinstanceproxy.ts
const hasHandler = {
  has(target, key) {
    const has = key in target
    const isAllowed =
      allowedGlobals(key) ||
      (typeof key === 'string' &&
        key.charAt(0) === '_' &&
        !(key in target.$data))
    if (!has && !isAllowed) {
      if (key in target.$data) warnReservedPrefix(target, key)
      else warnNonPresent(target, key)
    }
    return has || !isAllowed
  }
}

類似的,依然是對屬性是否在範例中存在進行了檢查,但是多了一步判斷,也就是allowedGlobals,它實際上是一個全域性方法列表,當模板中出現了裡面的方法名後,不會進行下一個步驟的判斷,也就不會因為在Vue範例和$options中找不到這個名字的屬性而彈出報錯,這些方法你在開發過程中肯定都用過:

// 原始碼檔案:srccoreinstanceproxy.ts
const allowedGlobals = makeMap(
  'Infinity,undefined,NaN,isFinite,isNaN,' +
    'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' +
    'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt,' +
    'require' // for Webpack/Browserify
)

總結

Vue範例化過程中,在開發環境下會呼叫initProxy方法來包裝render函數原本的執行上下文(也就是Vue範例本身),在它的getterin操作符中加入一部分屬性的檢查,當模板中呼叫的屬性不存在於範例中或不存在於$options中時,及時提出警告。

以上就是Vue範例初始化為渲染函數設定檢查原始碼剖析的詳細內容,更多關於Vue範例初始化渲染函數檢查的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com