So let’s take a look at the results

The name and age are rendered responsively, and after 2 seconds we change the value of name, which also updates correctly on the page.

Let’s take a look at the source code for minimized MVVM

class Vue{
    constructor(opt){
        this.opt = opt
        this.observe(opt.data)
        let root = document.querySelector(opt.el)
        this.compile(root)
    }
    // Bind an observer object to each key in the reactive data object
    observe(data){ 
        Object.keys(data).forEach(key= > {
            let obv = new Observer() 
            data["_"+key] = data[key]
            // The closure is generated by exposing the scoped OBV in the for loop through the getter setter
            Object.defineProperty(data, key, {
                get(){
                    Observer.target && obv.addSubNode(Observer.target);
                    return data['_'+key]
                }, 
                set(newVal){
                    obv.update(newVal)
                    data['_'+key] = newVal
                }
            })
        })
    }
    // Initialize the page, traverse the DOM, collect each key change, adjust the position, and store the observer method
    compile(node){
        [].forEach.call(node.childNodes, child =>{
            if(! child.firstElementChild &&/ \ {\ {(. *) \} \} /.test(child.innerHTML)){
                let key = RegExp. $1.trim()
                child.innerHTML = child.innerHTML.replace(new RegExp('\\{\\{\\s*'+ key +'\\s*\\}\\}'.'gm'),this.opt.data[key]) 
                Observer.target = child
                this.opt.data[key] 
                Observer.target = null
            }
            else if (child.firstElementChild) 
            this.compile(child)
        })
    }    
}
// Regular observer class
class Observer{
    constructor() {this.subNode = []    
    }
    addSubNode(node){
        this.subNode.push(node)
    }
    update(newVal){
        this.subNode.forEach(node= >{
            node.innerHTML = newVal
        })
    }
}
Copy the code

Let’s take a look at the idea of this code and the art of MVVM closures:

  • Observe function: First we need to respond to the requestdataObject, fordataEach and every onekeyMap an observer object
    • In ES6, each time the for loop is executed, it forms a closure, so the observer object is stored in the closure
    • The essence of closure formation is to expose the heap address in the inner scope, where we subtly expose the observer in the for loop with getter/setter functions
  • [compile function] : We go down the DOM from the root, and the text in the form of Mustache is mapped to the value of data.key and recorded to the observer
    • When iterating through text in the form {{XXX}}, we match the variable in it regularly and replace it with the value in data
    • To satisfy subsequent responsive updates and store the node in the observer object corresponding to the key, we cleverly manipulate the closure with the getter function
  • After the first rendering of the page, the subsequenteventLoopIf it is modifiedkeyThe actual response update is done by triggering the observer’s update function in the setter

The complete code for the above demo case is attachedMaking the address


      
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
</head>
<body>
    <div id='app'>
        <h3>The name</h3>
        <p>{{name}}</p>
        <h3>age</h3>
        <p>{{age}}</p>
    </div>
</body>
</html>
<script>
document.addEventListener('DOMContentLoaded'.function(){
    let opt = {el:'#app'.data: {name:'Searching for... '.age:30}}
    let vm = new Vue(opt)
    setTimeout((a)= > {
        opt.data.name = 'Wang Yongfeng'
    }, 2000);
}, false)
class Vue{
    constructor(opt){
        this.opt = opt
        this.observe(opt.data)
        let root = document.querySelector(opt.el)
        this.compile(root)
    }
    // Bind an observer object to each key in the reactive data object
    observe(data){ 
        Object.keys(data).forEach(key= > {
            let obv = new Observer() 
            data["_"+key] = data[key]
            // The closure is generated by exposing the scoped OBV in the for loop through the getter setter
            Object.defineProperty(data, key, {
                get(){
                    Observer.target && obv.addSubNode(Observer.target);
                    return data['_'+key]
                }, 
                set(newVal){
                    obv.update(newVal)
                    data['_'+key] = newVal
                }
            })
        })
    }
    // Initialize the page, traverse the DOM, collect each key change, adjust the position, and store the observer method
    compile(node){
        [].forEach.call(node.childNodes, child =>{
            if(! child.firstElementChild &&/ \ {\ {(. *) \} \} /.test(child.innerHTML)){
                let key = RegExp. $1.trim()
                child.innerHTML = child.innerHTML.replace(new RegExp('\\{\\{\\s*'+ key +'\\s*\\}\\}'.'gm'),this.opt.data[key]) 
                Observer.target = child
                this.opt.data[key] 
                Observer.target = null
            }
            else if (child.firstElementChild) 
            this.compile(child)
        })
    }    
}
// Regular observer class
class Observer{
    constructor() {this.subNode = []    
    }
    addSubNode(node){
        this.subNode.push(node)
    }
    update(newVal){
        this.subNode.forEach(node= >{
            node.innerHTML = newVal
        })
    }
}
</script>
Copy the code

Code github address