手机版

Vue如何实施响应系统

时间:2021-08-31 来源:互联网 编辑:宝哥软件园 浏览:

前言

最近,我深入研究了Vue的一些源代码来实现响应,并记录了我的一些收获和想法,希望对阅读这篇文章的人有所帮助。请指出任何问题,共同进步。

什么是响应系统

总之:数据变化驱动视图更新。这样,我们就可以用“数据驱动”的方式编写代码,更加关注业务而不是dom操作。事实上,Vue响应的实现是一个变更跟踪和变更应用的过程。

Vue响应原理

通过数据劫持拦截数据更改;在依赖集合模式下触发视图更新。Setter和getter用于通过使用es5 Object.defineProperty截取数据;Getter收集依赖项,setter触发依赖项更新,组件render成为一个观察器回调,它被添加到相应数据的依赖项中。

发布订阅

使用发布和订阅的设计模式,观察者是发布者,观察者是订阅者,两者之间没有直接的交互,所以通过Dep统一调度。观察者负责截取get和set获取时触发dep添加依赖项,设置时调度dep发布;添加观察器将触发获取订阅数据,并将其加入dep调度中心的订阅者队列。

下面的UML类图展示了Vue实现响应函数的类,以及它们之间的引用关系。

仅具有部分属性的方法

上图中的类已经明确标识,但是需要一个调用图来使调用过程更加清晰,如下图所示。

在响应数据对象中,每个键被劫持的get/set函数关闭Dep调度实例。此图显示了更改密钥过程中的数据流。

部分源代码

数据变更过程中的订阅/发布模型在上图中已经有了清晰的展示,从中我们已经知道可以通过添加watcher来订阅某个数据变更。那么,如果我们只需要以观察者的身份订阅组件渲染,数据驱动视图的渲染岂不是自然而然的?这正是Vue所做的!下面的代码片段来自Vue.prototype._mount函数

callHook(vm,‘BeforeMount’)VM。_watcher=新的Watcher(vm,()={ vm。_update(vm。_render(),水合)},noop)水合=false//手动挂载实例,思考一些问题调用挂载在自身//挂载是调用渲染创建的子组件在其插入的钩子if (VM。$ vnode==null) {VM。_已装载=真正的调用挂钩(虚拟机,“已装载”)}

#人员分配新对象。新对象中的属性也有响应吗?

Varvm=newvue ({el:' # app ',data :()=({ person : null })})VM . person={ name 3360 ' zs ' } settimeout(()={//change namevm . person . name='。

原因:当Vue劫持设置时,它将再次观察值。源代码如下。

函数反应设置器(newval) {/*.这里省略一些代码*///新值会被再次截取。Childob=observe (newval) dep。notify ()} #当我们听多层属性时,上层引用发生变化,会不会触发回调?

varvm=new vue({ data :()=({ person 3360 { name 3360 '令狐洋葱' }}),watch: {'person。name '(val){ console . log(' name updated ',val)}}}) vm.person。

原因:当person.name作为一个表达式传递给Watcher时,它将被解析成这样一个函数

()={this.vm.person.name}这将首先触发person get,然后触发nameget因此,我们配置的回调函数不仅被添加到名称依赖项中,还具有person。

#接下来是最后一个问题,如果一个人被分配了一个新的对象,那么旧对象和对旧对象的依赖关系是如何被垃圾收集的?

旧对象的回收:由于旧对象的直接引用只有vue实例上的person,而person切换到一个新的引用,因此如果旧对象没有被引用,它们将被回收。观察者的依赖中仍然存在对旧对象的依赖dep但是,在执行run时,将调用watcher的get()来获取当前值;新的依赖项收集将在get中执行,旧的依赖项将在收集后清空。具体源代码如下:

/***评估吸气剂,并重新收集依赖项*/get(){ PushTarget(this)常量值=this。吸气器。叫(这个。VM,this.vm) //'touch '每个属性,因此它们都被跟踪为//深度监视的依赖项if(this。deep){ traverse(value)} popTarget()这。cleanupdeps()返回值}#当我们多次同步修改名字时,回调函数是否会触发多次?

var VM=new Vue({ data :()=({ person : { name : ')令狐洋葱} }),观看: { '人。name ' :(val){ console。日志('名称已更新: ' val)} })VM。person={ name : ' zs ' } VM。人。名称='无敌'答案: 不会,因为看回调函数执行是异步的,且会去重。可以通过同步强制配置成同步快跑,就会执行2次了。

自己实现一个响应式系统

只包含核心功能,具体源码可以看这里https://github.com/Zenser/z-vue,欢迎来明星。

实现功能非常基础啦,重在理解,功能不全的。

观察者

类观察{构造函数(obj){对象。钥匙.forEach(prop={ reactive(obj,prop,obj[prop])})} } function reactive(obj,prop) { let value=obj[prop] //闭包绑定依赖让Dep=新Dep()对象。definepreproperty(obj,prop,{ configurable: true,enumerable: true,get() { //利用射流研究…单线程,在得到时绑定订阅者if (Dep.target) { //绑定订阅者dep.addSub(Dep.target) }返回值},set(newVal) { value=newVal //更新时,触发订阅者更新dep.notify() } }) //对象监听如果(值类型==='对象'值!==null) { Object.keys(值)。forEach(value prop={ reactive(value,valueProp) }) } }Dep

类Dep { constructor(){ this。subs=[]} AddSub(sub){ if(this。潜艇。indexof(sub)==-1){ this。潜艇。push(sub)} } notify(){ this。潜艇。foreach(sub={ const old val=sub value sub CB sub CB(sub get(),oldVal) }) }}Watcher

类Watcher {构造函数(数据,exp,CB){ this。数据=数据这个。exp=exp这个。CB=CB这个。get()} get(){ Dep。目标=这个。value=(函数calcvvalue(data,prop) { for(让i=0,len=prop.length我透镜;I){ data=data[prop[I]]} return data })(这个。数据,this.exp.split(' . '))Dep.target=null返回this.value }}参考文档:https://cn。vuejs。org/v2/指南/反应性。超文本标记语言

版权声明:Vue如何实施响应系统是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。