文章目录一、 Vue 3 的实现原理原型链Prototype Chain1. 关键的数据结构2. provide 源码逻辑与原型链构建原型链是如何建立起来的3. inject 源码逻辑直接属性查找二、 Vue 2 的实现原理沿父链向上递归遍历三、 响应式原理为什么 provide 的值能保持响应式四、 Vue 2 vs Vue 3 原理对比表五、 总结在 Vue 中provide和inject用于解决跨层级组件通信避免“Prop 逐级透传 / Prop Drilling”。它的核心设计非常优雅在Vue 3中实现原理本质上巧用了JavaScript 原型链Prototype Chain的查找特性而在Vue 2中则是基于组件实例链逐级向上遍历while循环。一、 Vue 3 的实现原理原型链Prototype ChainVue 3 的provide / inject依靠组件实例Component Instance上的provides对象来实现。1. 关键的数据结构每一个组件实例内部都有一个provides属性默认情况下子组件的instance.provides简单继承/指向父组件的provides。当子组件自己也调用provide()时Vue 会以父组件的provides为原型Object.create()创建一个新的provides对象。2.provide源码逻辑与原型链构建在 Vue 3 源码中简化版逻辑exportfunctionprovideT(key:InjectionKeyT|string|number,value:T){// 1. 获取当前正在初始化的组件实例constcurrentInstancecurrentInstanceif(currentInstance){letprovidescurrentInstance.providesconstparentProvidescurrentInstance.parentcurrentInstance.parent.provides// 2. 第一次在该组件中 provide 时进行初始化// 默认情况下 provides 和 parent.provides 指向同一个对象if(providesparentProvides){// 巧用 Object.create以父级的 provides 为原型创建一个新的对象providescurrentInstance.providesObject.create(parentProvides)}// 3. 将 key-value 挂载到全新的 provides 对象上provides[keyasstring]value}}原型链是如何建立起来的假设存在组件树App (Root) - Parent - ChildRoot 组件provides {}Parent 组件初始化默认继承 RootParent.provides Root.providesParent 调用provide(theme, dark)触发provides parentProvides校验执行Parent.provides Object.create(Root.provides)此时Parent.provides.__proto__ Root.providesChild 组件初始化默认继承 ParentChild.provides Parent.provides最终形成的provides原型链链路如下Child.provides ──(继承)── Parent.provides ──(__proto__)── Root.provides3.inject源码逻辑直接属性查找因为原型链已经建立好inject查找数据的实现变得极为简单和高效exportfunctioninject(key,defaultValue,treatDefaultAsFactoryfalse){// 1. 获取当前组件实例constinstancecurrentInstance||currentRenderingInstanceif(instance){// 2. 注意如果 ancestor/parent 存在优先从 parent.provides 中查找constprovidesinstance.parentnull?instance.vnode.appContextinstance.vnode.appContext.provides:instance.parent.provides// 3. 利用 JS 原型链机制直接检索 keyif(provideskeyinprovides){returnprovides[key]}elseif(arguments.length1){// 支持默认值returntreatDefaultAsFactoryisFunction(defaultValue)?defaultValue.call(instance.proxy):defaultValue}}}因为provides建立了原型链结构执行key in provides或provides[key]时JavaScript 引擎会自动沿着__proto__向上查找直到找到对应的属性或到达终端null。这意味着inject查找的时间复杂度接近O ( 1 ) O(1)O(1)效率极高。二、 Vue 2 的实现原理沿父链向上递归遍历在 Vue 2 的 Options API 中实现机制有所不同provide阶段在组件初始化initInjections-initProvide时运行provide选项若是函数则执行并获取返回的对象直接将其挂载在vm._provided属性上。inject阶段在子组件初始化initInjections时Vue 2 会通过一个while循环沿着$parent指针逐级向上查找// Vue 2 核心思想模拟functioninitInjections(vm){constresultresolveInject(vm.$options.inject,vm)// ... 将 result 定义为响应式/挂载到 vm 上}functionresolveInject(inject,vm){if(inject){constresult{}constkeysObject.keys(inject)for(leti0;ikeys.length;i){constkeykeys[i]letsourcevm// 沿 $parent 树一直向上搜寻直到找到 _provided 中包含 key 的祖先while(source){if(source._providedhasOwn(source._provided,key)){result[key]source._provided[key]break}sourcesource.$parent}}returnresult}}Vue 2 缺点如果有深度嵌套的组件树每次inject都需要运行while循环做链表遍历查找性能略低于 Vue 3 的原型链。三、 响应式原理为什么 provide 的值能保持响应式许多开发者有一个误区以为provide本身会自动把数据变成响应式的。事实上provide/inject本身只负责传递引用Pass by reference。传递非响应式数据如普通字符串/数字provide(color, red)- 子组件inject(color)拿到的是静态字面量后续父组件修改变量子组件不会触发视图更新。传递响应式数据如ref或reactiveprovide(color, ref(red))-provides对象上保存的是该ref的引用。当子组件在 Template 中使用这个inject进来的ref时子组件的渲染 Watcher 会自动收集这个ref的依赖。当父组件更新ref.value时依赖触发子组件自然随之重新渲染。四、 Vue 2 vs Vue 3 原理对比表维度Vue 2 实现Vue 3 实现内部存储字段vm._providedinstance.provides查找算法while(source source.$parent)递归向上遍历利用Object.create()建立原型链直接读取性能表现嵌套越深查找越慢借助 JS 引擎优化原型链查找性能更优与 Setup 配合Options 阶段通过initInjections确定可以在setup()中灵活多次调用provide/inject五、 总结Vue 的provide / inject依赖注入机制巧妙地避开了组件层层透传 Props 的尴尬。在 Vue 3 中它利用 JavaScript 的Object.create()在组件初始化时动态构建了一条基于provides的原型链让inject操作可以直接依赖引擎底层的属性查找机制高效且优雅。在响应式处理上它遵循“只传引用不改本质”的原则完美契合 Vue 自身的依赖收集与响应式更新体系。掌握了这一底层原理在日常架构大型应用或封装复杂组件库如 Form、Tree、Menu 等时我们就能更加游刃有余地运用provide / inject来设计解耦、高效的通信方案。 感谢阅读想了解更多 我的博客网站 | 记录思考分享干货 我的个人主页 | 关于我、开源项目