Vue實現(xiàn)原理

vue實現(xiàn)原理

1、了解Object的屬性defineProperty

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

2、vue中mvvm的實現(xiàn): 數(shù)據(jù)變化更新視圖,視圖變化更新數(shù)據(jù)

  • 在非MVVM中的視圖更新,是通過事件觸發(fā)決定dom層該如何渲染,是一種事件行為操作。
  • 在MVVM中,通過劫持數(shù)據(jù),監(jiān)聽數(shù)據(jù)變化,來決定一系列的dom操作,是數(shù)據(jù)驅(qū)動操作。
  • 在vue2.0中是通過Object.defineProperty監(jiān)聽對象屬性值是否發(fā)生變化,來驅(qū)動dom渲染操作。

3、 實現(xiàn)過程

  1. 實現(xiàn)一個監(jiān)聽器Observer,用來劫持并監(jiān)聽data所有屬性,如果有變動的,就通知訂閱者。
  2. 實現(xiàn)一個訂閱器Dep,用于收集訂閱者,當屬性變化,執(zhí)行對應的訂閱者函數(shù)。
  3. 實現(xiàn)一個訂閱者Watcher,可以收到屬性的變化通知并執(zhí)行相應的函數(shù),從而更新視圖。
  4. 實現(xiàn)一個解析器Compile,可以掃描和解析每個節(jié)點的相關指令,并根據(jù)初始化模板數(shù)據(jù)以及初始化相應的訂閱器.

3.1 監(jiān)聽器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]);
        });
    }
    /**
     * 監(jiān)聽data中子屬性的值
     * @param data
     */
    observerChildProperty (data) {
        if (!data || typeof data !== 'object') {
            return;
        } else {
            this.init(data)
        }
    }
    /**
     * @param data: 對象
     * @param key: 當前對象的鍵
     * @param val: 當前對象的值
     * 監(jiān)聽vue中data的屬性,變化時執(zhí)行對應的訂閱者處理器
     */
    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數(shù)據(jù)發(fā)生改變,執(zhí)行訂閱器存儲的函數(shù)(watcher.update方法)
             * @param newVal
             */
            set: (newVal) =>  {
                if (newVal === val) {
                    return;
                }
                val = newVal;
                dep.notify();
            }
        });
        /*監(jiān)聽對象的子屬性*/
        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 監(jiān)聽的值
     * @param cb 當值改變時的回調(diào)函數(shù)
     */
    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;
        // 強制執(zhí)行監(jiān)聽器里的get函數(shù)
        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結(jié)構(gòu)
     */
    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樹結(jié)構(gòu)
     * @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);
            }
        });
    }

    /**
     * 分發(fā)指令
     * @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>
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容