簡易版vue命令解析

36699 ワード

2、シミュレーションVue.js応答式ソースコードに基づいてv-html命令とv-on命令を実現
  • 回答:命令解析のコードは以下の通りである(完全なコードはhttps://github.com/smallSix6/fed-e-task-liuhuijun/tree/master/fed-e-task-03-01/code/liuzi-minVue ):
  • vue/vue.js
      class Vue {
        constructor(options) {
            // 1、           
            this.$options = options || {}
            this.$data = options.data || {}
            this.$methods = options.methods || {}
            this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el
                // 2、  data         getter   setter,     vue    
            this._proxyData(this.$data)
                //   methods         vue     
            this._proxyMethods(this.$methods)
                // 3、   observer   ,       
            new Observer(this.$data)
                // 4、   compiler   ,          
            new Compiler(this)
        }
        _proxyData(data) {
            //    data       
            Object.keys(data).forEach(key => {
                //   data        vue    
                Object.defineProperty(this, key, {
                    enumerable: true,
                    configurable: true,
                    get() {
                        return data[key]
                    },
                    set(newValue) {
                        if (newValue !== data[key]) {
                            data[key] = newValue
                        }
                    }
                })
            })
        }
        _proxyMethods(methods) {
            Object.keys(methods).forEach(key => {
                //   methods        vue    
                this[key] = methods[key]
            })
        }
    }
    
  • vue/compiler.js
    class Compiler {
        constructor(vm) {
                this.el = vm.$el
                this.vm = vm
                this.compile(this.el)
            }
            //     ,           
        compile(el) {
                let childNodes = el.childNodes
                Array.from(childNodes).forEach(node => {
                    if (this.isTextNode(node)) {
                        //       
                        this.compileText(node)
                    } else if (this.isElementNode(node)) {
                        //       
                        this.compileElement(node)
                    }
                    //    node   ,      ,      ,      compile
                    if (node.childNodes && node.childNodes.length) {
                        this.compile(node)
                    }
                })
            }
            //       ,    
        compileElement(node) {
                //          
                Array.from(node.attributes).forEach(attr => {
                    //        
                    let attrName = attr.name
                    if (this.isDirective(attrName)) {
                        // v-text --> text
                        attrName = attrName.substr(2)
                        let key = attr.value
                        if (attrName.startsWith('on')) {
                            const event = attrName.replace('on:', '') //      
                                //     
                            return this.eventUpdate(node, key, event)
                        }
                        this.update(node, key, attrName)
                    }
                })
            }
            //       ,       
        compileText(node) {
            let reg = /\{\{(.+?)\}\}/
            let value = node.textContent
            if (reg.test(value)) {
                let key = RegExp.$1.trim()
                node.textContent = value.replace(reg, this.vm[key])
                    //    watcher   ,          
                new Watcher(this.vm, key, (newValue) => {
                    node.textContent = newValue
                })
            }
    
        }
        update(node, key, attrName) {
            let updateFn = this[attrName + 'Updater']
            updateFn && updateFn.call(this, node, this.vm[key], key)
        }
        eventUpdate(node, key, event) {
            this.onUpdater(node, key, event)
        }
    
    
        //    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-model   
        modelUpdater(node, value, key) {
                node.value = value
                new Watcher(this.vm, key, (newValue) => {
                        node.value = newValue
                    })
                    //     
                node.addEventListener('input', () => {
                    this.vm[key] = node.value
                })
            }
            //    v-on   
        onUpdater(node, key, event) {
            node.addEventListener(event, (e) => this.vm[key](e))
        }
    
    
    
        //            
        isDirective(attrName) {
                return attrName.startsWith('v-')
            }
            //            
        isTextNode(node) {
                return node.nodeType === 3
            }
            //            
        isElementNode(node) {
            return node.nodeType === 1
        }
    }
    
  • vue/index.html
    <!DOCTYPE html>
    <html lang="cn">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Mini Vue</title>
        <script src="./vue/dep.js"></script>
        <script src="./vue/watcher.js"></script>
        <script src="./vue/compiler.js"></script>
        <script src="./vue/observer.js"></script>
        <script src="./vue/vue.js"></script>
    </head>
    
    <body>
        <div id="app">
            <h1 v-on:click="myClick">    </h1>
            <h3>{{ msg }}</h3>
            <h1>v-text</h1>
            <div v-text="msg"></div>
            <h1 v-html='html'></h1>
            <input type="text" v-model="msg">
            <input type="text" v-model="count">
            <h1>{{dog.name}}</h1>
        </div>
    </body>
    <script>
        let vm = new Vue({
            el: '#app',
            data: {
                msg: 'object1111',
                dog: {
                    name: ''
                },
                html: ''
            },
            methods: {
                myClick() {
                    alert('    ')
                },
                clickHandler() {
                    this.dog
                        //   name          
                    this.$set(dog, name, 'Trump')
                }
            }
        })
    </script>
    
    </html>
    
  • v-htmlの実現構想:
  • vueインスタンスの初期化時にnew Compiler解析命令と補間式
  • を呼び出す.
  • Compilerクラスでテンプレートをコンパイルし、テキストノードと要素ノードを処理する.compile(this.el)
  • 要素ノードを処理する方法はthisを参照する.compileElement(node)
  • compileElement(node)メソッドではこのノードの下の属性を巡回し,命令であれば使いたい命令処理関数を呼び出す.
  • textUpdater:v-text命令
  • の処理
  • htmlUpdater:v-html命令
  • の処理
  • modelUpdater:v-model命令
  • の処理
  • onUpdater:v-on命令
  • の処理

  • v-onの実現構想:
  • vueインスタンスの初期化時にnew Compiler解析命令と補間式
  • を呼び出す.
  • methodsのメンバーをvueインスタンスに注入します.this.proxyMethods(this.$methods)
  • Compilerクラスでテンプレートをコンパイルし、テキストノードと要素ノードを処理する.compile(this.el)
  • 要素ノードを処理する方法はthisを参照する.compileElement(node)
  • 属性名がonで始まるとthisが呼び出される.eventUpdate(node, key, event)
  • eventUpdateは、v-on命令
  • を処理するためにonUpdaterを呼び出す.