本文来自网易云社区

前言

MVVM模式相信做前端的人都不陌生,去网上搜MVVM,会出现一大堆关于MVVM模式的博文,但是这些博文大多都只是用图片和文字来进行抽象的概念讲解,对于刚接触MVVM模式的新手来说,这些概念虽然能够读懂,但是也很难做到理解透彻。因此,我写了这篇文章。

这篇文章旨在通过代码的形式让大家更好的理解MVVM模式,相信大多数人读了这篇文章之后再去看其他诸如regular、vue等基于MVVM模式框架的源码,会容易很多。

如果你对MVVM模式已经很熟悉并且也已经研读过并深刻理解了当下主流的前端框架,可以忽略下面的内容。如果你没有一点JavaScript基础,也请先去学习下再来阅读读此文。

引子

来张图来镇压此文:

MVVMModel-View-ViewModel的缩写。简单的讲,它将ViewModel层分隔开,利用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框架(上篇)的更多相关文章

  1. 一个只有十行的精简MVVM框架(下篇)

    本文来自网易云社区. 让我们来加点互动 前面学生信息的身高的单位都是默认m,如果新增一个需求,要求学生的身高的单位可以在m和cm之间切换呢? 首先需要一个变量来保存度量单位,因此这里必须用一个新的Mo ...

  2. 一个只有十行的精简MVVM框架

    本文来自网易云社区. 前言 MVVM模式相信做前端的人都不陌生,去网上搜MVVM,会出现一大堆关于MVVM模式的博文,但是这些博文大多都只是用图片和文字来进行抽象的概念讲解,对于刚接触MVVM模式的新 ...

  3. ViewModel从未如此清爽 - 轻量级WPF MVVM框架Stylet

    Stylet是我最近发现的一个WPF MVVM框架, 在博客园上搜了一下, 相关的文章基本没有, 所以写了这个入门的文章推荐给大家. Stylet是受Caliburn Micro项目的启发, 所以借鉴 ...

  4. 实现一个类 Vue 的 MVVM 框架

    Vue 一个 MVVM 框架.一个响应式的组件系统,通过把页面抽象成一个个组件来增加复用性.降低复杂性 主要特色就是数据操纵视图变化,一旦数据变化自动更新所有关联组件~ 所以它的一大特性就是一个数据响 ...

  5. 依赖注入[4]: 创建一个简易版的DI框架[上篇]

    本系列文章旨在剖析.NET Core的依赖注入框架的实现原理,到目前为止我们通过三篇文章(<控制反转>.<基于IoC的设计模式>和< 依赖注入模式>)从纯理论的角度 ...

  6. 迷你MVVM框架 avalonjs 学习教程18、一步步做一个todoMVC

    大凡出名的MVC,MVVM框架都有todo例子,我们也搞一下看看avalon是否这么便宜. 我们先从react的todo例子中扒一下HTML与CSS用用. <!doctype html> ...

  7. 如何实现一个简单的MVVM框架

    接触过web开发的同学想必都接触过MVVM,业界著名的MVVM框架就有AngelaJS.今天闲来无事,决定自己实现一个简单的MVVM框架玩一玩.所谓简单,就是仅仅实现一个骨架,仅表其意,不摹其形. 分 ...

  8. 剖析手写Vue,你也可以手写一个MVVM框架

    剖析手写Vue,你也可以手写一个MVVM框架# 邮箱:563995050@qq.com github: https://github.com/xiaoqiuxiong 作者:肖秋雄(eddy) 温馨提 ...

  9. 迷你MVVM框架 avalonjs 1.3.7发布

    又到每个月的15号了,现在avalon已经固定在每个月的15号发布新版本.这次发布又带来许多新特性,让大家写码更加轻松,借助于"操作数据即操作DOM"的核心理念与双向绑定机制,现在 ...

随机推荐

  1. 三.Shell脚本提取文件名称和所在的目录

    一·简介 提取文件名称或者目录,一般都会使用到#,##,%和%%,但是他们的区别很容易记混淆了.在一下4种方式中,目标匹配字符是不在结果中. #:表示从左开始算起,并且截取第一个匹配的字符 ##:表示 ...

  2. PHP------知识复习

    PHP概述 (1)PHP(Hypertext  Perprocessor)超级文本预处理器 (2) PHP是一种在服务器端执行的嵌入HTML文档的脚本语言 (3) 是一种网站开发语言(B/S结构) ( ...

  3. ASP.NET Web API编程——客户端调用

    可以使用HttpClient这个调用Web API,下面是HttpClient的定义,列举了一些常用的方法,其中还有一些没有列举,包括重载的方法. public class HttpClient : ...

  4. python2.7 安装Django

    目前Django最新版是2.0,不支持Python2,在使用pip 安装的时候会报错,pip默认安装的是最新的稳定版本 使用pip指定安装的版本:pip install django==1.11.4 ...

  5. Eclipse安装Sonarlint插件

    这里安装的是Sonarlint3.6.插件安装非常简单.插件比Sonar更为简单快捷. 一.首先通过点击Eclipse上方Help菜单会出现一个下拉列表,点击其中的Eclipse MarketPlac ...

  6. UglifyJS 压缩选项

    UglifyJS 压缩选项 1.使用逗号运算符连接简单语句 2.使用点符号代替中括号属性     foo [“bar”]→foo.bar 3.删除逻辑上走不到的代码 4.删除调试代码    debug ...

  7. sqoop 1.99.7 安装及配置

    一  下载sqoop 1.99.7 http://mirror.bit.edu.cn/apache/sqoop/1.99.7/ 二 解压安装文件 三 配置Sqoop 环境变量 最后把mysql的驱动j ...

  8. python中的sum函数.sum(axis=1)

    看起来挺简单的样子,但是在给sum函数中加入参数.sum(a,axis=0)或者是.sum(axis=1) 就有点不解了 在我实验以后发现 我们平时用的sum应该是默认的axis=0 就是普通的相加 ...

  9. ARM Cortex-A53 Cache与内存的映射关系以及Cache的一致性分析

    ARM Cortex-A53 Cache与内存的映射关系以及Cache的一致性分析 题记:如果文章有理解不对的地方,欢迎大家批评指正,谢谢大家. 摘要:本文以Cortex-A53为例,首先分析Cach ...

  10. Swift_错误处理

    Swift_错误处理 点击查看源码 //错误处理 func test() { //错误枚举 需ErrorType协议 enum ErrorEnum: Error { case `default` // ...