一个简单的 MVVM 实现
2016-09-22 20:03
281 查看
简介
一个简单的带有双向绑定的 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: 绑定事件,
*: 绑定特定属性
参考
本实现主要参考 rivets.js 的 es6 分支, 其中 Observer 类是参考 adapter.js 实现.Binding 就是 bindings.js 对应的简化, 相当于其他 MVVM 中指令, ViewModel 对应 view.js.
PS: 由于双向绑定只是简单的实现, 因此指令中的值只能是 Model 的属性
下面的代码采用 es6 实现, 如果想要本地运行的话, 请 clone git@github.com:445141126/mvvm.git, 然后执行 npm install 安装依赖, 最后 npm run dev 开启开发服务器, 浏览器中打开 http://127.0.0.1:8080/
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>MVVM</title> </head> <body> <div id="vm"> 设置style: <input type="text" data-value="text" data-*="text: style"> <br/> 显示: <input type="checkbox" data-value="show"> <br/> style: <span data-show="show" data-html="text"></span> <br/> <button data-on="reverse: click">reverse</button> </div> <script src="bundle.js"></script> </body> </html>
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)
相关文章推荐
- JavaScript数据绑定实现一个简单的 MVVM 库
- 基于vue实现一个简单的MVVM框架
- 利用 JavaScript 数据绑定实现一个简单的 MVVM 库
- 230行实现一个简单的MVVM
- 如何实现一个简单的MVVM框架
- js实现一个简单的MVVM框架示例
- 230行实现一个简单的MVVM
- 一个简单地MVVM模式实现,直接上码
- JavaScript数据绑定实现一个简单的 MVVM 库
- J2ME应用实例——一个简单的计算器实现(附源代码)
- 一个简单的用户登录接口asp实现
- 利用xmlHttp实现一个简单的Ajax无刷新
- 分形介绍 && 一个简单的Kotch curve实现代码
- 实现一个简单的图形菜单
- 一个简单的windows位图文件类的实现
- 一个购物车的简单实现(多层开发)
- 一个简单留言本的实现
- 一个简单的自定义ClassLoader的实现
- 又一个简单实现AJAX的好东西!!
- J2ME应用实例——一个简单的计算器实现(附源代码)