一个只有十行的精简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的同学,应该会觉得似曾相识吧?
本文来自网易云社区,经作者顾静授权发布。
了解网易云 :
网易云官网:https://www.163yun.com
网易云社区:https://sq.163yun.com/blog
网易云新用户大礼包:https://www.163yun.com/gift
更多网易研发、产品、运营经验分享请访问网易云社区。
一个只有十行的精简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 框架.一个响应式的组件系统,通过把页面抽象成一个个组件来增加复用性.降低复杂性 主要特色就是数据操纵视图变化,一旦数据变化自动更新所有关联组件~ 所以它的一大特性就是一个数据响 ...
- 依赖注入[4]: 创建一个简易版的DI框架[上篇]
本系列文章旨在剖析.NET Core的依赖注入框架的实现原理,到目前为止我们通过三篇文章(<控制反转>.<基于IoC的设计模式>和< 依赖注入模式>)从纯理论的角度 ...
- 迷你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"的核心理念与双向绑定机制,现在 ...
随机推荐
- mxnet 神经网络训练和预测
https://mxnet.incubator.apache.org/tutorials/basic/module.html import logging import random logging. ...
- 学会WCF之试错法——超时
服务契约 [ServiceContract] public interface IService { [OperationContract] string GetData(int value); [O ...
- Web Mercator公开的小秘密
网上已经有好多作者都不吝笔墨,写了好多有关 Web Mercator这个坐标系的前世今生.多搜罗多摄入,我们会得到很多有用的信息.今天讨论到 3758,3857,102100,900913…… 这些I ...
- java项目如何使用ajax来减少页面的刷新
之前写项目,总是用重定向或请求转发,导致每做一步动作就会刷新页面,客户体验不好,而且效率低下,这种问题可以使用ajax来有效的解决此类问题的发生. 我使用的框架:Spring boot 数据库:mys ...
- Java项目排查cpu负载高
背景 我负责的其中一个项目在空负载的情况下,CPU占用率依然保持着100%左右,线上.测试.开发的服务都一样:是什么导致的呢?在开发环境我查看了请求流量,这个流量可以忽略但CPU占用率一直在60%-1 ...
- Haroopad 中文不显示
https://blog.csdn.net/zgf19930504/article/details/51508111 1. 选择文件--> 偏好设置 2. 选择 编辑器--> 编辑--&g ...
- DQL-条件查询
二 :条件查询 语法:select 列表名 from 表名 where 筛选条件 例如: select salary from employees where salary> ...
- Oracle session相关数据字典(一)
(一)session相关视图 (1)视图 v$session v$active_session_history dba_hist_active_session_history 如果是多节点数据库,v$ ...
- Zabbix——设置模板
前提条件: Zabbix版本为4.0 可以正常使用Server 1. 添加H3C网络交换机的CPU和内存模板,并配置自动发现. 注意,需要添加完成后,重新进行搜索后再此进入才有下列选项. 添加CPU的 ...
- C语言的乱七八糟
Note For C Linux下C编程基础(gcc/gdb/make使用) 一.vi学习 二.初探emacs 三.gcc编译器 3.1 gcc所支持后缀名解释 后缀名 解释 后缀名 解释 .c C原 ...