美文网首页
Vue实现原理

Vue实现原理

作者: peroLuo | 来源:发表于2019-02-14 11:49 被阅读0次

    vue实现原理

    1、了解Object的属性defineProperty

    const Book = {}
        let name = ''
        Object.defineProperty(Book, 'name', {
            set: (value) => {
                name = value
            },
            get: () => {
                return `《${name}》`
            }
        })
        Book.name = 'vue实现原理'
        console.log(Book.name) // 《vue实现原理》
    

    2、vue中mvvm的实现: 数据变化更新视图,视图变化更新数据

    • 在非MVVM中的视图更新,是通过事件触发决定dom层该如何渲染,是一种事件行为操作。
    • 在MVVM中,通过劫持数据,监听数据变化,来决定一系列的dom操作,是数据驱动操作。
    • 在vue2.0中是通过Object.defineProperty监听对象属性值是否发生变化,来驱动dom渲染操作。

    3、 实现过程

    1. 实现一个监听器Observer,用来劫持并监听data所有属性,如果有变动的,就通知订阅者。
    2. 实现一个订阅器Dep,用于收集订阅者,当属性变化,执行对应的订阅者函数。
    3. 实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
    4. 实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器.

    3.1 监听器Observer(递归处理)

    /**
     * @author Pero
     * @date 2019/2/13
     * @Description: Observer实例
    */
    class Observer {
        constructor (data) {
            this.data = data
            this.init(data)
        }
        init (data) {
            Object.keys(data).forEach((key) => {
                this.defineReactive(data, key, data[key]);
            });
        }
        /**
         * 监听data中子属性的值
         * @param data
         */
        observerChildProperty (data) {
            if (!data || typeof data !== 'object') {
                return;
            } else {
                this.init(data)
            }
        }
        /**
         * @param data: 对象
         * @param key: 当前对象的键
         * @param val: 当前对象的值
         * 监听vue中data的属性,变化时执行对应的订阅者处理器
         */
        defineReactive (data, key, val) {
            const dep = new Dep();
            Object.defineProperty(data, key, {
                enumerable: true,
                configurable: true,
                /**
                 * @author Pero
                 * @date 2019/2/14
                 * @Description: 取值时添加订阅器
                 */
                get: () => {
                    if (Dep.target) {
                        dep.addSub(Dep.target);
                    }
                    return val;
                },
                /**
                 * 当data数据发生改变,执行订阅器存储的函数(watcher.update方法)
                 * @param newVal
                 */
                set: (newVal) =>  {
                    if (newVal === val) {
                        return;
                    }
                    val = newVal;
                    dep.notify();
                }
            });
            /*监听对象的子属性*/
            this.observerChildProperty(val);
        }
    }
    

    3.2 订阅器 Dep

    /**
     * @author Pero
     * @date 2019/2/13
     * @Description:  Dep订阅者收集器subs
    */
    class Dep {
        constructor () {
            this.subs = []
        }
        addSub (sub) {
            this.subs.push(sub);
        }
        notify () {
            this.subs.forEach((sub) => {
                sub.update();
            });
        }
    }
    Dep.target = null;
    

    3.3 订阅者 Watcher

    class Watcher {
        /**
         * @param vm vue实例
         * @param exp 监听的值
         * @param cb 当值改变时的回调函数
         */
        constructor (vm, exp, cb) {
            this.cb = cb;
            this.vm = vm;
            this.exp = exp;
            // 初始添加到订阅器
            this.value = this.get();
        }
        update () {
            const value = this.vm.data[this.exp];
            const oldVal = this.value;
            if (value !== oldVal) {
                this.value = value;
                this.cb.call(this.vm, value, oldVal);
            }
        }
        get () {
            Dep.target = this;
            // 强制执行监听器里的get函数
            const value = this.vm.data[this.exp]
            Dep.target = null;
            return value;
        }
    }
    

    3.4 解析器 Compile

    /**
     * @author Pero
     * @date 2019/2/14
     * @Description: Compile解析器
    */
    class Compile {
        constructor (el, vm) {
            this.vm = vm;
            this.el = document.querySelector(el);
            this.fragment = null;
            this.init();
        }
    
        /**
         * 初始化dom树,解析dom结构
         */
        init () {
            if (this.el) {
                this.fragment = this.nodeToFragment(this.el);
                this.compileElement(this.fragment);
                this.el.appendChild(this.fragment);
            }
        }
    
        /**
         * 将node添加到一个dom容器
         * @param el
         * @returns {DocumentFragment}
         */
        nodeToFragment (el) {
            const fragment = document.createDocumentFragment();
            let child = el.firstChild;
            while (child) {
                fragment.appendChild(child);
                child = el.firstChild
            }
            return fragment;
        }
    
        /**
         * 解析dom树结构
         * @param el
         */
        compileElement(el) {
            const childNodes = el.childNodes;
            [].slice.call(childNodes).forEach((node) => {
                const reg = /\{\{(.*)\}\}/;
                const text = node.textContent;
                if (this.isElementNode(node)) {
                    this.compile(node);
                } else if (this.isTextNode(node) && reg.test(text)) {
                    this.compileText(node, reg.exec(text)[1]);
                }
                if (node.childNodes && node.childNodes.length) {
                    this.compileElement(node);
                }
            });
        }
    
        /**
         * 分发指令
         * @param node
         */
        compile (node) {
            const nodeAttrs = node.attributes;
            Array.prototype.forEach.call(nodeAttrs, (attr) => {
                const attrName = attr.name;
                if (this.isDirective(attrName)) {
                    const exp = attr.value;
                    const dir = attrName.substring(2);
                    if (this.isEventDirective(dir)) {  // 事件指令
                        this.compileEvent(node, this.vm, exp, dir);
                    } else {  // v-model 指令
                        this.compileModel(node, this.vm, exp, dir);
                    }
                    node.removeAttribute(attrName);
                }
            });
        }
    
        /**
         * 解析textNode
         * @param node
         * @param exp
         */
        compileText (node, exp) {
            const initText = this.vm[exp];
            this.updateText(node, initText);
            new Watcher(this.vm, exp,  (value) => {
                this.updateText(node, value);
            });
        }
    
        /**
         * 解析事件
         * @param node
         * @param vm
         * @param exp
         * @param dir
         */
        compileEvent (node, vm, exp, dir) {
            const eventType = dir.split(':')[1];
            const cb = vm.methods && vm.methods[exp];
            if (eventType && cb) {
                node.addEventListener(eventType, cb.bind(vm), false);
            }
        }
    
        /**
         * 解析v-model
         * @param node
         * @param vm
         * @param exp
         * @param dir
         */
        compileModel (node, vm, exp, dir) {
            let val = this.vm[exp];
            this.modelUpdater(node, val);
            new Watcher(this.vm, exp,  (value) => {
                this.modelUpdater(node, value);
            });
    
            node.addEventListener('input', (e) => {
                const newValue = e.target.value;
                if (val === newValue) {
                    return;
                }
                this.vm[exp] = newValue;
                val = newValue;
            });
        }
    
        /**
         * 更新textNode的值
         * @param node
         * @param value
         */
        updateText (node, value) {
            node.textContent = typeof value == 'undefined' ? '' : value;
        }
    
        /**
         * 更新v-model的值
         * @param node
         * @param value
         * @param oldValue
         */
        modelUpdater (node, value, oldValue) {
            node.value = typeof value == 'undefined' ? '' : value;
        }
        isDirective (attr) {
            return attr.indexOf('v-') == 0;
        }
        isEventDirective (dir) {
            return dir.indexOf('on:') === 0;
        }
        isElementNode (node) {
            return node.nodeType == 1;
        }
        isTextNode (node) {
            return node.nodeType == 3;
        }
    }
    

    3.5 实例Vue

    /**
     * @author Pero
     * @date 2019/2/13
     * @Description: Vue实例
    */
    class Vue {
        /**
         * @param options
         */
        constructor (options) {
            this.data = options.data;
            this.methods = options.methods;
            Object.keys(this.data).forEach((key) => {
                this.proxyKeys(key);
            });
            new Observer(this.data);
            new Compile(options.el, this);
            options.mounted.call(this);
        }
        /**
         * this.data[xx]可以直接通过this[xx]修改属性值
         * @param key
         */
        proxyKeys (key) {
            Object.defineProperty(this, key, {
                enumerable: false,
                configurable: true,
                get: () => {
                    return this.data[key];
                },
                set: (newVal) => {
                    this.data[key] = newVal;
                }
            });
        }
    }
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>vue</title>
    </head>
    <style>
        #app {
            text-align: center;
        }
    </style>
    <body>
    <div id="app">
        <h2>{{title}}</h2>
        <input v-model="name">
        <h1>{{name}}</h1>
        <div>
            <button v-on:click="clickMe">click me!</button>
        </div>
    </div>
    </body>
    <script src="js/dep.js"></script>
    <script src="js/observer.js"></script>
    <script src="js/watcher.js"></script>
    <script src="js/compile.js"></script>
    <script src="js/index.js"></script>
    <script type="text/javascript">
        new Vue({
            el: '#app',
            data: {
                title: 'hello world',
                name: 'abc'
            },
            methods: {
                clickMe: function () {
                    this.title = 'hello world2';
                }
            },
            mounted: function () {
                window.setTimeout(() => {
                    this.title = '你好';
                }, 1000);
            }
        });
    </script>
    </html>
    

    相关文章

      网友评论

          本文标题:Vue实现原理

          本文链接:https://www.haomeiwen.com/subject/hwzseqtx.html