仿vue写一个极简版MVVM框架(二)(实现$nextTick优化)

188 阅读1分钟

目录结构

image.png

此次我们在上篇文章juejin.cn/post/700028… (一)的基础上新增了$nextTick异步更新策略 没有修改的文件就不展示了,只展示修改和新增的文件

image.png

index.html


<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="module">

        import MVVM from './MVVM.js'
        const vm = new MVVM({
            el: '#app',
            data: {
                name: 'wenhao',
                age: 21,
                intro: ''
            },
            mounted() {
                this.name = 1111
                this.name = 2222
                this.name = 3333
                this.name = 5555
                this.name = 666
                this.name = 777
                this.name = 'name'
                this.$nextTick(() => {
                    console.log(document.getElementById('name').textContent);
                })

            },
            methods: {
            

            },
            // watch: { 
            //     name(newValue, oldValue) {
            //         console.log('watch name');
            //     }
            // },
            // computed: {
            //     intro() {
            //         console.log('computed intro');
            //         return this.name + this.age
            //     }
            // }
        })


    </script>
</head>

<body>
    <div id="app">
        <p>
            我叫1<data id="name">name</data>
        </p>
        <p>
            我的年龄2 <data>age</data>
        </p>
        <p>
            我的年龄 3<data>age</data>
        </p>
        <p>
            我的年龄 3<data>age</data>
        </p>
        <p>
            intro:<data>intro</data>
        </p>

    </div>
</body>

</html>

dep.js

import queue from "./queue.js"
export default class Dep { // 观察者模式

    constructor() {
        this.subs = []

    }
    depend() {

        if (Dep.target && !this.subs.includes(Dep.target)) {
            this.subs.push(Dep.target)
        }
    }
    notify() {
        queue.collect(this.subs)     // 改动:我们把所有的要执行的watcher丢到队列中
    }
}


queue.js (异步缓冲队列)

export default {  
    _buffer: [],
    _pending: false,
    _nextTickCbs: [],
    collect(data) {
        if (Array.isArray(data)) {
            this._buffer = this._buffer.concat(data)
        } else {
            this._buffer.push(data)
        }
        if (!this._pending) {
            this._pending = true
            Promise.resolve().then(() => {
                this.release()
                this._pending = false
            })
        }

    },
    release() {
        this._buffer = [...new Set(this._buffer)]
        console.log(this._buffer);

        while (this._buffer.length > 0) {

            this._buffer.shift().update()
        }

        while (this._nextTickCbs.length > 0) {
            this._nextTickCbs.shift()()
        }
    },
    nextTick(cb) {
        this._nextTickCbs.push(cb)
    }
}

mvvm.js



import Watcher from "./Watcher.js"
import observe from "./observe.js"
import queue from "./queue.js"
export default class MVVM {
    constructor({ el, data, mounted, methods, watch, computed }) {
        this.el = document.querySelector(el)
        this._data = data
        this.methods = methods
        this._watch = watch
        this._computed = computed
        this.$initData(this._data)
        this.$compile(this.el)
        this.$handleWatch()
        this.$handleComputed()

        this.__proto__.__proto__ = this._data  // 改动:把data数据放到后面的原型链中不然会覆盖nextTick方法

        Object.assign(this, this.methods)
        mounted && mounted.call(this)
    }
    $initData(data) {
        observe(data) // 数据劫持
    }
    $compile(el) { // 模板编译
        if (el) {
            if (el.tagName == 'DATA') {
                new Watcher(this._data, el.textContent.trim(), (newVal, oldVal) => {
                    el.textContent = newVal
                }).update()
                return
            }
            const attrs = el.getAttributeNames && el.getAttributeNames()

            if (attrs && attrs.includes(':value')) {
                new Watcher(this._data, el.getAttribute(':value'), (newVal, oldVal) => { // 在数据劫持的基础上,加入回调

                    el.value = newVal
                }).update()

            }
            if (attrs && attrs.includes('@input')) {
                console.log(el);

                const prop = el.getAttribute('@input')

                el.addEventListener('input', this.methods[prop].bind(this))
            }
            if (el.nodeType === 1 && el.tagName !== 'DATA') {

                el.childNodes.forEach(element => {
                    this.$compile(element)
                });
            }
        }

    }
    $handleWatch() {
        if (this._watch)
            Object.keys(this._watch).forEach(n => {
                new Watcher(this._data, n, this._watch[n].bind(this._data))
            })
    }
    $handleComputed() {
        if (this._computed)
            Object.keys(this._computed).forEach(n => {
                new Watcher(this._data, n, this._computed[n].bind(this._data), {
                    computed: true
                })
            })
    }
    $nextTick(cb) {         // 改动:增加nextTick方法
        queue.nextTick(cb.bind(this))
    }
}