基于Vue的简易MVVM

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) 
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值