[Front-end dictionary] Vue's responsive principle is actually very easy to understand

Preface

This is the third in a series of ten Vue articles. In this article, we will talk about one of the core functions of Vue: the principle of responsiveness.

How to understand responsive

It can be understood like this: when a state changes, the transactions related to this state also change immediately. From the front-end point of view, the related DOM changes after the data state changes. The data model is just a normal JavaScript object. And when you modify them, the view will be updated.

Throw a question

Let's take a look at our common writing in Vue:

<div id="app" @click="changeNum">

  {{ num }}

</div>

var app = new Vue({

  el: '#app',

  data: {

    num: 1

  },

  methods: {

    changeNum() {

      this.num = 2

    }

  }

})

This way of writing is very common, but you have considered why the view will be updated when this.num=2 is executed? Through this article, I strive to make this point clear.

If we don't use Vue, how should we achieve it?

My first thought was to implement it like this:

let data = {

  num: 1

};

Object.defineProperty(data, 'num',{

  value: value,

  set: function( newVal ){

    document.getElementById('app').value = newVal;

  }

});

input.addEventListener('input', function(){

  data.num = 2;

});

In this way, you can roughly click on the element and automatically update the view.

Here we need to manipulate the object's accessor properties through Object.defineProperty. When the data changes are monitored, the relevant DOM is operated.

A common pattern is used here-the publish/subscribe pattern.

I drew a general flow chart to illustrate the observer model and the publish/subscribe model. as follows:

[Front-end dictionary] Vue's responsive principle is actually very easy to understand

Careful students will find that the difference between my rough process and using Vue is that I need to manipulate the DOM to re-render it myself.

If we use Vue, this step is handled by the code inside Vue. This is why we don't need to manually manipulate the DOM when using Vue.

About Object.defineProperty I mentioned in the previous article, so I won't repeat it here.

How does Vue achieve responsiveness

We know that an object can manipulate its accessor properties through Object.defineProperty, that is, the object has getter and setter methods. This is the cornerstone of achieving responsiveness.

First look at a very intuitive flowchart:
[Front-end dictionary] Vue's responsive principle is actually very easy to understand

initData method

When Vue is initialized, its _init() method will call and execute the initState(vm) method. The initState method mainly initializes properties such as props, methods, data, computed and wathcer.

Here we will do a more detailed analysis of the process of data initialization.

function initData (vm: Component) {

  let data = vm.$options.data

  data = vm._data = typeof data === 'function'

    ? getData(data, vm)

    : data || {}

  if (!isPlainObject(data)) {

    ......

  }

  // 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 (props && hasOwn(props, key)) {

      ......

    } else if (!isReserved(key)) {

      proxy(vm, `_data`, key)

    }

  }

  // observe data

  observe(data, true /* asRootData */)

}

The main process of initData to initialize data is to do two things:

  1. Proxy each value vm._data.[key] to vm.[key] through proxy;

  2. The observe method is called to observe the changes of the entire data, and the data becomes responsive (observable), and the corresponding attributes in the defined data return function can be accessed through vm._data.[key].

Data Hijacking — Observe

Through this method, all the attributes under data become responsive (observable).

// 给对象的属性添加 getter 和 setter,用于依赖收集和发布更新

export class Observer {

  value: any;

  dep: Dep;  

  vmCount: number; 

  constructor (value: any) {

    this.value = value

    // 实例化 Dep 对象

    this.dep = new Dep()

    this.vmCount = 0

    // 把自身实例添加到数据对象 value 的 __ob__ 属性上

    def(value, '__ob__', this)

    // value 是否为数组的不同调用

    if (Array.isArray(value)) {

      const augment = hasProto ? protoAugment : copyAugment

      augment(value, arrayMethods, arrayKeys)

      this.observeArray(value)

    } else {

      this.walk(value)

    }

  }

  // 取出所有属性遍历

  walk (obj: Object) {

    const keys = Object.keys(obj)

    for (let i = 0; i < keys.length; i++) {

      defineReactive(obj, keys[i])

    }

  }

  observeArray (items: Array<any>) {

    for (let i = 0, l = items.length; i < l; i++) {

      observe(items[i])

    }

  }

}

Object.defineProperty is encapsulated in the def function, so you console.log(data) will find an additional ob attribute.

defineReactive method to traverse all attributes

// 定义一个响应式对象的具体实现

export function defineReactive (

  obj: Object,

  key: string,

  val: any,

  customSetter?: ?Function,

  shallow?: boolean

) {

  const dep = new Dep()

  ..... // 省略部分兼容代码,但不影响理解

  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

      ..... // 省略部分兼容代码,但不影响理解

      if (setter) {

        setter.call(obj, newVal)

      } else {

        val = newVal

      }

      // 对新的值进行监听

      childOb = !shallow && observe(newVal)

      // 通知所有订阅者,内部调用 watcher 的 update 方法 

      dep.notify()

    }

  })

}

The defineReactive method first initializes an instance of the Dep object, and then recursively calls the observe method on the child object so that all the child properties can also become reactive objects. And call dep related methods in the getter and setter methods of Object.defineProperty.

which is:

  1. The work done by the getter method is dependency collection-dep.depend()

  2. The work done by the setter method is to post updates-dep.notify()

We found that there is a non-negligible relationship with the Dep object. Next we will look at the Dep object. This Dep

Dep of the dispatch center

In the previous article, we mentioned the publish/subscribe model. There is a dispatch center before the publisher and the subscriber. The role played by Dep here is the dispatch center, and its main role is:

  1. Collect subscribers Watcher and add to the watcher list subs

  2. Receive events from the publisher

  3. Notify subscribers of target updates and let subscribers execute their own update method

The detailed code is as follows:

// Dep 构造函数

export default class Dep {

  static target: ?Watcher;

  id: number;

  subs: Array<Watcher>;

  constructor () {

    this.id = uid++

    this.subs = []

  }

  // 向 dep 的观察者列表 subs 添加 Watcher

  addSub (sub: Watcher) {

    this.subs.push(sub)

  }

  // 从 dep 的观察者列表 subs 移除 Watcher

  removeSub (sub: Watcher) {

    remove(this.subs, sub)

  }

  // 进行依赖收集

  depend () {

    if (Dep.target) {

      Dep.target.addDep(this)

    }

  }

  // 通知所有订阅者,内部调用 watcher 的 update 方法

  notify () {

    const subs = this.subs.slice()

    for (let i = 0, l = subs.length; i < l; i++) {

      subs[i].update()

    }

  }

}

// Dep.target 是全局唯一的观察者,因为在任何时候只有一个观察者被处理。

Dep.target = null

// 待处理的观察者队列

const targetStack = []

export function pushTarget (_target: ?Watcher) {

  if (Dep.target) targetStack.push(Dep.target)

  Dep.target = _target

}

export function popTarget () {

  Dep.target = targetStack.pop()

}

Dep can be understood as a management of Watcher, and Dep and Watcher are closely related. So we must take a look at the implementation of Watcher.

Subscriber-Watcher

There are many prototype methods defined in Watcher. Here I will only briefly talk about update and get three methods.

  // 为了方便理解,部分兼容代码已被我省去

  get () {

    // 设置需要处理的观察者

    pushTarget(this)

    const vm = this.vm

    let value = this.getter.call(vm, vm)

    // deep 是否为 true 的处理逻辑

    if (this.deep) {

      traverse(value)

    }

    // 将 Dep.target 指向栈顶的观察者,并将他从待处理的观察者队列中移除

    popTarget()

    // 执行依赖清空动作

    this.cleanupDeps()

    return value

  }

  update () {

    if (this.computed) {

      ...

    } else if (this.sync) { 

      // 标记为同步

      this.run()

    } else {      

      // 一般都是走这里,即异步批量更新:nextTick

      queueWatcher(this)

    }

  }

This is probably the case with Vue's reactive process. Those interested can look at the source code.

Finally, we review it through this flowchart:
[Front-end dictionary] Vue's responsive principle is actually very easy to understand

Vue related articles output plan

Recently, friends have always asked me about Vue-related questions, so I will output 9 Vue-related articles next, hoping to help you. I will keep an update in 7 to 10 days.

  1. [Front-end dictionary] Vuex injects the Vue life cycle process (completed)

  2. [Front-end dictionary] The necessary knowledge reserve for learning Vue source code (completed)

  3. [Front-end dictionary] Vue's responsive principle is actually very easy to understand (complete)

  4. [Front-end Dictionary] The process of patching new and old VNodes

  5. [Front-end dictionary] How to develop functional components and upload npm

  6. [Front-end dictionary] Optimize your Vue project from these aspects

  7. [Front-end Dictionary] Talk about the development of front-end routing from the Vue-Router design

  8. [Front-end dictionary] How to use Webpack correctly in the project

  9. [Front-end dictionary] Vue server rendering

  10. [Front-end dictionary] How to choose between Axios and Fetch

I suggest you pay attention to my official account, you can receive the latest articles as soon as possible.
[Front-end dictionary] Vue's responsive principle is actually very easy to understand
If you want to join the group communication, you can also add a smart robot to automatically pull you into the group:

[Front-end dictionary] Vue's responsive principle is actually very easy to understand

Guess you like

Origin blog.51cto.com/15077552/2596430