首頁 > web前端 > Vue.js > 主體

聊聊Vue3中Provide和Inject的實作原理

青灯夜游
發布: 2022-02-16 20:09:59
轉載
2952 人瀏覽過

這篇文章帶大家深入了解Vue3,詳細介紹一下Provide / Inject 的實作原理,希望對大家有幫助!

聊聊Vue3中Provide和Inject的實作原理

Vue3 的Provide / Inject 的實作原理其實就是巧妙利用了原型和原型鏈來實現的,所以在了解Vue3 的Provide / Inject 的實作原理之前,我們先複習原型和原型鏈的知識。 【相關推薦:vue.js影片教學

原型與原型鏈的知識回顧

  • 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 &#39;vue&#39;

export default {
  setup() {
    provide(&#39;name&#39;, &#39;coboy&#39;)
  }
}
登入後複製

#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) {
            // Object.create() es6创建对象的另一种方式,可以理解为继承一个对象, 添加的属性是在原型下。
            provides = currentInstance.provides = Object.create(parentProvides)
        }
        provides[key] = value
    }
}
登入後複製

綜上所述provide API就是透過取得目前元件的實例對象,將傳進來的資料儲存在目前的元件實例物件上的provides上,並且透過ES6的新API Object.create把父組件的provides屬性設定到目前的組件實例物件的provides屬性的原型物件上。

元件實例物件初始化時provides屬性的處理

原始碼位置:runtime-core/src/component.ts

聊聊Vue3中Provide和Inject的實作原理

我們透過查看instance物件的來源碼,可以看到,在instance元件實例物件上,存在parent和provides兩個屬性。在初始化的時候如果存在父組件則把父組件的provides賦值給當前的組件實例對象的provides,如果沒有就創建一個新的對象,並且把應用上下文的provides屬性設置為新對象的原型對像上的屬性。

使用Inject

setup() 中使用inject 時,也需要從vue 明確導入。導入以後,我們就可以呼叫它來定義暴露給我們的元件方式。

inject 函數有兩個參數:

  • 要inject 的property 的name

  • 默認值(可選)

import { inject } from &#39;vue&#39;

export default {
  setup() {
    const name = inject(&#39;name&#39;, &#39;cobyte&#39;)
    return {
      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对象自身的属性,如果自身查找不到,则沿着原型链向上一个对象中去查找。

拓展:Object.create原理

方法说明

  • Object.create()方法创建一个新的对象,并以方法的第一个参数作为新对象的__proto__属性的值(以第一个参数作为新对象的构造函数的原型对象)
  • Object.create()方法还有第二个可选参数,是一个对象,对象的每个属性都会作为新对象的自身属性,对象的属性值以descriptor(Object.getOwnPropertyDescriptor(obj, 'key'))的形式出现,且enumerable默认为false

源码模拟

Object.myCreate = function (proto, propertyObject = undefined) {
    if (propertyObject === null) {
        // 这里没有判断propertyObject是否是原始包装对象
        throw &#39;TypeError&#39;
    } else {
        function Fn () {}
        // 设置原型对象属性
        Fn.prototype = proto
        const obj = new Fn()
        if (propertyObject !== undefined) {
            Object.defineProperties(obj, propertyObject)
        }
        if (proto === null) {
            // 创建一个没有原型对象的对象,Object.create(null)
            obj.__proto__ = null
        }
        return obj
    }
}
登入後複製

定义一个空的构造函数,然后指定构造函数的原型对象,通过new运算符创建一个空对象,如果发现传递了第二个参数,通过Object.defineProperties为创建的对象设置key、value,最后返回创建的对象即可。

示例

// 第二个参数为null时,抛出TypeError
// const throwErr = Object.myCreate({name: &#39;coboy&#39;}, null)  // Uncaught TypeError
// 构建一个以
const obj1 = Object.myCreate({name: &#39;coboy&#39;})
console.log(obj1)  // {}, obj1的构造函数的原型对象是{name: &#39;coboy&#39;}
const obj2 = Object.myCreate({name: &#39;coboy&#39;}, {
    age: {
        value: 18,
        enumerable: true
    }
})
console.log(obj2)  // {age: 18}, obj2的构造函数的原型对象是{name: &#39;coboy&#39;}
登入後複製

拓展:两个连续赋值的表达式

provides = currentInstance.provides = Object.create(parentProvides) 发生了什么?

Object.create(parentProvides) 创建了一个新的对象引用,如果只是把 currentInstance.provides 更新为新的对象引用,那么provides的引用还是旧的引用,所以需要同时把provides的引用也更新为新的对象引用。

来自《JavaScript权威指南》的解析

  • JavaScript总是严格按照从左至右的顺序来计算表达式
  • 一切都是表达式,一切都是运算
provides = currentInstance.provides = Object.create(parentProvides)
登入後複製

上述的provides是一个表达式,它被严格地称为“赋值表达式的左手端(Ihs)操作数”。 而右侧 currentInstance.provides = Object.create(parentProvides) 这一个整体也当做一个表达式,这一个整体赋值表达式的计算结果是赋值给了最左侧的providescurrentInstance.provides = Object.create(parentProvides) 这个表达式同时也是一个赋值表达式,Object.create(parentProvides)创建了一个新的引用赋值给了currentInstance这个引用上的provides属性

currentInstance.provides这个表达式的语义是:

  • 计算单值表达式currentInstance,得到currentInstance的引用
  • 将右侧的名字provides理解为一个标识符,并作为“.”运算的右操作数
  • 计算currentInstance.provides表达式的结果(Result)

currentInstance.provides当它作为赋值表达式的左操作数时,它是一个被赋值的引用,而当它作为右操作数时,则计算它的值。

注意:赋值表达式左侧的操作数可以是另一个表达式,而在声明语句中的等号左边,绝不可能是一个表达式。 例如上面的如果写成了let provides = xxx,那么这个时候,provides只是一个表达名字的、静态语法分析期作为标识符来理解的字面文本,而不是一个表达式

(学习视频分享:web前端教程

以上是聊聊Vue3中Provide和Inject的實作原理的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:juejin.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板