vue实现render中缺少变量的报错

在做vue开发的时候,会经常遇到下面的报错,那么这样的提示是如何实现的呢?

为什么有这样的报错?

是因为在模版(template)或者render函数中使用了没有声明过的属性或者方法。

那么vue又是如何发现用户使用了未定义的属性呢?

首先这段报错是出现在render期间,也就是触发update生成vnode期间,在源码中我们可以看到这样一段代码:

 try {
      vnode = render.call(vm._renderProxy, vm.$createElement);
    } catch (e) {
      handleError(e, vm, "render function");
      // return error render result,
      // or previous vnode to prevent render error causing blank component
      /* istanbul ignore else */
      if (process.env.NODE_ENV !== 'production') {
        vnode = vm.$options.renderError
          ? vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
          : vm._vnode;
      } else {
        vnode = vm._vnode;
      }
    }
复制代码

这段代码的作用就是根据render函数生成vnode,这段不多做解释,感兴趣的话,可以自己了解一下vue的源码。 可以看到render.call(vm._renderProxy, vm.$createElement),其中看到render的上下文并不是vue实例本身(vm),而是vm._renderProxy。其实vm._renderProxy指向是经过代理过的vm的属性访问的代理对象(使用Proxy实现)。

vm._renderProxy的实现(源码位于 src/core/instance/proxy.js中)
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
    }
  }
}
复制代码

在这段代码中,可以看出vue使用了Proxy来生成一个拦截对象。并且这里有一个判断语句

 const handlers = options.render && options.render._withStripped
        ? getHandler
        : hasHandler
复制代码

这里定义了两个函数分别是getHandler和hasHandler,这个两个函数分别定义拦截vm的get行为和has行为,源码如下:

const hasHandler = {
    // 拦截in操作符, 例如 'name' in vm
    has (target, key) {
      const has = key in target
      const isAllowed = allowedGlobals(key) || key.charAt(0) === '_'
      if (!has && !isAllowed) {
        warnNonPresent(target, key)
      }
      return has || !isAllowed
    }
  }

  const getHandler = {
   // 拦截.操作符
    get (target, key) {
      if (typeof key === 'string' && !(key in target)) {
        warnNonPresent(target, key)
      }
      return target[key]
    }
  }
复制代码

如上hasHandler和getHandler分别拦截了has操作(例如 'a' in obj 会触发has拦截)和get操作。

那么这里为什么需要分为getHandler和hasHandler,按理说只要拦截.操作符,就可以在模版取变量的时候,对属性值的取值过程进行处理,从而报错了,应该是不需要hasHandler呀。

其实是针对两种情况的:

  1. 用户书写模版,模版编译生成render函数
  2. 用户书直接写render函数

当options.render不存在的时候,也就是用户是通过书写模版(template)形式通过编译来生成reder函数的时候。此时render函数会生成下面这样的代码:

 function anonymous(
) {
  with(this){return _c('div')}
}
复制代码

那么此时用的是hasHandler也就是拦截in操作符,我们可以看到生成的render函数使用with包含者的,当去属性值的时候,会先检查正常的上下文上有没有绑定相应的属性,如果没有则查看绑定的this上下文中有没有这个属性,此时就会触发hasHandler拦截操作了。

当options.render存在的时候,则是用户自己编写render函数,例如:

 render (h) {
     return h('div', this.prop)
 }
复制代码

此时如果在render函数通过.操作符取值的话,则会触发getHandler操作

那么如果我使用Number,或者Infinity这样的全局变量或者方法,这些方法也没有绑定在this上下问上,会不会报错呢?

在拦截函函数里面我们可以看到:

 const has = key in target
      const isAllowed = allowedGlobals(key) || key.charAt(0) === '_'
      if (!has && !isAllowed) {
        warnNonPresent(target,key) 
    }
复制代码

不仅判断了当前变量是否存在与当前对象上,并且还有一个isAllowed变量表示是否可以允许。在allowedGlobals中就存储了绑定在全局上下文的变量。从而避免了对于全局变量和函数的拦截操作。

全局变量定义如下:

 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,' +
    'require' // for Webpack/Browserify
  )
复制代码
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值