博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
一个简单的 MVVM 实现
阅读量:4963 次
发布时间:2019-06-12

本文共 6410 字,大约阅读时间需要 21 分钟。

简介

一个简单的带有双向绑定的 MVVM 实现.

使用

新建一个 ViewModel 对象, 参数分别为 DOM 元素以及绑定的数据即可.

指令

本 MVVM 的指令使用 data 数据, 即 data-html = "text" 表示这个 DOM 元素的 innerHTMl 为 model 中的 text 属性.

对某些指令还可以添加参数, 比如 data-on="reverse:click", 表示 DOM 元素添加 click 事件, 处理函数为 model 中的 reverse 属性.

  • value: 可以在 input 中使用, 只对 checkbox 进行特殊处理
  • text, html: 分别修改 innerText 和 innerHTML
  • show: 控制指定元素显示与否
  • each: 循环 DOM 元素, 每个元素绑定新的 ViewModel, 通过 $index 可以获取当前索引, $root 表示根 ViewModel 的属性
  • on: 绑定事件,
  • *: 绑定特定属性

参考

本实现主要参考 , 其中 Observer 类是参考 实现.

Binding 就是 对应的简化, 相当于其他 MVVM 中指令, ViewModel 对应 .

PS: 由于双向绑定只是简单的实现, 因此指令中的值只能是 Model 的属性

下面的代码采用 es6 实现, 如果想要本地运行的话, 请 clone git@github.com:445141126/mvvm.git, 然后执行 npm install 安装依赖, 最后 npm run dev 开启开发服务器, 浏览器中打开 http://127.0.0.1:8080/

    
MVVM
设置style:
显示:
style:
import _ from 'lodash'function defined(obj) {    return !_.isUndefined(obj) && !_.isNull(obj)}class Observer {    constructor(obj, key, cb) {        this.obj = obj        this.key = key        this.cb = cb        this.obj.$$callbacks = this.obj.$$callbacks || {}        this.obj.$$callbacks[this.key] = this.obj.$$callbacks[this.key] || []        this.observe()    }    observe() {        const observer = this        const obj = observer.obj        const key = observer.key        const callbacks = obj.$$callbacks[key]         let value = obj[key]        const desc = Object.getOwnPropertyDescriptor(obj, key)        if(!(desc && (desc.get || desc.set))) {            Object.defineProperty(obj, key, {                get() {                    return value                },                set(newValue) {                    if(value !== newValue) {                        value = newValue                                                callbacks.forEach((cb) => {                            cb()                        })                    }                }            })        }        if(callbacks.indexOf(observer.cb) === -1) {            callbacks.push(observer.cb)        }    }    unobserve() {        if(defined(this.obj.$$callbacks[this.key])) {            const index = this.obj.$$callbacks[this.key].indexOf(this.cb)            this.obj.$$callbacks[this.key].splice(index, 1)        }    }    get value() {        return this.obj[this.key]    }    set value(newValue) {        this.obj[this.key] = newValue    }}class Binding {    constructor(vm, el, key, binder, type) {        this.vm = vm        this.el = el        this.key = key        this.binder = binder        this.type = type        if(_.isFunction(binder)) {            this.binder.sync = binder        }        this.bind = this.bind.bind(this)        this.sync = this.sync.bind(this)        this.update = this.update.bind(this)        this.parsekey()        this.observer = new Observer(this.vm.model, this.key, this.sync)    }    parsekey() {        this.args = this.key.split(':').map((k) => k.trim())        this.key = this.args.shift()    }    bind() {        if(defined(this.binder.bind)) {            this.binder.bind.call(this, this.el)        }        this.sync()    }    unbind() {        if(defined(this.observer)) {            this.observer.unobserve()        }        if(defined(this.binder.unbind)) {            this.binder.unbind(this.this.el)        }    }    sync() {        if(defined(this.observer) && _.isFunction(this.binder.sync)) {            this.binder.sync.call(this, this.el, this.observer.value)        }    }    update() {        if(defined(this.observer) && _.isFunction(this.binder.value)) {            this.observer.value = this.binder.value.call(this, this.el)        }    }}class ViewModel {    constructor(el, model) {        this.el = el        this.model = model        this.bindings = []        this.compile(this.el)        this.bind()    }    compile(el) {        let block = false        if(el.nodeType !== 1) {            return        }        const dataset = el.dataset        for(let data in dataset) {            let binder = ViewModel.binders[data]            let key = dataset[data]            if(binder === undefined) {                binder = ViewModel.binders['*']            }            if(defined(binder)) {                this.bindings.push(new Binding(this, el, key, binder))            }        }        if(!block) {            el.childNodes.forEach((childEl) => {                this.compile(childEl)            })        }    }    bind() {        this.bindings.sort((a, b) => {            let aPriority = defined(a.binder) ? (a.binder.priority || 0) : 0            let bPriority = defined(b.binder) ? (b.binder.priority || 0) : 0            return bPriority - aPriority        })        this.bindings.forEach(binding => {            binding.bind()        })    }    unbind() {        this.bindins.forEach(binding => {            binding.unbind()        })    }}ViewModel.binders = {    value: {        bind(el) {            el.addEventListener('change', this.update)        },        sync(el, value) {            if(el.type === 'checkbox') {                el.checked = !!value            } else {                el.value = value            }        },        value(el) {            if(el.type === 'checkbox') {                return el.checked            } else {                return el.value            }         }    },    html: {        sync(el, value) {            el.innerHTML = value        }    },    show: {        priority: 2000,        sync(el, value) {            el.style.display = value ? '' : 'none'        }    },    each: {        block: true    },    on: {        bind(el) {            el.addEventListener(this.args[0], () => { this.observer.value() })        }    },    '*': {        sync(el, value) {            if(defined(value)) {                el.setAttribute(this.args[0], value)            } else {                el.removeAttribute(this.args[0])            }        }    }}const obj = {    text: 'Hello',     show: false,    reverse() {        obj.text = obj.text.split('').reverse().join('')    }}const ob = new Observer(obj, 'a', () => {     console.log(obj.a) })obj.a = 'You should see this in console'ob.unobserve()obj.a = 'You should not see this in console'const vm = new ViewModel(document.getElementById('vm'), obj)
posted on
2016-09-22 20:03 阅读(
...) 评论(
...)

转载于:https://www.cnblogs.com/wbin91/p/5897746.html

你可能感兴趣的文章
二叉树的深度优先遍历与广度优先遍历
查看>>
2016工作计划
查看>>
python利用socket传输文件
查看>>
JavaEEMybatis基础整理
查看>>
剑指offer(41)和为S的连续正数序列
查看>>
IOS-UI-UILable
查看>>
bzoj1345
查看>>
对员工宽容的公司 都死掉了
查看>>
python基础五
查看>>
BZOJ 1013: [JSOI2008]球形空间产生器sphere
查看>>
DevExpress TreeList添加右键菜单问题
查看>>
AEAI Portal V3.5.2门户集成平台发版说明
查看>>
[转]我们这么努力,也不过是为了成为一个普通人。
查看>>
G面经prepare: Chucked Palindrome
查看>>
CSS3 -webkit-transform
查看>>
在Linux系统里安装Virtual Box的详细步骤
查看>>
手动卸载的vs2010
查看>>
C#_初识之HelloWorld
查看>>
QT5:先导篇 加载资源
查看>>
Linux的日常(1)--Linux系统
查看>>