Vue初始化中的initProxy——代理了個寂寞

vue的寂寞:_renderProxy屬性

在vue初始化函數_init()函數中,有這樣一段代碼:html

if (process.env.NODE_ENV !== 'production') {
    initProxy(vm)
} else {
    vm._renderProxy = vm
}

這段代碼的目的主要就是爲Vue實例的_renderProxy屬性賦值,而這個_renderProxy目測就是用在render函數中的。咱們在vue/src/core/instance/render.js中發現了這樣的代碼:vue

const { render, _parentVnode } = vm.$options
……
currentRenderingInstance = vm
vnode = render.call(vm._renderProxy, vm.$createElement)

當咱們建立Vue根實例時,一般會傳入一個render函數:node

new Vue({
    el: '#app',
    render: h => h(App),
    router
});

所以,這個vm._renderProxy實際上指定了咱們傳入的這個render函數在建立Vnode的時候執行的上下文this。
回到上面,那麼這個initProxy函數又是怎麼給_renderProxy屬性賦值的呢?咱們來看看具體代碼:react

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
    }
}

因此咱們的_renderProxy屬性賦值狀況能夠總結以下:api

  1. 當前環境是開發環境,而且hasProxy條件成立,則調用Proxy方法,給vue實例添加代理
  2. 若是其餘狀況,則vue實例的_renderProxy屬性指向vue實例自己。

要不要代理寂寞:hasProxy

export function isNative (Ctor: any): boolean {
    return typeof Ctor === 'function' && /native code/.test(Ctor.toString())
}
const hasProxy =
    typeof Proxy !== 'undefined' && isNative(Proxy)

結合着vue/src/core/util/env.js中的isNative函數咱們知道,hasProxy函數就是他的字面意思:當前環境中Proxy是否可用。
也就是說,當前環境是開發環境,而且Proxy是否可用,則調用Proxy方法,給vue實例添加代理。app

getHandler和hasHandler:寂寞的兩種爆發

Proxy的handler對象是一個佔位符對象,它包含了用於Proxy的陷阱(Trap)函數。從上面的代碼能夠知道咱們在代理vue實例時用了兩種Trap函數:當vue實例中的options.render存在,而且options.render._withStripped爲true時,咱們用getHandler函數,即handler.get()代理實例,它在讀取代理對象的某個屬性時觸發該操做,好比在執行 proxy.foo時。其餘狀況下用hasHandler,即handler.has()代理實例,它在判斷代理對象是否擁有某個屬性時觸發該操做,好比在執行 "foo" in proxy時。ide

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]
    }

}

第一種策略,咱們在讀取vm實例的某個屬性時,若是它不是string類型或者屬性值在vm實例上不存在,則拋出錯誤提示。
固然報錯也分爲兩類,若是該屬性在$data上找到了,就會報這樣一個錯:函數

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://vuejs.org/v2/api/#data',target
    )
}

緣由從報錯中就能知道,若是咱們在嚴格模式下代理了以$或者_開頭的屬性,那就必須經過$data.${key}的方式得到,以便跟vue的內部方法區分。
其餘狀況,當屬性真的不存在時,就會報下面這樣的警告,測試

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://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.',target
    )
}

這個警告是否是特別熟悉?在開發環境忘記在data或者method中加屬性或方法,常常會看到這個警告。ui

第二種策略,咱們在查看vm實例是否擁有某個屬性時,好比調用for in循環遍歷vm實例屬性時,會觸發hasHandler方法

const hasHandler = {
    has (target, key) {
      const has = key in target
      const isAllowed = allowedGlobals(key) || key.charAt(0) === '_'
      if (!has && !isAllowed) {
        warnNonPresent(target, key)
      }
      return has || !isAllowed
    }
  }

當讀取vm對象屬性時,若是屬性名在vm實例上不存在,且不在特殊屬性名稱映射表中,或沒有以_符號開頭。則拋出上面那個不存在的警告。

總結:用測試用例化解寂寞

上面說了不少源碼的東西,比較抽象,沒有場景落地,確實很差理解,這裏咱們就用vue的測試用例vue/test/unit/features/instance/render-proxy.spec.js來講明一下上面的兩種策略:

it('should warn missing property in render fns with `with`', () => {
new Vue({
    template: `<div>{{ a }}</div>`
}).$mount()
expect(`Property or method "a" is not defined`).toHaveBeenWarned()
})

這種狀況,咱們沒有傳入render函數,所以它觸發了hasHandler。而在其中它發現a在vm實例上不存在,且不在特殊屬性名稱映射表中,也沒有以_符號開頭,所以他拋出一個不存在的警告。

it('should warn missing property in render fns without `with`', () => {
const render = function (h) {
    return h('div', [this.a])
}
render._withStripped = true
new Vue({
    render
}).$mount()
expect(`Property or method "a" is not defined`).toHaveBeenWarned()
})

這種狀況,咱們傳入render函數,而且render._withStripped爲true所以它觸發了getHandler。而咱們在使用this.a時,觸發了get,它發現a在vm實例上不存在,且不在$data中,所以他拋出一個不存在的警告。

it('should warn properties starting with $ when not found (with stripped)', () => {
const render = function (h) {
    return h('p', this.$a)
}
render._withStripped = true
new Vue({
    data: { $a: 'foo' },
    render
}).$mount()
expect(`Property "$a" must be accessed with "$data.$a"`).toHaveBeenWarned()
})

這種狀況,咱們傳入render函數,而且render._withStripped爲true所以它觸發了getHandler。而咱們在使用this.$a時,觸發了get,它發現a在vm實例上不存在(vm沒有代理),但在$data中存在,所以他拋出一個前綴的警告。

相關文章
相關標籤/搜索