This article is based on x station in the first half of 2021 each major factory web core interview question analysis (second phase) – Road white, video more easy to understand!


Read about the principle of VUE responsiveness, with this note to record, to deepen the impression of vUE responsiveness principle


Before we can explain vue2’s response formula principle, we need to understand a few concepts, or core classes, and their respective roles

  1. Observe class, add getters/setters to data properties, hijack data, and distribute updates.
  2. There are many types of Watcher classes, such as computed Watcher and user Watcher.
  3. The Dep class is used to collect the dependencies of the current reactive object, each of which has a Dep instance. Dep.subs =watcher[], which is the Watcher dependency collected by the current responsive object DEP instance. When the data changes, the dep.notify method is triggered, which iterates through the subs array, calling each Watcher’s update method for a responsive update.

Well, after a brief understanding of the role of each core class, next to implement one by one, the file structure is as follows.

Index.html -- -- -- -- -- -- -- -- the HTML file, provide the root element index, js -- -- -- -- -- -- -- -- introduction of vue. Js. Add parameters watch. Js -- -- -- -- -- -- -- -- watcher class dep. Js -- -- -- -- -- -- -- -- dep class. Observe js -- -- -- -- -- -- -- -- the observer class compiler. Js -- -- -- -- -- -- -- -- used to compile instructions, such as v - HTML, V-model, V-text vue.js -------- main fileCopy the code


The first step is to finish the initial code of the index.html and index.js files

//index.js
import Vue from './vue.js'

const vm=new Vue({
    el:'#app'.data: {msg:'hello word'.text:'
      
hahaha
'
.html:'

is bigger

'
}, methods: {fn(){ alert("111"); }}})console.log(vm); //index.html <body> <div id="app"> {{msg}} <input type="text" v-model='msg'> <button type="button" v-on:click='fn'>button</button> <div v-text='text'></div> <div v-html='html'></div> </div> <script src="./index.js" type="module"></script> </body> Copy the code
  1. We know that Vue comes out of new, so we know that Vue is a class declared in class.
  2. The JS import of HTML says type=’module’, which tells the browser that we are using ESM syntax. Mainstream browsers support this declaration.


The second step is to write the body code of vue.js. There are a few things that need to be implemented

  1. Bind the passed options and related properties to the Vue instance
  2. Determines the el attribute passed in
  3. From the VM instance of VUE, you can see that Vue binds a copy of the attributes in data to the Vue instance
import Observe from "./observe.js";
import Complier from "./compiler.js";

export default class Vue{
    constructor(options){
        this.$options=options;
        this.$data=options.data;
        this.$methods=options.methods;

        // Initialize el to judge the passed root element
        this.initElement(options);

        // Bind data elements to vue instances
        this._proxyData (this.$data);

        // Data hijacking
        new Observe(this.$data);

        // Compile the template
        new Complier(this);
    }

    initElement(options){
        if(typeof options.el==='string') {this.$el=document.querySelector(options.el);
        }else if(options.el instanceof HTMLElement){
            this.$el=options.el;
        }

        // The type of the root element passed in is incorrect
        if(!this.$el){
            throw new Error('Please pass in CSS Selector or HTMLElement'); }}_proxyData(data){
        // Bind attributes in data to vue instances via Object.defineProperty
        Object.keys(data).forEach(key= >{
            Object.defineProperty(this, key, {
                // can be enumerated, i.e. can be looped out
                enumerable:true.// Indicates that corresponding configurations can be performed
                configurable:true.get(){
                    return data[key];
                },
                set(newValue){
                    if(newValue===data[key]){
                        return} data[key]=newValue; }})})}}Copy the code

The _proxyData function is used to bind attributes to vUE instances, not to collect dependencies and distribute updates, so there is no recursive hijacking of each value.

The third step is to sort out the architecture

At this time, we do not rush to implement the rest of the class, but to write the architecture first, to better comb out our knowledge, of course, when writing the architecture notes to add, or when writing, forget what this is used for, the loss outweighed the gain.

  1. Dep class
// The dep class is used to hold the watch class, so we start with an array to hold the watch
export default class Dep{
    constructor(){
        / / watch array
        this.subs=[];
    }

    // To put the subs array, you need an add method
    addSubs(watcher){}// Used to distribute updates
    notify(){}Copy the code


  1. Watch the class
export default class Watcher{
    constructor(vm, key, cb){}update(){}}Copy the code

Three parameters are required: the instance of the VM, the property name of data, and the callback function

  1. Observe the class
export default class Observe{
    constructor(data){}transver(data){}}Copy the code

We need a data parameter, which is the data passed in. We use Object.defineProperty to hijack each property in the data, plus getters/setters. This is where we implement our dependency collection and distribution of updates

  1. Complier class
export default class Complier{
    constructor(vm){}// Iterate over the elements and edit the template
    compile(el){}}Copy the code

With the basic architecture of the four major categories completed, adding the corresponding code on top of this is one step closer to completion, doesn’t it seem easy?

The fourth step is the overall implementation of deP class

// The dep class is used to hold the watch class, so we start with an array to hold the watch
export default class Dep{
    constructor(){
        / / watch array
        this.subs=[];
    }

    // To save into the subs array, you need an add method
    addSubs(watcher){
        if(watcher&&watcher.update){
            this.subs.push(watcher); }}// Used to distribute updates
    notify(){
        // Iterate through the subs array, calling each watcher's updatae method
        console.log(this.subs);
        this.subs.forEach(watcher= >{ watcher.update(); }}})Copy the code

It doesn’t seem easy but let’s think about two things.

  1. When is the DEP class instantiated? Where do I addSubs?
  2. When does the DEP class call notify?


Step 5: The whole implementation of the Watcher class

import Dep from "./dep.js";

export default class Watcher{
    / * * * *@param {*} Vm vm instance, get the desired value *@param {*} Property name of key data, get new value, old value *@param {*} Call the callback function */ when cb updates
    constructor(vm, key, cb){   
        this.vm=vm;
        this.key=key;
        this.cb=cb;

        // Why add a watcher instance to dep.target
        Dep.target=this;

        // Get the old value
        // Also note that this is where the variable's get method is triggered
        this.oldValue=vm[key];

        Dep.target=null;
    }

    update(){
        // Since the data has been updated, we get the latest value
        let newValue=this.vm[this.key];
        if(newValue===this.oldValue){
            return;
        }

        this.cb(newValue); }}Copy the code

Don’t worry, the real complexity lies in the Compiler module, where there is also a problem.

  1. Why add a Watcher instance to dep.target? In order to maintain only one watcher at a time, because in computed and watch, more than one Watcher will be added, which will easily cause data disorder. Therefore, only one Watcher at a time can ensure that the watcher is added normally.


The sixth step is the overall implementation of Compiler

This step will be more complicated, mainly for the element node analysis, traversal. The code is long, so don’t panic.

import Watcher from "./watch.js";

export default class Complier{
    constructor(vm){
        this.vm=vm;
        this.compile(vm.$el);
    }

    // Iterate over the elements to replace the template
    compile(el){
        // Get the element's child node, an array of classes
        const childNodes=el.childNodes;
        Array.from(childNodes).forEach(node= >{
            // Determine the node type
            if(this.isTextNode(node)){
                // Text node
                this.compileText(node);
            }else if(this.isElementType(node)){
                // Element node
                this.compileElement(node);
            }

            if(node.childNodes&&node.childNodes.length>0) {this.compile(node); }})}// Determine the text node
    isTextNode(node){
        return node.nodeType===3;
    }

    // Determine the element node
    isElementType(node){
        return node.nodeType===1;
    }

    // Text node compiles
    compileText(node){
        let reg=/ \ {\ {(. +?) \} \} /;
        let value=node.textContent;

        if(reg.test(value)){
            const key=RegExp.$1.trim();/ / get the MSG
            node.textContent=value.replace(reg, this.vm[key]);// The replacement succeeded

            // Data needs to change dynamically, so it depends on collection
            new Watcher(this.vm, key, (newValue) = >{ node.textContent=value.replace(reg, newValue);; }}})// Element node compiles
    compileElement(node){
        if(node.attributes.length>0) {Array.from(node.attributes).forEach(attr= >{
                / / the property name
                const attrName=attr.name;

                // Check whether it starts with v-
                if(this.isVStartsWith(attrName)){
                    // Check whether there are: signs, such as V-on :click V-model
                    const directiveName=attrName.indexOf(':') > -1? attrName.substr(5):attrName.substr(2);
                    // get the value, such as MSG where v-model=' MSG '
                    let key=attr.value;

                    this.update(node, key, directiveName); }}}})// Start with v-
    isVStartsWith(attr){
        return attr.startsWith('v-');
    }

    // concatenate the corresponding function
    update(node, key, directiveName){
        const updaterFn=this[directiveName+'Updater'];

        updaterFn && updaterFn.call(this, node, this.vm[key], key, directiveName);
    }

    //v-model
    modelUpdater(node, value, key){
        // corresponds to input
        node.value=value;
        new Watcher(this.vm, key, (newValue) = >{
                node.value=newValue;
        })

        node.addEventListener('input'.() = >{
            // Triggers the setter
            this.vm[key]=node.value; })}//v-text
    textUpdater(node, value, key){
        node.textContent=value;

        new Watcher(this.vm, key, (newValue) = >{ node.textContent=newValue; })}//v-html
    htmlUpdater(node, value, key){
        node.innerHTML=value;

        new Watcher(this.vm, key, (newValue) = >{ node.innerHTML=newValue; })}//v-on:click
    clickUpdater(node, value, key, directiveName){
        node.addEventListener(directiveName, this.vm.$methods[key]); }}Copy the code

Ah, done with this step, next is the last step, come on, almost done!!

Step 7 Implement the Observe class

import Dep from './dep.js'

export default class Observe{
    constructor(data){
        this.transver(data);
    }

    transver(obj){
        if(! obj ||typeofobj! = ='object') {return
        }

        // Iterate over the listening data
        Object.keys(obj).forEach(data= >{
            this.defineReactive(obj, data, obj[data]); })}defineReactive(obj, data, value){
        // Maybe an object
        this.transver(value);

        // Instantiate dep here
        let dep=new Dep();

        / / save this
        const that=this;

        Object.defineProperty(obj, data, {
            enumerable:true.configurable:true.get(){
                // Add the dependency here and get the watcher bound to the Dep
                Dep.target && dep.addSubs(Dep.target);

                // Note that obj[data] is not sufficient for this step, which results in the loop's get value
                return value;

            },
            set(newValue){
                if(value===newValue){
                    return;
                }

                value=newValue;

                // It can be an object
                that.transver(newValue);

                // Post updates heredep.notify(); }}}})Copy the code

At this point, caused by great success, can see the complete down really is perseverance commendable, might as well follow the video handwritten again, memory will be more profound.

The above has a personal understanding, if there is any wrong, please leave a message