博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
我对MVVM的学习笔记
阅读量:5930 次
发布时间:2019-06-19

本文共 3014 字,大约阅读时间需要 10 分钟。

前言

最近在学习MVVM的实现原理,刚好在sf上看到了一文,写的非常好,摘出Vue.js中的部分源码,改造后完成了一个简单的MVVM实现。实现了双向数据绑定,我自己在学习的过程中,也照着这篇文章中的源码重新实现了一遍。不同之处在于,我尽量将原来的实现写成了ES6的写法,比如使用class代替构造函数,将observer,dep,watcher,compiler分成不同的模块,然后使用import,export来互相引入,导出,最后使用打包了一下。所以这篇文章是对上面文章的学习总结,不会写的很细。大家也可以读一下上面的文章,简单易懂。

我重新写过的,有兴趣的可以看看。

整体结构

这个简易的MVVM总共由index.js(入口文件),compiler.js,dep.js,observer.js,watcher.js几部分组成。

.├── README.md├── dest│   ├── toy.es2015.js│   ├── toy.js│   └── toy.umd.js├── examples│   └── index.html├── package.json├── rollup.config.js└── src    ├── compiler.js    ├── dep.js    ├── index.js    ├── observer.js    └── watcher.js

index.js是整个框架的入口,比如我给这个框架起了个名字叫Toy,入口文件导出的其实就是Toy的构造函数:

//引入其它模块import { observe } from './observer.js'import { Compiler } from './compiler.js'import { Watcher } from './watcher.js'//具体实现class Toy {    constructor(options){        //...    }}//导出模块export { Toy }

初始化的过程分两步:

  1. 劫持监听所有属性,通过Object.defineProperty将数据变成响应式的,同时在getset上做一些手脚。

  2. 编译html模板,事实上我们在使用框架时写的html已经填充了很多框架自己的指令,语法,所以要先进行编译替换才能正确展示视图。

实现所有属性的监听就是通过Object.defineProperty递归地定义所以属性。每一个对象都会有一个对应的Observer实例,其中的每一个属性都对应有一个Dep的实例depdep使用自增的uid标识,作用是记录这个属性被那些订阅者(Watcher的实例)订阅了,好在属性变化时,通过遍历dep.subs去通知所有订阅了这个属性的watcher去做对应的更新。

实现Compiler就是对带有框架特殊API的模板进行编译,指令解析。同时将DOM与数据关联起来(其实是通过Watcher实现的)。

本质上说

每个部分负责的事情我是这样理解的:

  • index.js 框架的入口,提供对外的构造函数。

  • observer.js 将数据变成响应式,同时通过dep收集依赖(Watcher实例)。

  • dep.js 收集依赖用的,在get中收集依赖,在set中通知对应依赖更新。

  • watcher.js 数据的订阅者,一个Watcher的实例由vm,exp,cb,deps等几部分组成,vm是对ViewModel的引用,触发get方法将watcher自身添加至depsubs中时会用到,exp则是当前Watcher实例监听的表达式,即数据的keycb则是更新数据的回调。

    vm的数据改变后,会触发对应的set方法,这个属性对应的dep会通知所有的subs去执行自身的update方法,而这个update方法的内容其实只是this.cb.call(this.vm, value, oldValue)cb实际上是调用了updateFn(在compiler.js中绑定的),这时才将DOM的数据真正更新。

  • compiler.js 编辑DOM模板,并为每个node节点通过new Watcher的方式将属性表达式expupdateFn(真正更新DOM的函数)node关联,然后配合响应式数据就做到了viewmodel的双向绑定。

所以整个框架的运行过程是这样的:

  1. observe所有数据,改写了每个数据的get和set方法,并为每个数据关联了一个dep(通过闭包实现)。

  2. new Compiler开始编译模板,编译过程中,可以提取出指令,v-text,v-html等,可以分析出事件函数v-click和绑定的表达式,这时通过self.compileText(node, RegExp.$1),self.compile(node)将DOM节点和表达式建立关联。

  3. 建立的关联,是DOM节点和数据表达式的关联,这一步是通过new Watcher实现的

  4. new Watcher的时候,Watcher实例会将Dep.target这个全局属性指向自身,然后出发一下需要监听属性的getter,这时dep会将Watcher实例添加到它的subs中,Watcher实例也会标记一下这个dep已经添加过自己了,防止重复添加。这时dep和Watcher实例已经关联起来了,数据的变化可以通知到对应的Watcher实例,Watcher实例的update方法会正确地更新DOM。

其实到这里,数据的双向绑定就已经实现了。

过程中学习到的一些细节

记录一些在学习过程中遇到的小tips,其实都是很基础的东西。

  • : 表示一个节点及其内部节点的文本内容。之前一直都是用innerText的,看了MDN才知道innerText原来是IE私有的,textContent才是标准属性。而且innerText受样式影响,还会触发重排,所以还是用textContent代替吧。

  • : 这个API有一个很有意思的行为:如果被插入的节点已经存在于当前文档的文档树中,则那个节点会首先从原先的位置移除,然后再插入到新的位置.,当时我在看compiler.jsnode2Fragment方法:

node2Fragment(el){    let fragment = document.createDocumentFragment()    let child    while(child = el.firstChild){        fragment.appendChild(child)    }    return fragment}

当时很不解为什么while循环能成按照预期执行,我在浏览器多次调用el.firstChild拿到的也始终是第一个子节点,看了这个API的文档才发现还有这么个行为!

  • : 可以方便地获取DOM节点的属性,返回值是一个对象,其中name是属性名,value是属性值。

最后

终于明白了简易MVVM框架的运作原理,也发现了一些底层API的知识,写成一些总结,这篇文章中没有贴很多代码去说实现,因为一文已经很详细了,我也是按照这个去学习的,所以我记录的是我个人的一些思想上的总结,所以可能要先看代码才能了解。分享出来,希望能有人从中受益 :)

转载地址:http://wtutx.baihongyu.com/

你可能感兴趣的文章
Linux下系统诊断shell命令fuser
查看>>
C语言中 位运算总结
查看>>
狱警在死刑监区的21年:曾拉回临刑喊冤的嫌犯
查看>>
NVDIMM原理与应用之一:基本原理
查看>>
View is not applicable for the arguments 解决方案
查看>>
nagios+nrpe监控配置错误日志集
查看>>
web集群环境中的session同步几种方法
查看>>
Linux(ubuntu)命令行下显示方框的问题
查看>>
ZeroMQ安装
查看>>
Ceph:ceph-dash集群监控工具部署
查看>>
ss查看tcp链接数
查看>>
4、超链接和路径
查看>>
6、表格元素
查看>>
网站提示有风险?上海网站制作给你解决方案!
查看>>
阿里系网商银行获准开业 不设物理网点只服务长尾
查看>>
sync_inodes和sync_filesystems
查看>>
CUDA学习(五十六)
查看>>
项目中调试SQLServer 方便的查看SQL语句的执行时间的方法
查看>>
python并发编程之多线程编程(day9-day10)
查看>>
安装jumpserver
查看>>