手写实现vue的MVVM响应式原理
文中应用到的数据名词:
MVVM ------------------ 视图-----模型----视图模型 三者与 Vue
的对应:view
对应 template
,vm
对应 new Vue({…})
,model
对应 data
nodeType 判断节点是否是元素节点
querySelector 创建一个元素节点
createDocumentFragment 文档碎片
attributes 获取元素属性集合
textContent 获取文本内容
reduce( prev,next,currentIndex){} 一个可以用上一个元素和当前元素做处理的方法
defineProperty(obj,key,value){} 数据拦截的主要方法
MVVM响应式实现原理:
1.模板编译
2.数据劫持
3.watcher

首先建立一个vue的实例,建立mvvm.js ,构建 mvvm类。 获取el的节点 和 data 放入实例中,在将Observer.js(数据劫持)和Compile.js(模板编译) 放入mvvm的js ,全部在index页面运行.
第一步:模板编译
我首先制作Compile.js ,也就是模板编译 。
首先需要获取el 这个属性的值 用nodeType === 1判断是不是元素节点. 如果不是则用 queryselector() 生成一个节点 。 这样做的目的是,有些人el:#app 有些人是document.getElementById('app')。 不管俩者如何,我们都要生成一个节点来供后续使用。
随后判断el节点是否存在,如果存在。则进行编译 , 这里编译最好不要在dom里进行遍历编译,非常耗性能 。 我推荐的是用 createDocumentFragment() 方法. 建立一个虚拟节点对象, 在这个虚拟节点对象里进行遍历以及对应的操作。
那么说到虚拟节点, 我们需要将我们获取的el节点整个放入进去 ,进行遍历,将app里的每一个子节点都搬到fragement 变量中。
然后进行节点的编译。这里的节点又分为元素节点和文本节点。 还是用刚刚的nodeType判断区分吗,然后做对应的操作。
接下来我们先编译元素节点 首先我们需要知道,获取元素节点要做什么,为什么获取元素节点。 我是希望通过获取元素节点上的关于vue的指令,比如:v-model,v-html,v-for 。等等... 那么这些指令是放在元素节点上的属性里,所以我们用 attributes 获取元素节点的属性名的集合 ,也就是我们说的v-model 。通过遍历这个attr属性名的集合,获取每个属性名。通过isDirective函数判断attrName包含 v- 的属性,这里我做给假设,好方便理解。 这里通过上面的过滤,可以得出attrName 是一个指令名。那我假设这个指令名为v-model。 我首先获取v-model的值,也就是expr。然后做一个解耦对象CompileUtil ,方便后面制作其他的指令。所以这里调用的是CompileUtil[model]{node,this.v,,expr};
调用model的指令后,在model这个函数里做相对应的处理。这里的watcher构造函数先不用管,后面的事情。 这里的uptate['modelUptate']和model一样放在CompileUtil 中,方便管理。 如果updateFn存在的话,则执行updateFn(),将v-model的值赋予input节点的value.下图中的getVal 是防止 v-model=’messge.a' 这种嵌套对象的。这个函数里,首先利用split将messge.a 拆分成[messge,a] 数组。然后利用reduce方法 放回 上一个元素[当前元素],而最下面的vm.$data 是reduce方法遍历的初始值。也就是 data 。
因为data:{ messge:{ a:'hello.world' } }.这样的编译,元素节点就可以编译出来了,可以将data的值编译到元素节点上了。
接下来编译文本节点,那文本节点,我们首先获取文本节点里的值,然后利用正则的test找{{ a }} , 和之前的元素节点一样,执行对应的函数。,执行对应的行数。这里第86-90 可以先不管,不过这里的textVal和上面的getVal 函数不一样,首先是需要将符合条件的元素里的变量取出来 也就是 {{ a }}里的a ,argments[1] 就是a变量 。 在考虑到对象嵌套,就执行上面的getVal。然后就可以将data里的值替换到文本里了。
这样元素节点和文本节点都编译完成了。然后将整个虚拟节点丢回dom树里去 。MVVM的编译就结束了
第二步:数据劫持,函数很少。但比较绕.这里执行observe,利用递归遍历,将data里的键值对全部拿出来处理,执行defineReactive函数,这里18行可以先不看。 看下面的最重点的Object.defineProperty()。这里要传入劫持的对象,劫持的键,以及回调函数。这里回调函数里俩个参数在下图。
然后,get函数是取值是做对应的操作,set函数是设置值做对应的操作。至此数据劫持就完成了
第三步:watcher 监察者 ,一旦变化执行对应的操作。也就是将模板编译和数据劫持俩个函数联系在一起。有衔接。
这里创建watcher类,将需要的参数获取。 vm是实例,expr是值,cb是回调函数callback。watcher实例里的value = get方法的返回值,value执行一次嵌套处理返回。这里监察者作用主要是 一 更新值,二是执行callback回调函数cb。三将自己的实例,放入dep的target里。那么watcher监察者就制作好了。
最后的连接部分,首先data里的每个属性值都被加上了set和get
获取值
在最开始编译的时候,编译节点的文本节点处理和元素节点处理的时候执行watcher函数,在watcher函数里的get函数中将 watcher函数自己放入dep的target中。然后也在访问值的时候,则会执行get函数,将 每个watcher放入dep数组中 。
修改值
在修改值的时候,会触发Observer.js 的defineProperty的set函数,set函数里比较新的值和旧的值,value是编译时候的值,newValue是set函数的第一个参数,也就是修改后的新值 。 将俩者比较,如果不同,就执行Dep构造函数的notify函数。notify则会遍历全部存在的dep数组里的watcher的update方法。在watcher的update方法中,比较值的不同,如果不同就则执行回调函数,将视图更新。这个回调函数是嵌套在处理文本节点和元素节点的方法里。
v-model的双向绑定
至于v-model的双向绑定,其实是绑定输入框的输入事件。将输入事件新的值赋值给input节点的value值,然后值的改变,执行set函数,将视图改变。视图的改变,会执行wacther的回调函数,文本节点也会重新赋值。
这就是mvvm响应式原理的实现,如果有残缺讲不清楚的地方,欢迎指出。谢谢。
手写实现vue的MVVM响应式原理的更多相关文章
- Vue数据绑定和响应式原理
Vue数据绑定和响应式原理 当实例化一个Vue构造函数,会执行 Vue 的 init 方法,在 init 方法中主要执行三部分内容,一是初始化环境变量,而是处理 Vue 组件数据,三是解析挂载组件.以 ...
- Vue 2.0 与 Vue 3.0 响应式原理比较
Vue 2.0 的响应式是基于Object.defineProperty实现的 当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 prop ...
- vue 数据劫持 响应式原理 Observer Dep Watcher
1.vue响应式原理流程图概览 2.具体流程 (1)vue示例初始化(源码位于instance/index.js) import { initMixin } from './init' import ...
- vue学习之响应式原理的demo实现
Vue.js 核心: 1.响应式的数据绑定系统 2.组件系统. 访问器属性 访问器属性是对象中的一种特殊属性,它不能直接在对象中设置,而必须通过 defineProperty() 方法单独定义. va ...
- 学习 vue 源码 -- 响应式原理
概述 由于刚开始学习 vue 源码,而且水平有限,有理解或表述的不对的地方,还请不吝指教. vue 主要通过 Watcher.Dep 和 Observer 三个类来实现响应式视图.另外还有一个 sch ...
- vue核心之响应式原理(双向绑定/数据驱动)
实例化一个vue对象时, Observer类将每个目标对象(即data)的键值转换成getter/setter形式,用于进行依赖收集以及调度更新. Observer src/core/observer ...
- Vue.js响应式原理
写在前面 因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出. 文章的原地址:answershuto/learnV ...
- Vue 数据响应式原理
Vue 数据响应式原理 Vue.js 的核心包括一套“响应式系统”.“响应式”,是指当数据改变后,Vue 会通知到使用该数据的代码.例如,视图渲染中使用了数据,数据改变后,视图也会自动更新. 举个简单 ...
- 手摸手带你理解Vue响应式原理
前言 响应式原理作为 Vue 的核心,使用数据劫持实现数据驱动视图.在面试中是经常考查的知识点,也是面试加分项. 本文将会循序渐进的解析响应式原理的工作流程,主要以下面结构进行: 分析主要成员,了解它 ...
随机推荐
- PCA|factor extraction|CA
PCA:主成分分析 相关矩阵,找特征值,找每个特征值对应特征向量,即组成主组成式子: 每个式子指向一个结果y,找一条线将这些y分开.有11个变量就有11个新坐标轴,通过点到直线距离来区分. 信息必须集 ...
- myeclipse 编写java代码提示 dead code 原因
经常使用MyEclipse或Eclipse编辑器编写java代码的程序员,可能经常遇到一个黄线警告提示:dead code:一般程序员遇到这些问题都会置之不理,反正也不影响程序的编译执行.对,这不是b ...
- [单调队列]XKC's basketball team
XKC's basketball team 题意:给定一个序列,从每一个数后面比它大至少 \(m\) 的数中求出与它之间最大的距离.如果没有则为 \(-1\). 题解:从后向前维护一个递增的队列,从后 ...
- Spring Boot使用Liquibase最佳实践
Liquibase问题 随着项目的发展,一个项目中的代码量会非常庞大,同时数据库表也会错综复杂.如果一个项目使用了Liquibase对数据库结构进行管理,越来越多的问题会浮现出来. ChangeSet ...
- 2)thinkphp的带有命名空间的自动加载机制
(1)为啥thinkphp里面的文件要是写你的命名空间,要与你的路径一样,因为在thinkphp实现自动加载机制的原理,就是靠的你的命名空间对应这个路径,然后自动加载机制通过这个路径找到你的类文件,然 ...
- C# 关闭登录窗体,显示主窗体
首先在programm.cs里设置登录窗体显示 static class Program { /// <summary> /// The main ent ...
- 看了这个Java实习生入职测试题后,幸亏我不是实习生
看了这个Java实习生入职测试题后,幸亏我不是实习生 一个Java实习生的入职测试题,你能答对几个? 今天在某APP中看到,有实习生放出的Java实习生入职测试题.看完之后,很庆幸自己不是实习生. 本 ...
- 编译安装 logstash-output-jdbc
环境 mac https://github.com/theangryangel/logstash-output-jdbc logstash-plugin install logstash-output ...
- tomcat打印接口延迟时间
项目中有些页面时延不稳定,需要看每次接口调用时延,怎么看,有两种方法:一种是直接去catalina.out日志中看,一种是直接去localhost_access_log日志中看,第一种需要在代码中实现 ...
- signal——信号集
1.信号集 每个进程都有一个信号屏蔽字,它规定了当前要阻塞递送到该进程的信号集.对于每种可能的信号,该屏蔽字中都有一bit位与之对应.信号数可能会超过一个整型数所包含的二进制位数,因此POSIX.1 ...