在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
hasProxy
条件成立,则调用Proxy方法,给vue实例添加代理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
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中存在,所以他抛出一个前缀的警告。