乐趣区

关于vue.js:Vue初始化中的initProxy代理了个寂寞

vue 的寂寞:_renderProxy 属性

在 vue 初始化函数 _init() 函数中,有这样一段代码:

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

这段代码的目标次要就是为 Vue 实例的_renderProxy 属性赋值,而这个 _renderProxy 目测就是用在 render 函数中的。咱们在 vue/src/core/instance/render.js 中发现了这样的代码:

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

当咱们创立 Vue 根实例时,通常会传入一个 render 函数:

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

因而,这个 vm._renderProxy 实际上指定了咱们传入的这个 render 函数在创立 Vnode 的时候执行的上下文 this。
回到下面,那么这个 initProxy 函数又是怎么给_renderProxy 属性赋值的呢?咱们来看看具体代码:

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 属性赋值状况能够总结如下:

  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 实例增加代理。

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时。

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 中加属性或办法,常常会看到这个正告。

第二种策略,咱们在查看 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 中存在,因而他抛出一个前缀的正告。

退出移动版