人人都能懂的Vue源码系列(五)—initProxy

本文深入探讨了Vue.js中Proxy的使用方式及其背后的原理。详细分析了initProxy方法如何根据不同环境选择适当的代理处理逻辑,并解释了getHandler和hasHandler的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上篇文章中,主要讲了mergeOptions方法的处理逻辑,不明白的同学们可以点击这里查看。今天回到init方法,接下来看源码

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

上面的代码逻辑很简单,主要就是为Vue实例的_renderProxy属性赋值,不同的代码运行环境赋值的结果不同。

  1. 当前环境是开发环境,则调用initProxy方法
  2. 如果不是开发环境,则vue实例的_renderProxy属性指向vue实例本身。

initProxy

initProxy方法究竟怎么样代理vm属性呢?通过源码来一探究竟。

  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
    }
  }
复制代码

通过判断hasProxy,来执行不同的处理逻辑,我们先来看hasProxy的源码。

  const hasProxy =
    typeof Proxy !== 'undefined' &&
    Proxy.toString().match(/native code/)
复制代码

从变量的名字我们知道它的作用就是判断当前环境中Proxy是否可用,同学们如果不熟悉Proxy的用法,可以点击这里。这个判断方法在我们平时写代码中也可以进行借鉴。

回到代码中,如果当前环境存在Proxy,则执行块内的语句。

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

上面两行代码的逻辑是,如果options上存在render属性,且render属性上存在_withStripped属性,则proxy的traps(traps其实也就是自定义方法)采用getHandler方法,否则采用hasHandler方法。更多关于traps相关的内容可点击这里

接下来看看getHandler和hasHandler方法

getHandler

  const getHandler = {
    get (target, key) {
      if (typeof key === 'string' && !(key in target)) {
        warnNonPresent(target, key)
      }
      return target[key]
    }
  }
复制代码

getHandler方法主要是针对读取代理对象的某个属性时进行的操作。当访问的属性不是string类型或者属性值在被代理的对象上不存在,则抛出错误提示,否则就返回该属性值。

该方法可以在开发者错误的调用vm属性时,提供提示作用。

  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
    }
  }
复制代码

hasHandler

hasHandler方法的应用场景在于查看vm实例是否拥有某个属性—比如调用for in循环遍历vm实例属性时,会触发hasHandler方法。

首先使用in操作符判断该属性是否在vm实例上存在

const has = key in target
复制代码

接下来通过下列语句,确定属性名称是否可用

const isAllowed = allowedGlobals(key) || key.charAt(0) === '_'
复制代码

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
  )
复制代码

我们结合makeMap函数一起来看

export function makeMap (
  str: string,
  expectsLowerCase?: boolean
): (key: string) => true | void {
  const map = Object.create(null)
  const list: Array<string> = str.split(',')
  for (let i = 0; i < list.length; i++) {
    map[list[i]] = true
  }
  return expectsLowerCase
    ? val => map[val.toLowerCase()]
    : val => map[val]
}
复制代码

先来分析makeMap,其作用是通过传入的string参数来生成映射表。如传入下列参数

'Infinity,undefined,NaN,isFinite,isNaN,'
....
复制代码

通过makeMap方法可以生成下面这样的一个映射表

{
  Infinity: true,
  undefined: true
  ......
}
复制代码

这个方法我们在日常写代码的时候也可以进行借鉴。结合上面的分析,我们知道allowedGlobals最终存储的是一个代表特殊属性名称的映射表。

回到源码,结合has和isAllowed属性,我们知道当读取对象属性时,如果属性名在vm上不存在,且不在特殊属性名称映射表中,或没有以_符号开头,则抛出异常。最后回到initProxy代码中

  if (hasProxy) {
      ...
      vm._renderProxy = new Proxy(vm, handlers)
    } else {
      vm._renderProxy = vm
    }
  }
复制代码

如果Proxy属性存在,则把包装后的vm属性赋值给_renderProxy属性值,否则把vm是实例本身赋值给_renderProxy属性。

总结

The Proxy object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).

代理对象是es6的新特性,它主要用来自定义对象一些基本操作(如查找,赋值,枚举等)。

上述源码部分主要应用了lookup和enumeration部分的自定义能力。proxy是一个强大的特性,为我们提供了很多"元编程"能力。只不过目前规范还没有很完善,使用的时候要稍加注意。最后按照老规矩,以一张图完成今天的讲解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值