从零实现MVVM模式的Web前端框架的雏形
TNTWeb - 全称腾讯新闻前端团队,组内小伙伴在Web前端、NodeJS开发、UI设计、移动APP等大前端领域都有所实践和积累。
【从零实现MVVM模式的Web前端框架的雏形】目前团队主要支持腾讯新闻各业务的前端开发,业务开发之余也积累沉淀了一些前端基础设施,赋能业务提效和产品创新。
团队倡导开源共建,拥有各种技术大牛,团队Github地址:https://github.com/tnfe
本文作者ancai,项目地址:https://github.com/ancai/nvm
文章图片
一、前言 本文是自己在之前的业余时间做过的一个小项目的实践总结,主要是介绍关于现代Web前端框架的设计实现思路,参考了Vue2.x版本的设计思想。
二、MVVM模式 web开发的程序员对于MVC的设计思想应该并不陌生,网上相关的文章也有很多。M(model)-V(view)-C(Controller)代表了一种程序组织架构的模式,实现了数据、视图和逻辑的有效解耦。M,表示数据状态,V代表视图界面,Controller用于处理数据和视图之间的交互逻辑。后端的web框架(如Java中的springMVC、Struts2;nodejs中的express、koa等)依然沿用此设计模式至今。
不过在前端领域,涉及到用户界面的编程,MVC的架构模式现在已经不是主流了。究其原因,主要有几方面:
- 业务逻辑和UI混合在一起
- 不利于扩展和复用
- 无法验证测试
- 增加数据的复杂性
文章图片
后来微软提出了MVVM的架构模式,其中和MVC的区别之处是VM取代了C,即ViewModel(视图模型)替换了控制器。主要应用在用户界面编程的领域,提供了双向的数据绑定功能。View的变化会自动反映在ViewModel上,通过ViewModel来通知模型,数据模型Model变化时同样通过VM来关联到视图。MVVM架构模式的出现给前端的开发带来了新的生机,使得前端开发更容易编写单元测试。同时借助于ViewModel的中间桥梁,开发人员不用再关注一些DOM操作的交互细节,程序更简洁和优雅,结合组件化的编程设计思想,前端模块更易复用和拓展。
ViewModel,一般由框架来实现,主要是提供了双向数据绑定功能。
更多关于的比较推荐两篇文章:什么是MVVM框架?|MVC vs MVVM: Key Differences with Examples
三、流程架构 项目里面涉及到对模板、视图、数据、指令以及依赖的管理。
文章图片
从大的方面看,框架主要完成了模板和数据之间的双向绑定。使用数据劫持的相关技术,对数据的变化的进行追踪,同时引入发布订阅模式完成对属性的依赖管理。另一方面则涉及模板的解析,提取模板中的指令和表达式,关联到数据,来初始化视图。模板和数据通过watcher衔接起来,这样数据的变化直接映射到视图;同时需要监听视图上的输入相关的事件,这样当输入变化时直接映射到数据模型。
文章图片
从工程处理的角度上看,可以分成四个步骤:初始化、数据监测、模板编译和框架功能。初始化的过程主要是获得调用方传递的配置信息,模型数据和模板的预处理,某些关键属性代理到上层。数据监测的环节,涉及对普通JavaScript对象的劫持处理,针对数组的增强处理,通过一个发布-订阅模式完成对数据属性的依赖注入,同时关联到watcher。模板编译环节,解析一个类似HTML的模板,提取里面的变量表达式和相关指令,通过watcher关联到数据模型。
文章图片
上面给出了项目中的核心类图,Observer实现对数据的劫持,Dep相当于是一个发布-订阅器同时关联到Watcher和Observer,Compile负责解析模板,进一步提取获得Directives类同时关联到Parser和Watcher,Parser用于解析指令生产视图UI,Watcher使用到一个Batcher来批量处理更新变化。
文章图片
项目代码的目录结构比较简单,src目录下总共有14个代码文件,总代码行数在千行左右,理解起来不是很困难。index.js为入口文件,模板编译相关的处理逻辑均放在compile文件夹下,observer.js文件放置数据劫持相关的处理逻辑,value.js里面是字符串类型的属性表达式转化为对象数据的处理逻辑,dep.js实现了一个简单的发布-订阅模式来关联数据状态的变化以及watcher实例,watcher.js里面有详细的注释说明在哪个环节被调用。另外,整个项目使用webpack简单构建,打包后的体积也很小。
四、数据劫持实现
文章图片
利用ES5中提供的object.defineProperty 这一API,重新定义get和set属性访问器实现对数据的劫持。主要是对普通对象和数组的增强处理,监测数据的变化,通知依赖。在首次访问属性时注入依赖,通常发生在首次初始化模板时,获得状态属性的表达式,在此时注入状态属性的依赖,这里的依赖就是watcher实例;当数据发生变化时,通过dep的发布-订阅模式通知watcher实例,进而更新视图状态。
五、模板编译实现
文章图片
模板的编译,没有使用虚拟DOM结点,直接从原始的html模板入手,遍历字符串模板,收集里面的指令。同时解析表达式,获得对模型数据的依赖注入,在首次加载时,会关联到watcher上。里面针对不同类型的指令,对应不同的解析处理器,分为普通指令、事件指令、双向绑定指令、if/for指令等。其中涉及到if/for的指令,需要创建新的执行上下文,关联子级的环境数据。
六、关键代码实现
文章图片 |
文章图片 |
七、总结 本项目没有使用到virtual node(虚拟DOM结点),直接由字符串模板编译成了视图部分。其中稍微有点难理解的部分:属性表达式的提取解析、依赖的注入、if和for指令的处理、watcher的实现。
作为一个能力较弱的程序员,还无法像前端技术大牛那样创造出一个成熟的前端框架。通过本文的简单阐述,希望能帮助到有相同困惑的小伙伴加深对MVVM以及前端框架原理的理解,大牛请自动忽略。本项目的完整源码参见最后部分,给出了详细的示例。
八、代码示例 源码
示例
九、团队 TNTWeb - 腾讯新闻前端团队,TNTWeb致力于行业前沿技术探索和团队成员个人能力提升。为前端开发人员整理出了小程序以及web前端技术领域的最新优质内容,每周更新?,欢迎star,github地址:https://github.com/tnfe/TNT-Weekly
文章图片
推荐阅读
- Docker应用:容器间通信与Mariadb数据库主从复制
- 一个人的碎碎念
- 我从来不做坏事
- 关于QueryWrapper|关于QueryWrapper,实现MybatisPlus多表关联查询方式
- MybatisPlus使用queryWrapper如何实现复杂查询
- python学习之|python学习之 实现QQ自动发送消息
- 从蓦然回首到花开在眼前,都是为了更好的明天。
- 日志打卡
- 西湖游
- 孩子不是实现父母欲望的工具——林哈夫