Vue2.0 responsive principle source code analysis

Write directory title here

Responsive principle

The main core of the Vue application is the principle that the ViewModel drives the view and the data drives the ViewModel. It mainly applies the publisher-subscriber model. By redefining the getter and setter methods of the data, and collecting dependencies, the dependent components are notified to update the view when the data is updated.

Back to the source code. The initialization of Vue has a set of life cycle processes, and the main operation of the responsive type is after executing beforeCreate. The initData() method is executed in initializing a series of data.

Source code analysis

The source code implementation of different versions of Vue may be somewhat different. My version here is 2.6.14.
insert image description here
First of all, we need to know in which time period the definition of responsiveness is implemented. From the source code, we can see that it is executing the beforeCreate lifecycle function After, before Created. In other words, this is why we can't get the data in Data in beforeCreate.
insert image description here

export function initState (vm: Component) {
    
    
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    
    
    initData(vm)
  } else {
    
    
    observe(vm._data = {
    
    }, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    
    
    initWatch(vm, opts.watch)
  }
}

In the initState method, the props, methods, data, computed, watch, etc. of the component will be compiled and initialized

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) //将data上的属性代理到vm实例上。
    }
  }
  // observe data
  observe(data, true /* asRootData */)
}

initData() will first obtain the data data inside the component, and then judge the data and props in the data, or if it is the same as the name in the methods, an error message will be thrown, and then it will listen to the data and execute the observe method

/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 */
export function observe (value: any, asRootData: ?boolean): Observer | void {
    
    
  if (!isObject(value) || value instanceof VNode) {
    
    
    return
  }
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    
    
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    
    
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    
    
    ob.vmCount++
  }
  return ob
}


Here, it will first judge whether the data in data is an object, then judge whether there is an ob (that is, an Observer instance) in data, and finally judge whether the monitoring condition is met. will create a new Observer object

/**
 * Observer class that is attached to each observed
 * object. Once attached, the observer converts the target
 * object's property keys into getter/setters that
 * collect dependencies and dispatch updates.
 */
export class Observer {
    
    
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data

  constructor (value: any) {
    
    
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
    
    
      if (hasProto) {
    
    
        protoAugment(value, arrayMethods)
      } else {
    
    
        copyAugment(value, arrayMethods, arrayKeys)
      }
      this.observeArray(value)
    } else {
    
    
      this.walk(value)
    }
  }

  /**
   * Walk through all properties and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
  walk (obj: Object) {
    
    
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
    
    
      defineReactive(obj, keys[i])
    }
  }

  /**
   * Observe a list of Array items.
   */
  observeArray (items: Array<any>) {
    
    
    for (let i = 0, l = items.length; i < l; i++) {
    
    
      observe(items[i])
    }
  }
}

Each observer instance has its own Dep. After the new Oberver, it will judge whether the incoming value, that is, vm.data, is an array.

  1. If it is an array, the function hijacking method will be used to rewrite the method of the array. First determine whether the array supports the prototype chain. If it supports it, point the prototype of the current array to the arrayMethod that has rewritten the 7 methods in the Array. When the array in the array When the method is called, Dep will notify to notify the view to update, and then execute the ObserveArray method. If the data in the array is an object, then continue to call back observe();
  2. If it is an object, call this.walk(). In walk(), it will traverse the properties of data and execute defineReactive() to define the responsiveness
/**
 * Define a reactive property on an Object.
 */
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
    
    
  const dep = new Dep()

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    
    
    return
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    
    
    val = obj[key]
  }

  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    
    
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
    
    
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
    
    
        dep.depend()
        if (childOb) {
    
    
          childOb.dep.depend()
          if (Array.isArray(value)) {
    
    
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
    
    
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
    
    
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
    
    
        customSetter()
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      if (setter) {
    
    
        setter.call(obj, newVal)
      } else {
    
    
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
  })
}

Object.defineProperty() is used to redefine the object, adding getter and setter methods to each property of data.
When getting, it will call dep.depend; if it is an array, call dependArray (recursively call dep.depend for each element in the array);
when setting, it will first judge whether the data is updated, and do not operate if it is not updated. Update observe(), and dep.notify()
The following is the code of Dep, we can regard Dep as an observer.

/* @flow */

import type Watcher from './watcher'
import {
    
     remove } from '../util/index'
import config from '../config'

let uid = 0

/**
 * A dep is an observable that can have multiple
 * directives subscribing to it.
 */
export default class Dep {
    
    
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor () {
    
    
    this.id = uid++
    this.subs = [] //存储所有订阅的Watcher
  }

  addSub (sub: Watcher) {
    
    
    this.subs.push(sub)
  }

  removeSub (sub: Watcher) {
    
    
    remove(this.subs, sub)
  }

  depend () {
    
    
    if (Dep.target) {
    
    
      Dep.target.addDep(this)
    }
  }

  notify () {
    
    
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    if (process.env.NODE_ENV !== 'production' && !config.async) {
    
    
      // subs aren't sorted in scheduler if not running async
      // we need to sort them now to make sure they fire in correct
      // order
      subs.sort((a, b) => a.id - b.id)
    }
    for (let i = 0, l = subs.length; i < l; i++) {
    
    
      subs[i].update()
    }
  }
}

// The current target watcher being evaluated.
// This is globally unique because only one watcher
// can be evaluated at a time.
Dep.target = null
const targetStack = []

export function pushTarget (target: ?Watcher) {
    
    
  targetStack.push(target)
  Dep.target = target
}

export function popTarget () {
    
    
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]
}

The depend method is to add the current dep instance to the corresponding Watcher,
the notify method is to notify all collected Wachers to update, subs[i].update()

So far, I have a simple understanding of Vue's responsiveness

Guess you like

Origin blog.csdn.net/qq_41028148/article/details/122944775