一个只有十行的精简MVVM框架
本文来自网易云社区。
前言
MVVM模式相信做前端的人都不陌生,去网上搜MVVM,会出现一大堆关于MVVM模式的博文,但是这些博文大多都只是用图片和文字来进行抽象的概念讲解,对于刚接触MVVM模式的新手来说,这些概念虽然能够读懂,但是也很难做到理解透彻。因此,我写了这篇文章。
这篇文章旨在通过代码的形式让大家更好的理解MVVM模式,相信大多数人读了这篇文章之后再去看其他诸如regular、vue等基于MVVM模式框架的源码,会容易很多。
如果你对MVVM模式已经很熟悉并且也已经研读过并深刻理解了当下主流的前端框架,可以忽略下面的内容。如果你没有一点JavaScript基础,也请先去学习下再来阅读读此文。
引子
来张图来镇压此文:
MVVM是Model-View-ViewModel的缩写。简单的讲,它将View与Model层分隔开,利用ViewModel层将Model层的数据经过一定的处理变成适用于View层的数据结构并传送到View层渲染界面,同时View层的视图更新也会告知ViewModel层,然后ViewModel层再更新Model层的数据。
我们用一段学生信息的代码作为引子,然后一步步再重构成MVVM模式的样子。
编写类似下面结构的学生信息:
Name: Jessica Bre
Height: 1.8m
Weight: 70kg
用常规的js代码是这样的:
const student = {
'first-name': 'Jessica',
'last-name': 'Bre',
'height': 180,
'weight': 70,
}
const root = document.createElement('ul')
const nameLi = document.createElement('li')
const nameLabel = document.createElement('span')
nameLabel.textContent = 'Name: '
const name_ = document.createElement('span')
name_.textContent = student['first-name'] + ' ' + student['last-name']
nameLi.appendChild(nameLabel)
nameLi.appendChild(name_)
const heightLi = document.createElement('li')
const heightLabel = document.createElement('span')
heightLabel.textContent = 'Height: '
const height = document.createElement('span')
height.textContent = '' + student['height'] / 100 + 'm'
heightLi.appendChild(heightLabel)
heightLi.appendChild(height)
const weightLi = document.createElement('li')
const weightLabel = document.createElement('span')
weightLabel.textContent = 'Weight: '
const weight = document.createElement('span')
weight.textContent = '' + student['weight'] + 'kg'
weightLi.appendChild(weightLabel)
weightLi.appendChild(weight)
root.appendChild(nameLi)
root.appendChild(heightLi)
root.appendChild(weightLi)
document.body.appendChild(root)
好长的一堆代码呀!别急,下面我们一步步优化!
DRY一下如何
程序设计中最广泛接受的规则之一就是“DRY”: "Do not Repeat Yourself"。很显然,上面的一段代码有很多重复的部分,不仅与这个准则相违背,而且给人一种不舒服的感觉。是时候做下处理,来让这段学生信息更"Drier"。
可以发现,代码里写了很多遍document.createElement来创建节点,但是由于列表项都是相似的结构,所以我们没有必要一遍一遍的写。因此,进行如下封装:
const createListItem = function (label, content) {
const li = document.createElement('li')
const labelSpan = document.createElement('span')
labelSpan.textContent = label
const contentSpan = document.createElement('span')
contentSpan.textContent = content
li.appendChild(labelSpan)
li.appendChild(contentSpan)
return li
}
经过这步转化之后,整个学生信息应用就变成了这样:
const student = {
'first-name': 'Jessica',
'last-name': 'Bre',
'height': 180,
'weight': 70,
}
const createListItem = function (label, content) {
const li = document.createElement('li')
const labelSpan = document.createElement('span')
labelSpan.textContent = label
const contentSpan = document.createElement('span')
contentSpan.textContent = content
li.appendChild(labelSpan)
li.appendChild(contentSpan)
return li
}
const root = document.createElement('ul')
const nameLi = createListItem('Name: ', student['first-name'] + ' ' + student['last-name'])
const heightLi = createListItem('Height: ', student['height'] / 100 + 'm')
const weightLi = createListItem('Weight: ', student['weight'] + 'kg')
root.appendChild(nameLi)
root.appendChild(heightLi)
root.appendChild(weightLi)
document.body.appendChild(root)
是不是变得更短了,也更易读了?即使你不看createListItem函数的实现,光看const nameLi = createListItem('Name: ', student['first-name'] + ' ' + student['last-name'])也能大致明白这段代码时干什么的。
但是上面的代码封装的还不够,因为每次创建一个列表项,我们都要多调用一遍createListItem,上面的代码为了创建name,height,weight标签,调用了三遍createListItem,这里显然还有精简的空间。因此,我们再进一步封装:
const student = {
'first-name': 'Jessica',
'last-name': 'Bre',
'height': 180,
'weight': 70,
}
const createList = function(kvPairs){
const createListItem = function (label, content) {
const li = document.createElement('li')
const labelSpan = document.createElement('span')
labelSpan.textContent = label
const contentSpan = document.createElement('span')
contentSpan.textContent = content
li.appendChild(labelSpan)
li.appendChild(contentSpan)
return li
}
const root = document.createElement('ul')
kvPairs.forEach(function (x) {
root.appendChild(createListItem(x.key, x.value))
})
return root
}
const ul = createList([
{
key: 'Name: ',
value: student['first-name'] + ' ' + student['last-name']
},
{
key: 'Height: ',
value: student['height'] / 100 + 'm'
},
{
key: 'Weight: ',
value: student['weight'] + 'kg'
}])
document.body.appendChild(ul)
有没有看到MVVM风格的影子?student对象是原始数据,相当于Model层;createList创建了dom树,相当于View层,那么ViewModel层呢?仔细观察,其实我们传给createList函数的参数就是Model的数据的改造,为了让Model的数据符合View的结构,我们做了这样的改造,因此虽然这段函数里面没有独立的ViewModel层,但是它确实是存在的!聪明的同学应该想到了,下一步就是来独立出ViewModel层了吧~
// Model
const tk = {
'first-name': 'Jessica',
'last-name': 'Bre',
'height': 180,
'weight': 70,
} //View
const createList = function(kvPairs){
const createListItem = function (label, content) {
const li = document.createElement('li')
const labelSpan = document.createElement('span')
labelSpan.textContent = label
const contentSpan = document.createElement('span')
contentSpan.textContent = content
li.appendChild(labelSpan)
li.appendChild(contentSpan)
return li
}
const root = document.createElement('ul')
kvPairs.forEach(function (x) {
root.appendChild(createListItem(x.key, x.value))
})
return root
}
//ViewModel
const formatStudent = function (student) {
return [
{
key: 'Name: ',
value: student['first-name'] + ' ' + student['last-name']
},
{
key: 'Height: ',
value: student['height'] / 100 + 'm'
},
{
key: 'Weight: ',
value: student['weight'] + 'kg'
}]
}
const ul = createList(formatStudent(tk))
document.body.appendChild(ul)
这看上去更舒服了。但是,最后两行还能封装~
const smvvm = function (root, {model, view, vm}) {
const rendered = view(vm(model))
root.appendChild(rendered)
}
smvvm(document.body, {
model: tk,
view: createList,
vm: formatStudent
})
这种写法,熟悉vue或者regular的同学,应该会觉得似曾相识吧?
让我们来加点互动
前面学生信息的身高的单位都是默认m,如果新增一个需求,要求学生的身高的单位可以在m和cm之间切换呢?
首先需要一个变量来保存度量单位,因此这里必须用一个新的Model:
const tk = {
'first-name': 'Jessica',
'last-name': 'Bre',
'height': 180,
'weight': 70,
}
const measurement = 'cm'
为了让tk更方便的被其他模块重用,这里选择增加一个measurement数据源,而不是直接修改tk。
在视图部分要增加一个radio单选表单,用来切换身高单位。
一个只有十行的精简MVVM框架的更多相关文章
- 一个只有十行的精简MVVM框架(下篇)
本文来自网易云社区. 让我们来加点互动 前面学生信息的身高的单位都是默认m,如果新增一个需求,要求学生的身高的单位可以在m和cm之间切换呢? 首先需要一个变量来保存度量单位,因此这里必须用一个新的Mo ...
- 一个只有十行的精简MVVM框架(上篇)
本文来自网易云社区. 前言 MVVM模式相信做前端的人都不陌生,去网上搜MVVM,会出现一大堆关于MVVM模式的博文,但是这些博文大多都只是用图片和文字来进行抽象的概念讲解,对于刚接触MVVM模式的新 ...
- ViewModel从未如此清爽 - 轻量级WPF MVVM框架Stylet
Stylet是我最近发现的一个WPF MVVM框架, 在博客园上搜了一下, 相关的文章基本没有, 所以写了这个入门的文章推荐给大家. Stylet是受Caliburn Micro项目的启发, 所以借鉴 ...
- 实现一个类 Vue 的 MVVM 框架
Vue 一个 MVVM 框架.一个响应式的组件系统,通过把页面抽象成一个个组件来增加复用性.降低复杂性 主要特色就是数据操纵视图变化,一旦数据变化自动更新所有关联组件~ 所以它的一大特性就是一个数据响 ...
- 迷你MVVM框架 avalonjs 学习教程18、一步步做一个todoMVC
大凡出名的MVC,MVVM框架都有todo例子,我们也搞一下看看avalon是否这么便宜. 我们先从react的todo例子中扒一下HTML与CSS用用. <!doctype html> ...
- 如何实现一个简单的MVVM框架
接触过web开发的同学想必都接触过MVVM,业界著名的MVVM框架就有AngelaJS.今天闲来无事,决定自己实现一个简单的MVVM框架玩一玩.所谓简单,就是仅仅实现一个骨架,仅表其意,不摹其形. 分 ...
- 剖析手写Vue,你也可以手写一个MVVM框架
剖析手写Vue,你也可以手写一个MVVM框架# 邮箱:563995050@qq.com github: https://github.com/xiaoqiuxiong 作者:肖秋雄(eddy) 温馨提 ...
- 迷你MVVM框架 avalonjs 1.3.7发布
又到每个月的15号了,现在avalon已经固定在每个月的15号发布新版本.这次发布又带来许多新特性,让大家写码更加轻松,借助于"操作数据即操作DOM"的核心理念与双向绑定机制,现在 ...
- Vue.js-----轻量高效的MVVM框架(一、初识Vue.js)
1.什么是Vue.js? 众所周知,最近几年前端发展非常的迅猛,除各种框架如:backbone.angular.reactjs外,还有模块化开发思想的实现库:sea.js .require.js .w ...
随机推荐
- JQuery获取和设置Select选项的常用方法总结
1.获取select 选中的 text: $("#cusChildTypeId").find("option:selected").text(); $(&q ...
- 【[USACO08FEB]酒店Hotel】
比较基础的线段树了 我们要维护最大连续子串,这个可以说是一个比较套路的操作了 我们在[SHOI2009]会场预约这道题中已经比较深刻的认识到了这个套路了 对于这道题,我们显然要知道一个区间内最大的全为 ...
- C/C++心得-结构体
先说句题外话,个人认为,基本上所有的高级语言被设计出来的最终目的是降低软件开发难度,提升软件开发人员素质和团队协作能力,降低软件维护的难度.在学习语言的时候,可以从这么方面来推测各种语言语法设计的原因 ...
- luogu P4275 萃香的请柬
嘟嘟嘟 打表不难发现,序列的长度以及序列中1的个数都是斐波那契数列.因为第 i 秒1的个数由 i - 1的1和 i - 2的0变换而来,那么f[i] = f[i - 1] + f[i - 2].序列的 ...
- php中文正则匹配
今天接到一个需求,用户昵称系统需要将昵称输入的字符类型限定为 中文,英文,数字, -,_ 显然这个应该用正则来实现,那么最终的规则是怎么样的呢?示例代码如下: <?php $str = '我爱北 ...
- 时钟系统与UART
关于时钟设置原理: 关于如何设置系统时钟的思路. 其中锁定时间需要一个寄存器,PLL需要一个寄存器(MPLLCL),分频还需要一个寄存器,总计三个寄存器.步骤如下: 1. 上电后,FCLK=Fin 2 ...
- POST接口底层方法
对于POST请求的接口,我们如何调用它获取到数据,这其中自然少不了底层代码 底层公共类的书写 public class ThirdOpenAPIService { public static Thir ...
- java中printf的用法
目前printf支持以下格式: %c 单个字符 %d 十进制整数 %f 十进制浮点数 ...
- Css中路径data:image/png;base64的用法详解 (转载)
大家可能注意到了,网页上有些图片的src或css背景图片的url后面跟了一大串字符,比如: background-image:url(data:image/png;base64, iVBORw0KGg ...
- 一致性模型(consistency model)
比如下面的例子: 一行X值在节点M和节点N上有副本 客户端A在节点M上写入行X的值 一段时间后,客户端B在节点N上读取行X的值 一致性模型所要做的就是决定客户端B能否看到客户端A写的值.一致性模型分为 ...