欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

基于Vue的简易MVVM

程序员文章站 2022-07-01 20:54:10
...

MVVM设计模式:

基于Vue的简易MVVM

index.html

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

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="renderer" content="webkit|ie-comp|ie-stand">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">
    <meta name="apple-mobile-web-app-title" content="Web App">
    <meta name="format-detection" content="telephone=no">
    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=yes, minimum-scale=1, maximum-scale=1">
    <title>Document</title>
    <style>

    </style>
</head>

<body>
    <body> 
        <div id="app">
            <input type="text" v-model="message">
            {{message}}
        </div>
    </body>
</body>
<script src="./watcher.js"></script>
<script src="./observer.js"></script>
<script src="./compile.js"></script>
<script src="./mvvm.js"></script>
<!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->
<script type="text/javascript">

let vm = new MVVM({
    el: '#app',
    data: {
        message: '12345'
    }
})

</script>

</html>

mvvm.js

class MVVM {
    constructor(options) {
        this.$el = options.el;
        this.$data = options.data;

        if (this.$el) {
            // 数据劫持
            new Observer(this.$data)
            this.proxyData(this.$data)
            // 模板编译
            new Compile(this.$el, this)
        }
    }
    proxyData(data) {
        Object.keys(data).forEach(key => {
            Object.defineProperty(this, key, {
                get() {
                    return data[key]
                },
                set(newValue) {
                    data[key] = newValue
                }
            })
        })
    }
}

compile.js

class Compile {
    constructor(el, vm) {
        this.el = this.isElementNode(el) ? el :document.querySelector(el)
        this.vm = vm
        if (this.el) {
            let fragment = this.node2fragment(this.el)
            
            this.compile(fragment)

            this.el.appendChild(fragment)
        }
    }

    node2fragment(el) {
        let fragment = document.createDocumentFragment()
        let firstChild
        while(firstChild = el.firstChild) {
            fragment.appendChild(firstChild)
        }
        return fragment // 内存中的节点
    }
    // 编译指令元素
    compileElement(node) {
        let attrs = node.attributes 
        Array.from(attrs).forEach(attr => {
            let attrName = attr.name
            if (this.isDirective(attrName)) {
                let expr = attr.value
                let [, type] = attrName.split('-')
                CompileUtil[type](node, this.vm, expr)
            }
        })
    }
    //编译文本节点
    compileText(node) {
        let expr = node.textContent
        let reg = /\{\{([^}]+)\}\}/g  
        if (reg.test(expr)) {
            CompileUtil['text'](node, this.vm, expr)
        }
    }
    compile(fragment) {
        let childNodes = fragment.childNodes 
        Array.from(childNodes).forEach(node => {
            if (this.isElementNode(node)) {
                // 元素节点
                this.compileElement(node)
                this.compile(node)
            } else {
                // 文本节点
                this.compileText(node)

            }
        })
    }

    // 辅助函数
    isElementNode(node) {
        return node.nodeType === 1
    }
    isDirective(name) {
        return name.includes('v-')
    }
}

// 编译工具方法
CompileUtil = {
    getVal(vm, expr) {
        let result
        expr = expr.split('.')
        expr.reduce((prev, next) => {
            result = prev[next]
        }, vm.$data)
        return result
    },
    getTextVal(vm, expr) {
        return expr.replace(/\{\{([^}]+)\}\}/g, (...arguments) => {
            return this.getVal(vm, arguments[1]) 
        })
    },
    setVal(vm, expr, value) {
        expr = expr.split('.')
        return expr.reduce((prev, next, currentIndex) => {
            if (currentIndex === expr.length - 1) {
                return prev[next] = value
            }
            return prev[next]
        }, vm.$data)
    },
    // 编译- 文本处理
    text(node, vm, expr) {
        let updaterFn = this.updater['textUpdater']
        let value = this.getTextVal(vm, expr) // expr存在{{a.b.c.d}}这种结构,需要对这种情况进行取值

        expr.replace(/\{\{([^}]+)\}\}/g, (...arguments) => {
            new Watcher(vm, arguments[1], (newValue) => {
                updaterFn && updaterFn(node, this.getTextVal(vm, expr))
            })
        })

        updaterFn && updaterFn(node, value)
    },
    // 编译- 指令处理
    model(node, vm, expr) { 
        let updaterFn = this.updater['modelUpdater']

        new Watcher(vm, expr, (newValue) => {
            updaterFn && updaterFn(node, this.getVal(vm, expr))
        })
        node.addEventListener('input', (e) => {
            let newValue = e.target.value
            this.setVal(vm, expr, newValue)
        })
        let abc = this.getVal(vm, expr)
        updaterFn && updaterFn(node, this.getVal(vm, expr))
    },
    updater: {
        textUpdater(node, value) {
            node.textContent = value
        },
        modelUpdater(node, value) {
            console.log(444, value);
            node.value = value
        }
    }
}

observer.js

// 数据劫持
class Observer {
    constructor(data) {
        this.observe(data)
    }
    observe(data) {
        if (!data || typeof data !== 'object') {
            return;
        }  
        Object.keys(data).forEach(key => {
            this.defineReactive(data, key, data[key])
            this.observe(data[key]) // 深度递归劫持
        })
    }
    defineReactive(obj, key, value) {
        let that = this
        let dep = new Dep()
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get() {
                Dep.target && dep.addSub(Dep.target)
                return value
            },
            set(newValue) {
                if (newValue != value) {
                    that.observe(newValue)
                    value = newValue
                    dep.notify()
                }
            }
        })
    }
}

// 发布订阅  
class Dep {
    constructor() {
        this.subs = []
    }
    addSub(watcher) {
        this.subs.push(watcher)
    }
    notify() {
        this.subs.forEach(watcher => {
            watcher.update()
        })
    }
}

watcher.js

// 观察者
class Watcher {
    constructor(vm, expr, cb) {
        this.vm = vm
        this.expr = expr
        this.cb = cb
        this.value = this.get()

    }
    getVal(vm, expr) {
        let result
        expr = expr.split('.')
        expr.reduce((prev, next) => {
            result = prev[next]
        }, vm.$data)
        return result
    }
    get() {
        Dep.target = this 

        let value = this.getVal(this.vm, this.expr) 
        
        Dep.target = null
        
        return value
    }
    update() {
        let newValue = this.getVal(this.vm, this.expr)
        let oldValue = this.value

        if (newValue != oldValue) {
            this.cb(newValue) 
        }
    }
}
相关标签: vue