vue2响应式原理
Posted 一身风骨擎天破
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue2响应式原理相关的知识,希望对你有一定的参考价值。
首先要知道vue2 是2013年 基于 ES5开发出来的
我们常说的重渲染就是重新运行render函数
vue2响应式原理简单来说就是vue官网上的这图片
通过 Object.defineProperty 遍历对象的每一个属性,把每一个属性变成一个 getter 和 setter 函数,读取属性的时候调用 getter,给属性赋值的时候就会调用 setter.
当运行 render 函数的时候,发现用到了响应式数据,这时候就会运行 getter 函数,然后 watcher(发布订阅)就会记录下来。当响应式数据发生变化的时候,就会调用 setter 函数,watcher 就会再记录下来这次的变化,然后通知 render 函数,数据发生了变化,然后就会重新运行 render 函数,重新生成虚拟 dom 树。
深入了解:
我们要明白,响应式的最终目标:是当对象本身或对象属性发生变化时,会运行一些函数,最常见的就是 render 函数。不是只有 render,只要数据发生了变化后运行了一些函数,就是响应式,比如 watch。
在具体实现上,vue 采用了几个核心部件:
1.Observer
2.Dep
3.Watcher
4.Scheduler
Observer
observer 要实现的目标非常简单,就是把一个普通的对象转换成响应式的对象
为了实现这一点,observer 把对象的每个属性通过 object.defineProperty 转换为带有 getter 和 setter 的属性,这样一来,当访问或者设置属性时,vue 就会有机会做一些别的事情。
在组件的生命周期中,这件事发生在 beforeCreate 之后,create 之前。
具体实现上,他会递归遍历对象的所有属性,以完成深度的属性转换。
但是由于遍历只能遍历到对象的当前属性,无法监测到将来动态添加或者删除的属性,因此 vue 提供了$set和$delete 两个实例方法,但是 vue 并不提倡这样使用,我讲到 dep 的时候我再说为什么。
对于数组的话,vue 会更改它的隐式原型,之所以这样做是因为 vue 需要监听那些可能改变数组内容的方法。
数组 --> vue 自定义的对象 --> Array.prototype
总之,observer 的目标,就是要让一个对象,它属性的读取,赋值,内部数组的变化都要能够被 vue 感知到。
Dep
这里有两个问题没解决,就是读取属性时要做什么事,属性变化时又要做什么事,这个问题就得需要 dep 来解决
dep 的含义是 dependency 表示依赖的意思。
vue 会为响应式对象中的每一个属性,对象本身,数组本身创建一个 dep 实例,每个 dep 实例都可以做两件事情:
1,记录依赖:是谁在用我
2,派发更新:我变了,我要通知那些用我的人
当读取响应式对象的某一个属性时,他会进行依赖收集,有人用到了我
当改变某个属性时,他会派发更新,那些用我的人听好了,我变了
为什么尽量不要使用$set $delete ?
因为如果模板上没有用到值的话,你凭空加了一个数据,理论上来说应该不会重新运行render函数,但是上一级的dep发现自身发生改变了,所以也会导致重新运行render函数。
所以vue不建议使用$set 和$delete,最好提前先写上数据,哪怕先给数据赋值为 null;
watcher
这里又出现了一个问题,就是 dep 如何知道是谁在用我呢
watcher 就解决了这个问题
当函数执行的过程中,用到了响应式数据,响应式数据是无法知道是谁在用自己的
所以,我们不要直接执行函数,而是把函数交给一个 watcher 的东西去执行,watch 是一个对象,每个函数执行时都应该创建一个 watcher,通过 wacher 去执行
watcher 会创建一个全局变量,让全局变量记录当前负责执行的 watcher 等于自己,然后再去执行函数,在函数执行的过程中,如果发生了依赖记录,那么 dep 就会把这个全局变量记录下来,表示有一个 wathcer 用到了我这个属性。
当 dep 进行派发更行时,他会通知之前记录的所有 watcher,我变了。
Scheduler
现在还剩下最后一个问题啊,就是 dep 通知 watcher 之后,如果 wathcer 执行重新运行对应的函数,就有可能导致频繁运行,从而导致效率低下,试想,如果一个交给 watcher 的函数,它里面用到了属性 a,b,c,d,那么 a,b,c,d 都会记录依赖,然后这四个值都以此重新赋值,那么就会触发四次更新,这样显然不行啊,所以当 watcher 收到派发更新的通知后,实际上并不是立即执行,而是通过一个叫做 nextTick 的工具方法,把这些需要执行的 watcher 放到事件循环的微队列,nextTick 是通过 Promise then 来完成的。
也就是说,在响应式数据发生变化时,render 函数执行是异步的,并且在微队列中。
【手把手教你搓Vue响应式原理】(一)初识Vue响应式
参考技术A在讲这个之前,首先要明白一点,这个所谓的响应式,其实本身就是对 MVVM 的理解。
MVVM 其实就是所谓的 Modal View ViewModal 。
简单理解,就是你的 data 中的数据,和 template 模板中的界面,本身就是两个东西。
但是, Vue 给你做了一层中间的 ViewModal ,让视图上的改变能反映到 data 中, data 中的改变能反映到视图上。
在这个反映过程中,ViewModal就是视图和数据的一个桥梁。
同样是让 a + 1 。
在 Vue 中,这个桥梁是你看不见的,因为 Vue 都帮你完成了视图和数据的变化传递。
而 React 就是侵入式的,因为要显式地声明 setState ,通过它,来设置变量的同时,设置视图的改变。
所以,所谓的侵入式,其实就是对于桥梁的侵入。
所以, Vue 的神奇之处就在于,不需要我们手动地显示调用 setState ,也就是这个桥梁, Vue 已经帮我们桥接上了。
要让 data 改变的同时,视图也发生改变,所以,问题的所在,就是我们需要监听,什么时候,这个变量发生了变量。
然而, ES5 中,就有那么一个特性,可以做到对于数据的劫持(监听)。
它就是 Object.defineProperty 。
Object.defineProperty( obj, prop, descriptor ) 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象,与此同时,它可以对 对象的一些额外底层的属性进行设置 。例如可以设置 writable , enumerable , configurable 等属性。
后面的额外属性设置,才是我们使用它的重点。
但是,我们使用的不是上面的几个属性,最主要的还是它的 get set ,可以对属性值的获取和设置操作进行拦截。
get主要是可以对值的获取进行拦截,,它必须要传入一个 return ,并且, 该函数的返回值会被用作属性的值 。我们可以来看一个例子:
由于设置了 get ,所以,输出 a.name 的时候直接会被拦截,走 get() 中的 return 所以,此时, a.name 的值应该是 你已经被拦截了!。
set主要是可以对值的设置进行拦截,该方法会接受一个参数,那就是 被赋予的新值 。我们可以来看一个例子:
由于设置了 set ,所以,设置值的时候会被拦截,走 set() 中的方法。
所以, Vue 能自动获取data中的改变,反映到视图的原因,就是有对于变量的获取和设置的劫持,当变量发生改变的同时, Vue 能在第一时间知道,并且对视图做出相应的改变操作。
而这把钥匙就是 Object.defineProperty 。
【尚硅谷】Vue源码解析之数据响应式原理
Object.defineProperty() - MDN
以上是关于vue2响应式原理的主要内容,如果未能解决你的问题,请参考以下文章