preface

Hello everyone, my name is Good. This chapter is suitable for those who have read some of the source code but do not understand it thoroughly.

  • If you don’t know the JS built-in API, you can go toMDNLooking updeveloper.mozilla.org/zh-CN/

The source code analysis is divided into the following sections, which will be updated later

  1. Vue initialization process – Data binding
  2. Vue template compilation principle
  3. Vue relies on the collection principle
  4. Vue global hook functions and lifecycle hooks
  5. Vue Diff core process principles

Vue responsive principle

Initialization process analysis: Initialize instance > Initialize Status > Bind data

So this is our template

let vm = new Vue({
    el: '#app'.data() { // Data is a function in the root component
        return {
            name: 'vue'.obj: {
                name: 'obj'
            },
            ary: ['one', {
                message: 'inAry'}}}});Copy the code

Initialize instance

Without further ado, let’s get right to the code

import { initMixin } from './init'

// When new Vue, the _init method in the instance is executed and the configuration is initialized
function Vue (options) {
  // if (process.env.NODE_ENV ! == 'production' &&
  / /! (this instanceof Vue) // error if this is not an instanceof Vue
  // ) {
  // warn('Vue is a constructor and should be called with the `new` keyword')
  // }
  this._init(options); // Pass the configuration object
}

initMixin(Vue); // Pass Vue before new Vue
Copy the code

If there is no file module partition, it will be difficult to manage in the case of excessive code

Go to the _init method

export function initMixin (Vue) {
  Vue.prototype._init = function (options) {
    const vm = this
    
    // omit validation source code...
    // if (options && options._isComponent) {we initialize the page not as a component so go else
    // initInternalComponent(vm, options)
    // } else { 
    // We will talk about merging in more detail in a later section, but first we need to mount options on the instance
      vm.$options = mergeOptions(  
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    // }
    
    // initLifecycle(vm) comment section, ignored for the moment, more on in a later chapter
    // initEvents(vm)
    // initRender(vm)
    // callHook(vm, 'beforeCreate') // Lifecycle hook
    // initInjections(vm) // resolve injections before data/props
    initState(vm) // Initialization state
    // initProvide(vm) // resolve provide after data/props
    // callHook(VM, 'created') // Lifecycle hooks
    
    // Page mount logic
    $mount('#app') $mount('#app')
    // The $mount method is still used
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}
Copy the code

_init mainly does initialization of state and mount pages, but there are other initialization processes as well. We’ll talk about them all later

Initialization state

Enter the initState method

export function initState (vm) {
  vm._watchers = []
  const opts = vm.$options
  // Since we only have data in the template, we will omit the other source code
  if (opts.data) { 
    initData(vm) // Pass the VM in to bind the attributes in data
  } else {
    observe(vm._data = {}, true /* asRootData */)}}Copy the code

Enter the initData method

const sharedPropertyDefinition = { // Object.defineProperty's third argument
  enumerable: true.configurable: true.get: () = > {},
  set: () = >{}};/* Proxy function proxy */
export function proxy (target, sourceKey, key) {
                     // vm, '_data', key for clarity, note that '_data' is a string and other variables
                     
  // Reassign get/set to sharedPropertyDefinition
  sharedPropertyDefinition.get = function proxyGetter () { 
    return this[sourceKey][key]; ['_data'][key]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val;  // equivalent to vm['_data'][key] = val
  }
  // Call defineProperty to proxy, and get/set is called when we call vm[key]
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

/* initData method */
function initData (vm) {
  let data = vm.$options.data; // give vm.$options.data to data
    
  // Assign the result to data and vm._data
  data = vm._data = typeof data === 'function'  // If it is a function, execute it and return it
    ? getData(data, vm)  // Our data is a function, so execute first before returning the result
    : data || {}
  
  // omit validation source code...
  
  const keys = Object.keys(data); // Get the key from data
  let i = keys.length
  while (i--) {
    const key = keys[i]; // Get the current I in keys
    
    // omit validation source code...
    
    // We delegate data[key] to vm only if it does not begin with _ and $
    if(! isReserved(key)) { proxy(vm,`_data`, key);  // The attributes of data. XXX can be accessed directly from vm. XXX}}// The core method of data binding
  observe(data, true /* asRootData */)}Copy the code

Data binding

  • The heart of data binding

Enter the Observer (Core method)

export function observe (value, asRootData) {
  if(! isObject(value) || valueinstanceof VNode) { // If not an object type or a VNode instance
    return
  }
  
  let ob;
  // If you already have an __ob__ song attribute and the attribute value is an instance of an Observer
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { 
    ob = value.__ob__;  // Assign the value directly to ob
  } else if ( 
    shouldObserve && // Whether to observe, the default is true! isServerRendering() &&// Not server rendering
    (Array.isArray(value) || isPlainObject(value)) && // Vlaue is an array or object
    Object.isExtensible(value) && // Vlaue is an object to which new attributes can be added! value._isVue// value is not Vue
  ) {
    ob = new Observer(value); // Create an observer to observe value
  }
  
  if (asRootData && ob) { // Initialize bound data asRootData is true
    ob.vmCount++
  }
  return ob
}
Copy the code

Enter the Observer method

import { arrayMethods } from './array'

export const hasProto = '__proto__' in {}; // Whether __proto__ is available

function protoAugment (target, src) { // Point the target prototype chain to SRC
  target.__proto__ = src
}

export class Observer {
  constructor (value) {
    this.value = value
    // this.dep = new Dep(); Dependency collection, which will be covered below but ignored for now
    // this.vmCount = 0
    
    def(value, '__ob__'.this); Value ['__ob__'] = this
    if (Array.isArray(value)) { // Handle the array differently
      if (hasProto) { __proto__ / / available
        protoAugment(value, arrayMethods) // Point the prototype chain of Value to arrayMethods
      }
      this.observeArray(value); // The listener array contains the value of the object type
    } else { // The object gets the key listener directly
      this.walk(value)
    }
  }

  walk (obj) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i]); // Bind each value of the object
    }
  }

  observeArray (items) {
    for (let i = 0, l = items.length; i < l; i++) {
      // Recursively inspect each item in the array, and return if it is not an objectobserve(items[i]); }}}export function defineReactive (obj, key, val) {
  // const dep = new Dep(); Objects depend on collection, which will be covered later

  // getOwnPropertyDescriptor gets a descriptor of itself
  const property = Object.getOwnPropertyDescriptor(obj, key);
  // Object properties cannot be changed or deleted
  if (property && property.configurable === false) { 
    return
  }
 
  
  const getter = property && property.get;
  const setter = property && property.set;
  if((! getter || setter) &&arguments.length === 2) {
    /* * There are several cases: * 1. We define getters externally for properties, so vue stores our getters and has setters * 2. Without a getter, vue will assign obj[key] to val, and it has a setter * 3. Getter /setter is not available, so it will assign val * 4. Getter has but setter does not, so it will not go there */ 
    val = obj[key];
  }

  // recursively listen on val
  letchildOb = ! shallow && observe(val);Object.defineProperty(obj, key, {  // Bind data
    enumerable: true.configurable: true.get: function reactiveGetter () {
      // As long as we pass the getter function, the return value of the getter is what we pass
      // But without the getter, it will make val = obj[key]
      const value = getter ? getter.call(obj) : val
      // if (dep.target) {// Collect the dependency, ignore it for now
      // dep.depend()
      // if (childOb) {
      // childOb.dep.depend()
      // if (Array.isArray(value)) {
      // dependArray(value)
      / /}
      / /}
      // }
      // Return the content
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      // Check parameters
      if(newVal === value || (newVal ! == newVal && value ! == value)) {return
      }
      if(process.env.NODE_ENV ! = ='production' && customSetter) {
        customSetter()
      }
      if(getter && ! setter)return
      if (setter) { // Notify the setter we defined to execute and pass newVal
        setter.call(obj, newVal)
      } else{ val = newVal } childOb = ! shallow && observe(newVal);// let newVal listen again
      // dep.notify() updates the view}})}Copy the code

ArrayMethods Array variation methods

  • The one above that deals with arraysprotoAugment(value, arrayMethods)ArrayMethods comes from here
  • The mutation method is an array prototype method that can change the original array
const arrayProto = Array.prototype; // Get the array prototype
export const arrayMethods = Object.create(arrayProto); // Create an empty object and the prototype points to arrayProto

const methodsToPatch = [ // None of these methods can change the original array
  'push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse'
]

methodsToPatch.forEach(function (method) {
  const original = arrayProto[method]; // Get the method on the array prototype
  
  ArrayMethods [method] = function mutator (... args) {... }
  def(arrayMethods, method, function mutator (. args) { // args is an array whose parameters are the parameters of array methods
   // This is whoever calls the mutator method
    const result = original.apply(this, args); // Call the method of the source array, but apply changes this to the array that is currently calling it
    const ob = this.__ob__; // Get the __ob__ attribute that was assigned earlier in creating an Observer. You can go back to the last code block if you forget
    let inserted;
    switch (method) { 
      case 'push':
      case 'unshift':
      // Assign the new value of the array to the inserted position
        inserted = args;  
        break
      case 'splice':
      // Cut above the second value added to the array and assign a value to the inserted inserted
        inserted = args.slice(2);  // For example: [0, 0, 4]. Slice (2)
        break
    }
    // If there is a value inserted, listen for the new value in the array
    if (inserted) ob.observeArray(inserted); 
    
    // ob.dep.notify() relies on views to update data, as discussed in a later chapter
    return result
  });
});
Copy the code

conclusion

  1. Vue can pass directlyvm.xxxAccess to thedataProperties, all of themproxywillvm._dataTo the VM.
  2. Vue hijacks the data object in the configuration object and then fetches the key listening binding for data. ifdata[key]Is also the value of an object data type, recursivelyobserve(data[key])If it is an array, it does not loop through the index directly. Instead, it redirects the array prototype chain, adding 7 variantspush/pop/unshift/shift/splice/reverse/sortLet the observer instanceobserveArrayInner loop array each item, each item will be calledobserveRecursive listening

At the end

Thank you for seeing to the end! If there is any incorrect place welcome in the comment area or private letter I correct, thank you!