Remember a VUE principle learning record, involving how to carry out data hijacking, data proxy, observer mode (publish and subscribe), data bidirectional binding function and other knowledge points, but also a simple realization of some common functions in VUE, code made more detailed notes, you can directly copy the execution to view the results

index.html


      
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title>Principle of MVVM</title>
</head>
<body>
    <div id="app">
        {{school.name}}{{age}}
        <div>{{age}}</div>
        <input type="text" v-model="school.name">
        <div>{{school.name}}</div>
        <div>compute: {{getNewName}}</div>
        <ul>
            <li>li_1</li>
            <li>li_2</li>
        </ul>
        <button v-on:click="changeName">methods</button>
        <div v-html="htmlName"></div>
    </div>
    <! -- <script src="./node_modules/vue/dist/vue.js"></script> -->
    <script src="mvvm.js"></script>
    <script>
        let vm = new Vue({
            // el: document.querySelector('#app'),
            el: '#app'.data: {
                school: {
                    name: 'School Name'
                },
                age: 20.htmlName: '<h1>inner html</h1>'
            },
            methods: {
                changeName () {
                    this.school.name = 'I am modified by Methods'}},computed: {
                getNewName () {
                    return this.school.name + 'new'}}})</script>
</body>
</html>
Copy the code

mvvm.js

// The base class is responsible for scheduling
class Vue {
    constructor (options) {
        this.$el = options.el
        this.$data = options.data
        let computed = options.computed
        let methods = options.methods
        // Build the template
        if (this.$el) {
            // Data hijacking converts all data to Object.defineProperty
            new Observer(this.$data)
            // computed There are dependencies, and executing a computed method triggers get
            for (let key in computed) {
                Object.defineProperty(this.$data, key, {
                    get: (a)= > {
                        return computed[key].call(this)}}}for (let key in methods) {
                Object.defineProperty(this, key, {
                    get () {
                        return methods[key]
                    }
                })
            }
            // Data proxy Data operations on the VM are proxy to vm.$data
            this.proxyVm(this.$data)
            // Build the template
            new Compiler(this.$el, this)}}// Delegate the VM operation to vm.$data
    proxyVm (data) {
        for (let key in data) {
            Object.defineProperty(this, key, {
                get () {
                    return data[key] // Perform the conversion
                }, 
                set (newVal) {
                    data[key] = newVal
                }
            })
        }
    }
}

// Data hijacking
class Observer {
    constructor (data) {
        this.observer(data)
    }
    observer (data) {
        // If it is an object, observe it
        if (data && typeof data === 'object') {
            for (let key in data) {
                this.defineReactive(data, key, data[key])
            }
        }
    }
    defineReactive (obj, key, value) {
        // Add a publish/subscribe function to each data
        let dep = new Dep()
        Object.defineProperty(obj, key, {
            get () {
                // The value will be fetched when the watcher is created, get will be triggered, and the watcher will be placed globally
                Dep.target && dep.addSub(Dep.target)
                return value
            },
            set: (newVal) = > {
                if(value ! == newVal) {this.observer(newVal) // Hijack the newly assigned value
                    value = newVal
                    dep.notify() // Triggers the publish method to notify Watcher that the data has been modified}}})this.observer(value) // Perform recursive hijacking of data}}// Observer mode (publish-subscribe mode) The observer is observed
class Watcher { / / observer
    constructor (vm, expr, cb) {
        this.vm = vm
        this.expr = expr
        this.cb = cb
        this.oldVal = this.getValue() // Store an old value
    }
    getValue () {
        Dep.target = this // Put yourself in dep.target first
        let value = CompilerUtils.getValue(this.vm, this.expr)
        Dep.target = null // Clear when get is triggered
        return value
    }
    update () { // The observer update method is executed when the update data is changed
        let newVal = CompilerUtils.getValue(this.vm, this.expr)
        if(newVal ! = =this.oldVal) {
            this.cb(newVal)
        }
    }
}
// Publish a subscription
class Dep {
    constructor () {
        this.subs = [] // Store all the watcher
    }
    / / subscribe
    addSub (watcher) {
        this.subs.push(watcher)
    }
    / / release
    notify () { // Execute all watcher update methods
        this.subs.forEach(watcher= > watcher.update())
    }
}

/ / compile
class Compiler {
    constructor (el, vm) { / / incoming '# app' | | document. QuerySelector (' # app)
        this.vm = vm
        // Check whether el is an element node
        this.el = this.isElementNode(el) ? el : document.querySelector(el)
        // console.log(this.el)
        // Store elements in memory using document fragments
        this.fragment = this.el2fragment(this.el)
        // console.log('fragment: ', this.fragment)
        // The compile template replaces the variable in the fragment
        this.compile(this.fragment)
        // Re-insert the fragment back into el
        this.el.appendChild(this.fragment)
    }
    isElementNode (node) { // Determine if there is an element node
        return node.nodeType === 1
    }
    // Save the node to memory
    el2fragment (node) {
        // Create a document fragment
        let fragment = document.createDocumentFragment()
        let firstChild
        while (firstChild = node.firstChild) {
            // console.log(firstChild)
            // Fragment. appendChild is mobile and deletes the corresponding node
            fragment.appendChild(firstChild)
        }
        return fragment
    }
    compile (fragment) { // it is used to compile dom nodes in memory
        let childNodes = [...fragment.childNodes]/ / array
        childNodes.forEach(child= > {
            if (this.isElementNode(child)) {
                this.compileElement(child)
                // Recursively compile child nodes
                this.compile(child)
            } else {
                // console.log('text: ', child)
                this.complieText(child)
            }
        })
    }
    // Compile the node
    compileElement (node) {
        let attrs = [...node.attributes]
        // console.log('attrs:', [...attrs])
        attrs.forEach(attr= > {
            let { name, value:expr } = attr
            // Check whether a node exists
            if (this.isDirecttive(name)) {
                let [, directive] = name.split(The '-')
                let arr
                if (directive.split(':').length > 0) {
                    arr = directive.split(':')}else {
                    arr = [directive]
                }
                let [directiveName, eventName] = arr
                CompilerUtils[directiveName](node, expr, this.vm, eventName)
            }
        })
    }
    // Compiles the text node
    complieText (node) {
        let content = node.textContent
        if (/ \ {\ {(. +?) \} \} /.test(content)) {
            // console.log(content)
            CompilerUtils['text'](node, content, this.vm)
        }
    }
    // Determine whether to command
    isDirecttive (attrName) {
        return attrName.startsWith('v-')}}// Compile tool methods
CompilerUtils = {
    // Take the value based on the expression
    getValue (vm, expr) {
        return expr.split('. ').reduce((data, curKey) = > {
            return data[curKey]
        }, vm.$data)
    },
    setValue (vm, expr, value) {
        expr.split('. ').reduce((data, curKey, index, arr) = > {
            if (index === arr.length - 1) { // Assign the last value to the expression
                return data[curKey] = value
            }
            return data[curKey]
        }, vm.$data)
    },
    updater: { // Update the value of the method
        modelUpdater (node, value) {
            node.value = value
        },
        htmlUpdater (node, value) {
            node.innerHTML = value
        },
        textUpdater (node, value) { // Update the text node
            node.textContent = value
        }
    },
    model (node, expr, vm) { // Node Expr expression VM current instance
        // Assign value to the input field
        let value = this.getValue(vm, expr)
        let fn = this.updater['modelUpdater']
        // Add the observer
        new Watcher(vm, expr, (newVal) => {
            fn(node, newVal) // Assign the new value to the input box
        })
        fn(node, value)
        // View-driven data binds the INPUT event to the V-Model
        node.addEventListener('input', evt => {
            let val = evt.target.value
            this.setValue(vm, expr, val)
        })
    },
    on (node, expr, vm, eventName) {
        node.addEventListener(eventName, (evt) => {
            // console.log(vm, expr)
            vm[expr].call(vm, evt) // Execute the corresponding methods
        })
    },
    getContentVal (vm, expr) {
        let content = expr.replace(/ \ {\ {(. +?) \}\}/g, (... args) => {return this.getValue(vm, args[1])})return content
    },
    text (node, expr, vm) { // Process text nodes
        let fn = this.updater.textUpdater
        let content = expr.replace(/ \ {\ {(. +?) \}\}/g, (... args) => {// Add observer to {{}}
            new Watcher(vm, args[1], () => {
                fn(node, this.getContentVal(vm, expr)) // Return a full string
            })
            return this.getValue(vm, args[1])
        })
        fn(node, content) / / update the value
    },
    html (node, expr, vm) {
        let fn = this.updater['htmlUpdater']
        new Watcher(vm, expr, (newVal) => {
            fn(node, newVal) // Assign the new value to V-html
        })
        fn(node, this.getValue(vm, expr))
    }
}



Copy the code