Template ref在Vue3中的实现原理详解

目录
  • 背景
  • 模板的编译
  • setup 函数返回值的处理
  • 组件的渲染
  • Template Ref 的注册
  • 总结

背景

最近我的 Vue3 音乐课程后台问答区频繁出现一个关于 Template ref 在 Composition API 中使用的问题,于是我就想写一篇文章详细解答这个问题。

先来看一个简单的例子:

<template>
  <div ref="root">This is a root element</div>
</template>
<script>
  import { ref, onMounted } from 'vue'
  export default {
    setup() {
      const root = ref(null)
      onMounted(() => {
        // DOM 元素将在初始渲染后分配给 ref
        console.log(root.value) // <div>This is a root element</div>
      })
      return {
        root
      }
    }
  }
</script>

首先我们在模板中给 div 添加了 ref 属性,并且它的值是 root 字符串,接下来我们在 setup 函数中使用 ref API 定义了一个 root 的响应式对象,并把它放到 setup 函数的返回对象中。

那么有小伙伴就问了,setup 函数中使用 ref API 定义的 root 和在模板中定义的 ref 是同一个东西吗?如果不是,那为什么需要同名呢,不同名可以吗?

带着这些疑问,我们来分析一下 Template ref 的实现原理。

模板的编译

我们还是结合示例来分析,首先借助于模板导出工具,可以看到它编译后的 render 函数:

import { openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
const _hoisted_1 = { ref: "root" }
export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("div", _hoisted_1, "This is a root element", 512 /* NEED_PATCH */))
}

可以看到,render 函数内部使用了 createElementBlock 函数来创建对应的元素 vnode,来看它实现:

function createElementBlock(type, props, children, patchFlag, dynamicProps, shapeFlag) {
    return setupBlock(createBaseVNode(type, props, children, patchFlag, dynamicProps, shapeFlag, true /* isBlock */));
}
function createBaseVNode(type, props = null, children = null, patchFlag = 0, dynamicProps = null, shapeFlag = type === Fragment ? 0 : 1 /* ELEMENT */, isBlockNode = false, needFullChildrenNormalization = false) {
  const vnode = {
    __v_isVNode: true,
    __v_skip: true,
    type,
    props,
    key: props && normalizeKey(props),
    ref: props && normalizeRef(props),
    scopeId: currentScopeId,
    slotScopeIds: null,
    children,
    component: null,
    suspense: null,
    ssContent: null,
    ssFallback: null,
    dirs: null,
    transition: null,
    el: null,
    anchor: null,
    target: null,
    targetAnchor: null,
    staticCount: 0,
    shapeFlag,
    patchFlag,
    dynamicProps,
    dynamicChildren: null,
    appContext: null
  }
  if (needFullChildrenNormalization) {
    normalizeChildren(vnode, children)
    if (shapeFlag & 128 /* SUSPENSE */) {
      type.normalize(vnode)
    }
  }
  else if (children) {
    vnode.shapeFlag |= isString(children)
      ? 8 /* TEXT_CHILDREN */
      : 16 /* ARRAY_CHILDREN */
  }
  // ...
  // 处理 Block Tree
  return vnode
}

这里我们先不用管 Block 的相关逻辑,重点看 vnode 的创建过程。

createElementBlock 函数内部通过 createBaseVNode 函数来创建生成 vnode 对象,其中第二个参数 props 就是用来描述 vnode 的一些属性,在这个例子中 props 的值是 {ref: "root"}。

生成的 vnode 对象中,有一个 ref 属性,在 props 存在的情况下,会经过一层 normalizeRef 的处理:

const normalizeRef = ({ ref }) =&gt; {
  return (ref != null
  ? isString(ref) || isRef(ref) || isFunction(ref)
   ? { i: currentRenderingInstance, r: ref }
   : ref
   : null)
}

至此,我们已知 div 标签在 render 函数执行之后转换成一个元素 vnode,它对应的 type 是 div,props 值是 {ref: "root"}、ref 的值是 {i: currentRenderingInstance, r: "root"}。

setup 函数返回值的处理

接下来,我们顺着组件的挂载过程,来分析 setup 函数的返回值是如何处理的。一个组件的挂载,首先会执行 mountComponent 函数:

const mountComponent = (initialVNode, container, anchor, parentComponent) => {
  // 创建组件实例
  const instance = initialVNode.component = createComponentInstance(initialVNode, parentComponent)
  // 设置组件实例
  setupComponent(instance)
  // 设置并运行带副作用的渲染函数
  setupRenderEffect(instance, initialVNode, container, anchor)
}

可以看到,mountComponent 主要做了三件事情:创建组件实例、设置组件实例和设置并运行带副作用的渲染函数。

其中 setup 函数的处理逻辑在 setupComponent 函数内部:

function setupComponent (instance, isSSR = false) {
  const { props, children, shapeFlag } = instance.vnode
  // 判断是否是一个有状态的组件
  const isStateful = shapeFlag & 4
  // 初始化 props
  initProps(instance, props, isStateful, isSSR)
  // 初始化 插槽
  initSlots(instance, children)
  // 设置有状态的组件实例
  const setupResult = isStateful
    ? setupStatefulComponent(instance, isSSR)
    : undefined
  return setupResult
}

setupComponent 内部会根据 shapeFlag 的值判断这是不是一个有状态组件,如果是则要进一步去设置有状态组件的实例。

通常我们写的组件就是一个有状态的组件,所谓有状态,指的就是组件在渲染过程中,会把它的一些状态挂载到组件实例对应的属性上。

接下来看 setupStatefulComponent 函数:

function setupStatefulComponent (instance, isSSR) {
  const Component = instance.type
  // 创建渲染代理的属性访问缓存
  instance.accessCache = {}
  // 创建渲染上下文代理
  instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers))
  // 判断处理 setup 函数
  const { setup } = Component
  if (setup) {
    // 如果 setup 函数带参数,则创建一个 setupContext
    const setupContext = (instance.setupContext =
      setup.length > 1 ? createSetupContext(instance) : null)
    // 执行 setup 函数,获取返回值
    const setupResult = callWithErrorHandling(setup, instance, 0 /* SETUP_FUNCTION */, [instance.props, setupContext])
    // 处理 setup 返回值
    handleSetupResult(instance, setupResult)
  }
  else {
    // 完成组件实例设置
    finishComponentSetup(instance)
  }
}

setupStatefulComponent 函数主要做了三件事:创建渲染上下文代理、判断处理 setup 函数和完成组件实例设置。

这里我们重点关注判断处理 setup 函数部分,首先它会通过 callWithErrorHandling 的方式来执行组件定义的 setup 函数,并把它的返回值放到 setupResult 中,然后执行 handleSetupResult 函数来处理 setup 执行结果,来看它的实现:

function handleSetupResult(instance, setupResult) {
  if (isFunction(setupResult)) {
    // setup 返回渲染函数
    instance.render = setupResult
  }
  else if (isObject(setupResult)) {
    // 把 setup 返回结果做一层代理
    instance.setupState = proxyRefs(setupResult)
  }
  finishComponentSetup(instance)
}

可以看到,如果 setup 函数返回值是一个函数,那么该函数就作为组件的 render 函数;如果 setup 返回值是一个对象,那么则把它的值做一层代理,赋值给 instance.setupState。

组件的渲染

setupComponent 函数执行完,就要执行 setupRenderEffect 设置并运行带副作用的渲染函数,简化后的实现如下:

const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) => {
  // 组件的渲染和更新函数
  const componentUpdateFn = () => {
    if (!instance.isMounted) {
      // 渲染组件生成子树 vnode
      const subTree = (instance.subTree = renderComponentRoot(instance))
      // 把子树 vnode 挂载到 container 中
      patch(null, subTree, container, anchor, instance, parentSuspense, isSVG)
      // 保留渲染生成的子树根 DOM 节点
      initialVNode.el = subTree.el
      instance.isMounted = true
    }
    else {
      // 更新组件
    }
  }
  // 创建组件渲染的副作用响应式对象
  const effect = new ReactiveEffect(componentUpdateFn, () => queueJob(instance.update), instance.scope)
  const update = (instance.update = effect.run.bind(effect))
  update.id = instance.uid
  // 允许递归更新自己
  effect.allowRecurse = update.allowRecurse = true
  update()
}

setupRenderEffect 函数内部利用响应式库的 ReactiveEffect 函数创建了一个副作用实例 effect,并且把 instance.update 函数指向了 effect.run。

当首次执行 instance.update 时,内部就会执行 componentUpdateFn 函数触发组件的首次渲染。

当组件的数据发生变化时,组件渲染函数 componentUpdateFn 会重新执行一遍,从而达到重新渲染组件的目的。

componentUpdateFn 函数内部会判断这是一次初始渲染还是组件的更新渲染,目前我们只需关注初始渲染流程。

初始渲染主要做两件事情:执行 renderComponentRoot 函数渲染组件生成 subTree 子树 vnode,执行 patch 函数把 subTree 挂载到 container 中。

renderComponentRoot 内部会执行组件的 render 函数渲染生成一棵 vnode 树,然后在 patch 过程中把 vnode 树渲染生成真正的 DOM 树。

接下来看 patch 函数的实现:

const patch = (n1, n2, container, anchor = null, parentComponent = null, parentSuspense = null, isSVG = false, slotScopeIds = null, optimized = false) => {
  const { type, ref, shapeFlag } = n2
  switch (type) {
    case Text:
      // 处理文本节点
      break
    case Comment:
      // 处理注释节点
      break
    case Static:
      // 处理静态节点
      break
    case Fragment:
      // 处理 Fragment 元素
      break
    default:
      if (shapeFlag & 1 /* ELEMENT */) {
        // 处理普通 DOM 元素
        processElement(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized)
      }
      else if (shapeFlag & 6 /* COMPONENT */) {
        // 处理组件
        processComponent(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized)
      }
      else if (shapeFlag & 64 /* TELEPORT */) {
        // 处理 TELEPORT
      }
      else if (shapeFlag & 128 /* SUSPENSE */) {
        // 处理 SUSPENSE
      }
  }
  // 设置 ref
  if (ref != null && parentComponent) {
    setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2)
  }
}

在组件的首次渲染阶段,patch 函数内部会根据 vnode 节点类型的不同,执行不同的处理逻辑,最终目的就是构造出一棵 DOM 树。

Template Ref 的注册

遍历渲染 DOM 树的过程,实际上就是一个递归执行 patch 函数的过程,在 patch 函数的最后,也就是当前节点挂载后,会判断如果新的 vnode 存在 ref 属性,则执行 setRef 完成 Template Ref 的注册逻辑。

显然,对于我们示例来说,div 标签生成的元素 vnode,它对应的 ref 的值是 {i: currentRenderingInstance, r: "root"},满足条件,则会执行 setRef 函数,来看它的实现:

function setRef(rawRef, oldRawRef, parentSuspense, vnode, isUnmount = false) {
  // 如果 rawRef 是数组,则遍历递归执行 setRef
  if (isArray(rawRef)) {
    rawRef.forEach((r, i) => setRef(r, oldRawRef && (isArray(oldRawRef) ? oldRawRef[i] : oldRawRef), parentSuspense, vnode, isUnmount))
    return
  }
  if (isAsyncWrapper(vnode) && !isUnmount) {
    return
  }
  // 如果 vnode 是组件 vnode,refValue 指向组件的实例,否则指向元素的 DOM
  const refValue = vnode.shapeFlag & 4 /* STATEFUL_COMPONENT */
    ? getExposeProxy(vnode.component) || vnode.component.proxy
    : vnode.el
  const value = isUnmount ? null : refValue
  const { i: owner, r: ref } = rawRef
  if ((process.env.NODE_ENV !== 'production') && !owner) {
    warn(`Missing ref owner context. ref cannot be used on hoisted vnodes. ` +
      `A vnode with ref must be created inside the render function.`)
    return
  }
  const oldRef = oldRawRef && oldRawRef.r
  const refs = owner.refs === EMPTY_OBJ ? (owner.refs = {}) : owner.refs
  const setupState = owner.setupState
  // ref 动态更新,删除旧的
  if (oldRef != null && oldRef !== ref) {
    if (isString(oldRef)) {
      refs[oldRef] = null
      if (hasOwn(setupState, oldRef)) {
        setupState[oldRef] = null
      }
    }
    else if (isRef(oldRef)) {
      oldRef.value = null
    }
  }
  if (isString(ref)) {
    const doSet = () => {
      {
        refs[ref] = value
      }
      if (hasOwn(setupState, ref)) {
        setupState[ref] = value
      }
    }
    if (value) {
      doSet.id = -1
      queuePostRenderEffect(doSet, parentSuspense)
    }
    else {
      doSet()
    }
  }
  else if (isRef(ref)) {
    const doSet = () => {
      ref.value = value
    }
    if (value) {
      doSet.id = -1
      queuePostRenderEffect(doSet, parentSuspense)
    }
    else {
      doSet()
    }
  }
  else if (isFunction(ref)) {
    callWithErrorHandling(ref, owner, 12 /* FUNCTION_REF */, [value, refs])
  }
  else if ((process.env.NODE_ENV !== 'production')) {
    warn('Invalid template ref type:', value, `(${typeof value})`)
  }
}

setRef 函数的目的就是对当前节点的引用求值,并保存下来。节点的引用值 refValue 会根据 vnode 的类型有所区别,如果 vnode 是组件 vnode,refValue 指向组件的实例,否则指向元素的 DOM。

这就是平时我们给普通元素节点设置 ref 能拿到对应的 DOM,而对组件节点设置 ref 能拿到组件实例的原因。

从传递的参数 rawRef 中,可以获取到当前组件实例 owner,以及对应的 ref 值,对于我们的示例,rawRef 的值是 {i: currentRenderingInstance, r: "root"},那么对应的 ref 就是 root 字符串。

如果 ref 是字符串类型,且 owner.setupState 中包含了这个字符串属性,那么则把这个 refValue 保留到 owner.setupStatep[ref] 中。

前面说到,在 handleSetupResult 的时候,我们已经把 setup 函数的返回值保留到 instance.setupState 中了:

instance.setupState = proxyRefs(setupResult)

这里要注意,使用了 proxyRefs 函数对 setupResult 做了响应式处理,来看它的实现:

function proxyRefs(objectWithRefs) {
  return isReactive(objectWithRefs)
    ? objectWithRefs
    : new Proxy(objectWithRefs, shallowUnwrapHandlers)
}
const shallowUnwrapHandlers = {
  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),
  set: (target, key, value, receiver) => {
    const oldValue = target[key]
    if (isRef(oldValue) && !isRef(value)) {
      oldValue.value = value
      return true
    }
    else {
      return Reflect.set(target, key, value, receiver)
    }
  }
}

如果 setupResult 不是响应式对象,那么会使用 Proxy 对它做一层代理,它有什么作用呢?接下来结合前面的示例进行分析。

示例中,在 setup 函数内部,我们利用了 ref API 定义了响应式对象 root:

const root = ref(null)

然后把这个响应式对象作为 setupResult 返回:

const root = ref(null)
return {
  root
}

在 handleSetupResult 的时候,相当于执行:

instance.setupState = proxyRefs({ root: root})

经过 setRef 的处理,会执行:

instance.setupState['root'] = refValue // DOM

执行这个操作的时候,会触发 shallowUnwrapHandlers 的 setter:

const shallowUnwrapHandlers = {
  set: (target, key, value, receiver) => {
    const oldValue = target[key]
    if (isRef(oldValue) && !isRef(value)) {
      oldValue.value = value
      return true
    }
    else {
      return Reflect.set(target, key, value, receiver)
    }
  }
}

这里的 target 指向的是 { root: root },key 是 "root",value 是 DOM 对象。那么 oldValue 指向的就是响应式对象 root,并且满足 isRef(oldValue) && !isRef(value),因此会执行:

oldValue.value = value

这样响应式对象 root 的值就指向了 DOM 对象,所以在 onMounted 后就可以通过 root.value 访问到对应的 DOM 对象了。

总结

ref API 定义的 root 和在模板中定义的 ref=root 是并不是一个东西,它们之所以能关联起来,是返回的 setupResult 中的属性名和 Template ref 中指向的字符串同名。我们对示例稍加修改:

<template>
  <div ref="root">This is a root element</div>
</template>
<script>
  import { ref, onMounted } from 'vue'
  export default {
    setup() {
      const rootRef = ref(null)
      onMounted(() => {
        // DOM 元素将在初始渲染后分配给 ref
        console.log(rootRef.value) // <div>This is a root element</div>
      })
      return {
        root: rootRef
      }
    }
  }
</script>

这样也可以在 onMounted 后通过 rootRef.value 访问到对应的 DOM 对象的。

因此局部定义的 ref 响应式变量并不需要和 Template ref 指向的字符串同名,只需要 setupResult 中保存 ref 响应式变量的属性名和 Template ref 指向的字符串同名即可,因为内部是通过该字符串检索的。

以上就是Template ref在Vue3中的实现示例详解的详细内容,更多关于Vue3实现Template ref的资料请关注我们其它相关文章!

(0)

相关推荐

  • Vue3.0中Ref与Reactive的区别示例详析

    目录 Ref与Reactive Ref Reactive Ref与Reactive的区别 shallowRef 与shallowReactive toRaw ---只修改数据不渲染页面 markRaw --- 不追踪数据 toRef --- 跟数据源关联 不修改UI toRefs ---设置多个toRef属性值 customRef ---自定义一个ref ref 捆绑页面的标签 总结 Ref与Reactive Ref Ref 用来创建基础类型的响应式数据,模板默认调用value显示数据.方法中修

  • vue3如何使用ref获取元素

    目录 vue3使用ref获取元素 vue3中ref的理解 1.什么是ref? 2.ref本质 3.ref注意点 4.ref获取元素 vue3使用ref获取元素 在vue2.x中,可以通过给元素添加ref='xxx'属性,然后在代码中通过this.$refs.xxx获取到对应的元素 然而在vue3中时没有$refs这个东西的,因此vue3中通过ref属性获取元素就不能按照vue2的方式来获取 vue3需要借助生命周期方法,原因很简单,在setup执行时,template中的元素还没挂载到页面上,所

  • vue3中$refs的基本使用方法

    1.在vue2中可以通过this来访问到$refs,vue3中由于没有this所以获取不到了,但是官网中提供了方法来获取 知道了怎么获取后,我们结合ElementPlus来使用,因为项目中用到了ElementPlus的表单验证,官网也是醉了,还写的是vue2的写法 话不多说,上代码 由于我得form表单外面包了一层el-dialog,是不能在onMounted中获取到$refs的,应该在触发对应事件,让dialog绑定的v-model变为true的获取获取 label-position="lef

  • vue3中defineProps传值使用ref响应式失效详解

    子组件接收父组件的传参. 父组件: <template> <Son :data="data"/> </template> <script setup> import { ref } from "vue"; let data = ref('hello') setTimeout(() => { data.value = 'how are you doing' }, 2000) </script> 子组件:

  • vue3基于script setup语法$refs的使用

    目录 一.vue2语法 二.vue3使用 1. 组件设置ref值 2. 组件实例获取 3. 子组件内设置对外公开的变量 一.vue2语法 vue2语法在组件上设置ref属性后,在代码里可以通过 this.$refs.ref值 访问到对应的子组件. 一个设置ref值的组件: <base-input ref="usernameInput"></base-input> 在js代码中可以通过如下代码访问到这个组件: this.$refs.usernameInput 可以

  • Vue3中的Refs和Ref详情

    小编同样和大家分享关于Vue3中的数据相应的问题,下面我们来例举一个这样的例子 Vue.createApp({ template: `<div>{{ nameObj.name }}</div>`, setup() { const { reactive } = Vue const nameObj = reactive({name:'lilei',age:18}) setTimeout(() => { nameObj.name = 'hanmeimei' },2000) retu

  • Template ref在Vue3中的实现原理详解

    目录 背景 模板的编译 setup 函数返回值的处理 组件的渲染 Template Ref 的注册 总结 背景 最近我的 Vue3 音乐课程后台问答区频繁出现一个关于 Template ref 在 Composition API 中使用的问题,于是我就想写一篇文章详细解答这个问题. 先来看一个简单的例子: <template> <div ref="root">This is a root element</div> </template>

  • Vue3插槽Slot实现原理详解

    目录 Vue官方对插槽的定义 Slot到底是什么 如何使用插槽 回顾组件渲染的原理 插槽的初始化原理 解析插槽中的内容 作用域插槽原理 具名插槽原理 默认内容插槽的原理 Vue官方对插槽的定义 Vue 实现了一套内容分发的 API,这套 API 的设计灵感源自 Web Components 规范草案,将 <slot> 元素作为承载分发内容的出口. Slot到底是什么 那么Slot到底是什么呢?Slot其实是一个接受父组件传过来的插槽内容,然后生成VNode并返回的函数. 我们一般是使用 <

  • vue2.x中h函数(createElement)与vue3中的h函数详解

    目录 1. vue2.x的 h 函数(createElement) 2. vue3 h函数配置项 2.1 v-model实现(以下开始为官网实现) 2.2 v-on 2.3 事件修饰符 2.4 插槽 2.5 component 和 is 2.6 自定义指令 2.7 内置组件 2.8 渲染函数的返回值 2.9 JSX 总结 1. vue2.x的 h 函数(createElement) 使用方法及介绍:(参考官网提取) h函数第一个是标签名字 或者是组件名字,第二个参数是配置项,第三个参数是 inn

  • Java中synchronized实现原理详解

    记得刚刚开始学习Java的时候,一遇到多线程情况就是synchronized,相对于当时的我们来说synchronized是这么的神奇而又强大,那个时候我们赋予它一个名字"同步",也成为了我们解决多线程情况的百试不爽的良药.但是,随着我们学习的进行我们知道synchronized是一个重量级锁,相对于Lock,它会显得那么笨重,以至于我们认为它不是那么的高效而慢慢摒弃它. 诚然,随着Javs SE 1.6对synchronized进行的各种优化后,synchronized并不会显得那么

  • Android中Lifecycle的原理详解

    目录 一.基本使用 二.LifecycleObserver接口和LifecycleOwner接口 三.getLifecycle() 四.绑定生命周期 总结 Lifecycle是Android Architecture Components的成员,是一个生命周期感知组件,能够感知Activity.Fragment等组件的生命周期变化,并将变化通知到已注册的观察者.正确的使用有助于更好地组织代码,减少内存泄漏,增强稳定.下面分析他的实现原理,看看到底只怎么感知生命周期的. 一.基本使用 1.引入依赖

  • mysql中的mvcc 原理详解

    目录 简介 前言 一.mysql 数据写入磁盘流程 二.redo log 1.redolog 的整体流程 2.为什么需要 redo log 三.undo log 1.undo log 特点 2.undo log 类型 3.undo log 生成过程 4.undo log 回滚过程 5.undo log的删除 四.mvcc 1.什么是MVCC 2.MVCC组成 3.快照读与当前读 快照读 当前读 五.mvcc操作演示 1.READ COMMITTED 隔离级别 2.REPEATABLE READ 

  • Vue3中watch的使用详解

    目录 Vue3中watch的详解 Vue2使用watch Vue3使用watch 情况1 情况2 情况3 情况4 情况5 特殊情况 Vue3中watch的详解 Vue2使用watch <template> <div>总合:{{ sum }}<button @click="sum++">点击累加</button></div> </template> <script> import { ref } from

  • vue3中defineComponent 的作用详解

    vue3中,新增了 defineComponent ,它并没有实现任何的逻辑,只是把接收的 Object 直接返回,它的存在是完全让传入的整个对象获得对应的类型,它的存在就是完全为了服务 TypeScript 而存在的. 我都知道普通的组件就是一个普通的对象,既然是一个普通的对象,那自然就不会获得自动的提示, import { defineComponent } from 'vue' const component = { name: 'Home', props:{ data: String,

  • C#中foreach实现原理详解

    本文主要记录我在学习C#中foreach遍历原理的心得体会. 对集合中的要素进行遍历是所有编码中经常涉及到的操作,因此大部分编程语言都把此过程写进了语法中,比如C#中的foreach.经常会看到下面的遍历代码: var lstStr = new List<string> { "a", "b" }; foreach (var str in lstStr) { Console.WriteLine(str); } 实际此代码的执行过程: var lstStr

  • C++中typeid实现原理详解

    最近看了boost::any类源码,其实现主要依赖typeid操作符.很好奇这样实现的时间和空间开销有多大,决定探一下究竟. VS2008附带的type_info类只有头文件,没有源文件,声明如下: class type_info { public: virtual ~type_info(); _CRTIMP_PURE bool __CLR_OR_THIS_CALL operator==(const type_info& rhs) const; _CRTIMP_PURE bool __CLR_O

随机推荐