Vue source analysis - object change detection

When the application is running, the internal state is constantly changing. And for web applications which will directly lead to constantly re-render the page. So how do you determine which specific part of the state to re-render it by change? Before MVVM framework appears, most of the time needed to manually create and maintain contact with the display layer, along with the complexity of applications increase, the internal state and UI ties become complicated, difficult to maintain. It is through the front MVVM framework of the preparation of a generic ViewModel layer, responsible for letting Model layer changes automatically synchronized to the View layer, it is also responsible to let View modified layer sync back Model . Today we take a look at the analysis, when the application of the internal state of change, Vue.js is how do detect the change.

Different Vue.js change detection and React, for React, when the state changes, it does not know exactly which state has changed, only that the state may have changed, and then send a signal to the frame, the inner frame is received after the signal, it will be to find out violent than those DOM nodes need to re-render. For Vue.js, when the state changes, it immediately will know, and know exactly what the state has changed, and if a bound state more dependent on a certain extent, when the state change, will all We rely send a notification binding. However, the finer the particle size is to pay a certain price, the more dependent binding of each state, depending on the track memory consumption is even greater, the situation improved a lot after the introduction of virtual Vue.js 2.0 DOM .

One problem: how to listen for a change object

In JS, the means at our listener objects no more than two changes: Object.defineProperty and ES6 the Proxy . Because ES6 support is not ideal, Vue.js 2.0 is used in the first method, but in the new version should give up Object.defineProperty choose Proxy . Because Object.defineProperty is obvious defects will be mentioned later. First, we can use the following function to encapsulate Object.defineProperty :

function defineReactive(data, key, val) {
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
    get: function() {
      return val
    },
    set: function(newVal) {
      if (val === newVal) return
      val = newVal
    }
  })
}
复制代码

At this time, think about what the real purpose is to observe the data?

The purpose is that when data changes, you can notify those places have used the data. So we need to collect rely, in the notification to these dependencies so that when data changes. Obviously, you can rely collected in getter, the trigger reliance in the setter.

Second problem: Where to rely collection

First, we can rely on a common package type, in the Vue.js Dep categories:

class Dep {
  constructor () {
    this.subs = []
  }

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

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

  depend () {
    if (somethingToWatch) {
      this.addSub(somethingToWatch)
    }
  }

  notify () {
    const subs = this.subs.slice()
    for (let sub of subs) {
      sub.update()
    }
  }
}

function remove (arr, items) {
  if (arr.length) {
    const index = arr.indexOf(item)
    if (index > -1) {
      return arr.splice(index, 1)
    }
  }
}
复制代码

Then transform the look defineReactive :

function defineReactive(data, key, val) {
  let dep = new Dep()
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
    get: function() {
      dep.depend()
      return val
    },
    set: function(newVal) {
      if (val === newVal) return
      val = newVal
      dep.notify()
    }
  })
}
复制代码

Question three: Who is dependent

In the above Dep appeared class somethingToWatch , it is clear that the object is exactly what we needed after the data change notification. In Vue.js, we notice there are many places to use data, such as templates, or customize one of the Watch . At this time, it is an abstract class needs to cover these cases, Vue.js in this class Watcher :

class Watcher {
  constructor (vm, expOrFn, cb) {
    this.vm = vm
    this.getter = parsePath(expOrFn)
    this.cb = cb
    this.value = this.get()
  }

  get () {
    somethingToWatch = this
    let value = this.getter.call(this.vm, this.vm)
    somethingToWatch = undefined
    return value
  }

  update() {
    const oldValue = this.value
    this.value = this.get()
    this.cb.call(this.vm, this.value, oldValue)
  }
}
复制代码

In this code, when Watcher initialization, calls the get method, and in the get method, we will somethingToWatch points to the current Watcher instance, when we get value will trigger when the value of the data getter , thereby automatically Watcher instance to Dep in. When the data changes, Dep triggers all the dependencies of dependencies list update method, which is the Watcher in the update method, Watcher in the update method.

You can see a vm. $ Watch ( 'abc' , (oldVal, newVal) => {}) example, when abc when changes, the callback function to be called later. First, we must resolve abc , in using Vue.js parsePath to complete:

const bailRE = /^\w+.$/
function parsePath (path) {
  if (bailRE.test(path)) {
    return
  }
  const segments = path.split('.')
  return function (obj) {
    for (let segment of segments) {
      if (!obj) return
      obj = obj[segment]
    }
  }
  return obj
}
复制代码

At this point, we got the abc of this property, and the Watcher 's get access to its methods, its trigger getter , so that the current Watcher add an instance to abc -dependent in the list. And when abc time changes, the callback function will Watcher in the update method is called in.

Question 4: how to detect all key

We can see the use of Object.defineProperty can detect changes in the value of a property of the object, but we need to listen to all attribute values (including sub-attributes) changes. Now packaged Observer class to achieve this:

/**
 * Observer 类会被附加到每一个被侦测的 object上。
 * 一旦加上,会将 object 所有的属性都转化为 getter/setter 的形式
 * 来收集属性依赖,并且在属性变化时通知这些依赖
 */
class Observer {
  constructor(value) {
    this.value = value

    if (!Array.isArray(value)) {
      this.walk(value)
    }
  }

  /**
   * walk 将每一个属性都转为 getter/setter
   */
  walk (obj) {
    const keys = Object.keys(obj)
    for (let key of keys) {
      defineReactive(obj, key, obj[key])
    }
  }
}

function defineReactive(data, key, val) {
  // 新增,用于递归子属性
  if (typeof val == 'object') {
    new Observer(val)
  }
  ...
}
复制代码

By defining the Observer class, we will be a normal object conversion for the detection of the object . Then determine the type of data, only Object type of data will be called walk method to each attribute into a getter / setter mode. After transformation and defineReactive plus some new code for determining when the sub-attributes Object , the sub-property called new new the Observer (Val) , to form a recursion. So we put all attributes become getter / setter in the form of a.

Five questions: Object.defineProperty bring hidden problems

Consideration a scenario where we have a Vue example, define Data: {A: {}} , and defines a method of action () = {this.a.name 'Jay'} , if the called action method can listening to the object a change? The answer is no, because the initialization process, A and did not name the property, that is to say in the walk method, we do not have the name property into getter / setter mode, it can not detect this change, not to We rely send notifications.

As another example, we are in action delete a property value already existing, Object.defineProperty can only judge whether a data is modified, it is also unable to detect the change. To solve these two problems, we can call vm. set * and * vm.The Delete these two API.

Object of detecting changes in the process of combing

Data by Observe tracked changes convert getter / setter form. When the outside world by Watcher read data that will trigger getter thus Watcher added to the dependencies. When the data changes, it will trigger setter , so as to Dep send notification of dependence. Watcher receipt of the notification, notification is sent to the outside world, the outside world after the receipt of the notification, may trigger a view update, the user may also trigger callback function.

This series of articles are easy to understand Vue.js study notes, there is little interest partner can read a book ha.

Reproduced in: https: //juejin.im/post/5d09f8875188252f921b13b7

Guess you like

Origin blog.csdn.net/weixin_34217711/article/details/93173522