Vue source link

Different builds of Vue

The term

  • Full version: Includes both compiler and runtime versions.
  • Compiler: Code used to compile template strings into JavaScript rendering functions. Bulky and inefficient.
  • Runtime: Small and efficient code for creating Vue instances, rendering, processing virtual DOM, etc. It’s basically the code to remove the compiler.
  • UMD: UMD version The UMD version can be used in multiple module modes. The vue.js default file is the UMD version of the runtime + compiler
  • CommonJS(CJS) : The CommonJS version is used with older packaging tools such as Browserify or WebPack 1.
  • ES Modules: Starting with 2.6, Vue will provide two ES Modules (ESM) build files, versions of which are available with modern packaging tools.
    • The ESM format is designed to be statically analyzed, so the packaging tool can take advantage of this to “tree-shaking” and remove unwanted code from the final package.

Runtime + Compiler vs. Runtime-only

// Compiler
// We need the compiler to convert the template to render
const vm = new Vue({
  el: "#app".template: "<h1>{{ msg }}</h1>".data: {
    msg: "Hello Vue",}});// Runtime
// No compiler is required
const vm = new Vue({
  el: "#app".render(h) {
    return h("h1".this.msg);
  },
  data: { msg: "Hello Vue"}});Copy the code

The runtime version is recommended because it is approximately 30% smaller than the full version.

Projects created based on vue-CLI use vue.runtime.esM.js by default.

Note: The templates in the *.vue file are precompiled at build time, and the final packaged result does not require a compiler, just the runtime version.

Find entry file

  • View the build process for dist/vue.js

Execute a build

npm run dev
# "dev": "rollup -w -c scripts/config.js --sourcemap --environment
TARGET:web-full-dev"# --environment TARGET:web-full-dev set environment variable TARGETCopy the code
  • The execution of script/config.js

Function: Generates configuration files for the rollup build

Use the environment variable TARGET = web-full-dev

// Determine whether the environment variable has a TARGET
// Generate a rollup configuration file using genConfig(), if available
if (process.env.TARGET) {
  module.exports = genConfig(process.env.TARGET)
} else {
  // Otherwise get all configurations
  exports.getBuild = genConfig
  exports.getAllBuilds = () = > Object.keys(builds).map(genConfig)
}
Copy the code
  • The genCon fi g (name)

Obtain configuration information based on the environment variable TARGET

Builds [name] gets information about the build configuration

// Runtime+compiler development build (Browser)
'web-full-dev': {
  entry: resolve('web/entry-runtime-with-compiler.js'),
  dest: resolve('dist/vue.js'),
  format: 'umd'.env: 'development'.alias: { he: './entity-decoder' },
  banner
},
Copy the code
  • resolve()

Gets the absolute path to the entry and exit files

const aliases = require('./alias')
const resolve = p= > {
  // Find the alias in alias according to the first half of the path
  const base = p.split('/') [0]
  if (aliases[base]) {
    return path.resolve(aliases[base], p.slice(base.length + 1))}else {
    return path.resolve(__dirname, '.. / ', p)
  }
}
Copy the code

The results of

  • SRC /platforms/web/entry-runtime-with-compiler.js build dist/vue. Js, if set to -sourcemap, vue
  • Under the SRC/Platform folder are Vue libraries that can be built for use on different platforms. Currently there are weeX and Web, as well as libraries for server-side rendering

Start at the entrance

src/platform/web/entry-runtime-with-compiler.js

Solve the following problem by looking at the source code

  • Look at the following code and answer the output on the page by reading the source code
const vm = new Vue({
  el: '#app'.template: '<h3>Hello template</h3>',
  render (h) {
    return h('h4'.'Hello render')}})Copy the code
  • Reading source records
    • El cannot be a body or HTML tag
    • If there is no render, convert the template to the render function
    • If there is a render method, mount the DOM directly by calling mount
// 1. El cannot be body or HTML
if (el === document.body || el === document.documentElement) { process.env.NODE_ENV ! = ='production' && warn(
    `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
  )
  return this
}
const options = this.$options
if(! options.render) {// 2. Convert template/el to render function... }// 3. Call the mount method to mount the DOM
return mount.call(this, el, hydrating)
Copy the code
  • Debugging code
    • Methods of debugging
const vm = new Vue({
  el: '#app'.template: '<h3>Hello template</h3>', 
  render (h) {    
    return h('h4'.'Hello render')}})Copy the code

The final page displays Hello Render

Where is the constructor for Vue

  • SRC /platform/web/entry-runtime-with-compiler.js
  • src/platform/web/runtime/index.js
    • Set the Vue. Con fi g
    • Set the directives and components related to the platform
      • Command V-model, V-show
      • Components Transition and transition-group
    • Set platform-related__patch__Method (patch method, compare old and new VNodes)
    • Set the $mount method to mount the DOM
// install platform runtime directives & components
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)

// install platform patch function
Vue.prototype.__patch__ = inBrowser ? patch : noop

// public mount method
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
) :Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}
Copy the code
  • SRC/platform/web/runtime/index. The cited ‘core/index in js
  • src/core/index.js
    • Defines static methods for Vue
    • initGlobalAPI(Vue)
  • SRC /core/index.js references ‘./instance/index’
  • SRC/core/instance/index. Js defines the Vue constructor
function Vue (options) {
  if(process.env.NODE_ENV ! = ='production' && !(this instanceof Vue)) {
    warn('Vue is a constructor and should be called with the `new` keyword')}// Call the _init() method
  this._init(options)
}
// Register the VM's _init() method to initialize the VM
initMixin(Vue)
$data/$props/$set/$delete/$watch
stateMixin(Vue)
// Initialize event-related methods
// $on/$once/$off/$emit 
eventsMixin(Vue)
// Initialize life cycle related blending methods
// _update/$forceUpdate/$destroy
lifecycleMixin(Vue)
/ / in the render
// $nextTick/_render
renderMixin(Vue)
Copy the code

Four modules export Vue

  • src/platforms/web/entry-runtime-with-compiler.js
    • Web platform-related entries
    • Overrides the platform-specific $mount() method
    • Register the Vue.com compile() method and pass an HTML string back to the Render function
  • src/platforms/web/runtime/index.js
    • Related to Web Platform
    • Registration and platform-related global directives: V-Model, V-show
    • Register global platform-related components: V-Transition, V-Transition-group
    • Global methods:
      • __patch__: Converts the virtual DOM to the real DOM
      • $mount: Indicates the mounting method
  • src/core/index.js
    • Platform-independent
    • Set the static method for Vue, initGlobalAPI(Vue)
  • src/core/instance/index.js
    • Platform-independent
    • The constructor is defined and the this._init(options) method is called
    • Added common instance members to Vue

Initialization of Vue

src/core/global-api/index.js

  • Initialize the static method of Vue
// Register the static properties/methods of Vue
initGlobalAPI(Vue)

// src/core/global-api/index.js
// Initialize the vue. config object
Object.defineProperty(Vue, 'config', configDef)

// exposed util methods.
// NOTE: these are not considered part of the public API - avoid relying on 
// them unless you are aware of the risk. 
// These tools are not considered part of the global API, so don't rely on them unless you are aware of certain risks
Vue.util = {
  warn,
  extend,
  mergeOptions,
  defineReactive
} 

// Static method set/delete/nextTick
Vue.set = set 
Vue.delete = del
Vue.nextTick = nextTick

// 2.6 explicit observable API 
// Make an object responsive
Vue.observable = <T>(obj: T): T= > {
  observe(obj)
  return obj
} 

// Initialize the vue. options object and extend it
// components/directives/filters/_base 
Vue.options = Object.create(null) ASSET_TYPES.forEach(type= > {
  Vue.options[type + 's'] = Object.create(null)})// this is used to identify the "base" constructor to extend all plainobject 
// components with in Weex's multi-instance scenarios. 
Vue.options._base = Vue

// Set the keep-alive component
extend(Vue.options.components, builtInComponents)

// Register vue.use () to register the plugin
initUse(Vue)
// Register vue.mixin () to implement mixin
initMixin(Vue)
// Register vue.extend () to return a component constructor initExtend(Vue) based on the options passed in
// Register vue.directive (), Vue.component(), vue.filter ()
initAssetRegisters(Vue)
Copy the code

src/core/instance/index.js

  • Define the constructor for Vue
  • Initialize the instance member of Vue
// The reason for not using class here is that it is convenient to mix the instance members into the Vue instance
function Vue (options) {
  if(process.env.NODE_ENV ! = ='production' && !(this instanceof Vue)) {
    warn('Vue is a constructor and should be called with the `new` keyword')}this._init(options)
} 
// Register the VM's _init() method and initialize it
vm initMixin(Vue)
$data/$props/$set/$delete/$watch
stateMixin(Vue)
// Initialize event-related methods
// $on/$once/$off/$emit 
eventsMixin(Vue)
// Initialize life cycle related blending methods
// _update/$forceUpdate/$destroy
lifecycleMixin(Vue)
/ / in the render
// $nextTick/_render
renderMixin(Vue)
Copy the code
  • initMixin(Vue)
    • Initialize the _init() method
// src\core\instance\init.js
export function initMixin (Vue: Class<Component>) {
  // Add _init() method to Vue instance
  // Merge options/initialize operations
  Vue.prototype._init = function (options? :Object) {
    // a flag to avoid this being observed
    // If the instance is Vue, it does not need to be observed
    vm._isVue = true
    // merge options 
    / / merge options
    if (options && options._isComponent) { 
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the 
      // internal component options needs special treatment.
      initInternalComponent(vm, options)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if(process.env.NODE_ENV ! = ='production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    // The lifecycle variables of the VM are initialized
    // $children/$parent/$root/$refs
    initLifecycle(vm)
    // The vm event listener is initialized, and the parent component is bound to the event on the current component
    initEvents(vm)
    // Compile the VM render initialization
    // $slots/$scopedSlots/_c/$createElement/$attrs/$listeners
    initRender(vm)
    // beforeCreate Life hook callback
    callHook(vm, 'beforeCreate')
    // Inject the inject member into the VM
    initInjections(vm)
    // resolve injections before data/props
    // Initialize _props/methods/_data/computed/watch for the VM
    initState(vm)
    // Initialize provide
    initProvide(vm)
    // resolve provide after data/props
    // Created life hook callback
    callHook(vm, 'created')
    /* istanbul ignore if */
    if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }
    // If el is not provided, call $mount()
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}
Copy the code

Data response principle

Data-responsive and bidirectional binding mechanisms are used as a cornerstone of data-driven development. Data responsive means that the view is automatically updated when the data changes, without manipulating the DOM.

Solve the following problem by looking at the source code

  • Vm. MSG = {count: 0};
  • Vm. Arr [0] = 4, assign an array element, will the view update?
  • Vm.arr. length = 0, change the length of the array, will the view be updated?
  • Vm.arr.push (4), will the view be updated?

Entry for responsive processing

The whole process of reactive processing is quite complicated, so let’s start with

  • src\core\instance\init.js
    • InitState (VM) Initializes the VM state
    • The _data, _props, and methods classes are initialized
  • src\core\instance\state.js
// Initialize the data
if (opts.data) {
  initData(vm)
} else {
  // The entry for responsive processing
  observe(vm._data = {}, true /* asRootData */)}Copy the code
  • initData(vm)
    • Initialization of VM data
function initData (vm: Component) {
  let data = vm.$options.data
  // Initialize _data. Data is a function in the component. Calling the function returns the result
  // Otherwise, return data directly
  data = vm._data = typeof data === 'function'? GetData (data, vm) : data | | {}...// proxy data on instance
  // Get all attributes in data
  const keys = Object.keys(data)
  // Props/methods
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  // Determine whether the member on data has the same name as props/methods...// observe data
  // Data response processing
  observe(data, true /* asRootData */)}Copy the code
  • src\core\observer\index.js
    • observe(value, asRootData)
    • Responsible for creating an observer instance for each value of type Object
export function observe (value: any, asRootData: ? boolean) :Observer | void {
  // Check whether value is an object
  if(! isObject(value) || valueinstanceof VNode) {
    return
  }
  let ob: Observer | void
  End if value has an __ob__(observer object) attribute
  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 ) {// Create an Observer object
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++  
  }
  return ob
}
Copy the code

Observer

  • src\core\observer\index.js
    • Reactive processing of the object
    • Do reactive processing of the array
export class Observer {
  // Object to be observed
  value: any;
  // Dependent objects
  dep: Dep;
  // Instance counter
  vmCount: number; // number of vms that have this object as root $data

  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    // Initialize the instance vmCount to 0
    this.vmCount = 0
    // Mount the instance to the observed object's __ob__ attribute
    def(value, '__ob__'.this)
    if (Array.isArray(value)) {
      // Array response processing
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      // Create an observer instance for each object in the array
      this.observeArray(value)
    } else {
      // Reactive processing of objects
      // Compile each property in the object and convert it to a setter/getter
      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) {
    // Get each property of the observed object
    const keys = Object.keys(obj)
    // Iterate over each attribute and set it to responsive data
    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])
    }
  }
}
Copy the code
  • walk(obj)
    • Go through all the properties of obj, call the defineReactive() method for each property and set the getter/setter

DE fi neReactive ()

  • src\core\observer\index.js
  • De fineReactive(obj, key, val, customSetter, shallow)
    • Define a responsive property for an object, each property corresponding to a DEP object
    • If the value of the property is an object, the call to Observe continues
    • If a new value is assigned to the property, the call to observe continues
    • Send notifications if data is updated

Object reactive processing

// Define a responsive property for an object
/** * Define a reactive property on an Object. */ 
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function, shallow? : boolean) {
  // Create dependent object instances for each attribute
  const dep = new Dep()
  // Get the property descriptor object for obj
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }
  // Provides predefined accessor functions
  // 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]
  }
  // Determine whether to recursively observe the child object, and turn the child object properties into getters/setters, returning the child observation object
  letchildOb = ! shallow && observe(val)Object.defineProperty(obj, key, {
    enumerable: true.configurable: true.get: function reactiveGetter () {
      // Value equals the return value of the getter call if a predefined getter exists
      // Otherwise, give the attribute value directly
      const value = getter ? getter.call(obj) : val
      // Create a dependency if the current dependency target, a watcher object, exists
      if (Dep.target) {
        // dep() adds mutual dependencies
        // One component corresponds to one watcher object
        // One watcher will correspond to multiple DEPs (many properties to observe)
        // We can manually create multiple watcher to listen for one property change. One DEP can correspond to multiple watcher
        dep.depend()
        // If the subobject of observation exists, establish dependencies between the subobjects
        if (childOb) {
          childOb.dep.depend()
          // If the property is an array, the special treatment is to collect array object dependencies
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      // Return the attribute value
      return value
    },
    set: function reactiveSetter (newVal) {
      // Value equals the return value of the getter call if a predefined getter exists
      // Otherwise, give the attribute value directly
      const value = getter ? getter.call(obj) : val
      // If the new value is equal to the old value or the old value is null, no execution is performed
      /* 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()
      }
      Return if there is no setter
      // #7981: for accessor properties without setter
      if(getter && ! setter)return
      // Call if the predefined setter exists, otherwise update the new value directly
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      // If the new value is an object, observe the child object and return the child observer objectchildOb = ! shallow && observe(newVal)// Publish the change notification
      dep.notify()
    }
  })
}
Copy the code

Array reactive processing

  • The constructor for the Observer
// Array response processing
if (Array.isArray(value)) {
  if (hasProto) {
    protoAugment(value, arrayMethods)
  } else {
    copyAugment(value, arrayMethods, arrayKeys)
  }
  // Create an observer instance for each object in the array
  this.observeArray(value)
} else {
  // Compile each property in the object and convert it to a setter/getter
  this.walk(value)
}

function protoAugment (target, src: Object) {
  /* eslint-disable no-proto */
  target.__proto__ = src
  /* eslint-enable no-proto */
}

function copyAugment (target: Object, src: Object, keys: Array<string>) {
  for (let i = 0, l = keys.length; i < l; i++) {
    const key = keys[i]
    def(target, key, src[key])
  }
}
Copy the code
  • A method that modifies data in an array
    • src\core\observer\array.js
const arrayProto = Array.prototype
// Clone the array prototype
export const arrayMethods = Object.create(arrayProto)
// Methods to modify the elements of an array
const methodsToPatch = [
  'push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse'
]

/** * Intercept mutating methods and emit events */
methodsToPatch.forEach(function (method) {
  // cache original method
  // Save the original array method
  const original = arrayProto[method]
  // Call object.defineProperty () to redefine the method that modifies the array
  def(arrayMethods, method, function mutator (. args) {
    // Execute the original method of the array
    const result = original.apply(this, args)
    // Get the ob object of the array object
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    // For the inserted new element, the iterated array elements are set to responsive data
    if (inserted) ob.observeArray(inserted)
    // notify change
    // The method that modifies the array is called, and the array's OB object is called to send the notification
    ob.dep.notify()
    return result
  })
})
Copy the code

Dep class

  • src\core\observer\dep.js
    • Dependent objects
    • Record the Watcher object
    • Depend () — the deP of the watcher record
    • notice
  1. Create a dep object in the getter of defineReactive() and determine if dep.target has a value. Call dep.depend()
  2. Addde.depend () calls dep.target.adddep (this) internally, which is watcher’s addDep() method. It calls dep.addsub (this) internally, which takes the watcher object, Add to dep.subs.push(watcher), that is, add subscribers to dep’s subs array, and call the update() method on the watcher object when the data changes
  3. When was dep. target set? Debug observation through a simple case. When the mountComponent() method is called, the render Watcher object is created and the get() method in watcher is executed
  4. The get() method internally calls pushTarget(this) to push the current dep. target = watcher and the current watcher on the stack. After processing the watcher of the child component, the watcher corresponding to the parent component is removed from the stack and the operation continues
  5. Dep. Target is used to store the watcher currently in use. Only one watcher can be used at a time
// Dep is an observable to which multiple instructions can subscribe
/** * A dep is an observable that can have multiple * directives subscribing to it. */
export default class Dep {
  // Static property, watcher object
  statictarget: ? Watcher;// Dep Instance Id
  id: number;
  // Watcher object/subscriber array corresponding to the dep instance
  subs: Array<Watcher>;

  constructor () {
    this.id = uid++
    this.subs = []
  }

  // Add a new subscriber watcher object
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }

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

  // Create dependencies between the observed objects and the watcher
  depend () {
    if (Dep.target) {
      // If the target exists, add the dep object to the watcher dependency
      Dep.target.addDep(this)}}// Issue a notification
  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)
    }
    // Call each subscriber's update method to implement the update
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}
// dep. target is used to store the watcher currently in use
// Only one watcher can be used at a time
// 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 = []
// Push the stack and assign the current watcher to dep.target
export function pushTarget (target: ? Watcher) {
  targetStack.push(target)
  Dep.target = target
}

export function popTarget () {
  // Exit the stack
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]}Copy the code

Watcher class

  • There are three types of Watcher: Computed Watcher, user Watcher, and render Watcher
  • Render Watcher creation timing
    • /src/core/instance/lifecycle.js
export function mountComponent (vm: Component, el: ? Element, hydrating? : boolean) :Component {
  vm.$el = el
  if(! vm.$options.render) { vm.$options.render = createEmptyVNodeif(process.env.NODE_ENV ! = ='production') {
      /* istanbul ignore if */
      if ((vm.$options.template && vm.$options.template.charAt(0)! = =The '#') ||
        vm.$options.el || el) {
        warn(
          'You are using the runtime-only build of Vue where the template ' +
          'compiler is not available. Either pre-compile the templates into ' +
          'render functions, or use the compiler-included build.',
          vm
        )
      } else {
        warn(
          'Failed to mount component: template or render function not defined.',
          vm
        )
      }
    }
  }
  callHook(vm, 'beforeMount')

  let updateComponent
  /* istanbul ignore if */
  if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
    updateComponent = () = > {
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${id}`
      const endTag = `vue-perf-end:${id}`

      mark(startTag)
      const vnode = vm._render()
      mark(endTag)
      measure(`vue ${name} render`, startTag, endTag)

      mark(startTag)
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`vue ${name} patch`, startTag, endTag)
    }
  } else {
    updateComponent = () = > {
      vm._update(vm._render(), hydrating)
    }
  }

  // Create render Watcher, expOrFn as updateComponent
  // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  new Watcher(vm, updateComponent, noop, {
    before () {
      if(vm._isMounted && ! vm._isDestroyed) { callHook(vm,'beforeUpdate')}}},true /* isRenderWatcher */)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')}return vm
}
Copy the code
  • Render in the mountComponent function created by Wacher in the location lifecycle. Js
  • The constructor of Wacher is initialized, handles the expOrFn (render watcher and listener handle differently)
  • Call this.get(), which calls pushTarget() and then this.getter.call(VM, VM) (updateComponent for rendering wacher), If it’s a user, wacher gets the value of the property.
  • Dep calls notify() when the data is updated, and notify() calls wacher’s update() method
  • Call queueWatcher() from update()
  • QueueWatcher () is a core method of removal of duplicate operations, calling flushSchedulerQueue() to refresh the queue and execute watcher
  • Ordered wacher in flushSchedulerQueue(), traversed all Wacher, if there is before, trigger life cycle hook function beforeUpdate, execute wacher.run(), which calls this.get() internally, Then call this.cb() (rendering wacher’s CB is NOOP)
  • End of the process

conclusion

Instance method/data

vm.$set

function

Add a property to the responsive object and ensure that the new property is also responsive and triggers the view update. It must be used for object type to add new attributes to response, because the Vue cannot detect common new properties (such as this. MyObject. NewProperty = ‘hi’)

Note: The object cannot be a Vue instance, or the root data object of a Vue instance.

The sample

vm.$set(obj, 'foo'.'test')
Copy the code

Define the location

  • Vue.set()
    • global-api/index.js
 // Static method set/delete/nextTick
 Vue.set = set
 Vue.delete = del
 Vue.nextTick = nextTick
Copy the code
  • vm.$set()
    • instance/index.js
$data/$props/$set/$delete/$watch
// instance/state.js
stateMixin(Vue)

// instance/state.js
Vue.prototype.$set = set
Vue.prototype.$delete = del
Copy the code

The source code

  • The set () method
    • observer/index.js
export function set (target: Array<any> | Object, key: any, val: any) :any {
  if(process.env.NODE_ENV ! = ='production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)}// Check whether target is an object and key is a valid index
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    // Replace the key element with splice
    // Splice performs a reactive processing on array.js
    target.splice(key, 1, val)
    return val
  }
  // If the key already exists in the object
  if (key intarget && ! (keyin Object.prototype)) {
    target[key] = val
    return val
  }
  // Get the observer object in target
  const ob = (target: any).__ob__
  // If target is a vue instance or $data is returned
  if(target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV ! = ='production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    )
    return val
  }
  // If ob does not exist, target is not directly assigned to a reactive object
  if(! ob) { target[key] = valreturn val
  }
  // Set the key to a responsive property
  defineReactive(ob.value, key, val)
  // Send a notification
  ob.dep.notify()
  return val
}
Copy the code

debugging

<div id="app">
  {{ obj.msg }}
  <br>
  {{ obj.foo }}
</div>

<script src=".. /.. /dist/vue.js"></script>
<script>
  const vm = new Vue({
    el: '#app'.data: {
      obj: {
        msg: 'hello set'}}})// Non-responsive data
  // vm.obj.foo = 'test'
  vm.$set(vm.obj, 'foo'.'test')
</script>
Copy the code

Going back to the childOb from de neReactive, set an OB object for each responder. When the call $set is made, the OB object is fetched and a notification is sent via ob.deb.notify ()

vm.$delete

function

Deletes the properties of an object. If the object is reactive, make sure the deletion triggers an update to the view. This method is mainly used to get around the limitation that Vue cannot detect when an attribute is removed, but you should rarely use it.

Note: The target object cannot be a Vue instance or the root data object of a Vue instance.

The sample

vm.$delete(vm.obj, 'msg')
Copy the code

Define the location

  • Vue.delete()
    • global-api/index.js
// Static method set/delete/nextTick
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
Copy the code
  • vm.$delete()
    • instance/index.js
$data/$props/$set/$delete/$watch
stateMixin(Vue)

// instance/state.js
Vue.prototype.$set = set
Vue.prototype.$delete = del
Copy the code

The source code

  • src\core\observer\index.js
export function del (target: Array<any> | Object, key: any) {
  if(process.env.NODE_ENV ! = ='production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`)}// Check whether it is an array and whether the key is valid
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    // Delete the array by splice
    // Splice has done reactive processing
    target.splice(key, 1)
    return
  }
  // Get the target ob object
  const ob = (target: any).__ob__
  // If the target is a Vue instance or $data object, return the target
  if(target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV ! = ='production' && warn(
      'Avoid deleting properties on a Vue instance or its root $data ' +
      '- just set it to null.'
    )
    return
  }
  // If the target object does not have a key attribute, return it directly
  if(! hasOwn(target, key)) {return
  }
  // Delete the attribute
  delete target[key]
  if(! ob) {return
  }
  // Send notification via OB
  ob.dep.notify()
}
Copy the code

vm.$watch

vm.$watch( expOrFn, callback, [options] )

  • function
    • An expression or evaluated property function that observes changes in a Vue instance. The callback function takes new and old values as parameters. The expression accepts only supervised key paths. For more complex expressions, use a function instead.
  • parameter
    • ExpOrFn: Attribute or function in $data to monitor
    • Callback: a function that is executed after data changes
      • Functions: callback functions
      • Object: Has a handler property (string or function), and the corresponding definition in methods if the property is a string
    • Options: Indicates optional options
      • Deep: Boolean type, deep listening
      • Immediate: Indicates the Boolean type. It indicates whether to execute the callback immediately

The sample

const vm = new Vue({
  el: '#app'.data: {
    a: '1'.b: '2'.msg: 'Hello Vue'.user: {
      firstName: 'various ge'.lastName: 'bright'}}})// expOrFn is an expression
vm.$watch('msg'.function (newVal, oldVal) {
  console.log(newVal, oldVal)
})
vm.$watch('user.firstName'.function (newVal, oldVal) { 
  console.log(newVal)
})
// expOrFn is a function
vm.$watch(function () {
  return this.a + this.b
}, function (newVal, oldVal) {
  console.log(newVal)
})
// Deep is true and consumes performance
vm.$watch('user'.function (newVal, oldVal) {
  // In this case newVal is the user object
  console.log(newVal === vm.user)
}, { deep: true })
/ / immediate is true
vm.$watch('msg'.function (newVal, oldVal) {
  console.log(newVal)
}, { immediate: true })
Copy the code

Three types of Watcher objects

  • There are no static methods, because an instance of Vue is used in the $watch method
  • There are three types of Watcher: computed property Watcher, user Watcher (listener), and render Watcher
  • Creation order: compute property Watcher, user Watcher (listener), render Watcher
  • vm.$watch()
    • src\core\instance\state.js

The source code

Vue.prototype.$watch = function (
  expOrFn: string | Function, cb: any, options? :Object
) :Function {
  // Get the Vue instance this
  const vm: Component = this
  if (isPlainObject(cb)) {
    // Execute createWatcher if cb is an object
    return createWatcher(vm, expOrFn, cb, options)
  }
  options = options || {}
  // Mark user watcher
  options.user = true
  // Create user watcher object
  const watcher = new Watcher(vm, expOrFn, cb, options)
  // Judge immediate if true
  if (options.immediate) {
    // Immediately execute the cb callback and pass in the current value
    try {
      cb.call(vm, watcher.value)
    } catch (error) {
      handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)}}// Return the method to cancel listening
  return function unwatchFn () {
    watcher.teardown()
  }
}
Copy the code

debugging

View the watcher creation order

  • Calculate the property watcher

  • User Wacher

  • Rendering wacher

View rendering watcher in action

  • When the data is updated, the de neReactive set method calls deb.notify ()
  • Call watcher’s update()
  • Call queueWatcher() to queue wacher, if already queued, do not add wacher again
  • flushSchedulerQueue()
    • Through nextTick(), flushSchedulerQueue() was called just before the end of the message loop
  • Call wacher. The run ()
    • Call wacher. Get () to get the latest value
    • If it is rendered wacher ends
    • If it’s user watcher, call this.cb()

Asynchronous update queue -nextTick()

  • Vue DOM updates are executed asynchronously and in batches
    • The deferred callback is performed after the next DOM update loop ends. Use this method immediately after modifying the data to get the updated DOM.
  • Vm.$nextTick(function () {/* operate DOM */})

Vm. $nextTick() code demo

<div id="app">
  <p ref="p1">{{ msg }}</p>
</div>

<script src=".. /.. /dist/vue.js"></script>
<script>
  const vm = new Vue({
    el: '#app'.data: {
      msg: 'Hello nextTick'.name: 'Vue.js'.title: 'Title'
    },
    mounted() {
      this.msg = 'Hello World'
      this.name = 'Hello snabbdom'
      this.title = 'Vue.js'
      this.$nextTick(() = > {
        console.log(this.$refs.p1.textContent)
      })
    }
  })
</script>
Copy the code

Define the location

src\core\instance\render.js

Vue.prototype.$nextTick = function (fn: Function) {
  return nextTick(fn, this)}Copy the code

The source code

  • Call vm.$nextTick() manually
  • Execute nextTick() in Watcher’s queueWatcher
  • src\core\util\next-tick.js
let timerFunc

// The nextTick behavior leverages the microtask queue, which can be accessed
// via either native Promise.then or MutationObserver.
// MutationObserver has wider support, however it is seriously bugged in
// UIWebView in iOS >= 9.3.3 when triggered in touch event handlers
// completely stops working after triggering a few times... so, if native
// Promise is available, we will use it:
/* istanbul ignore next, $flow-disable-line */
if (typeof Promise! = ='undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () = > {
    p.then(flushCallbacks)
    // In problematic UIWebViews, Promise.then doesn't completely break, but
    // it can get stuck in a weird state where callbacks are pushed into the
    // microtask queue but the queue isn't being flushed, until the browser
    // needs to do some other work, e.g. handle a timer. Therefore we can
    // "force" the microtask queue to be flushed by adding an empty timer.
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if(! isIE &&typeofMutationObserver ! = ='undefined' && (
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  // Use MutationObserver where native Promise is not available,
  PhantomJS, iOS7, Android 4.4
  // (#6466 MutationObserver is unreliable in IE11)
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () = > {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
} else if (typeofsetImmediate ! = ='undefined' && isNative(setImmediate)) {
  // Fallback to setImmediate.
  // Technically it leverages the (macro) task queue,
  // but it is still a better choice than setTimeout.
  timerFunc = () = > {
    setImmediate(flushCallbacks)
  }
} else {
  // Fallback to setTimeout.
  timerFunc = () = > {
    setTimeout(flushCallbacks, 0)}}export function nextTick (cb? :Function, ctx? :Object) {
  let _resolve
  // Add the cb exception to the callbacks array
  callbacks.push(() = > {
    if (cb) {
      try {
        / / call the cb ()
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')}}else if (_resolve) {
      _resolve(ctx)
    }
  })
  if(! pending) { pending =true
    / / call
    timerFunc()
  }
  // $flow-disable-line
  if(! cb &&typeof Promise! = ='undefined') {
    // Return the promise object
    return new Promise(resolve= > {
      _resolve = resolve
    })
  }
}
Copy the code