vue2 responsive details

How is the data in data processed?

Each instantiation of a component calls initData and invokes the observe method. The observe method calls new Observer(value) and returns to the ob__ .

Two things are done in new Observer:

  1. Mount the current instance to the of the data__ ob__ Property, this instance is useful later.
  2. It is handled differently according to the data type (array or object)

If object:

Traverse the object attributes horizontally and call defineReactive;

Recursively call the observe method, and stop recursion when the attribute value is not an array or an object

The defineReactive method is commented in detail below:

export function defineReactive(
  obj: Object,
  key: string,
  val: any,
  customSetter ? : ? Function,
  shallow ? : boolean
) {
  const dep = new Dep(); // Closures create dependent objects; Each object's attribute has its own dep
  // The following is for the object that has passed Defineproperty or object seal Object. Freeze processed data
  const property = Object.getOwnPropertyDescriptor(obj, key);
  // If configurable is false, select object again Defineproperty (obj, key) will report an error and will not succeed; So go straight back
  // So you can use object Free / seal optimizes performance.
  if (property && property.configurable === false) {
  const getter = property && property.get;
  const setter = property && property.set;
  // Normally, the data getter and setter do not exist, and the middle note in new Observer() has only two parameters.
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]; // In other words, this line of code will generally be executed
  // Generally, share is false; childOb is the returned Observer instance, which is stored in the database__ ob__ On properties
  let childOb = !shallow && observe(val);
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      const value = getter ? : val; // getter does not exist, use val directly
      if ( {
        // is a global data. It stores the watcher at the top of the watcher stack (targetStack),
        dep.depend(); // The closure dep collects the current watcher; The collection dependency really occurs when the render method is executed (that is, when the virtual dom is generated)
        if (childOb) {
          // val is not an object (not Array or object). Only the observe method will return an Observer instance, otherwise it will return undefined

          // Why execute childob here What about dep.depend()?
          // The effect of this is: mount on the object__ ob__ The dep object adds the pre point watcher to the dependency. This dep is not the same as the closure Dep.
          // The purpose is to:
          // 		1. Target: to think about this$ Set/$del can trigger the component to be rendered again, and save the middle note watcher, then call ob. in $set. dep.notify(); It's used here__ ob__ attribute
          // 		2. For arrays: in the interception of arrays (call the methods such as splice push) to trigger re rendering, call ob Dep.notify() is used here__ ob__ attribute
          if (Array.isArray(value)) {
            // If value is an array, the array program is used in the observe method, and these elements are not used by object Defineproperty is a series of processing (elements are treated as val). Even if the element is object/array, there is no childob A process such as dep.depend() causes this$ Set / $del, array cannot trigger re rendering;
            // Therefore, we call dependArray to process the array, which is used here__ ob__ attribute
      return value;
    set: function reactiveSetter(newVal) {
      // Generally, there is no getter
      const value = getter ? : val;
      // Value unchanged, newval== newVal && value !==  Value should be for NaN
      if (newVal === value || (newVal !== newVal && value !== value)) {
      if (process.env.NODE_ENV !== "production" && customSetter) {
      // Getters and setter s should be paired
      if (getter && !setter) return;
      if (setter) {, newVal);
      } else {
        val = newVal;
      // After resetting the value, you need to re observe and update the closure variable childOB
      childOb = !shallow && observe(newVal);
      // to update

If array:

Modify the of the array__ proto__ Attribute value, pointing to a new object;

function protoAugment (target, src: Object) {
  target.__proto__ = src

Redefine the following methods in this new object:


At the same time, this object's__ proto__ Point to array prototype.

const arrayProto = Array.prototype;
export const arrayMethods = Object.create(arrayProto);

Finally, the code of the project prints the following screenshot on the console

data() {
  return {
    data1: [{
      name: 1

At the same time, observe recursively for each element in the array.

How is the watch option handled?

The usage of watch is generally as follows:

watch: {
  a: function(newVal, oldVal) {
    console.log(newVal, oldVal);
  b: 'someMethod',
  c: {
    handler: function(val, oldVal) {
      /* ... */
    deep: true
  d: {
    handler: 'someMethod',
    immediate: true
  e: [
    function handle2(val, oldVal) {},
      handler: function handle3(val, oldVal) {},
  'e.f': function(val, oldVal) {
    /* ... */

The key codes of watch are listed according to the following process:

-- > initData() // Called when initializing the component. If there is a watch option in the component, call initWatch
  -- > initWatch()
if (Array.isArray(handler)) { // Here is the case of processing arrays, that is, the case of e above
  for (let i = 0; i < handler.length; i++) {
    createWatcher(vm, key, handler[i])
} else {
  createWatcher(vm, key, handler)
-- > createWatcher()
if (isPlainObject(handler)) { // Compatible C (object)
  options = handler
  handler = handler.handler
// In the case of b string, you need to have corresponding data on vm
if (typeof handler === 'string') {
  handler = vm[handler]
// The default is a (function)
vm.$watch(expOrFn, handler, options)
  -- > vm.$watch()
options.user = true // Add parameter options User = true, handle the case of immediate:true
const watcher = new Watcher(vm, expOrFn, cb, options)
  -- > new Watcher() // Create a watcher
this.getter = parsePath(expOrFn) // This getter method is mainly to get the variable of watch, trigger dependency collection in the process of getting, and add the current watcher to the dependency
this.value = this.lazy // The option lazy is false
  undefined :
  this.get() // Call the get method directly in the constructor
  -- > watcher.get()
pushTarget(this) // Push the current watcher to the top of the stack
value =, vm) // At this time, the current watcher is included in the dependency of the watch variable
  -- > watcher.getter() // Place dependent on collection

When the variable of watch changes, the run method of watcher will be executed:

run() {
  if ( {
    const value = this.get()
    // In the case of rendering the watcher, value is undefined
    // In the case of custom watcher, value is the listening value
    if (
      value !== this.value || // When the value of watch changes
      isObject(value) ||
    ) {
      // set new value
      const oldValue = this.value
      this.value = value
      // The user of the custom watcher is true, and cb is the handler
      if (this.user) {
        try {
, value, oldValue)
        } catch (e) {
          handleError(e, this.vm, `callback for watcher "${this.expression}"`)
      } else {, value, oldValue)

Value in the above code== this. Value and deep are easy to understand, and the change of value triggers the handler;

But what does isObject(value) correspond to? Just look at the following example:

data() {
  return {
    data1: [{
      name: 1
computed: {
  data2() {
    let value = this.data1[0].name //
    return this.data1 // The returned is an array, so data2 consistency is unchanged
watch: {
  data2: function() {
    // Although the value of data2 has always been data1, it has not changed; However, because data2 satisfies isObject, the handler can still be triggered
    // It is conceivable that we can take the initiative to obtain a data attribute in calculated to trigger the watch, and avoid using deep in the watch
    // However, this is not appropriate, because you can directly use the example of 'e.f' instead;
    // Therefore, it should be determined according to the actual situation
created() {
  setInterval(() => {
  }, 2000)

How is computed data processed?

computed first creates a watcher, which is different from rendering watchers and custom watchers: the get method will not be executed during initialization, that is, dependency collection will not be done.

In addition, use object Defineproperty defines the get method:

function createComputedGetter(key) {
  return function computedGetter() {
    const watcher = this._computedWatchers && this._computedWatchers[key];
    if (watcher) {
      // lazy=true, then dirty is also true
      if (watcher.dirty) {
        watcher.evaluate(); // Add the calculated watcher to the dependencies of all variables involved;
      if ( {
        watcher.depend(); // Actively call the dependent method; If this computed is used for page rendering, the render watcher will be added to the dependency of the variable
      return watcher.value;

When the computed data is in the initial rendering:

-- > render // Render
-- > computedGetter // computed    Object.defineProperty defines the get method:
-- > watcher.evaluate() // Calculate the watcher value
-- > watcher.get()
-- > pushTarget(this) // Push the current computed watcher to the top of the watcher stack
-- > watcher.getter() // getter method is the method defined by computed in the component, and dependency collection will be done during execution
-- > dep.depend() // Add the current computed watcher to the dependency of the variable
-- > popTarget() // Put the current 	 The computed watcher removes the stack. Generally speaking, the rendered Watcher will be pushed to the top of the stack
-- > cleanupDeps() // Clear redundant watcher s and DEPs
-- > watcher.depend() // This is a special place for computed. If the calculated depends on the data in the variable data, this step adds the current watcher to the dependence of the variable; Why do you do this? My guess is that the purpose of computed is to be a bridge for processing data. The real response is still the data that needs to be implemented in the data.

When the dependent data in computed changes, the following process will be followed:

-- > watcher.update() // This is a computed watcher, where lazy is true, so it won't go down
if (this.lazy) {
  this.dirty = true
-- > watcher.update() // The process after rendering watcher render is the same as the first rendering

How to render the watcher?

Render watcher is relatively easy to understand

New watcher - > watcher get-> pushTarget(this) ->watcher. getter()-> render -> Object. defineProperty(get) -dep.depend()-> popTarget()->watcher. cleanupDeps()

watcher.getter is the following method:

updateComponent = () => {
   vm._update(vm._render(), hydrating)

Dependency cleanup in Vue?

The source code is in Vue / SRC / core / observer / watcher JS;

It should be noted that vue has a scheme to clear watcher and dep; Dependency collection in vue is not one-time. Re render will trigger a new dependency collection. At this time, invalid watchers and DEPs will be removed, so as to avoid invalid updates.

Calculated as follows: once temp < = 0.5, changing b will no longer print temp; The reason is that when temp < 0.5, this b will not put the current a into its own dep, so it will not trigger the calculated monitor again

data() {
  return {
    b: 1
computed: {
  a() {
    var temp = Math.random()
    console.log(temp); // Once a < = 0.5, temp will not be printed
    if (temp > 0.5) {
      return this.b
    } else {
      return 1
created() {
  setTimeout(() => {
  }, 5000)

There are mainly watchers The cleanupDeps method in JS is processing;

cleanupDeps() {
  let i = this.deps.length
  // Traverse the last saved deps
  while (i--) { // i--
    const dep = this.deps[i]
    // newDepIds is the new depId set added in this dependency collection
    // Clear dep not in newDepIds
    if (!this.newDepIds.has( {
  // depIds is a es6 set set that refers to class data
  // newDepIds is similar to a temporary storage place. Finally, you need to save the data to depIds. Left hand to right hand trick
  // newDeps and newDepIds are the same
  let tmp = this.depIds
  this.depIds = this.newDepIds
  this.newDepIds = tmp
  tmp = this.deps
  this.deps = this.newDeps
  this.newDeps = tmp
  this.newDeps.length = 0

vuex response principle?

Depending on the response principle of Vue itself, the collection of dependencies is completed in the render process by constructing a Vue instance.

store._vm = new Vue({
  data: {
    $$state: state, // Custom state data

Vue router reactive processing?

  beforeCreate() {
    if (isDef(this.$options.router)) {
      this._routerRoot = this;
      this._router = this.$options.router;
      // The key is this line of code_ The route attribute is processed responsively
    } else {
      this._routerRoot =
        (this.$parent && this.$parent._routerRoot) || this;
    registerInstance(this, this);
  destroyed() {

Object.defineProperty(Vue.prototype, "$route", {
  get() {
    return this._routerRoot._route;

The above will be triggered when rendering router view

// render method when rendering this component
render() {
  // Dependency collection is triggered when $route is called
  var route = parent.$route;

Posted by d3vilr3d on Tue, 19 Apr 2022 02:22:29 +0930