Series of articles:

  • Vue source code interpretation (Design)
  • Vue source code interpretation (Rollup)
  • Vue source code interpretation (entry to constructor overall flow)
  • Vue source code interpretation (Responsive principle introduction and Prop)

The methods to deal with

After analyzing the logic related to props, we will examine the logic related to methods, which is much simpler than props.

export function initState (vm: Component) {
  // omit the code
  const opts = vm.$options
  if (opts.methods) initMethods(vm, opts.methods)
}
Copy the code

In the initState() method, initMethods() is called and passed in the current instance VM and the written methods. Next, look at the concrete implementation of the initMethods method:

function initMethods (vm: Component, methods: Object) {
  const props = vm.$options.props
  for (const key in methods) {
    if(process.env.NODE_ENV ! = ='production') {
      if (typeofmethods[key] ! = ='function') {
        warn(
          `Method "${key}" has type "The ${typeof methods[key]}" in the component definition. ` +
          `Did you reference the function correctly? `,
          vm
        )
      }
      if (props && hasOwn(props, key)) {
        warn(
          `Method "${key}" has already been defined as a prop.`,
          vm
        )
      }
      if ((key in vm) && isReserved(key)) {
        warn(
          `Method "${key}" conflicts with an existing Vue instance method. ` +
          `Avoid defining component methods that start with _ or $.`
        )
      }
    }
    vm[key] = typeofmethods[key] ! = ='function' ? noop : bind(methods[key], vm)
  }
}
Copy the code

As you can see from the above code, the most important piece of code in the initMethods() method implementation is:

/ / empty function
function noop () {}

vm[key] = typeofmethods[key] ! = ='function' ? noop : bind(methods[key], vm)
Copy the code

It first determines whether the methods defined are of type function, if not, it assigns an empty noop function, and if so, it binds the method to the current vm instance. The purpose of this is to point this in methods to the current instance, so that instance-related properties or methods such as props, data, and computed can be easily accessed in methods in the form of this. XXX.

In a development environment, it also makes the following judgments:

  • Must befunctionType.
// Throw an error: Method sayHello has type null in the Component definition.
// Did you reference the function correctly?
export default {
  methods: {
    sayHello: null}}Copy the code
  • Naming cannot be combined withpropsConflict.
// Throw error: Method name has already been defined as a prop.
export default {
  props: ['name']
  methods: {
    name () {
      console.log('name')}}}Copy the code
  • Names cannot conflict with existing instance methods.
Method $set conflicts with an existing Vue instance Method.
// Avoid defining component methods that start with _ or $.
export default {
  methods: {
    $set () {
      console.log('$set')}}}Copy the code

After analyzing the above initMethods process, the following flow chart can be obtained:

The data processing

Data processing in Vue is a little different between the root instance and the child component. Next, we will focus on data processing in the child component.

export function initState (vm: Component) {
  const opts = vm.$options
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)}}Copy the code

In the above code, opts.data is first judged, if true it is a child component (the default is used if the child component does not display defined data), otherwise it is the root instance. There is no need to perform initData for the root instance, just observe vm._data.

Next, a detailed analysis of the initData process, it is defined in SRC/core/instance/state. A method of js file:

function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  if(! isPlainObject(data)) { data = {} process.env.NODE_ENV ! = ='production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    )
  }
  // proxy data on instance
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  while (i--) {
    const key = keys[i]
    if(process.env.NODE_ENV ! = ='production') {
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    if(props && hasOwn(props, key)) { process.env.NODE_ENV ! = ='production' && warn(
        `The data property "${key}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if(! isReserved(key)) { proxy(vm,`_data`, key)
    }
  }
  // observe data
  observe(data, true /* asRootData */)}Copy the code

Although the initData() method is a bit long, a closer look reveals that it does four things: type judgment, naming conflict judgment, proxy proxy, and observe(data).

Then, the above pieces are explained in detail:

  • Type determination value: For subcomponents, since components can be reused multiple times, the function must return an object through the factory function pattern so that the type reference problem can be avoided when components are reused multiple times.
// Child Component
// Throw error: data functions should return an object
export default {
  data: {
    msg: 'Hello, Data'}}Copy the code

In the case where data is a function, the getData method is called to evaluate, and the getData method is defined as follows:

export function getData (data: Function, vm: Component) :any {
  pushTarget()
  try {
    return data.call(vm, vm)
  } catch (e) {
    handleError(e, vm, `data()`)
    return{}}finally {
    popTarget()
  }
}
Copy the code

Code analysis: pushTarget is related to reactive dependency collection and will be explained in more detail later. The value of getData is wrapped in a try/catch, returned by a call to data.call(vm, VM), and handled with handleError if the function call fails.

  • Naming conflict judgment: due to thepropsandmethodsHas a higher priority, thereforedataAttribute names cannot be combinedprops,methodsIn naming conflicts, as bothprops,methodsordataIt’s all reflected in the example. Another naming conflict is that you cannot start with$or_Because it is easy to expose methods, attributes, or methods private to the instance$Initial method/attribute conflict.
// 1. Name conflict with methods
Method name has already been defined as a data property.
export default {
  data () {
    return {
      name: 'data name'}},methods: {
    name () {
      console.log('methods name')}}}// 2. Name conflict with props
// The data property name is already declared as a prop.
// Use prop default value instead.
export default {
  props: ['name'],
  data () {
    return {
      name: 'data name'}}}// 3. Cannot start with $or _
export default {
  data () {
    return {
      $data: '$data'
      _isVue: true}}}Copy the code
  • The proxy agent: It has been introduced beforeproxyThe role of agency, we talked aboutproxyThe agent_propsFor example, here is the proxy_dataWith the agent_propsIt’s the same thing.
export default {
  data () {
    return {
      msg: 'Hello, Msg'}}}/ / agent before
console.log(this._data.msg)
proxy(vm, '_data'.'msg')
/ / agent
console.log(this.msg)
Copy the code
  • observe(data):observeThe function is to recursively respond to all attributes (including nested attributes) of the passed valuedefineReactive, which will be covered in more detail in a later chapterobserveThe realization principle of theinitDataAs long as you knowobserve(data)thedataMake all properties of the object returned by the function reactive.

After analyzing the implementation of initData, you can get the overall flow chart of initData.