在明白原理之前,我们有很多表面现象、使用场景需要记忆。明白了原理后,你会发现它们已经不需要记了,因为从原理出发,你自己都能把它们推导出来,一切是那么的自然而然。感觉就是:这还用记吗?很明显嘛!

之前我对vue的响应式原理,只是一知半解,导致开发中经常会出现疑问,比如:为什么有的数据它不响应?模板中用到的methods方法什么时候会执行?什么时候模板会重新渲染?渲染的过程是什么等等。所有的这些开发过程中的疑惑,都是因为不了解底层原理造成的。

今天我们就来一起捋一下,vue的响应式原理。当然,只是入门级的,可以帮助和我一样不了解原理的同学,大佬勿喷。

一、数据劫持:getter和setter

在vue的data初始化阶段,vue会递归地遍历data的每一个属性,把它们处理成响应式数据。这是一个深层次的遍历,也就是说data的属性如果是一个对象,这个对象的属性也是响应式的,不管嵌套几层。

具体来说,vue对每一个属性执行Object.defineProperty(),把每一个属性转换为getter和setter,以此实现对属性取值、赋值的劫持,称为数据劫持。

二、watcher和dep

我们知道,模板中会用到data的数据,计算属性也是如此,它们会各用一个列表来保存自己用到了哪些data数据,称为依赖列表。

而data的属性,则可能会被模板以及多个计算属性用到,它也会用一个列表来保存哪些模板或计算属性用到了自己,也叫依赖列表。

模板和计算属性,通过watcher对象来做这件事,依赖列表存放在watcher的一个数组里。每一个vue实例,有一个watcher,称为渲染watcher。每一个计算属性,各自有一个wathcer,称为计算属性watcher。

data的属性,通过dep对象来做这件事,依赖列表存放在dep的一个数组里。data的每一个属性,都有一个dep对象。

watcher的这个数组,成员是dep对象。dep的这个数组,成员是watcher对象。

也就是说,通过维护对方的列表,模板和计算属性,知道我用到了哪些属性。data的属性,也知道哪些模板和计算属性用到了我。

三、依赖收集

在模板第一次渲染、计算属性第一次被使用时,它们所依赖属性的getter会触发,然后就把这个模板或计算属性的watcher添加到该属性的依赖列表里(dep对象的数组)。

同时,这些属性的dep对象,也会被添加到模板或计算属性的依赖列表里(watcher对象的数组)。

这个过程是双向的。我曾经疑问为什么需要在watcher里维护依赖列表?因为看上去,属性更新时,通知它的依赖列表里的每一个watcher,让它们去更新,这个模型似乎就可以了。

原来,有时我们是需要模板主动更新的,比如$forceUpdate函数,这时通过watcher的依赖列表,就可以查看这些依赖有没有更新,如果都没有更新,就无需重新渲染,提高了性能。

四、依赖更新

在一个属性发生变化时,这个属性的setter被触发,它会通知依赖列表里的每一个watcher,让它们去更新。

渲染watcher接到通知,会重新渲染页面。计算属性watcher接到通知,会进行重新计算。

实际的模型比这要复杂。组件的更新过程是异步的,当被通知重新渲染时,不会立即触发,而是将组件标记为“待更新”。Vue 使用一个异步队列来批量处理这些更新,以提高性能。这意味着在同一事件循环中,多次改变数据只会导致一次组件更新和重新渲染。

同样,通知计算属性重新计算,也不会立即触发,而是把计算属性标记为“待更新”,直到该计算属性下一次被使用时(比如重新渲染),才会重新计算。

五、原理之上的应用

明白了原理,我们可以弄清楚很多问题,比如:

(1)vue中的哪些数据是响应式的?

props、data、computed:前两个我们好理解,这里需要注意的是计算属性。思考下面一个问题:

模板中用到一个计算属性,那么它的渲染watcher的依赖列表里,是这个计算属性,还是这个计算属性所依赖的data属性?

答案是:这个计算属性。这是因为,计算属性本身也是响应式的,同样会被Object.defineProperty处理。计算属性的效果就是一层缓存,它不仅会被模板用到,还可能被其他计算属性用到。在这个案例中,当计算属性依赖的data改变时,会先触发计算属性的重新计算,只有计算后的值和原来不同,模板才会重新渲染,反之,就无需重新渲染。

另外,$route和$store.state也是响应式的,原理和其他的一样。意味着,如果模板中用到了它俩,它俩改变时模板是会重新渲染的(计算属性也一样,会重新计算)。

(2)我们知道,一个模板中会用到各种数据:data属性、计算属性、表达式、methods中的方法、全局的自定义函数。那么当模板重新渲染时,它们各自会怎么样呢?

计算属性:只有计算属性的依赖发生变化时,它才会在重新渲染时重新计算。前者会把计算属性标记为“待更新”,重新计算则会等到下一次被使用(比如重新渲染)时才会进行。

表达式、methods中的方法、全局的自定义函数:每次重新渲染都会重新计算,因为它们的值不会被缓存,所以要尽可能多的使用计算属性。

(3)什么会触发组件的重新渲染?

组件只有在模板依赖的数据发生变化时,才会重新渲染。那些模板中没用到的数据,改变并不会让模板重新渲染。并且,这种依赖是属性级别的,也就是说,模板中用到了data中的一个对象,但这个对象的改变不一定导致重新渲染,因为改变的属性不一定是模板用到的那个。

父组件和子组件,它们的渲染也没有必然的联系。子组件的data发生变化,不会导致父组件重新渲染,因为父组件不会用到子组件的数据。父组件的data发生变化,也只有它自己,和用到该数据的子组件会重新渲染。不过要注意,如果父组件是销毁了重新创建,那么子组件也只能跟着销毁重新创建。另外,如果父组件对子组件使用了v-if、v-for(搭配key使用)、key,那么子组件很可能会随着它们的变化而销毁重建。

(4)为什么vue无法监听对象和数组的某些操作?

明白了vue2的响应式原理,也就理解了为什么,vue无法监听到对象属性的添加和删除,因为vue2只能劫持对象属性的取值和赋值。想给响应对象添加属性,应该使用Vue.set()或者this.$set()。

数组的限制是,无法监听到通过索引直接赋值和修改数组的长度。我暂时无法解释,不过我的方法时统一用splice方法来替代。

vue的响应式原理:依赖追踪的更多相关文章

  1. vue深入响应式原理

    vue深入响应式原理 深入响应式原理 — Vue.jshttps://cn.vuejs.org/v2/guide/reactivity.html 注意:这里说的响应式不是bootsharp那种前端UI ...

  2. Vue.js响应式原理

      写在前面 因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出. 文章的原地址:answershuto/learnV ...

  3. vue.js响应式原理解析与实现

    vue.js响应式原理解析与实现 从很久之前就已经接触过了angularjs了,当时就已经了解到,angularjs是通过脏检查来实现数据监测以及页面更新渲染.之后,再接触了vue.js,当时也一度很 ...

  4. Vue 数据响应式原理

    Vue 数据响应式原理 Vue.js 的核心包括一套“响应式系统”.“响应式”,是指当数据改变后,Vue 会通知到使用该数据的代码.例如,视图渲染中使用了数据,数据改变后,视图也会自动更新. 举个简单 ...

  5. 深入解析vue.js响应式原理与实现

    vue.js响应式原理解析与实现.angularjs是通过脏检查来实现数据监测以及页面更新渲染.之后,再接触了vue.js,当时也一度很好奇vue.js是如何监测数据更新并且重新渲染页面.vue.js ...

  6. Vue的响应式原理

    Vue的响应式原理 一.响应式的底层实现 1.Vue与MVVM Vue是一个 MVVM框架,其各层的对应关系如下 View层:在Vue中是绑定dom对象的HTML ViewModel层:在Vue中是实 ...

  7. 一探 Vue 数据响应式原理

    一探 Vue 数据响应式原理 本文写于 2020 年 8 月 5 日 相信在很多新人第一次使用 Vue 这种框架的时候,就会被其修改数据便自动更新视图的操作所震撼. Vue 的文档中也这么写道: Vu ...

  8. Vue的响应式原理---(v-model中的双向绑定原理)

    Vue响应式原理 不要认为数据发生改变,界面跟着更新是理所当然. 具体代码实现:https://gitee.com/ahaMOMO/Vue-Responsive-Principle.git 看下图: ...

  9. vue系列---响应式原理实现及Observer源码解析(一)

    _ 阅读目录 一. 什么是响应式? 二:如何侦测数据的变化? 2.1 Object.defineProperty() 侦测对象属性值变化 2.2 如何侦测数组的索引值的变化 2.3 如何监听数组内容的 ...

  10. VUE响应式原理-如何追踪变化

    Vue 最独特的特性之一,是其非侵入性的响应式系统.数据模型仅仅是普通的 JavaScript 对象.而当你修改它们时,视图会进行更新.这使得状态管理非常简单直接 如何追踪变化 当你把一个普通的 Ja ...

随机推荐

  1. VMware三种连接模式的区别

    安装了vm软件后,该软件会虚拟出两张虚拟网卡vmnet1和vmnet8 网卡在控制面板->网络和internet->更改适配器设置 三种网络连接模式: 桥接模式:使用主机的无线网卡或者有线 ...

  2. ZS Shuffles Cards 题解

    ZS Shuffles Cards 题解 我们把每一次抽一些数字牌再抽到 joker 视作一局游戏. 每局期望轮数 首先考虑 \(f_i\) 表示每一局游戏抽出 \(i\) 张牌的概率. 那么就是先抽 ...

  3. QA||TypeError: ‘module‘ object is not callable报错怎么debugIHRM接口自动化测试

    unittest.py生成测试报告时执行报错:TypeError: 'module' object is not callable 代码如下 原因:结合pycharm自动标注和报错信息,分析出应该是H ...

  4. Solution -「GXOI / GZOI 2019」宝牌一大堆

    Description Link. Summarizing the fucking statement is the last thing in the world I ever want to do ...

  5. tensorboard可视化点云

    tensorboard可视化点云 用 tensorboard 自带的 add_mesh 方法:支持可视化点云和网格,参考链接: tensorflow 网站的tensorboard pytorch 网站 ...

  6. Django框架——模板层

    文章目录 1 模板层 一 模版简介 二 模版语法之变量 views.py html文件 三 模版之过滤器 语法: default length filesizeformat date slice tr ...

  7. Android news Display Owner Info on Your Android Device in Case It Gets Lost

    Display Owner Info on Your Android Device in Case It Gets Lost The latest versions of Android includ ...

  8. Fortran 的简单入门和使用 OpenMPI

    Fortran 与 C-like 语言的区别简单总结 无大括号,使用关键字画出范围: C++: int main() { } Fortran: program test implicit none e ...

  9. docker入门加实战—部署Java和前端项目

    docker入门加实战-部署Java和前端项目 部署之前,先删除nginx,和自己创建的dd两个容器: docker rm -f nginx dd 部署Java项目 作为演示,我们的Java项目比较简 ...

  10. Net 高级调试之二:CLR和Windows加载器及应用程序域介绍

    一.简介 今天是 Net 高级调试的第二篇文章,第一篇文章记录了自己学习 Net 高级调试的第一步,认识一些调试工具,有了工具的倚仗,我们开始仗剑走天涯了,开始Net 高级调试正式的征程了.我先说一下 ...