Vue实例初始化为渲染函数设置检查源码剖析

2022-10-07,,,,

引言

之前的文章提到,vue实例化时_init方法做了很多处理,其中就有这么一段:

if (__dev__) {
  initproxy(vm)
} else {
  vm._renderproxy = vm
}

在生产模式下,_renderproxy直接指向了vue实例本身,而在开发环境下调用了initproxy方法,那么它究竟是做什么的呢?

_renderproxy是干什么的

通过对_renderproxy进行全局搜索,我们在src\core\instance\render.ts文件中找到了它:

// 源码文件 src\core\instance\render.ts
vnode = render.call(vm._renderproxy, vm.$createelement)

也就是说,_renderproxy渲染函数render的执行上下文,在生产环境下,执行上下文就是实例本身,而在开发环境下,执行上下文则使用initproxy进行了处理,我们接下来看看它究竟做了什么。

initproxy方法

首先判断了当前环境下proxy对象是否存在进行了判断:

//源码文件 src\core\instance\proxy.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:

// 源码文件:src\core\instance\proxy.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中进行了检查,如果不存在则发出相应的警告:

// 源码文件:src\core\instance\proxy.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()

那么做了什么呢:

// 源码文件:src\core\instance\proxy.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中找不到这个名字的属性而弹出报错,这些方法你在开发过程中肯定都用过:

// 源码文件:src\core\instance\proxy.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实例初始化渲染函数检查的资料请关注其它相关文章!

《Vue实例初始化为渲染函数设置检查源码剖析.doc》

下载本文的Word格式文档,以方便收藏与打印。