vue数据监听与依赖收集

更新日期: 2020-04-17 阅读: 2.7k 标签: 数据

文章围绕下面demo进行分析

<div id="app">
    <span>{{a.b}} {{c}} {{d}}</span>
</div>

<script>
    var app = new vue({
        el: "#app",
        data: function(){
            return {
                a: {
                    b: 1
                },
                c: 1
            }
        },
        watch:{
          'a.b': function(){
            console.log(22)
          }
        },
        computed:{
          d: function (){
            return this.c
          }
        }
    });
</script>


数据监听:从initData开始

vue监听data数据的步骤可以概括为下面几步:

1、使用initData初始化data数据

2、将data挂载到vm._data中(后面会将_data赋值给$data,这里的data如果是函数,就是返回的数据)

3、通过observe监听数据

4、如果数据是一个非object类型的数据(typeof == object,且不为null),终止程序

5、observe中使用new Observer生成ob

6、Observer中使用`this.dep = new Dep()`挂载dep

7、Observer中将__ob__指向this,所以可以使用`__ob__.dep`找到dep

8、遍历属性,使用definereactive监听属性

9、defineReactive中生成`var dep = new Dep();`,这个dep是一个闭包

10、defineReactive中使用observe监听属性所代表的值,也就是步骤3,至此循环递归


依赖收集

三种watcher

1、normal-watcher(watch中的数据,通过initWatch生成) 记录在_watchers中(所有的watch都会存放在这里)

2、computed-watcher(computed) 记录在_computedWatchers中

3、render-watcher 就是vm._watcher


normal-watcher:运行initWatch

我们在组件钩子函数watch 中定义的,都属于这种类型,即只要监听的属性改变了,都会触发定义好的回调函数,这类watch的expression是计算属性中的属性名。

在初始化watch的时候(initWatch),会调用vm.watch函数会直接使用Watcher构建观察者对象。watch中属性的值作为watcher.cb存在,在观察者update的时候,在watcher.run函数中执行。watch中属性的key会进行parsePath处理,并用parsePath返回的函数,获取watch的初始值value。

比如把a.b解析为监听a中的b属性,这样会先寻找a,也就是触发a的get。

1、赋值cb为a.b的值,赋值expression为a.b

2、使用parsePath解析expOrFn,即a.b,并将返回的函数赋值给该watcher实例的getter即this.getter

3、运行this.get获取a.b的值,进行依赖收集,this指向a.b的 watcher实例

4、运行pushTarget将Dep.target指向该watcher实例

5、运行this.getter,会先获取a,运行defineReactive中的get

6、运行dep.depend(此时的dep指的是data.a的dep,在闭包中),进而运行Dep.target.addDep,将data.a的dep追加进该watcher实例中,并将该watcher实例追加进data.a的dep.subs中,因为a具有__ob__,所以会运行a.__ob__.dep.depend,将a的dep追加进该watcher实例中,并将该watcher实例追加进a的dep.subs中

7、利用获取到的a去获取属性b

8、运行dep.depend(此时的dep指的是a.b的dep,在闭包中),进而运行Dep.target.addDep,将a.b的dep追加进该watcher实例中,并将该watcher实例追加进a.b的dep.subs中,因为b不具有__ob__,所以不会继续追加

9、到这里就获取到了a.b的值1,并将这个值赋值给该watcher实例的value


computed-watcher:运行initComputed

我们在组件钩子函数computed中定义的,都属于这种类型,每一个 computed 属性,最后都会生成一个对应的 watcher 对象,但是这类 watcher 有个特点:当计算属性依赖于其他数据时,属性并不会立即重新计算,只有之后其他地方需要读取属性的时候,它才会真正计算,即具备 lazy(懒计算)特性。这类watch的expression是计算属性中的属性名。

在初始化computed的时候(initComputed),会先生成watch实例,然后监测数据是否已经存在data或props上,如果存在则抛出警告,否则调用defineComputed函数,监听数据,为组件中的属性绑定getter及setter。

注意:computed中的属性是直接绑定在vm上的,所以如果写a.d,那就是属性名是a.d,而不是a对象的属性d。

1、执行initComputed,遍历computed生成watch实例,并挂载到vm._computedWatchers上

    (1)赋值cb为空函数,赋值expression为expOrFn(d的值,函数或对象的get)的字符串形式,赋值this.getter为expOrFn

    (2)默认的computed设置lazy为true,不运行this.get获取值,所以到这里watch实例就生成了。

2、执行defineComputed函数,如果d的值是函数,或者d的cache属性不是false,那么会使用createComputedGetter函数生成computedGetter函数,作为d的getter函数,如果cache设置为false,不经过createComputedGetter封装,每次获取都会运行get,而d的setter就是他的set或者空函数(默认)

3、当获取d的值时(比如渲染,此时Dep.target为渲染watcher),会运行computedGetter函数

4、根据watcher.dirty的值决定是否运行watcher.evaluate重新获取属性值,这是懒计算的关键。dirty的值默认为true,在依赖改变时或update时变为true,在evaluate后变为false

    (1)watcher.evaluate中运行this.get获取d的值,进行依赖收集,this指向d的 watcher实例

    (2)运行pushTarget将Dep.target指向d的watcher实例

    (3)运行this.getter,会先获取this.c的值,运行defineReactive中的get

    (4)运行dep.depend(此时的dep指的是data.c的dep,在闭包中),进而运行Dep.target.addDep,将data.c的dep追加进d的watcher实例中,并将d的watcher实例追加进data.c的dep.subs中

    (5)d的watcher出栈,将Dep.target重新设置为渲染watcher

5、运行watcher.depend,遍历watcher.deps(这里主要是data.c的dep),将他们与渲染watcher互相关联

注意:computed中的数据不经过Observer监听,所以不存在dep


render-watcher:运行mountComponent挂载组件

每一个组件都会有一个 render-watcher, 当 data/computed 中的属性改变的时候,会调用该 render-watcher 来更新组件的视图。这类watch的expression是 function () {vm._update(vm._render(), hydrating);}。

1、生成updateComponent函数,

2、实例化一个渲染watcher,把updateComponent当作expOrFn参数传入

3、赋值cb为空函数,赋值expression为updateComponent的字符串形式,赋值this.getter为expOrFn

4、运行this.get,进行依赖收集

5、运行pushTarget将Dep.target指向该渲染watcher实例

6、运行this.getter,即updateComponent函数

7、用render函数生成vnode,并将其作为第一个参数,传入_update

8、render函数中会对用到的变量进行getter操作,并完成依赖收集

    (1)获取a,将data.a的dep追加进该渲染watcher实例中,并将该渲染watcher实例追加进data.a的dep.subs中

    (2)获取a.b,将a.b的dep追加进该渲染watcher实例中,并将该渲染watcher实例追加进a.b的dep.subs中

    (3)获取c,将data.c的dep追加进该渲染watcher实例中,并将该渲染watcher实例追加进data.c的dep.subs中

    (4)获取d,运行d的getter函数computedGetter(详情看上面computed-watcher中的步骤3-5)

9、完成依赖收集后,变量修改,会触发dep.notify,通知渲染watcher实例的update操作,重新进行渲染

来自:https://segmentfault.com/a/1190000022422120

本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://fly63.com/article/detial/8751

相关推荐

js判断数字是奇数还是偶数的2种方法实现

奇数和偶数的判断是数学运算中经常碰到的问题,这篇文章主要讲解通过JavaScript来实现奇偶数的判断。2种判断方法:求余% 、&1

进制转换_二进制、八进制和十六进制数之间的转换

在计算机语言中常用的进制有二进制、八进制、十进制和十六进制,十进制是最主要的表达形式。对于进制,有两个基本的概念:基数和运算规则。

mock.js模拟数据

开发时,后端还没完成数据输出,前端只好写静态模拟数据。数据太长了,将数据写在js文件里,完成后挨个改url。某些逻辑复杂的代码,加入或去除模拟数据时得小心翼翼。想要尽可能还原真实的数据,要么编写更多代码,要么手动修改模拟数据

几个数据持久化框架Hibernate、JPA、Mybatis、JOOQ和JDBC Template的比较

因为项目需要选择数据持久化框架,看了一下主要几个流行的和不流行的框架,对于复杂业务系统,最终的结论是,JOOQ是总体上最好的,可惜不是完全免费,最终选择JDBC Template。

nodejs 使用 xlsx 实现导入导出

将数据导出成excel方法,下面介绍两种方式,一种是将数组数据导出成excel,一种是将json数据导出成excel,都非常简单

web scraper 抓取网页数据的几个常见问题

如果你想抓取数据,又懒得写代码了,可以试试 web scraper 抓取数据。如果你在使用 web scraper 抓取数据,很有可能碰到如下问题中的一个或者多个,而这些问题可能直接将你计划打乱,甚至让你放弃 web scraper 。

双向数据绑定与单向数据绑定的各自优势和关系

在react中是单向数据绑定,而在vue和augular中的特色是双向数据绑定。为什么会选择两种不同的机制呢?我猜测是两种不同的机制有不同的适应场景,查了一些资料后,总结一下。

原生JS数据绑定的实现

双向数据绑定是非常重要的特性 —— 将JS模型与HTML视图对应,能减少模板编译时间同时提高用户体验。我们将学习在不使用框架的情况下,使用原生JS实现双向绑定 —— 一种为Object.observe

JavaScript判断数据类型的多种方法【 js判断一个变量的类型】

js判断数据类型的多种方法,主要包括:typeof、instanceof、 constructor、 prototype.toString.call()等,下面就逐一介绍它们的异同。

Nginx返回大长度的JSON数据被截断

1 添加Nginx参数,增加缓存字符串大小;2 遇到权限问题,原因是大文件会先缓存到/proxy-temp文件夹下面,然后再返回;修改文件夹的权限为Nginx用户

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!