Vue is a bidirectional data binding framework, data driven is its soul, its implementation principle is known to be Object. DefineProperty method implementation get, set rewrite, but it is too far-fetched. This article gives a general overview of his implementation

  • Using the vue
  • Analysis of the Object. DefineProperty
  • Simple source code analysis
    • Start from scratch
    • Data driven section – Observer
    • Vue is mounted to dom
    • Outlining the Watcher
    • Look at the big picture
  • Analysis is carried out through cases
    • Vue data driven premise
    • What you see may not be true
    • What you see is not necessarily true
  • Matters needing attention
  • Additional discussion

Using the vue

Take a very simple chestnut

# html
<div id="#app">
  {{msg}}
</div>

# script
<script>
new Vue({
  el: '#app',
  data: {
    msg: 'hello'
  },
  mounted() {
    setTimeout(() => {
      this.msg = 'hi'
    }, 1000);
  }
})
</script>Copy the code

In the above code, new Vue creates Vue object, el property is the mounted DOM selector, select the DOM id of APP here, data object stores the attributes of all the data response, when one of the attributes changes, it triggers the view rendering, so as to achieve the dynamic response of “data -> view”;

In the example, MSG starts with Hello, so the page renders as Hello, and after a second, MSG changes to Hi, triggering the view rendering, and we see hello changes to Li. So next from this simple chestnut to explain vUE data driven.

Analysis of the Object. DefineProperty

How does vUE implement two-way data binding? Object. DefineProperty is implemented, so let’s focus directly on Object.defineProperty

Here is the code

functionDefineReactive (obj, key, val, customSetter, shallow) {var dep = new dep (); var property = Object.getOwnPropertyDescriptor(obj, key);if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  var getter = property && property.get;
  var setter = property && property.set;
  if((! getter || setter) && arguments.length === 2) { val = obj[key]; } var childOb = ! shallow && observe(val); Object.defineProperty(obj, key, { enumerable:true,
    configurable: true,
    get: function reactiveGetter() { var value = getter ? getter.call(obj) : val; // Collect dependent objectsif (Dep.target) {
        dep.depend();
        if (childOb) {
          childOb.dep.depend();
          if(Array.isArray(value)) { dependArray(value); }}}return value
    },
    set: function reactiveSetter (newVal) {
      var value = getter ? getter.call(obj) : val;
      /* eslint-disable no-self-compare */
      if(newVal === value || (newVal ! == newVal && value ! == value)) {return
      }
      /* eslint-enable no-self-compare */
      if ("development"! = ='production' && customSetter) {
        customSetter();
      }
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      childOb = !shallow && observe(newVal);
      dep.notify();
    }
  });
}
Copy the code

Vue performs defineReactive on each data attribute for data binding purposes. A few points can be seen from the code:

  1. For each data binding, a new Dep is created. What is the function of a dispatcher? Rely on collection for event distribution;
  2. In the property GET, in addition to getting the value of the current property, it also doesdep.depend()Operation;
  3. What is the purpose of DEP.Depend? Dep target collects dependencies in its own dispatcher. Dep target collects dependencies in its own dispatcher
  4. In the case of a property set, that is, when changing the value of the property, in addition to changing the value, it is executeddep.notify()Operation;
  5. What is the purpose of DEp.notify? Look at the code, it is still very simple, will oneself dispatcher all dependencies trigger update function;

This part is easy to understand. When data’s property get triggers the dispatcher’s dependency collection (dep.Depend), and when data’s property set triggers the dispatcher’s event notification (dep.notify).

Given that Vue’s data binding is a side effect of the above function, it can be concluded that:

  1. When we change a property value, the dispatcher Dep notifies the View layer to update it
  2. The Dep. Target is a dependency of the dispatcher Dep collection and triggers the update function when the property value changes. The update of the View layer is necessarily related to the DEP. target. In other words: Data -> view’s data-driven equivalent to dep.target.update ()

Simple source code analysis

The previous section determined that dep.target. update updates the view when a property value is changed, so with that in mind, this section does a simple source code parsing

Start from scratch

function Vue (options) {
  this._init(options);
}

Vue.prototype._init = function (options) {
  var vm = this;
  callHook(vm, 'beforeCreate');
  initState(vm);
  callHook(vm, 'created');

  if (vm.$options.el) {
    vm.$mount(vm.$options.el); }};function initState (vm) {
  vm._watchers = [];
  var opts = vm.$options;
  if (opts.data) {
    initData(vm);
  } else {
    observe(vm._data = {}, true/* asRootData */); }}function initData (vm) {
  var data = vm.$options.data;
  observe(data, true /* asRootData */);
}

function observe (value, asRootData) {
  if(! isObject(value) || value instanceof VNode) {return
  }
  var ob = new Observer(value);;
  return ob
}
Copy the code

Starting from the beginning, step by step, we found that the data passed in by new Vue eventually entered the New Observer;

Data driven section – Observer

var Observer = function Observer (value) {
  this.value = value;
  this.dep = new Dep();
  this.vmCount = 0;
  def(value, '__ob__', this);
  if (Array.isArray(value)) {
    var augment = hasProto
      ? protoAugment
      : copyAugment;
    augment(value, arrayMethods, arrayKeys);
    this.observeArray(value);
  } else{ this.walk(value); }}; Observer.prototype.walk =function walk (obj) {
  var keys = Object.keys(obj);
  for(var i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]); }};Copy the code

In the Observer constructor, defineReactive is finally performed to define each attribute and is called recursively, treading through all the node attributes of the data object we pass in. Each node is wrapped as an Observer. When data gets, dependencies are collected. Event distribution.

If you look at this, it feels like there’s something missing, like the data ends here, but you don’t understand why the data changes and updates the view, so keep going

Vue is mounted to dom

Looking back at the _init method from scratch, in this method, vm.$mount(vm.$options.el) is finally called, which is where the VM is mounted to the real DOM and the view is rendered, so keep going.

Vue.prototype.$mount = function (
  el,
  hydrating
) {
  returnmountComponent(this, el, hydrating) }; // The actual function to render domfunction mountComponent (
  vm,
  el,
  hydrating
) {
  vm.$el = el;
  callHook(vm, 'beforeMount');

  var updateComponent;
  updateComponent = function() { vm._update(vm._render(), hydrating); }; New Watcher(vm, updateComponent, noop, {before:function before () {
      if (vm._isMounted) {
        callHook(vm, 'beforeUpdate'); }}},true /* isRenderWatcher */);
  hydrating = false;
  if (vm.$vnode == null) {
    vm._isMounted = true;
    callHook(vm, 'mounted');
  }
  return vm
}
Copy the code

As you can see in the section above, vue’s experience of mounting vue objects to the real DOM ends with a new Watcher and a callback to vm._update(vm._render(), hydrating). As the name implies, vue updates the view. This article focuses on the data-driven part.

Q: Why is New Watcher a data-driven journey? What does Watcher do?

Outlining the Watcher

If Object.defineProperty is vue’s data-driven soul, Watcher is its skeleton.

Var Watcher = var Watcher =functionWatcher ( vm, expOrFn, cb, options, isRenderWatcher ) { this.cb = cb; this.deps = []; this.newDeps = []; // Calculate the properties to goif
  if (this.computed) {
    this.value = undefined;
    this.dep = new Dep();
  } else{ this.value = this.get(); }}; Watcher.prototype.get =function get () {
  pushTarget(this);
  var value;
  var vm = this.vm;
  try {
    value = this.getter.call(vm, vm);
  } catch (e) {
    if (this.user) {
      handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\" "));
    } else {
      throw e
    }
  } finally {
    popTarget();
    this.cleanupDeps();
  }
  return value
};Copy the code

PushTarget (this); pushTarget(this); pushTarget(this); pushTarget(this)

function pushTarget (_target) {
  if (Dep.target) { targetStack.push(Dep.target); }
  Dep.target = _target;
}Copy the code

PushTarget assigns the Watcher Object passed in to dep. target. Remember from object.defineProperty that dep.target. update is the trigger for updating the view.

See below Dep. Targe. Update

Watcher.prototype.update = function update () {
  var thisThe $1 = this;

  /* istanbul ignore else* /if (this.computed) {
    if (this.dep.subs.length === 0) {
      this.dirty = true;
    } else {
      this.getAndInvoke(function () {
        thisThe $1.dep.notify(); }); }}else if (this.sync) {
    this.run();
  } else{// Update performs queueWatcher(this); }};Copy the code

We see that the update method ends up executing queueWatcher. If we look further, we see that this is actually an update queue. Vue collects all updates from the same microtask and finally executes watcher.run. GetAndInvoke again executes the this.get method.

Watcher’s this.get method was passed in to the dep. target function when the value of the property was changed. Recall from earlier that when Vue is mounted to the real DOM, the callback passed in by New Watcher is updateComponent. In series, the conclusion is reached:

  1. When we get the property, the Dep dispatcher collects the Watcher as a dependency
  2. When we set the property, the Dep dispatcher event is dispatched, causing all the collected dependencies to executethis.get, the view will update.

At this point, is it clear why dispatchers with all attributes collect the updateComponent Watcher to notify updates when they set? If you don’t understand, go to the next analysis

Look at the big picture

  1. When we new Vue, the incoming data will first be wrapped by Vue as an observer, and all get and set actions will be captured and the response will be performed
  2. The vue then mounts the vue object to the real DOM, which is a new Watcher, and executes it once at new Watcherthis.getInitialize the value once against the updateComponent function, which triggers the Vue rendering process
  3. In the vue rendering process, all data need to get the value and assign the value to the real DOM. Therefore, get of all data attributes is triggered, and dep. target is the Watcher of updateComponent. Therefore, all data attribute dispatchers collect this Watcher. When set, the dispatcher notify distributes events. The collected dependent Watcher is notified to update, and all of them execute updateCompoent to update the view

Analysis is carried out through cases

Vue data driven premise

Vue data drive has a prerequisite, not all can be used, the prerequisite is that the attributes declared in data will participate in the data drive, data -> view. Look at the chestnuts

There is the following HTML:

<div id="app">
    <div>{{prev}}{{next}}</div>
</div>Copy the code

Js:

new Vue({
  el: "#app",
  data: {
    prev: 'hello',},created() {},mounted() {
    this.next = 'world'; }})Copy the code

What is the result of the page rendering?

A: hello.

Why is this.next assigned and not rendered into the view? Since it does not participate in data-driven observers, remember that Vue wraps the incoming data object in a deep loop as an observer. Here, the next property is not called an observer, so it does not trigger a view update.

What you see may not be true

We can see only Hello in the view, but does the data really have hello? Not necessarily. Look at the chestnuts.

new Vue({
  el: "#app",
  data: {
    prev: 'hello',},created() {},mounted() {
    this.next = 'world';
    setTimeout(() => {
      this.prev = 'hi'; }, 1000); }})Copy the code

After rendering the page for 1 second, change the prev value to HI. What happens to the page?

A: hi world

As you can see here, although the next assignment does not cause the view to update, the data does change successfully. When the prev changes, update is triggered, which updates the view, and next has a value, which is displayed in the View. This is one of the problems that many beginners encounter when they click on something else, but assignment is not shown.

What you see is not necessarily true

Or according to the first chestnut extended a case as follows:

new Vue({
  el: "#app",
  data: {
    prev: 'hello',},created() {
    this.next = 'world';
  },
  mounted() {
    setTimeout(() => {
      this.next = 'memory'}}}, 1000))Copy the code

If next is added to the Created lifecycle and next is added to the Mounted lifecycle by 1 second, the result will be this.

Answer: Always display HelloWorld

Those of you who already know the vUE instantiation process can probably guess why

When the Created lifecycle is executed, the data property has already been delegated to this, so modifying this.next changes the value of the data object to {prev: ‘hello’, next: ‘world’}, so next is rendered on the page in render

In addition, the data-driven soul step (wrapping the data traversal as an observer) has been completed, so changing the next value after a 1s delay will still not cause the view to update, just like chestnut 2.

Therefore,, write vUE appear above change data view is not updated, first check your code, rather than suspect vue framework problems.

Matters needing attention

  • This article refers to vUE version V2.5.17-beta.0
  • This paper focuses on the data-driven main line, data, computed and other secondary lines are not introduced, so the code posted has been largely deleted
  • The data attribute collects not only the updateComponent Watcher, but also several, such as computed attributes

Additional discussion

Created or Mounted when asynchronously retrieving data and changing the value of data.

I don’t feel like I have any answers to either of them, but of course for code optimizations, it’s better to put them in the created area earlier.

Turn the; www.cnblogs.com/xujiazheng/…