The realization principle of Provide/Inject of Vue3 is actually achieved by cleverly making use of prototype and prototype chain. Therefore, before understanding the realization principle of Provide/Inject of Vue3, we first review the knowledge of prototype and prototype chain.

A knowledge review of prototypes and prototype chains

  • The prototype and__proto__

Prototype is usually called explicit prototype and __proto__ is usually called implicit prototype. After each function is created, by default, it has a property called Prototype, which represents the prototype object of the function.

  • Prototype chain

When we access a JS object property, JS will first look in the property defined by the object. If it cannot find it, JS will look up an object along the chain associated with the object’s __proto__ implicit prototype, which is called the prototype chain.

function Fn() {}
Fn.prototype.name = 'coboy'
let fn1 = new Fn()
fn1.age = 18
console.log(fn1.name) // coboy
console.log(fn1.age) / / 18
Copy the code

Fn1. Age is the attribute on this instance object, and Fn1. name is derived from Fn. Prototype because fn1’s __proto__ implicitly refers to Fn’s prototype object. A stereotype chain is a way of having one reference type inherit the properties and methods of another.

function Fn() {}
Fn.prototype.name = 'coboy'
let fn1 = new Fn()
fn1.name = 'cobyte'
console.log(fn1.name) // cobyte
Copy the code

When accessing the name attribute of fn1 instance object, JS will first look up the name attribute of fn1 instance object. Fn1 defines a name attribute, so it will return its own attribute value cobyte. Otherwise, it will continue to look up the prototype chain to fn. prototype. Coboy will be returned.

After reviewing the knowledge of prototype and prototype chain, we start to explore the implementation principle of Provide/Inject.

Using dojo.provide

When using provide in setup(), we first import the provide method explicitly from vue. This allows us to call provide to define each property.

The provide function allows you to define property with two parameters

  1. name (<String>Type)
  2. value
import { provide } from 'vue'

export default {
  setup() {
    provide('name'.'coboy')}}Copy the code

Provide API implementation principle

So what is the implementation of the Provide API?

The provide function can be simplified as

export function provide(key, value) {
    // Get the current component instance
    const currentInstance: any = getCurrentInstance()
    if(currentInstance) {
        // Gets the provides property on the current component instance
        let { provides } = currentInstance
        // Gets the provides property of the current parent component
        const parentProvides = currentInstance.parent.provides
        If the current provides is the same as the parent's provides, no value has been assigned
        if(provides === parentProvides) {
            // object.create () Another way es6 creates objects, which can be understood as inheriting an Object, adding properties under the stereotype.
            provides = currentInstance.provides = Object.create(parentProvides)
        }
        provides[key] = value
    }
}
Copy the code

The provide API takes the component instance object and stores the data in the provides on the component instance object. ES6’s new API Object.create sets the parent component’s provides attribute to the prototype Object for the component instance Object’s provides attribute.

Processing of the provides property when a component instance object is initialized

Run-time core/ SRC /component.ts

In the instance instance, the parent and provides properties exist. At initialization, if a parent exists, the parent’s provider is assigned to the provider of the current component instance object. If not, a new object is created and the provider property of the application context is set to that of the prototype object.

Use the Inject

When using Inject in setup(), it also needs to be imported explicitly from vue. After the import, we can call it to define the component methods exposed to us.

The Inject function takes two parameters:

  1. The name of the property to inject
  2. Default value (Optional)
import { inject } from 'vue'

export default {
  setup() {
    const name = inject('name'.'cobyte')
    return {
      name
    }
  }
}
Copy the code

Inject API implementation principle

So what is the inject API implementation principle?

The Inject function can be simplified as

export function inject(
  key,
  defaultValue,
  treatDefaultAsFactory = false
) {
  // Get the current component instance object
  const instance = currentInstance || currentRenderingInstance
  if (instance) {
    // If the inttance is in the root directory, the provider of the appContext is returned, otherwise the provider of the parent component is returned
    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) {
      // If more than one parameter exists
      return treatDefaultAsFactory && isFunction(defaultValue)
        // If the default content is a function, execute and bind the component instance's proxy object to the function's this via the call method
        ? defaultValue.call(instance.proxy) 
        : defaultValue
    }
  }
}
Copy the code

By analyzing the source code of Inject, we can know that inject first obtains the instance object of the current component, and then determines whether it is the root component. If it is the root component, it returns the provider of appContext; otherwise, it returns the provider of the parent component.

If the currently fetched key has a value for provides, the value is returned. If not, the default content is determined. If the default content is a function, it is executed and the proxy object of the component instance is bound to this by the call method, otherwise the default content is returned.

Provide/Inject Implementation principle summary

Through the above analysis, it can be known that the implementation principle of provide/ Inject is relatively simple, that is, the prototype and prototype chain are skillfully used for data inheritance and acquisition. When the provide API is invoked, set the parent provides as the properties of the current provides object prototype object. When the Provide object is provided, the provide object’s properties are obtained first. If the provider cannot be found, It looks up an object along the prototype chain.

Extension: Object. Create principle

Method statement

  • The object.create () method creates a new Object and takes the method’s first argument as the value of the new Object__proto__Property (the prototype object that takes the first argument as the constructor of the new object)
  • The object.create () method takes a second optional argument, which is an Object. Each property of the Object is treated as a property of the new Object. Object attribute value to descriptor (Object. GetOwnPropertyDescriptor (obj, ‘key’)) in the form of and enumerable to false by default

The source code to simulate

Object.myCreate = function (proto, propertyObject = undefined) {
    if (propertyObject === null) {
        // There is no check on whether the propertyObject is the original wrapper object
        throw 'TypeError'
    } else {
        function Fn () {}
        // Set the properties of the prototype object
        Fn.prototype = proto
        const obj = new Fn()
        if(propertyObject ! = =undefined) {
            Object.defineProperties(obj, propertyObject)
        }
        if (proto === null) {
            // Create an Object with no prototype Object, object.create (null)
            obj.__proto__ = null
        }
        return obj
    }
}
Copy the code

Define an empty constructor, then specify the prototype Object of the constructor, create an empty Object with the new operator, and if a second argument is passed, set the key, value for the created Object with Object.defineProperties, and return the created Object.

The sample

// When the second argument is null, TypeError is raised
// const throwErr = Object.myCreate({name: 'coboy'}, null) // Uncaught TypeError
// Build a
const obj1 = Object.myCreate({name: 'coboy'})
console.log(obj1)  // {}, the prototype object of obj1's constructor is {name: 'coboy'}
const obj2 = Object.myCreate({name: 'coboy'}, {
    age: {
        value: 18.enumerable: true}})console.log(obj2)  // {age: 18}, obj2's constructor prototype object is {name: 'coboy'}
Copy the code

Extension: Expressions for two consecutive assignments

Provides = CurrentInstance.provides = Object.create(parentProvides) What happens?

Object. Create (parentProvides) creates a new Object reference. If currentInstance.provides is updated to the new Object reference, the provides reference will still be the old one. So you need to update the provides reference to the new object reference as well.

Parsing from the Definitive guide to JavaScript

  • JavaScript always evaluates expressions in strict left-to-right order
  • Everything is an expression, everything is an operation
provides = currentInstance.provides = Object.create(parentProvides)
Copy the code

The provides described above is an expression that is strictly referred to as the “left hand (Ihs) operand of an assignment expression.” Currentinstance.provides = Object.create(parentProvides) as an expression Currentinstance.provides = Object.create(parentProvides) this is also an assignment expression, Object.create(parentProvides) creates a new reference assignment to the currentInstance provides property on the reference

Currentinstance. provides This expression has the semantics:

  • Evaluates the single-valued expression currentInstance to get a reference to currentInstance
  • Interpret the name on the right as an identifier and write it as “. The right-hand operand of the operation
  • To calculatecurrentInstance.providesThe Result of an expression

Currentinstance.provides When it is the left-hand operand of an assignment expression, it is an assigned reference, and when it is the right-hand operand, its value is evaluated.

Note: The operand to the left of an assignment expression can be another expression, and it can never be an expression to the left of the equals sign in a declaration statement. For example, if let provides = XXX is written above, the provides is simply a literal text that expresses the name and is understood as an identifier during static parsing, not an expression.