The responsiveness of Vue2 is achieved through data hijacking (Object.defineProperty) and publish-subscribe. The core content is Observer, Watcher, Compile and Dep.

To introduce the general process: Whenever a new Vue is created, it mainly does two things: first, it listens to data: Observer (data); Compile HTML: nodeToFragement (ID).

While listening for data, a DEP container (observable) is generated for each property in the data.

During HTML compilation, a subscriber Watcher is produced for each node associated with data binding, and the Watcher adds itself to the corresponding attribute (subs) of the DEP. For example, when we modify the content of the input box => modify the attribute value => trigger the set method for that attribute.

Property’s set method executes dep.notify(), notifying all the subscribers (Watcher) to execute the uptade method to update the view.

The Observer observers

With Object.defineProperty, it listens for changes to data and notifies publishers of changes to the page content when there are changes.

var data = {name: 'kindeng'};
observe(data);
data.name = 'dmq'; Kindeng --> DMQ

function observe(data) {
    if(! data ||typeofdata ! = ='object') {
        return;
    }
    // Retrieve all attributes to traverse
    Object.keys(data).forEach(function(key) {
        defineReactive(data, key, data[key]);
    });
};

function defineReactive(data, key, val) {
    observe(val); // Listen for child attributes
    Object.defineProperty(data, key, {
        enumerable: true./ / can be enumerated
        configurable: false.// cannot define again
        get: function() {
            return val;
        },
        set: function(newVal) {
            console.log('Hahaha, I'm listening for a change in value', val, '- >', newVal); val = newVal; }}); }Copy the code

So that we can monitor the change of each data, then after listening to change is how to notify the subscriber, so we need to implement a message subscriber, is very simple, maintain an array, used to gather the subscriber, trigger notify data changes, then call the subscriber the update method, code after improvement is this:

/ /... omit
function defineReactive(data, key, val) {
    var dep = new Dep(); // There is a Dep container for each data in data, which stores all dependencies on the data
    observe(val); // Listen for child attributes

    Object.defineProperty(data, key, {
        / /... omit
        get() {
                if (Dep.target) {// dep.target stores specific dependencies, which are assigned at compile time when the dependencies are detected
                    dep.addDep(Dep.target); // Rely on collection
                }
                return val;
        },
        set: function(newVal) {
            if (val === newVal) return;
            console.log('Hahaha, I'm listening for a change in value', val, '- >', newVal);
            val = newVal;
            dep.notify(); // Notify all subscribers // Notify all dependencies to display updates when data changes}}); }// The Dep container, one for each data in data, is used to collect and store dependencies
function Dep() {
    this.subs = []; // All dependencies will be stored in this array
}
Dep.prototype = {
    addSub: function(sub) {
        this.subs.push(sub); // Collect dependencies/subscribers
    },
    notify: function() {
        this.subs.forEach(function(sub) { sub.update(); }); }};Copy the code

So the question is, who are the subscribers? How do I add a subscriber to a subscriber? Var dep = new dep (); var dep = new dep (); DefineReactive is defined inside the defineReactive method, so adding subscribers via deP must be done inside the closure, so we can do it in the getter:

// Observer.js // ... Omit object.defineProperty (data, key, {get: Function () {Dep () {Dep () {Dep () {Dep (); return val; } / /... Omit}); // Watcher.js Watcher.prototype = { get: function(key) { Dep.target = this; this.value = data[key]; // The getter for the property is triggered to add the subscriber dep. target = null; }}Copy the code

The watcher subscriber

Watcher subscribers, acting as a bridge between the Observer and Compile, mainly do the following: Update () = deP () = deP () = deP () = deP () = deP () = deP ();

function Watcher(vm, exp, cb) {
    this.cb = cb;
    this.vm = vm;
    this.exp = exp;
    // Add yourself to the DEp by triggering the getter for the property
    this.value = this.get(); 
}
Watcher.prototype = {
    update: function() {
        this.run();    // Attribute value changes are notified
    },
    run: function() {
        var value = this.get(); // Get the latest value
        var oldVal = this.value;
        if(value ! == oldVal) {this.value = value;
            this.cb.call(this.vm, value, oldVal); // Execute the callback bound in Compile to update the view}},get: function() {
        Dep.target = this;    // Point the current subscriber to yourself
        var value = this.vm[exp];    // Trigger the getter to add itself to the property subscriber
        Dep.target = null;    // Set the value
        returnvalue; }};// The Observer and Dep are again listed here for easy comprehension
Object.defineProperty(data, key, {
    get: function() {
        // Since we need to add watcher to the closure, we can define a global target attribute in the Dep to temporarily store the watcher and remove it after adding it
        // Finally, set dep. target to null. Because it is a global variable and the only bridge between Watcher and deP, you must ensure that dep. Target has only one value at all times.
        Dep.target && dep.addDep(Dep.target);  // Rely on collection
        return val;
    }
    / /... omit
     set: function(newVal) {
            if (val === newVal) return;
            console.log('Hahaha, I'm listening for a change in value', val, '- >', newVal);
            val = newVal;
            dep.notify(); // Notify all subscribers // Notify all dependencies to display updates when data changes}}); Dep.prototype = {notify: function() {
        this.subs.forEach(function(sub) {
            sub.update(); // Call the subscriber's update method to notify the change}); }};Copy the code

When instantiating Watcher, the get() method is called, marking the subscriber to the current Watcher instance with dep.target = watcherInstance, forcing the getter method defined by the property to fire. The current Watcher instance is added to the subscriber DEP of the property so that the watcherInstance is notified of updates when the property value changes.

Compiler Template parser

Compile is a template parser that parses template instructions, replaces variables in the template with data, initializes the render page view, binds the update function corresponding to each instruction node, adds subscribers to listen for data, and receives notifications when data changes, The update view compileElement method will traverse all nodes and their children, scan and parse, call the corresponding instruction rendering function for rendering and call the corresponding instruction update function for binding.

Compile.prototype = {
    / /... omit
    compileElement: function(el) {
        var childNodes = el.childNodes, me = this;
        [].slice.call(childNodes).forEach(function(node) {
            var text = node.textContent;
            var reg = / \ {\ {(. *) \} \} /;    // Expression text
            // Compile as element node
            if (me.isElementNode(node)) {
                me.compile(node);
            } else if (me.isTextNode(node) && reg.test(text)) {
                me.compileText(node, RegExp. $1); }// Iterate over compiled child nodes
            if(node.childNodes && node.childNodes.length) { me.compileElement(node); }}); },compile: function(node) {
        var nodeAttrs = node.attributes, me = this;
        [].slice.call(nodeAttrs).forEach(function(attr) {
            // Directive named after V-xxx
             the command is V-text
            var attrName = attr.name;    // v-text
            if (me.isDirective(attrName)) {
                var exp = attr.value; // content
                var dir = attrName.substring(2);    // text
                if (me.isEventDirective(dir)) {
                    // Event directives, such as V-on :click
                    compileUtil.eventHandler(node, me.$vm, exp, dir);
                } else {
                    // Common instructioncompileUtil[dir] && compileUtil[dir](node, me.$vm, exp); }}}); }};// Instruction processing set
var compileUtil = {
    text: function(node, vm, exp) {
        this.bind(node, vm, exp, 'text');
    },
    / /... omit
    bind: function(node, vm, exp, dir) {
        var updaterFn = updater[dir + 'Updater'];
        // Initialize the view for the first time
        updaterFn && updaterFn(node, vm[exp]);
        // Instantiate the subscriber, which adds the subscriber watcher to the corresponding attribute message subscriber
        new Watcher(vm, exp, function(value, oldValue) {
            // If the property value changes, it will be notified to execute this update function to update the viewupdaterFn && updaterFn(node, value, oldValue); }); }};// Update function
var updater = {
    textUpdater: function(node, value) {
        node.textContent = typeof value == 'undefined' ? ' ' : value;
    }
    / /... omit
};
Copy the code

Key point implementation

How to add watcher to the associated DEP container.

1. How to add Watcher to the associated DEP container

Target = this; var value = this.vm[exp]; AddDep. AddDep (dep.target) to subs, and set the dep. Target property to null, since it is a global variable and the only bridge between wather and dep.

export default class Dep {
 addSub (sub: Watcher) {
    this.subs.push(sub)
  }
  notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}
Copy the code

A lot of content is borrowed from others, here is just used to record, review their own look, do not spray thank ~

Ultimate flow chart

The resources

  1. Focus on source code analysis, observer, DEP, Compiler, Watcher, etc Segmentfault.com/a/119000001…
  2. Dependency Collection (Dep and Watcher) www.cnblogs.com/samwu/p/123…
  3. Article 1, actually reading this is enough, know better than the article below www.jianshu.com/p/d723f3dc4…
  4. Good article 2 Segmentfault.com/a/119000000…
  5. The best article ever Blog.csdn.net/huolinianyu…