With the rise of Vue and React, MVVM mode has become the mainstream development idea. What is the implementation principle of this mode? How does two-way data binding work? What is publish subscription? This article with Vue design ideas to take you to solve these puzzles

Object.defineProperty()

Vue does not support browsers below IE8 because it uses ECMAScript5 features that IE8 cannot emulate :Object.defineProperty()

Usually we define an object literally, without the get and set methods, and with properties that can be changed or deleted at will

The object.defineProperty () method directly defines a new property on an Object, or modifies an existing property of an Object, and returns the Object. Vue uses this method for data hijacking, mounting data to Vue instances

var o = {}; // Create a new Object // Add an example of attributes and data descriptors to the Object object.defineProperty (o,"a", {
  value : 37,
  writable : true,
  enumerable : true,
  configurable : true}); // Object O has property A, value 37 // Add an example of property and access descriptor to object var bValue; Object.defineProperty(o,"b", {
  get : function() {return bValue;
  },
  set : function(newValue){
    bValue = newValue;
  },
  enumerable : true,
  configurable : true}); o.b = 38; // Object O has property B, value 38 // the value of O.B is now always the same as bValue, unless o.b is redefined // Data descriptors and access descriptors cannot be mixed with Object.defineProperty(o,"conflict", {
  value: 0x9f91102, 
  get: function() { 
    return0xdeadbeef; }}); // throws a TypeError: value appears onlyin data descriptors, get appears only in accessor descriptors
Copy the code

Object.defineproperty () ¶

Release subscription

What’s the difference between publish subscribe and observer

The publish-subscribe pattern is one of the most common implementations of the observer pattern and is superior to the typical observer pattern in terms of decoupling and reuse

In the observer mode, the observer needs to subscribe directly to the target event; After the target issues an event that changes the content, the event is received directly and the response is made

In common terms:

A tells B to do three things (buy shoes, trousers and ties) and to tell A when each thing is finished.

B bought the shoes to tell A “bought the shoes”, A received the news sent A circle of friends;

After B bought the pants, he told A that “the pants are available”. A sent A microblog after receiving the news.

After B bought the tie, he told A, “I got the tie”. A took A selfie after receiving the message.

In common terms:

N people follow A’s public account (subscription)

A has written the article and submitted it to the wechat public platform (for publication).

The wechat public account platform pushed to the wechat client of these N individuals (broadcast)

All subscribers receive the message and can choose their own actions to read/ignore/share

Object.defineProperty()
Release subscription

MVVM

Let’s take a look at how MVVM works, and follow this diagram step by step

Vue
MyMVVM

<div id="app">
    <p>The value of a {{a.a}}</p>
    <div>The value of b {{b}}</div>
    <input type="text" v-model="b">
    {{hello}}
</div>
Copy the code
  let myMVVM = new MyMVVM({
        el:"#app".data: {a: {a:"a"},
            b:"B"}})Copy the code

new MyMVVM()

The MyMVVM instance mounts the incoming data at instantiation time

function MyMVVM(options = {}) {
    this.$options = options; // Mount all attributes in $options
    let data = this._data = this.$options.data;
    // this proxies this._data
    for(let key in data){
        Object.defineProperty(this,key,{
            enumerable:true,
            get(){
                return this._data[key]
            },
            set(newVal){
                this._data[key] = newVal
            }
        })
    }
}
Copy the code

Compile template

function Compile(el,vm) {
    // el indicates the range of substitution
    // When a DOM node is inserted into a fragment, the original node is removed
    vm.$el = document.querySelector(el);
    let fragment = document.createDocumentFragment();
    while(child = vm.$el.firstChild){ // Insert the element node into the fragment to operate in memory
        fragment.appendChild(child)
    }

    replace(fragment);
    function replace(fragment){
        Array.from(fragment.childNodes).forEach(function (node) { // Class array to array, loop
            let text = node.textContent;
            let reg = / \ {\ {(. *) \} \} /;
            // Data render view
            if(node.nodeType === 3 && reg.test(text)){ // Text node
                let arr = RegExp. $1.split('. ');
                let val = vm;
                arr.forEach(function (k) { // take this.a.a/this.b
                    val = val[k]
                });
                / / replace
                node.textContent = text.replace(reg,val) // Replace the template
            }
            // The view updates the data, which then renders the view
            if(node.nodeType === 1) {// The DOM node updates the data as it enters the content
                let nodeAttrs = node.attributes;
                Array.from(nodeAttrs).forEach(function (attr) {
                    let name = attr.name; 
                    let exp = attr.value;
                    if(name.indexOf("v-") = = =0) {// DOM node with v-instruction
                        node.value = vm[exp];
                    }
                    node.addEventListener("input".function (e) {
                        let newVal = e.target.value;
                        vm[exp] = newVal  // Call the set method of data on the VM with the new view data})})}if(node.childNodes){ // Node depth recursion
                replace(node)
            }
        });
    }
    vm.$el.appendChild(fragment)
}
Copy the code

Observer data hijacking

The process of data hijacking is to redefine the properties of the Object passed to the instance through Object.defineProperty(), so that we have get and set methods, so that we can observe the data changes later

The vUE feature is that you can’t add nonexistent attributes, because nonexistent attributes can’t be redefined during data hijacking, so you can’t add get and set

// Add ObjectDefineProperty to the object
function Observe(data) {
    for(let key in data){  // Define the data attribute via object.defineProperty ()
        let val = data[key];
        Object.defineProperty(data,key,{
            enumerable:true,
            get(){
                return val
            },
            set(newVal){
                if(val === newVal) {
                    return; } val = newVal; }}}})Copy the code

Watcher

Watcher is a class that creates instances with update methods that perform updates when data changes

function Watcher(vm,exp,fn) {
    this.fn = fn;
    this.vm = vm;
    this.exp = exp;
    Dep.target = this;
    let val = vm;
    let arr = exp.split('. ');
    arr.forEach(function (k) { // The purpose is to trigger the get method, which adds watcher to the queue
        val = val[k]
    });
    Dep.target = null; // Target is set to null

}
Watcher.prototype.update = function () {
    let val =this.vm;
    let arr = this.exp.split('. ');
    arr.forEach(function (k) {
        val = val[k]
    });
    this.fn(val);
};
Copy the code

Release subscription

Subscribe before you publish

function Dep() {
    this.subs = []; // Store the subscription queue
}
Dep.prototype.addSub = function (sub) { // Store subscription information in the container
    this.subs.push(sub)
};

Dep.prototype.notify = function () {  / / release
    this.subs.forEach(sub= > sub.update())
};
Copy the code

conclusion

We completed the MVVM framework, the correct call combination of these methods can achieve two-way data binding, source please refer to here