vue source code analysis series-compute implementation mechanism

Posted by Swerve1000 on Thu, 20 Jun 2019 22:54:08 +0200

Originally, the response of Vue should be the most important. But there are many articles on the Internet. Before looking at the implementation of computer. I'm sure we need to take a good look at how vue's response is implemented. Or they are basically the same thing. Here we recommend some articles about the response of vue.

Simple implementation of vue response

Responsive Handwriting of vue Mu Course

Or take a look at the official website's explanation of responsiveness:

In general. There are three keys to vue's response: watcher,dep,observe;

  1. observe: Traverse the properties in data. Setting up core data hijacking in get,set method

  2. Dep: Each property has its own dep (from message subscription) to customize all observers on that property

  3. watcher: The observer, through dep, monitors the response attributes. After observing the results, actively trigger their own callbacks

Take a look at these three parts of vue2.3 source code. There are still many exquisite designs in the middle. For example, a globally unique Dep.target is the only value at any time. Make sure that only one observer subscribes at the same time. For example, the Watcher will also store relevant subscribers to realize the de-duplication and grouping of the same observer (which is the key to achieving computed), and so on. The id in watcher is also unique. Used for asynchronous updates to start the same subscription at different times. Look carefully and you'll get a lot. I'll sort out all the responsive code the other day.
In the case of understanding the response. Let's look at the implementation of computer. The simplest demo is as follows:


<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<div id="app">
  <div name="test">{{computeA}}</div>

</div>
</body>
<script src="vue.js"></script>
<script type="text/javascript">
  new Vue({
  el: '#app',
  data: function () {
    return {
      firstName: 111,
      lastName: 222
    }
  },
  computed: {
    computeA: function () {
      return this.firstName + ' ' + this.lastName
    }
  },
  created(){
    setTimeout(
      () => {
        this.firstName = 333;
      },1000
    )
  }
})
</script>
</html>

Let's look at what happened from the source point of view:

  1. When initializing an instance to create a response. computed in options is specially processed:

function initComputed (vm, computed) {
  var watchers = vm._computedWatchers = Object.create(null);

  for (var key in computed) {
    var userDef = computed[key];
    var getter = typeof userDef === 'function' ? userDef : userDef.get;
    {
      if (getter === undefined) {
        warn(
          ("No getter function has been defined for computed property \"" + key + "\"."),
          vm
        );
        getter = noop;
      }
    }
    // create internal watcher for the computed property.
    watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions);//Customize a watcher for each computed project

    // component-defined computed properties are already defined on the
    // component prototype. We only need to define computed properties defined
    // at instantiation here.
    if (!(key in vm)) {
      defineComputed(vm, key, userDef);
    } else {
      if (key in vm.$data) {
        warn(("The computed property \"" + key + "\" is already defined in data."), vm);
      } else if (vm.$options.props && key in vm.$options.props) {
        warn(("The computed property \"" + key + "\" is already defined as a prop."), vm);
      }
    }
  

function defineComputed (target, key, userDef) {
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = createComputedGetter(key);
    sharedPropertyDefinition.set = noop;
  } else {
    sharedPropertyDefinition.get = userDef.get
      ? userDef.cache !== false
        ? createComputedGetter(key)
        : userDef.get
      : noop;
    sharedPropertyDefinition.set = userDef.set
      ? userDef.set
      : noop;
  }
  Object.defineProperty(target, key, sharedPropertyDefinition);
}

function createComputedGetter (key) {//Constructing the get function of the computed
  return function computedGetter () {
    var watcher = this._computedWatchers && this._computedWatchers[key];
    if (watcher) {
      if (watcher.dirty) {
        watcher.evaluate();//Collect subscriptions to the watcher
      }
      if (Dep.target) {
        watcher.depend();//The same set of subscriptions plus a component re-render subscription (which updates the component)
      }
      return watcher.value
    }
  }
}

In general. After understanding the responsive construction. Looking at the implementation of computer is still very intuitive. When components are initialized. Response expressions are established in computed items and data respectively. Data in data directly intercepts the get and set of attributes. Computed creates a new watcher for component rendering. First touch the getter function of this computer. Subscribe to this watcher. Here the watcher equivalent to this computer subscribes to first name and last name. After touching. Dep.target then becomes the previous one used to update components. This group is then unified with this subscription through watcher.depend(). So once the first name and last name change. Two subscription updates are triggered at the same time. One of them is to update components. re-render functions. I don't feel like I've seen enough detail.

Topics: Javascript Vue