Vue3中Provide  Inject的实现原理分享
⽬录
前⾔
原型和原型链的知识回顾
使⽤ Provide
provide API实现原理
组件实例对象初始化时provides属性的处理
使⽤ Inject
inject API实现原理
provide/inject实现原理总结
拓展:ate原理
拓展:两个连续赋值的表达式
总结
前⾔
Vue3 的 Provide / Inject 的实现原理其实就是巧妙利⽤了原型和原型链来实现的,所以在了解Vue3 的 Provide / Inject 的实现原理之前,我们先复习⼀下原型和原型链的知识。
原型和原型链的知识回顾
prototype 与 __proto__
prototype ⼀般称为显式原型,__proto__⼀般称为隐式原型。每⼀个函数在创建之后,在默认情况下,会拥有⼀个名为prototype 的属性,这个属性表⽰函数的原型对象。
原型链
当我们访问⼀个JS对象属性的时候,JS先会在这个对象定义的属性⾥,不到就会沿着这个对象的__proto__这个隐式原型关联起来的链条向上⼀个对象查,这个链条就叫原型链。
function Fn() {}
Fn.prototype.name = 'coboy'
let fn1 = new Fn()
fn1.age = 18
console.log(fn1.name) // coboy
console.log(fn1.age) // 18
fn1是Fn函数new出来的实例对象,fn1.age是这个实例对象上属性,fn1.name则从Fn.prototype原型对象⽽来,因为fn1的
__proto__隐式原型就是指向Fn这个函数的原型对象Fn.prototype。原型链某种意义上是让⼀个引⽤类型继承另⼀个引⽤类型的属性和⽅法。
function Fn() {}
Fn.prototype.name = 'coboy'
let fn1 = new Fn()
fn1.name = 'cobyte'
console.log(fn1.name) // cobyte
当访问fn1这个实例对象的属性name的时候,JS先会在fn1这个实例对象的属性⾥查,刚好fn1定义了⼀个name属性,所以就直接返回⾃⾝属性的值cobyte,否则就会继续沿着原型链向Fn.prototype上去,那么就会返回coboy。
复习完原型和原型链的知识之后,我们就开始进⼊Provide/Inject的实现原理探索。
使⽤ Provide
在 setup() 中使⽤ provide 时,我们⾸先从 vue 显式导⼊ provide ⽅法。这使我们能够调⽤ provide 来定义每个 property。
provide 函数允许你通过两个参数定义 property
name (<String> 类型)
value
import { provide } from 'vue'
export default {
setup() {
provide('name', 'coboy')
}
}
provide API实现原理
那么这个provide API实现原理是什么呢?
provide 函数可以简化为
export function provide(key, value) {
/
/ 获取当前组件实例
const currentInstance: any = getCurrentInstance()
if(currentInstance) {
// 获取当前组件实例上provides属性
let { provides } = currentInstance
// 获取当前⽗级组件的provides属性
const parentProvides = currentInstance.parent.provides
// 如果当前的provides和⽗级的provides相同则说明还没赋值
if(provides === parentProvides) {
// ate() es6创建对象的另⼀种⽅式,可以理解为继承⼀个对象, 添加的属性是在原型下。
provides = currentInstance.provides = ate(parentProvides)
}
provides[key] = value
}
}
综上所述provide API就是通过获取当前组件的实例对象,将传进来的数据存储在当前的组件实例对象上的provides上,并且通过ES6的新ate把⽗组件的provides属性设置到当前的组件实例对象的provides属性的原型对象上。
组件实例对象初始化时provides属性的处理
源码位置:runtime-core/src/component.ts
我们通过查看instance对象的源码,可以看到,在instance组件实例对象上,存在parent和provides两个属性。在初始化的时候如果存在⽗组件则把⽗组件的provides赋值给当前的组件实例对象的provides,如果没有就创建⼀个新的对象,并且把应⽤上下⽂的provides属性设置为新对象的原型对象上的属性。
使⽤ Inject
在 setup() 中使⽤ inject 时,也需要从 vue 显式导⼊。导⼊以后,我们就可以调⽤它来定义暴露给我们的组件⽅式。
inject 函数有两个参数:
要 inject 的 property 的 name
默认值 (可选)
import { inject } from 'vue'
export default {
setup() {
const name = inject('name', 'cobyte')
return {js原型和原型链的理解
name
}
}
}
inject API实现原理
那么这个inject API实现原理是什么呢?
inject 函数可以简化为
export function inject(
key,
defaultValue,
treatDefaultAsFactory = false
) {
// 获取当前组件实例对象
const instance = currentInstance || currentRenderingInstance
if (instance) {
// 如果intance位于根⽬录下,则返回到appContext的provides,否则就返回⽗组件的provides
const provides =
instance.parent == null
instance.vnode.appContext && instance.vnode.appContext.provides
: instance.parent.provides
if (provides && key in provides) {
return provides[key]
} else if (arguments.length > 1) {
// 如果存在1个参数以上
return treatDefaultAsFactory && isFunction(defaultValue)
// 如果默认内容是个函数的,就执⾏并且通过call⽅法把组件实例的代理对象绑定到该函数的this上
defaultValue.call(instance.proxy)
: defaultValue
}
}
}
通过inject源码分析我们可以知道,inject⾥⾯先获取当前组件的实例对象,然后判断是否根组件,如果是根组件则返回到appContext的provides,否则就返回⽗组件的provides。
如果当前获取的key在provides上有值,那么就返回该值,如果没有则判断是否存在默认内容,默认内容如果是个函数,就执⾏并且通过call⽅法把组件实例的代理对象绑定到该函数的this上,否则就直接返回默认内容。
provide/inject实现原理总结
通过上⾯的分析,可以得知provide/inject实现原理还是⽐较简单的,就是巧妙地利⽤了原型和原型链进⾏数据的继承和获取。provide API调⽤设置的时候,设置⽗级的provides为当前provides对象原型对象上的属性,在inject获取provides对象中的属性值时,优先获取provides对象⾃⾝的属性,如果⾃⾝查不到,则沿着原型链向上⼀个对象中去查。
拓展:ate原理
⽅法说明
源码模拟
if (propertyObject === null) {
// 这⾥没有判断propertyObject是否是原始包装对象
throw 'TypeError'
} else {
function Fn () {}
// 设置原型对象属性
Fn.prototype = proto
const obj = new Fn()
if (propertyObject !== undefined) {
Object.defineProperties(obj, propertyObject)
}
if (proto === null) {
// 创建⼀个没有原型对象的对象,ate(null)
obj.__proto__ = null
}
return obj
}
}
定义⼀个空的构造函数,然后指定构造函数的原型对象,通过new运算符创建⼀个空对象,如果发现传递了第⼆个参数,通过Object.defineProperties为创建的对象设置key、value,最后返回创建的对象即可。
⽰例
// 第⼆个参数为null时,抛出TypeError
// const throwErr = Create({name: 'coboy'}, null)  // Uncaught TypeError
// 构建⼀个以
const obj1 = Create({name: 'coboy'})
console.log(obj1)  // {}, obj1的构造函数的原型对象是{name: 'coboy'}
const obj2 = Create({name: 'coboy'}, {
age: {
value: 18,
enumerable: true
}
})
console.log(obj2)  // {age: 18}, obj2的构造函数的原型对象是{name: 'coboy'}
拓展:两个连续赋值的表达式
provides = currentInstance.provides = ate(parentProvides)发⽣了什么?
来⾃《JavaScript权威指南》的解析
JavaScript总是严格按照从左⾄右的顺序来计算表达式
⼀切都是表达式,⼀切都是运算
provides = currentInstance.provides = ate(parentProvides)
上述的provides是⼀个表达式,它被严格地称为“赋值表达式的左⼿端(Ihs)操作数”。⽽右侧currentInstance.provides = ate(parentProvides)这⼀个整体也当做⼀个表达式,这⼀个整体赋值表达式的计算结果是赋值给了最左侧的provides currentInstance.provides = ate(parentProvides)这个表达式同时也是⼀个赋值表达式,ate(parentProvides)创建了⼀个新的引⽤赋值给了currentInstance这个引⽤上的provides属性
currentInstance.provides这个表达式的语义是:
计算单值表达式currentInstance,得到currentInstance的引⽤
将右侧的名字provides理解为⼀个标识符,并作为“.”运算的右操作数
计算currentInstance.provides表达式的结果(Result)
currentInstance.provides当它作为赋值表达式的左操作数时,它是⼀个被赋值的引⽤,⽽当它作为右操作数时,则计算它的值。注意:赋值表达式左侧的操作数可以是另⼀个表达式,⽽在声明语句中的等号左边,绝不可能是⼀个表达式。例如上⾯的如果写成了let provides = xxx,那么这个时候,provides只是⼀个表达名字的、静态语法分析期作为标识符来理解的字⾯⽂本,⽽不是⼀个表达式。
总结
到此这篇关于Vue3中Provide / Inject实现原理的⽂章就介绍到这了,更多相关Vue3 Provide / Inject实现原理内容请搜索以前的⽂章或继续浏览下⾯的相关⽂章希望⼤家以后多多⽀持!

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。