Vue源碼核心(手寫)

vue核心就是雙向綁定我們都知道是用Object.defineProperty
但是我們在用vue得時候 可以通過this.xx訪問修改屬性, 不需要this.$data
還有如何實現(xiàn)數(shù)據(jù)變動dom就更新
如何解析dom上得指令和方法

下面代碼將一步一步實現(xiàn)一個小vue

<!DOCTYPE html>
<html>

<head>
    <title></title>
</head>

<body>
    <div id="app">
        <input type="text" v-model="name">
        <div v-html="name"></div>
        <button @click="changeName">
            按鈕
        </button>
    </div>

</body>
<script type="text/javascript">
    // 負(fù)責(zé)收集更新函數(shù),屬于發(fā)布訂閱模式中的調(diào)度中心
    class Dep {
        constructor() {
            this._listeners = []
        }

        add(obj) {
            this._listeners.push(obj)
        }

        notify() { // 更新dom
            this._listeners.forEach(item => item.update && item.update())
        }
    }
    // 觀察者,直接跟DOM,負(fù)責(zé)將更新函數(shù)傳遞給Dep
    class Watcher {
        constructor(options) {
            this.update = options.update
            Dep.target = this
            this.val = options.allVal[options.key] // 觸發(fā)get方法 將數(shù)據(jù)插入到Dep
            Dep.target = null
        }
    }
    class Vue {
        constructor(options) {
            this.$options = options
            this.$data = options.data()
            // 劫持this, 始得vue實例得可以直接this.xxx訪問 this.$data得屬性
            this.observerRoot() // 劫持根實例
            this.observerData(this.$data) // 劫持$data 修改每一個值都知道
            this.createFragment() // 創(chuàng)建虛擬元素,避免頻繁操作真實DOM
            this.compile() // 編譯元素,解析指令、事件、方法
        }

        observerRoot() {
            Object.keys(this.$data).forEach(item => {
                Object.defineProperty(this, item, {
                    get: function () {
                        return this.$data[item]
                    },
                    set: function (newVal) {
                        this.$data[item] = newVal
                    }
                })
            })
        }

        // 劫持$data
        observerData(obj) {
            if (!obj || typeof obj != 'object') return
            Object.keys(obj).forEach(item => {
                let val = obj[item] // 防止死循環(huán)
                if (typeof val === 'object') {
                    this.observerData(obj[item])
                } else {
                    let dep = new Dep()
                    Object.defineProperty(obj, item, {
                        get: function () {
                            Dep.target && dep.add(Dep.target)
                            return val
                        },
                        set: function (newVal) {
                            val = newVal
                            dep.notify()
                        }
                    })
                }
            })
        }

        // 創(chuàng)建虛擬dom
        createFragment() {
            this.$el = document.querySelector(this.$options.el)
            this.$fragment = document.createDocumentFragment()

            while (this.$el.firstChild) {
                this.$fragment.appendChild(this.$el.firstChild)
            }
        }

        // 編譯
        compile() {
            // 1. 解析
            this._compileElement(this.$fragment)
            // 2. 重新append到根元素下
            this.$el.appendChild(this.$fragment)
        }

        _compileElement(ele) {
            Array.from(ele.childNodes).forEach(node => {
                // 解析節(jié)點(diǎn)
                this._compileNode(node)
                if (node.childNodes) this._compileElement(node)
            })
        }

        _compileNode(node) {
            // 1. 解析節(jié)點(diǎn)包含的指令、事件...
            let res = this._checkHasBind(node)
            // 2. 處理解析結(jié)果
            this._resolveBind(node, res)
        }

        _checkHasBind(node) {
            // 獲取node節(jié)點(diǎn)上得屬性
            let attributes = node.attributes
            let dir_reg = /^v\-\w*$/
            let event_reg = /^\@\w*/
            let result = {
                directives: [], // 指令
                events: [] // 事件
            }

            if (attributes) Array.from(attributes).forEach(item => {
                if (dir_reg.test(item.name)) result.directives.push({ name: item.name, value: item.value })
                if (event_reg.test(item.name)) result.events.push({ name: item.name, value: item.value })
            })
            return result
        }

        _resolveBind(node, res) {
            let _this = this
            let data = this.$data
            let { directives, events } = res
            // 解析指令
            directives.length && directives.forEach(item => {
                // node有指令得節(jié)點(diǎn) item指令
                let update = () => {
                    switch (item.name) {
                        case 'v-model':
                            node.oninput = (val) => {
                                _this[item.value] = node.value || ''
                            }
                            node.value = data[item.value]
                            break;
                        case 'v-html':
                            node.innerHTML = data[item.value]
                            break;
                        default: break;
                    }
                }

                update()

                // 此處這個配置,沒有直接和dep交互。所以是發(fā)布訂閱模式而不是觀察者模式。
                let watch_option = {
                    allVal: data,
                    key: item.value,
                    directive: item.name,
                    node: node,
                    update
                }
                new Watcher(watch_option)
            })

            // 解析綁定事件
            events.length && events.forEach(item => {
                let method_name = item.value
                // 解析出來的事件,是以@為開頭,需要處理
                let target_event = item.name.slice(1, item.name.length)

                node.addEventListener(target_event, () => {
                    this.$options.methods[method_name].call(this)
                })
            })
        }
    }

    let app = new Vue({
        el: '#app',
        data() {
            return {
                name: '二狗子'
            }
        },
        methods: {
            changeName() {
                this.name = Math.floor(Math.random() * 10)
            }
        }
    })
</script>

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

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