阅读更多

0顶
0踩

企业架构
引用
【前言】本文将帮助大家了解Vue的双向数据绑定原理、核心代码模块,以及如何实现双向绑定。为了便于说明原理与实现,本文相关代码主要摘自Vue源码, 并进行了简化改造,相对较简陋,并未考虑到数组的处理、数据的循环依赖等,也难免存在一些问题,欢迎大家指正。不过这些并不会影响大家的阅读和理解,相信看完本文后对大家在阅读Vue源码的时候会更有帮助。

所有相关代码均详见GitHub:https://github.com/DMQ/mvvm


相信大家对MVVM双向绑定应该都不陌生了,一言不合上代码,下面先看一个本文最终实现的效果吧,和Vue一样的语法,如果还不了解双向绑定,猛戳 Google


效果:


几种实现双向绑定的做法
目前几种主流的MVC(VM)框架都实现了单向数据绑定,而我所理解的双向数据绑定无非就是在单向绑定的基础上给可输入元素(input、textare等)添加了change(input)事件,来动态修改model和view,并没有多高深。所以无需太过介怀是实现的单向或双向绑定。

实现数据绑定的做法有大致如下几种:
  • 发布者-订阅者模式(backbone.js)
  • 脏值检查(angular.js)
  • 数据劫持(vue.js)
发布者-订阅者模式: 一般通过sub, pub的方式实现数据和视图的绑定监听,更新数据方式通常做法是vm.set('property', value),这里有篇文章讲的比较详细,有兴趣可点击查看

但我们更希望通过vm.property = value这种方式更新数据,同时自动更新视图,于是有了下面两种方式:

脏值检查:Angular.js是通过脏值检测的方式比对数据是否有变更,来决定是否更新视图,最简单的方式就是通过setInterval()定时轮询检测数据变动,当然Google不会这么low,Angular只有在指定的事件触发时进入脏值检测,大致如下:
  • DOM事件,譬如用户输入文本,点击按钮等。(ng-click)
  • XHR响应事件 ($http)
  • 浏览器Location变更事件 ($location)
  • Timer事件($timeout , $interval)
  • 执行$digest()或$apply()
数据劫持:Vue.js则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

思路整理
已经了解到vue是通过数据劫持的方式来做数据绑定的,其中最核心的方法便是通过Object.defineProperty()来实现对属性的劫持,达到监听数据变动的目的,无疑这个方法是本文中最重要、最基础的内容之一,如果不熟悉defineProperty,可以点击此处查看。

整理了一下,要实现MVVM的双向绑定,就必须要实现以下几点:
  • 实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者
  • 实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
  • 实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图
  • MVVM入口函数,整合以上三者

上述流程如图所示:


1、实现Observer
ok, 思路已经整理完毕,也已经比较明确相关逻辑和模块功能了,let’s do it!

我们知道可以利用Obeject.defineProperty()来监听属性变动
那么将需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter,这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到数据变化。相关代码可以是这样:

这样我们已经可以监听每个数据的变化了,那么监听到变化之后就是怎么通知订阅者了,所以接下来我们需要实现一个消息订阅器,很简单,维护一个数组,用来收集订阅者,数据变动触发notify,再调用订阅者的update方法,代码改善之后是这样:

那么问题来了,谁是订阅者?怎么往订阅器添加订阅者?

没错,上面的思路整理中我们已经明确订阅者应该是Watcher,而且var dep = new Dep();是在defineReactive方法内部定义的,所以想通过dep添加订阅者,就必须要在闭包内操作,所以我们可以在getter里面动手脚:

这里已经实现了一个Observer了,已经具备了监听数据和数据变化通知订阅者的功能。那么接下来就是实现Compile了。

2、实现Compile
Compile主要做的事情是解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图,如图所示:

因为遍历解析的过程有多次操作dom节点,为提高性能和效率,会先将跟节点el转换成文档碎片fragment进行解析编译操作,解析完成,再将fragment添加回原来的真实dom节点中:

compileElement方法将遍历所有节点及其子节点,进行扫描解析编译,调用对应的指令渲染函数进行数据渲染,并调用对应的指令更新函数进行绑定,详看代码及注释说明:


这里通过递归遍历保证了每个节点及子节点都会解析编译到,包括了{{}}表达式声明的文本节点。指令的声明规定是通过特定前缀的节点属性来标记,如<span v-text="content" other-attr中v-text便是指令,而other-attr不是指令,只是普通的属性。

监听数据、绑定更新函数的处理是在compileUtil.bind()这个方法中,通过new Watcher()添加回调来接收数据变化的通知。

至此,一个简单的Compile就完成了。接下来要看看Watcher这个订阅者的具体实现了。

3、实现Watcher
Watcher订阅者作为Observer和Compile之间通信的桥梁,主要做的事情是:
  • 在自身实例化时往属性订阅器(dep)里面添加自己;
  • 自身必须有一个update()方法;
  • 待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。
如果有点乱,可以回顾下前面的思路整理:

实例化Watcher的时候,调用get()方法,通过Dep.target = watcherInstance标记订阅者是当前Watcher实例,强行触发属性定义的getter方法,getter方法执行的时候,就会在属性的订阅器dep添加当前Watcher实例,从而在属性值有变化的时候,watcherInstance就能收到更新通知。

Ok,Watcher也已经实现了。

基本上Vue中数据绑定相关比较核心的几个模块也是这几个,点击此处,在src目录可找到Vue源码。最后来讲讲MVVM入口文件的相关逻辑和实现吧,相对就比较简单了。

4、实现MVVM
MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。

一个简单的MVVM构造器是这样子:

但是这里有个问题,从代码中可看出监听的数据对象是options.data,每次需要更新视图,则必须通过var vm = new MVVM({data:{name: 'kindeng'}}); vm._data.name = 'dmq';这样的方式来改变数据。

显然不符合我们一开始的期望,我们所期望的调用方式应该是这样的:var vm = new MVVM({data: {name: 'kindeng'}}); vm.name = 'dmq';

所以这里需要给MVVM实例添加一个属性代理的方法,使访问VM的属性代理为访问vm._data的属性,改造后的代码如下:

这里主要还是利用了Object.defineProperty()这个方法来劫持了VM实例对象的属性的读写权,使读写VM实例的属性转成读写了vm._data的属性值,达到鱼目混珠的效果。

至此,全部模块和功能已经完成了,如本文开头所承诺的两点。一个简单的MVVM模块已经实现,其思想和原理大部分来自经过简化改造的Vue源码,在这里可以看到本文的所有相关代码。

由于本文内容偏实践,所以代码量较多,且不宜列出大篇幅代码,所以建议想深入了解的童鞋可以再次结合本文源代码来进行阅读,这样会更加容易理解和掌握。

总结
本文主要围绕“几种实现双向绑定的做法”、“实现Observer”、“实现Compile”、“实现Watcher”、“实现MVVM”这几个模块来阐述双向绑定的原理和实现。并根据思路流程渐进梳理讲解了一些细节思路和比较关键的内容点,以及通过展示部分关键代码讲述怎样一步步实现一个双向绑定MVVM。文中可能会有一些不够严谨的思考和错误,欢迎大家指正,有兴趣欢迎一起探讨和改进。

引用
声明:本文来自腾讯增值产品部官方公众号小时光茶社,为CSDN原创投稿,未经许可,禁止任何形式的转载。
作者:邓木琴,腾讯前端工程师,手Q阅读前端开发,参与过手Q阅读、企鹅电竞、原创社区等项目。热衷于新技术研究,处女座,典型强迫症患者,注重细节。
责编:钱曙光,关注架构和算法领域,寻求报道或者投稿请发邮件qianshg@csdn.net,另有「CSDN 高级架构师群」,内有诸多知名互联网公司的大牛架构师,欢迎架构师加微信qianshuguangarch申请入群,备注姓名+公司+职位。
  • 大小: 35.8 KB
  • 大小: 8.5 KB
  • 大小: 29.8 KB
  • 大小: 45.4 KB
  • 大小: 37.3 KB
  • 大小: 28.4 KB
  • 大小: 15.7 KB
  • 大小: 36.1 KB
  • 大小: 71.1 KB
  • 大小: 37.8 KB
  • 大小: 68.2 KB
  • 大小: 13.2 KB
  • 大小: 44.1 KB
0
0
评论 共 1 条 请登录后发表评论
1 楼 semmy 2017-01-09 10:05

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • 81192!请返航!

    今天的文字就这么多,明白的,不明白的,就这些吧。

  • 现实版的黑客大战,这可能是中国黑客做过最燃的事情了

    由一场撞击事件引发的黑客大战,80000人参与、被攻击网站超4000多个……这场看似没有硝烟的战争当时是何情景?让我们梦回2001,一起看看吧。 “81192无法返航” ——“呼叫81192,这里是553,我奉命接替你机执行巡航任务,请返航!” ——“81192收到,我已无法返航,你们继续前进,...

  • 铅笔头识别数据集,1692张原始训练图,640*640分辨率,91.1%的正确识别率,标注支持coco json格式

    铅笔头识别数据集,1692张原始训练图,640*640分辨率,91.1%的正确识别率,标注支持coco json格式

  • 高校网络教学的体系规划与创建.docx

    高校网络教学的体系规划与创建.docx

  • SpringBoot的学生心理咨询评估系统,你看这篇就够了(附源码)

    SpringBoot的学生心理咨询评估系统,你看这篇就够了(附源码)

  • 遗传算法优化BP神经网络提升交通流量预测精度的技术实现与应用

    内容概要:本文详细介绍了如何使用遗传算法优化BP神经网络,以提高交通流量预测的准确性。文中首先解释了BP神经网络的基本结构及其局限性,即容易陷入局部最优解的问题。随后,作者展示了遗传算法的工作原理,并将其应用于优化BP神经网络的权重和偏置。通过定义适应度函数、选择、交叉和变异等步骤,实现了对BP神经网络的有效改进。实验结果显示,优化后的BP神经网络在交通流量预测中的精度显著高于传统的BP神经网络,特别是在处理复杂的非线性问题时表现出色。 适用人群:对机器学习、深度学习以及交通流量预测感兴趣的科研人员和技术开发者。 使用场景及目标:适用于需要进行精确交通流量预测的应用场景,如智能交通系统、城市规划等领域。主要目标是通过遗传算法优化BP神经网络,解决其易陷入局部最优的问题,从而提高预测精度和稳定性。 其他说明:文中提供了详细的Python代码实现,帮助读者更好地理解和实践这一优化方法。同时,强调了遗传算法在全局搜索方面的优势,以及其与BP神经网络结合所带来的性能提升。此外,还讨论了一些具体的实施技巧,如适应度函数的设计、交叉和变异操作的选择等。 标签1,标签2,标签3,标签4,标签5

  • H5U PLC与触摸屏集成框架:总线伺服控制及跨平台移植的最佳实践

    内容概要:本文详细介绍了H5U框架在PLC与触摸屏集成方面的应用,特别是在总线伺服控制和跨平台移植方面。文章首先解析了伺服控制的核心代码,如使能模块和绝对定位指令,强调了标准化控制流程的优势。接着讨论了触摸屏交互,通过直接映射PLC的DB块地址简化了数据处理。然后介绍了总线配置,尤其是EtherCAT总线初始化及其容错设计。此外,文章还探讨了框架的移植性和报警处理设计,展示了其在不同PLC品牌间的易用性和高效的故障恢复能力。 适合人群:从事工业自动化领域的工程师和技术人员,特别是有PLC编程经验和需要进行伺服控制系统开发的人群。 使用场景及目标:①快速搭建和调试基于PLC和触摸屏的自动化控制系统;②提高多轴设备的调试效率;③实现跨平台的无缝移植;④优化报警管理和故障恢复机制。 其他说明:该框架不仅提供了详细的代码示例和注释,还包含了丰富的实战经验和最佳实践,使得新手能够快速上手,而资深工程师可以在此基础上进一步创新。

  • 游戏开发UE5引擎核心技术解析与应用:涵盖安装配置、项目创建及蓝图编辑器详解文档的主要内容

    内容概要:本文档《UE5开发.txt》全面介绍了Unreal Engine 5(UE5)的基本概念、安装配置、项目创建、文件结构及常用功能。UE5是一款强大的游戏引擎,支持实时渲染、蓝图创作、C++编程等功能。文档详细描述了UE5的安装步骤,包括硬件要求和环境配置;项目创建过程,涵盖项目模板选择、质量预设、光线追踪等设置;文件结构解析,重点介绍了Config、Content和.uproject文件的重要性。此外,文档深入讲解了蓝图编辑器的使用,包括变量、数组、集合、字典等数据类型的操作,以及事件、函数、宏和事件分发器的应用。蓝图作为一种可视化脚本工具,使开发者无需编写C++代码即可快速创建逻辑,适用于快速开发和迭代。 适合人群:具备一定编程基础的游戏开发者、设计师和对游戏开发感兴趣的初学者,尤其是希望深入了解UE5引擎及其蓝图系

  • 餐馆点菜系统概要设计说明书.doc

    餐馆点菜系统概要设计说明书.doc

  • 5+1档轿车手动变速箱设计说明书.doc

    5+1档轿车手动变速箱设计说明书.doc

  • 1万吨自来水厂详细设计说明书.doc

    1万吨自来水厂详细设计说明书.doc

  • wordpress外贸电商企业产品主题

    wordpress外贸电商企业产品主题 页面展示图https://i-blink.csdnimg.cn/direct/e45b2e2e8e27423eb79bda5f4c1216d7.png

  • 低效林改造作业设计说明书.doc

    低效林改造作业设计说明书.doc

  • 西门子200smart编程软件V2.8.2.1

    西门子200smart编程软件V2.8.2.1

  • 135调速器操纵手柄 设计说明书.doc

    135调速器操纵手柄 设计说明书.doc

  • 蓝桥杯全国软件和信息技术专业人才竞赛指导文档.pdf

    内容概要:本文档为蓝桥杯全国软件和信息技术专业人才竞赛提供了全面的指导,涵盖竞赛概述、流程与规则、核心考点与备赛策略、实战技巧与避坑指南以及备赛资源推荐。蓝桥杯竞赛由工信部人才交流中心主办,涉及算法设计、软件开发、嵌入式系统、电子设计等领域。文档详细介绍了参赛流程(报名、省赛、国赛、国际赛),并针对软件类和电子类竞赛分别阐述了高频考点和备赛建议。对于软件类,强调了算法与数据结构的重要性,如排序、动态规划、图论等;对于电子类,则侧重于硬件基础和开发工具的使用。此外,还提供了详细的答题策略、常见陷阱规避方法及工具调试技巧。; 适合人群:高校本专科生、研究生,尤其是对算法设计、软件开发、嵌入式系统等领域感兴趣的计算机科学及相关专业的学生。; 使用场景及目标:①帮助参赛选手熟悉竞赛流程和规则,明确各阶段任务;②提供系统的备赛策略,包括高频考点的学习和专项突破;③指导选手掌握实战技巧,避免常见错误,提高答题效率和准确性。; 阅读建议:此文档不仅提供了理论知识,还包含了大量实战经验和备赛资源推荐,建议读者结合自身情况制定个性化的备赛计划,充分利用提供的资源进行练习和准备。

  • 基于行块抽取正文内容的java版本的改进算法.zip

    基于行块抽取正文内容的java版本的改进算法.zip

Global site tag (gtag.js) - Google Analytics