This is the seventh day of my participation in the First Challenge 2022. For details: First Challenge 2022.

Previously on & Background

The _init() method is at the heart of what happened in new Vue. Now let’s briefly review the logic of _init() in detail:

  1. vmVueAn instance of “, appears latervmTo think ofVueAn instance of the;
  2. According to theoptions._isComponentProcess the merge of options, and the root instance is obtainedvm.$optionsProperties;
  3. The agent_renderProxyvm;
  4. A series of initialization sumsbeforeCreatedcreatedHook calls;
  5. According to thevm.$options.elProperty determines whether to callvm.$mountMethods Implement mount;

In the last article we discussed the details of mergeOptions, the suboptions passed when vue.options is merged with new Vue(), and how option merging implements global components, directives, and filters. This article will continue to discuss other details in _init.

_renderProxy proxies to vm

2.1 Proxy Settings

So what does this _renderProxy do? The vm._renderProxy is passed to the render function to generate vNodes. Only for the development environment and the implementation of the online environment is different, the development environment is using ES6 native Proxy implementation.

Vue.prototye._init = function () {
  // ...
  if(process.env.NODE_ENV ! = ='production') {
    // Set the proxy to delegate attributes on the VM instance to vm._renderProxy
    initProxy(vm)
  } else {
    vm._renderProxy = vm
  }
}
Copy the code

2.2 initProxy method

Methods location: SRC/core/instance/proxy. Js – > initProxy

initProxy = function initProxy (vm) {
  if (hasProxy) {
    // determine which proxy handler to use
    const options = vm.$options
    const handlers = options.render && options.render._withStripped
      ? getHandler
      : hasHandler
    vm._renderProxy = new Proxy(vm, handlers)
  } else {
    vm._renderProxy = vm
  }
}
Copy the code

innew VueIn the process, see through the breakpointProxyWhich one is usedhandlerAnd then look at thishandlerWhat did you do

If the property you are accessing exists correctly on the target object or is a global property on the window, e.g. Math/Date, or if it does not, pop out a message telling you that a property used in rendering does not exist on the instance:

This is a development environment warning, belongs to the improvement of DX prompt message;

const hasHandler = {
  has (target, key) {
    const has = key in target
    const isAllowed = allowedGlobals(key) ||
      (typeof key === 'string' && key.charAt(0) = = ='_' && !(key in target.$data))
    if(! has && ! isAllowed) {if (key in target.$data) warnReservedPrefix(target, key)
      else warnNonPresent(target, key)
    }
    returnhas || ! isAllowed } }Copy the code

InitLifecycle method

Methods location: SRC/core/instance/lifecycle. Js – > initLifeCycle

3.1 call in _init, parameter is VM;

Vue.prototye._init = function () {
  // ...
  initLifeCycle(vm)
}
Copy the code

3.2 initLifeCycle source

This method initializes the following properties:

  1. Properties of relationships between components, such as$parent/$children/$root/$refs
  2. Properties representing instance state:_inactive/_isDestroyed/_isMounted
export function initLifecycle (vm: Component) {
  const options = vm.$options

  // Locate the parent of the first non-abstract meta-instance
  let parent = options.parent
  if(parent && ! options.abstract) {while(parent.$options.abstract && parent.$parent) { parent = parent.$parent } parent.$children.push(vm) } vm.$parent = parent  vm.$root = parent ? parent.$root : vm// if no parent says it is $root

  vm.$children = []
  vm.$refs = {}

  vm._watcher = null
  vm._inactive = null
  vm._directInactive = false
  vm._isMounted = false
  vm._isDestroyed = false
  vm._isBeingDestroyed = false
}
Copy the code

InitEvents ()

Methods location: SRC/core/instance/events. Js – > initEvents

This method initializes the instance’s custom event, which is registered on the component and whose listener is itself;

4.1 Method Invocation

Vue.prototye._init = function () {
  // ...
  initLifeEvents(vm)
}
Copy the code

4.2 initEvents source

The breakpoint tells us that this method initializes mv._events and the vm._hasHookEvent property. We’ll see later that one property is called hookEvent, which is a way of naming events with a lifecycle prefix. Events hit in this way are invoked along with a lifecycle hook call

export function initEvents (vm: Component) {
  vm._events = Object.create(null)
  vm._hasHookEvent = false
  // init parent attached events
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)
  }
}
Copy the code

Fifth, initRender

Methods location: SRC/core/instance/render. Js – > initRender

5.1 Called in _init

Vue.prototye._init = function () {
  // ...
  initRender(vm)
}
Copy the code

5.2 initRender source

In this method, the following things are done:

  1. Parse the slot information and assign a value tovm.$slotsProperties;
  2. Initializes an important tool method method that will be used later,vm._cvm.$createElement;
export function initRender (vm: Component) {
  vm._vnode = null // the root of the child tree
  vm._staticTrees = null // v-once cached trees
  const options = vm.$options
  const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
  const renderContext = parentVnode && parentVnode.context
  vm.$slots = resolveSlots(options._renderChildren, renderContext)
  vm.$scopedSlots = emptyObject

  // initialize _c, which is a Corey method of createElement, in the sense that the VM is pre-bound
  vm._c = (a, b, c, d) = > createElement(vm, a, b, c, d, false)

  // Initialize $createElement
  vm.$createElement = (a, b, c, d) = > createElement(vm, a, b, c, d, true)

  const parentData = parentVnode && parentVnode.data

  /* istanbul ignore else */
  if(process.env.NODE_ENV ! = ='production') {
    Development environment verification $attrs and $Listeners cannot be modified
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () = > {
      !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
    }, true)
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () = > {
      !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
    }, true)}else {
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null.true)
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null.true)}}Copy the code

Six, callHook (vm, ‘beforeCreate’)

Methods location: SRC/core/instance/lifecycle. Js – > callHook

6.1 Method Invocation

Vue.prototye._init = function () {
  // ...
  callHook(vm, 'beforeCreate')}Copy the code

Trigger the beforeCreate life cycle hook, that’s right, the beforeCareate life cycle hook of the Vue. Let’s look at the callHook method:

export function callHook (vm: Component, hook: string) {
  pushTarget()
  const handlers = vm.$options[hook]
  const info = `${hook} hook`
  if (handlers) {
    for (let i = 0, j = handlers.length; i < j; i++) {
      invokeWithErrorHandling(handlers[i], vm, null, vm, info)
    }
  }
  // vm._hasHookEvent is a hook event. Where is this value handled?
  // this is handled by the vm.$on method that handles event listening
  //vm._hasHookEvent = /^hook:/g.test(eventName) 
  if (vm._hasHookEvent) {
    vm.$emit('hook:' + hook)
  }
  popTarget()
}
Copy the code

InitEvents () {hookEvent (); $emit(‘hook:’, hook); $emit(‘hook:’, hook); $emit(‘hook:’, hook);

<comp @hook:created="eventHandler" />
Copy the code

Seven, initInjections

Methods location: SRC/core/instance/inject js – > initInjections

7.1 Method Call:

Vue.prototye._init = function () {
  // ...
  initInjections (vm)
}
Copy the code

7.2 the source code

Initialize the inject configuration item. Inject and provide are used in combination to receive data provided by provide from ancestor components across domains, similar to the React Provider.

It also follows the principle of one-way data flow. Do not modify the data in the inject component, because once the data on the ancestor component changes, the data in your child component will be overwritten

export function initInjections (vm: Component) {
  // First parse the Inject configuration item,
  // Then find each key, value in the configuration item from the ancestor component configuration.
  // result[key] = val
  const result = resolveInject(vm.$options.inject, vm)
  // Render the result as a response, and inject each key into the VM instance as a proxy.
  // Do not update the data in the child component, because the provide in the ancestor component is changed, and your changes in the component will be overwritten
  if (result) {
    toggleObserving(false)
    Object.keys(result).forEach(key= > {
      /* istanbul ignore else */
      if(process.env.NODE_ENV ! = ='production') {
        defineReactive(vm, key, result[key], () = > {
          warn(
            `Avoid mutating an injected value directly since the changes will be ` +
            `overwritten whenever the provided component re-renders. ` +
            `injection being mutated: "${key}"`,
            vm
          )
        })
      } else {
        defineReactive(vm, key, result[key])
      }
    })
    toggleObserving(true)}}Copy the code

7.3 resolveInject

Methods location: SRC/core/instance/inject js – > resolveInject

7.3.1 resolveInject source

NormalizeInject {from, default}; normalizeInject {from, default}; normalizeInject {from, default}; The while loop then fetches the _provide attribute on the current instance’s $parent, fetches the value from it, and then uses default if it doesn’t find it at the end of the loop, or throws an error if it doesn’t

export function resolveInject (inject: any, vm: Component): ? Object {if (inject) {const result = object.create (null) // Inject configuration item All keys const keys = hasSymbol? Reflect.ownkeys (inject) : object.keys (inject) // Iterate key for (let I = 0; i < keys.length; I ++) {const key = keys[I] // skip __ob__ object if (key === '__ob__') continue NormalizeInject (options.inject), // make it {from, Const provideKey = inject[key]. From let source = VM // Iterate over all ancestor components, Result [key] = provide[provideKye] result[key] = provide[provideKye] While (source) {if (source._provided && hasOwn(source._provided, ProvideKey) {result[key] = source._provided[provideKey] break Provide} source = source.$parent} // Inject [key]. Default If the default value is not set, throw if (! source) { if ('default' in inject[key]) { const provideDefault = inject[key].default result[key] = typeof provideDefault  === 'function' ? provideDefault.call(vm) : provideDefault } else if (process.env.NODE_ENV ! == 'production') { warn(`Injection "${key}" not found`, vm) } } } return result } }Copy the code

Eight, initState

Vue.prototye._init = function () {
  // ...
  initState(vm)
}
Copy the code

This method is used to initialize data in the props/ methods/ data/ computed/ watch mode, and to initialize data in the props/ methods/ data/ computed/ watch mode.

Many people see here is not to be disappointed ~~~~~

You do not be disappointed, stirring is for the most beautiful ordinary

Nine, callHook (vm, ‘created’)

This is a hook that triggers the Created lifecycle. The data response is created only after the data response is initialized

Vue.prototye._init = function () {
  // ...
  callHook(vm, 'created')}Copy the code

$mount = vm.$options.el

Vue.prototye._init = function () {
  // ...
  if (vm.$options.el) {
    // Call the $mount method to enter the mount phase
    vm.$mount(vm.$options.el)
  }
}
Copy the code

$options. El = vm.$options. El = vm.$options. El = vm.

You know this, but when we create the root instance, new Vue, we pass in an EL property, usually #app. Auto mount is right here.

In fact, this design is common. In the source code of Webpack, after the compiler is created, webpack will determine whether the callback is passed in, and if so, it will automatically call compiler.run to enter the traversal. Otherwise, the Compiler instance is returned.

In fact, as we’ll see later, only the root instance will have the EL attribute, and we will not pass the EL when we develop our own component options. In this case, it is up to Vue to determine the mount point.

We’re not done with data responsiveness, so we won’t expand the $mount details for now.