SoFunction
Updated on 2025-04-05

State initialization of Vue source code exploration

Continue to explore other modules as the core class is initialized. This article will study the state initialization of Vue. The state initialization here refers to how the attributes, data variables, methods, etc. defined in the configuration object are initially processed when creating an instance. Since subsequent data updates are left to the observation system to be responsible, after understanding the principle of data binding in advance, you only need to focus on this part.

Let's take a closer look at the source code for the state part that is first executed in the core class:

initState

// Define and export the initState function and receive the parameters vmexport function initState (vm: Component) {
 // Initialize the private attributes of the instance _watchers // This is the object that will be used in the observation system to store all the explicit monitors vm._watchers = []
 // Get the configuration object of the instance const opts = vm.$options
 // If props are defined, initialize props if () initProps(vm, )
 // If methods are defined, methods are initialized if () initMethods(vm, )
 // If data is defined, then data is initialized if () {
 initData(vm)
 } else {
 // Otherwise, the private property of the instance is initialized as an empty object and enable observation observe(vm._data = {}, true /* asRootData */)
 }
 // If computed is defined, the computed attribute is initialized if () initComputed(vm, )
 // If the watch is defined and not nativeWatch, then the watch is initialized // nativeWatch is the prototype method of the object defined under Firefox browser if ( &&  !== nativeWatch) {
 initWatch(vm, )
 }
}

This code is very straightforward and is mainly used to perform the initialization of the state defined in the configuration object. Here are five configuration objects: props, data, methods, computed, and watch, and each has its own initialization methods. Before carefully studying their specific implementations, let’s take a look at a helper function that will be used in each initialization function.

// Define shared property definition descriptor object sharedPropertyDefinition// Both the enumeration and configurable properties of the descriptor object are set to true// Set methods are set to empty functionsconst sharedPropertyDefinition = {
 enumerable: true,
 configurable: true,
 get: noop,
 set: noop
}

// Define and export the proxy function, which is used to define and proxy properties on the target object// Receive three parameters: target, path key name sourceKey, attribute key nameexport function proxy (target: Object, sourceKey: string, key: string) {
 // Set the get method of the attribute descriptor object  = function proxyGetter () {
 return this[sourceKey][key]
 }
 // Setting a set of attribute descriptive objects is illegal  = function proxySetter (val) {
 this[sourceKey][key] = val
 }
 // Define attributes on target object (target, key, sharedPropertyDefinition)
}

The definition of the proxy function is very important. In the various initialization functions to be explored below, it will define all the properties we set in the configuration object into the instance object, but our operations on these properties are performed through the corresponding proxy properties of each part. The implementation of the get and set methods clearly expresses this process, and then defines the properties into the instance. Based on this function, let’s continue to look at the contents of the initialization function in the other five states.

initProps

// Define the initProps function, receive two parameters: vm and propsOptionsfunction initProps (vm: Component, propsOptions: Object) {
 // Assign propsData, propsData is the assignment object passed in by global extension // It will be used when using extend, and it is less used in actual development const propsData = vm.$ || {}
 // Define the _props private property of the instance and assign it to props const props = vm._props = {}
 // Cache prop keys so that future prop updates can be iterated using Array instead of dynamic object key enumeration. // cache prop keys so that future props updates can iterate using Array
 // instead of dynamic object key enumeration.
 const keys = vm.$options._propKeys = []
 // Is it a root instance const isRoot = !vm.$parent
 // For non-root instances, turn off the observation flag // root instance props should be converted
 if (!isRoot) {
 toggleObserving(false)
 }
 // traverse props configuration object for (const key in propsOptions) {
 // Add key name to cache key value array (key)
 // Verify the value of the prop, validateProp performs type checking and default assignment of the initialized defined props // If there is a definition type check, if the boolean value has no default value, the string will be assigned false by default. // Comparison of propsOptions is also meaningful when using extend extension // For specific implementation, please refer to src/core/util/. There are no difficulties and will not explain in detail here. const value = validateProp(key, propsOptions, propsData, vm)

 // Check and prompt in non-production environment /* istanbul ignore else */
 if (.NODE_ENV !== 'production') {
  // Convert key names to convert camel to hyphen key names  const hyphenatedKey = hyphenate(key)
  // Give a prompt for key names that conflict with the reserved variable name  if (isReservedAttribute(hyphenatedKey) ||
   (hyphenatedKey)) {
  warn(
   `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
   vm
  )
  }
  // Establish observations on attributes and give warnings when using attributes directly  defineReactive(props, key, value, () => {
  if (vm.$parent && !isUpdatingChildComponent) {
   warn(
   `Avoid mutating a prop directly since the value will be ` +
   `overwritten whenever the parent component re-renders. ` +
   `Instead, use a data or computed property based on the prop's ` +
   `value. Prop being mutated: "${key}"`,
   vm
   )
  }
  })
 } else {
  // Direct accessor packaging of attributes in non-production environments and establish dependency observations  defineReactive(props, key, value)
 }
 // When extending attributes using the() method, the static attributes have been proxied. // Here you only need to perform proxy operations on the properties during instantiation // static props are already proxied on the component's prototype
 // during (). We only need to proxy props defined at
 // instantiation here.
 // When there is no attribute of the same name on the instance, perform proxy operations on the attribute // Point the reference to the key name to the vm._props object if (!(key in vm)) {
  proxy(vm, `_props`, key)
 }
 }
 // Turn on the observation status flag toggleObserving(true)
}

The main content of the initProps function is two points. One is to observe the defined data, and the other is to proxy the data. This is the role of the private variable _props. The variables obtained and set afterwards are operated as attributes of _props.

In addition, the process of initializing props is initialized for the propsData property that will be used by the extend method. The specific use is to define some props when extending the object, and then pass in the propsData configuration object during the creation of the instance. The corresponding props attribute in the extension object will receive the value passed by propsData. Similar to passing in props value in the parent component, the value must be explicitly passed through the propsData configuration object.

initData

// Define the initData functionfunction initData (vm: Component) {
 // Get the data attribute of the configuration object let data = vm.$
 // Determine whether data is a function // If it is a function, assign the return of the getData function to data and instance private attributes_data // Otherwise, directly assign data to the instance_data attribute and assign null objects when there is no data data = vm._data = typeof data === 'function'
 ? getData(data, vm)
 : data || {}
 // If data is not an object, assign data to an empty object // Further ensure that data is the object type if (!isPlainObject(data)) {
 data = {}
 // Give warning prompts in non-production environment .NODE_ENV !== 'production' && warn(
  'data functions should return an object:\n' +
  '/v2/guide/#data-Must-Be-a-Function',
  vm
 )
 }
 // Instance object proxy data // proxy data on instance
 // Get all data key values const keys = (data)
 // Get the props of the configuration object const props = vm.$
 // Get the methods of the configuration object const methods = vm.$
 // traverse keys let i = 
 while (i--) {
 const key = keys[i]
 // Non-production environment gives warnings that conflict with the method name defined by methods if (.NODE_ENV !== 'production') {
  if (methods && hasOwn(methods, key)) {
  warn(
   `Method "${key}" has already been defined as a data property.`,
   vm
  )
  }
 }
 // Detect whether it conflicts with props if (props && hasOwn(props, key)) {
  // Non-production environment gives conflict warnings  .NODE_ENV !== 'production' && warn(
  `The data property "${key}" is already declared as a prop. ` +
  `Use prop default value instead.`,
  vm
  )
 // When there is no conflict with props and non-reserved words, the proxy key name is on the instance's _data object } else if (!isReserved(key)) {
  proxy(vm, `_data`, key)
 }
 }
 // Observe the data // observe data
 observe(data, true /* asRootData */)
}

// Define and export getData function, accept function type data objects, and Vue instance objectsexport function getData (data: Function, vm: Component): any {
 // pushTarget and popTarget are to solve the problem that the defect of Vue dependency detection may lead to redundant dependency // For details, please refer to /vuejs/vue/issues/7573 // This operation will be set to undefined, and calling() when initializing option will not create a dependency // #7573 Disable dep collection when calling data getter // #7573 disable dep collection when invoking data getters
 pushTarget()
 // Try to call the data function on the vm and return the execution result try {
 return (vm, vm)
 } catch (e) {
 // If an error is caught, the error will be processed and an empty object will be returned handleError(e, vm, `data()`)
 return {}
 } finally {
 popTarget()
 }
}

Similar to props processing, the function of the initData function is to establish an observed dependency on the data, and to proxy the data to the private variable _data, and also includes detection of the key names of data and other configuration object properties.

initComputed

// Set computedWatcherOptions objectconst computedWatcherOptions = { computed: true }

// Define the initComputed function, accept instance vm, and computed objectfunction initComputed (vm: Component, computed: Object) {
 // $flow-disable-line
 // Define watchers and instance_computedWatchers attributes, initially assign null objects const watchers = vm._computedWatchers = (null)
 // Whether it is a server rendering, the computed property can only be a getter during server rendering // computed properties are just getters during SSR
 const isSSR = isServerRendering()

 // traversal computed for (const key in computed) {
 // Get user-defined values const userDef = computed[key]
 // If the user defines a function, then assign to getter Otherwise j will assign the method to getter const getter = typeof userDef === 'function' ? userDef : 
 // Non-production environment throws an error warning of missing calculation attributes if (.NODE_ENV !== 'production' && getter == null) {
  warn(
  `Getter is missing for computed property "${key}".`,
  vm
  )
 }

 // under non-server rendering if (!isSSR) {
  // Create an internal monitor for computed properties  // create internal watcher for the computed property.
  watchers[key] = new Watcher(
  vm,
  getter || noop,
  noop,
  computedWatcherOptions
  )
 }

 // The internal computed properties defined by the component have been defined on the component's prototype // So here we just focus on the user-defined calculation properties when instance initialization // component-defined computed properties are already defined on the
 // component prototype. We only need to define computed properties defined
 // at instantiation here.
 // When the key name is not an instance root property, define the computed property, refer to the definedComputed function for details. if (!(key in vm)) {
  defineComputed(vm, key, userDef)
 // In non-production environment, detect conflicts with data attribute names and give warnings } else if (.NODE_ENV !== 'production') {
  if (key in vm.$data) {
  warn(`The computed property "${key}" is already defined in data.`, vm)
  } else if (vm.$ && key in vm.$) {
  warn(`The computed property "${key}" is already defined as a prop.`, vm)
  }
 }
 }
}

// Define and export defineComputed Ha number// Receive instance target, calculate attribute key name key, calculate attribute value userDef parameterexport function defineComputed (
 target: any,
 key: string,
 userDef: Object | Function
) {
 // Set up cache under non-server rendering const shouldCache = !isServerRendering()
 // When calculating the property value is a function if (typeof userDef === 'function') {
 // Set the getter for the calculated attribute, the setter is an empty function  = shouldCache
  ? createComputedGetter(key)
  : userDef
  = noop
 } else {
 // When the computed attribute is an object, set the getter and setter of the computed attribute  = 
  ? shouldCache &&  !== false
  ? createComputedGetter(key)
  : 
  : noop
  = 
  ? 
  : noop
 }
 // In a non-production environment, if there is no setter that defines the calculation attribute // Give a warning when you want to set the calculation properties if (.NODE_ENV !== 'production' &&
   === noop) {
  = function () {
  warn(
  `Computed property "${key}" was assigned to but it has no setter.`,
  this
  )
 }
 }
 // Define the calculated attribute on the instance object based on the reset attribute descriptor (target, key, sharedPropertyDefinition)
}

// Define createComputedGetter and create computed property getter// The purpose is to establish observation dependencies for computed attributes in non-server rendering situations.// and return the calculated value based on its dependency attributefunction createComputedGetter (key) {
 return function computedGetter () {
 const watcher = this._computedWatchers && this._computedWatchers[key]
 if (watcher) {
  ()
  return ()
 }
 }
}

The initialization of computed attributes is relatively complicated. First, you need to observe the computed attributes, then redefine the computed attributes on the instance, and execute the attribute proxy. Since the server rendering function is added, when defining the computing properties, the use environment is judged, and non-server rendering will affect the definition of the computing properties. This is because when using the framework under server rendering, the computing properties do not provide a setter; in addition, the computing properties must be redefined by the user-defined value being a function or object. From this code, we can see a very important program, that is, when obtaining the calculated attribute, it only calculates its value, which is the implementation of lazy loading.

initMethods

// Define the initMethods method, accept the instance vm, and configure the attribute methodsfunction initMethods (vm: Component, methods: Object) {
 // Get the props of the instance const props = vm.$
 // traverse the methods object for (const key in methods) {
 // Warning is given in non-production environments if (.NODE_ENV !== 'production') {
  // Unassigned method warning  if (methods[key] == null) {
  warn(
   `Method "${key}" has an undefined value in the component definition. ` +
   `Did you reference the function correctly?`,
   vm
  )
  }
  // Warning of conflict with props attribute name  if (props && hasOwn(props, key)) {
  warn(
   `Method "${key}" has already been defined as a prop.`,
   vm
  )
  }
  // Conflict with reserved words warning  if ((key in vm) && isReserved(key)) {
  warn(
   `Method "${key}" conflicts with an existing Vue instance method. ` +
   `Avoid defining component methods that start with _ or $.`
  )
  }
 }
 // Define the method on the instance, assign the value to the user undefined function or the empty function vm[key] = methods[key] == null ? noop : bind(methods[key], vm)
 }
}

The initMethods function is very simple. Except for a large piece of code that reports checking conflicts in a non-production environment, the only content is to define the corresponding method on the instance and bind the context to the instance object. In this way, even if the arrow function is not used, this is used to refer to the instance object by default in the method.

initWatch

// Define the initWatch function, accept the instance vm and configuration property watchfunction initWatch (vm: Component, watch: Object) {
 // traverse the watch for (const key in watch) {
 // The value of the temporary attribute is saved const handler = watch[key]
 // If handler is an array if ((handler)) {
  // traverse the array to create a corresponding watcher for each element  for (let i = 0; i < ; i++) {
  createWatcher(vm, key, handler[i])
  }
 } else {
  //Otherwise the handler should be a function, directly create a watcher for the key  createWatcher(vm, key, handler)
 }
 }
}

// Define the createWatcher function// Accept instance vm, expression or function expOrFn, processor handler, optional optionsfunction createWatcher (
 vm: Component,
 expOrFn: string | Function,
 handler: any,
 options?: Object
) {
 // If handler is an object if (isPlainObject(handler)) {
 // Assign handler to options. options = handler
 // Reassign handler handler = 
 }
 // If the handler is a string, look for the handler on the instance and assign it to the handler if (typeof handler === 'string') {
 handler = vm[handler]
 }
 // Create an observation and return return vm.$watch(expOrFn, handler, options)
}

initWatcher creates a monitor for incoming observation objects, which is relatively simple. It is worth noting that the parameter is passed in. There are two ways to observe the object expOrFn, one is a string and the other is a function. This parameter is detected in the Watcher class, but it is not processed in the initialized function. The handler object can also accept object or string types, make judgments on these two ways of passing in in the code, and finally find the function referenced by the handler to pass in $watch.

stateMixin

After exploring the initState function, continue to look at the state mixed method stateMixin. This function will provide the specific implementation of the $watch method that has not been mentioned above:

// Define and export the stateMixin function and receive the parameter Vueexport function stateMixin (Vue: Class<Component>) {
 // When using the method to declare the object directly, a problem will occur in flow // So the object must be defined programmatically here // flow somehow has problems with directly declared definition object
 // when using , so we have to procedurally build up
 // the object here.
 // Define dataDef object const dataDef = {}
 // Define the get method of dataDef and return the private attribute of Vue instance _data  = function () { return this._data }
 // Define propsDef object const propsDef = {}
 // Define the get method of propsDef and return the private attribute of the Vue instance _props  = function () { return this._props }
 // Define the set method of dataDef and propsDef in non-production environment if (.NODE_ENV !== 'production') {
 // The set method of dataDef receives newData parameters of Object type  = function (newData: Object) {
  // Prompt to avoid overwriting the property $data incoming objects  // It is recommended to use nested data attributes instead  warn(
  'Avoid replacing instance root $data. ' +
  'Use nested data properties instead.',
  this
  )
 }
 // Set the set method of propsDef to read-only  = function () {
  warn(`$props is readonly.`, this)
 }
 }
 // Define the public property $data of the Vue prototype object and assign the value to dataDef (, '$data', dataDef)
 // Define the public property $props of the Vue prototype object and assign the value to propsDef (, '$props', propsDef)

 // Define the $set method of the Vue prototype object and assign it to the set function imported from the observer .$set = set
 // Define the $delete method of the Vue prototype object and assign it to the del function imported from the observer .$delete = del

 // Define the $watch method of Vue prototype object // Receive expOrFn of string or function type, and you can see from the naming that you want to be an expression or function // Receive any type of cb, here it is called a callback function or an object // Receive object type options // Requires return function type .$watch = function (
 expOrFn: string | Function,
 cb: any,
 options?: Object
 ): Function {
 // Assign the instance to the vm variable, the type must be Component const vm: Component = this
 // If cb is pure object type if (isPlainObject(cb)) {
  // Return the createWatcher function  return createWatcher(vm, expOrFn, cb, options)
 }
 // Define the options for the observation target, which is undefined in most cases options = options || {}
 // The user attribute value of the defined options is true, and the identifier is user-defined  = true
 // Create a watcher instance const watcher = new Watcher(vm, expOrFn, cb, options)
 // If the immediate of options is true if () {
  // Call the cb callback function on vm and pass it as a parameter  (vm, )
 }
 // Return unwatchFn function return function unwatchFn () {
  // Execute the() method to clear the observation  ()
 }
 }
}

stateMixin performs a mix of a series of methods about state observation, mainly in three aspects:

  • Accessories that define instances $data and $props properties
  • Define the $set and $delete methods of the instance, and the specific reality is defined in the observer module
  • Define the $watch method of the instance

At this point, the part about state initialization has been explored. Next, we will continue to study another part closely related to the development process - virtual nodes and template rendering.

State initialization is the most closely related to our development. In the configuration object that creates instance objects, we set these properties and methods. During the instance initialization process, these incoming configurations are processed a lot in advance. This is the logic behind state initialization. When I explored this part, I really felt that it was finally related to my usual development.

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.