preface

Is reading the Vue source code just for the interview? I think, probably a lot of people will feel that way! But I don’t think so… Why is that? (PS: More on that later…) Of course, it takes some courage when you choose to read the Vue source code. Why do you say so? If you spend your time and don’t get anything out of it, you’re definitely frustrated. Fortunately, the hard work paid off, and the lessons I learned during this time were accompanied by… It was a success.

The initial _init

The Vue constructor works from the _init API and initializes a lot of apis in init.js file.

The initialinitComputedinitWatch

Computed, as it is commonly known, has the effect of caching and changes depending on the change of certain data. Watch Why is the immediate: true function executed earlier than mounted hook? And what is the difference between them, how to choose and reject them in the project and so on…

All of this is what the initComputed and initWatch functions tell me.

Source ideas

  • InitComputed: Internally, it works using the Watcher class

When we write each object for the computed option, we construct a computedWatcher instance with a New Watcher wrapper and implement the caching mechanism by passing in a computedLazy: True identifier.

  • theWatcherWhat exactly does the class do? (PS: Implementation of the Watcher class, more on this later)

The first evaluation takes place inside the Watcher class constructor. So it looks like every computed watcher Object is wrapped as a computedWatcher instance, and every computedWatcher instance is wrapped again with object.defineProperty, which triggers get when used, The get function will intercept the value of the judgment computedWatcher instance dirty to true is triggered when the watcher of the evaluate function, this function is used to get the value and then close the dirty, dirty is false returned directly to obtain the value of the first time.

With this in mind, I wrote some pseudo-code to implement the logic described above:

The point to make here is that computed caching has nothing to do with whether or not it relies on responsive data.

thinking

Then I thought: if I write a computed object that doesn’t depend on the data option or the props data, how can I not be listened to when I change computed? (You can do it with your hand)

computed: {
    getC: {
      get: () = > {
        return 1;
      },
      set: (newVal) = > {
        returnnewVal; }},},mounted() {
    setTimeout(() = > {
      this.getC = 8;
    }, 800);
  },
watch: {
    getC: {
      handler: function () {
        console.log(this.getC, "C cannot be listened to.");// Cannot print}},},Copy the code

Set in computed internal Object.defineProperty does nothing to notify dependency changes. InitComputed ultimately gets a value that may or may not depend on some responsive data. If it depends on one or more responsive data, then when the responsive data changes, it changes too. This is because the new Dep is used to create the Dep instance in the responsive data and the watcher that depends on it is notified in the set function.

So why isn’t it monitored by the Watch option?

The watch option listens for responsive data, and callbacks that run the Watch option are executed when THE DEP notifies you of changes, so how can a computed object that doesn’t rely on responsive data be notified of changes? Watch is used to listen for changes in a value and receive a new value.

To return to the original question, what is the implementation principle of immediate: true?

If immediate: true is enabled, the watch callback is executed first. This is because js is single-threaded. InitWatch is initialized earlier than Mounted when _init is initialized. The key to initWatch is vm.$watch, which checks whether immediate is true and executes the callback function immediately.

The idea of implementing a caching mechanism has come to mind. Using Object.defineProperty properly can do some data interception, which is quite ok!

Initial responsive data

The props and data options are both responsive data. How does Vue make the props and data data observable?

Objects are wrapped and consolidated using Object.defineProperty, and arrays are overridden using array methods.

In the Vue source code, data hijacking is implemented through an Observer class, that is, the Observer method is called when the data and props are initialized. This method makes a datatype judgment to decide whether to rewrap the object or override the method. So what does this have to do with responsiveness?

confusion

When I define a variable a in the data option and change the value of variable A, why do I automatically update the value everywhere I use it?

thinking

The key to reactive data is that the Watcher class and the Dep class play a key role in the middle. So what exactly do these two classes do?

  • The Watcher acts

    1. Add yourself to the attribute subscriber (DEP) during self instantiation.

    2. It must have an update() method.

    Update (); dep.notice();

  • Dep (Observer) effect

    1. There’s a box for every Watcher.

    2. There is an addSub() method to collect watcher.

    3, there is a notification method, notify Watcher to update.

  • How do they work?

    When instantiated, the Wtacher class associates the Watcher instance by defining the target attribute on the Dep class. So for variable A, when data initialization is wrapped with Object.defineProperty, there is a box through the new Dep class (ps: this Dep instance shows a frame, The watcher instances that implement the dependency variable A are collected.

    This deP instance is actually a box that belongs to variable A, and it puts its dependent Watcher inside the box defined by deP. Waiting for variable A to change, the notify function of the DEP is triggered in the set function to notify all watcher calls to update the value.

For example, if I define a getA attribute in computed selection, the new Watcher will set its getA to dep.target when computed is initialized. If the getA attribute has a value of variable A that depends on the data option definition, the object.defineProperty get method is fired when the value of variable A is fetched. The Watcher dependency of getA is collected in this method, which means that getA is dropped into the DEP box of variable A.

So the implementation of the Observer class is not difficult; the key is the Watcher class and the Dep class.

  • How to implement Watcher class and Dep class

    I have written a pseudo-code by simulating Vue source code for your reference:

Watcher classes and utility functions:

Dep class:

By now, you should have a clear idea of how responsive data is implemented, not just because object.defineProperty ~

Back to Vue’s responsive data/props, there is a layer of proxies in the source code, so we can access variables directly through this. XXX when we write code.

Writing this pseudo-code reminds me of the ES6 Proxy class.

The initial nextTick

Before we get to nextTick, let’s take a look at an example.

case

In Mounted mode, the value of test is executed 1000 times. Every time ++ triggers setter->Dep->Watcher-> Update -> Patch in response.

Consider: How does Vue update views and how does it relate to nextTick?

Vue updates views asynchronously and implements a queue. The main thread is executed first, and the next tick will execute the Watcher run in the queue. I’m talking about the next tick so you have to understand the event loop. Can refer to learning big guy wrote: event loop

At the same time, Watcher with the same ID will not be added to the queue repeatedly, so Watcher’s run will not be executed 1000 times. The final update to the view simply changes the DOM corresponding to test from 0 to 1000.

The reason why you can get DOM changes after data modification in the nextTick callback is because the timerFunc function is defined in the nextTick function. This function, wrapped with Promise and MutationObserver and setImmediate and setTimeout, initiates another macro task. The current macro task is completed and the current macro task is empty (ps: Watcher’s run is executed).

If you want to get the DOM change after asynchronously updating the view, you need to turn on another tick, so use the callback function passed in by nextTick to manipulate the DOM change. You can write your code logic here. This is why the DOM element that operates on the updated data is in nextTick.

The reason for using callbacks in The nextTick function rather than executing callbacks directly in nextTick is to ensure that executing nextTick multiple times does not start multiple asynchronous tasks, but instead pushes them all into a single synchronous task. The next tick completes. Procedure

If the view is not updated asynchronously, then the DOM update view is directly manipulated every time ++ is performed, which can be very costly.

Initial event mechanism

Vue. Js provides four event apis: $ON, $once, $off, and $emit.

Analysis of the

  • $onSubscription functions, incoming event names, and cb. An event can be subscribed more than once, so an event is collected as cb in a queue.
  • $emitPublish functions that execute the cb functions just collected by using the corresponding event names.
  • $onceSubscribe once. The idea here is to use function wrapping: Wrap the cb passed in, then subscribe to the wrapper function, whose logic is to call cb first and then unsubscribe. So when does that trigger the wrapper function? Is that when$emitTime.

There is one more small detail: bind cb passed to one of the properties of the wrapper function by binding attributes to the wrapper function using the properties of the function. What does that do? (PS: Leave a question, I’ll tell you later.)

  • $off removes the event listener.

    1. If lenght is set to 0, clear all events.

    When an event is passed in an array, recursively close the event one by one.

    3. Determine the incoming event name and FN and delete it from the queue corresponding to the event name. What’s special here is if I first $once an event, but I don’t trigger it, I just $off the event. At this time, when deleting fn, we use the above mentioned function properties to bind properties to the wrapper function.

thinking

Do you subscribe to publish only if you subscribe first

Obviously not, in this Eevnt class I implement publish before subscribe (ps: for example, receive messages offline). The main idea is that when publishing, I will first collect the function package of publishing, and when the subscribers subscribe to all empty, it should be noted that the life cycle of the publisher should be once. (PS: Subscribers can only subscribe once) Wait until the subscription time to send results to subscribers.

The specific implementation code is as follows

For event mechanisms, generally used to communicate transmitted data. I thought about another question:

How to implement broadcast event in Vue (PS: broadcast event is parent to child broadcast notification layer by layer) and dispatch event (child to parent layer by layer).

There are also two types of broadcast events and distribution events:

It’s distributed at every level

Assign a tier of distribution

Good use of the event mechanism will add a different color to the project data communication oh ~

conclusion

Learning itself is a progressive process, everyone’s learning methods are not the same, but I feel that the way to ask yourself the answer is also a learning method!

That’s why I think looking at the source code is not just for interviews, it’s also a benefit to learn about the idea of a framework and how the internal API works. This period of time source learning notes, I hope to help you ~

In addition, I learned the process also manually wrote some code for reference: Vue source code learning summary

Please don’t be stingy with your 👍👍