vue渲染之计算属性watcher
- 首先我们先来认识一下computed的特性吧, computed是有缓存的, 只有当依赖的数据发生改变才会重新去执行computed, 否则就一直使用缓存的值, 原理是计算属性watcher内部有一个dirty属性控制着computed函数的执行, 只有dirty为true的时候才会执行, 为false的时候就不会执行使用返回的结果
- 计算属性watcher lazy(表示当前是计算属性watcher) dirty(computed缓存的主要实现就是有这个变量控制的) value缓存的结果
- new Vue的时候会传入一个配置对象, 执行initState方法, 判断如果配置对象里面有computed就执initComputed方法,
- initComputed方法会遍历computed中的每一项, 取出每一项的key和value, 先创建每一项所对应的计算属性watcher, 将value函数作为watcher的getter方法, 同时传入一个lazy属性, 计算属性watcher收到之后, 同时将这个lazy赋值到watcher上的lazy和dirty, watcher中的lazy表示当前是一个计算属性watcher, dirty给一个初始值true, 接着Watcher的constructor里面还会判断当前如果是计算属性watcher(根据lazy判断)就不会去立即执行get方法, 因为立即执行get方法computed函数就会立即执行了, 但是computed是要等到用户执行computed的key的get方法时才要执行
- 计算属性watcher创建完之后, 紧接着就需要创建get和set方法, 因为计算属性本质上就是Object.defineProperty get中去执行watcher的一种操作, 通过Object.defineProperty(vm, key, 描述符对象),
这个描述符对象里面包含两部分, 一是get方法, 二是set方法, set方法就是用户computed如果传的是一个对象的话, 对象的set就是这里的set, 没有的话就给一个空函数好了, 另一个是get方法, 我们先来理
一下, 当用户在页面上使用了computed, 就会去执行这里定义的get方法, 而在这个get方法中, 我们可以拿到刚才创建的计算属性watcher, 拿到之后我们可以先判断, 当前dirty是否是true, 如果是true,
我们就去执行watcher的evaluate方法,evaluate方法会先去执行get方法, get方法执行完会有一个返回值, 这个返回值是getter方法的返回结果, evaluate方法拿到之后, 将其赋值到watcher的value属性身上
,这样在计算属性watcher身上就有一个缓存的结果了, get方法中会先将当前活跃的watcher添加到数组里面, 用完就删掉(用完就是get方法执行完, 至于为什么是要存在数组里面, 我马上就要说到了), 接着执行getter方法
保存getter方法返回值, 从数组中删除当前watcher, Dep.target就会取这个数组里面的最后一项, get方法执行完之后, 接着执行evaludate方法, 这个方法目前已经拿到了最新的watcher value, 然后再将dirty
属性修改成false, evaludate方法执行完毕, 回到计算属性watcher的get方法中, 经过evaludate方法之后, 计算属性watcher身上就有value属性值了, 而且计算属性对应的函数也已经完成第一次执行了, 接着
再看一下Dep.target有没有值, 如果有值, 会拿到当前计算属性watcher身上绑定的所有dep, 遍历所有的dep, dep执行depend方法, 与当前活跃的watcher相互绑定, 这样就将当前的一个渲染watcher也绑定上了
为什么dep和watcher要做一个相互绑定, 其实在这块就已经用到了, 我再给你从头理一下, 为什么这里需要将计算属性的watcher与Dep.target所对应的watcher相互绑定
执行渲染watcher, 渲染watcher的get方法执行, 渲染watcher会首先添加到保存所有watcher的数组中, 执行getter方法, getter方法执行 _render方法执行 _render方法执行 计算属性get方法执行
get方法执行计算属性第一次watcher的evaluate方法执行 evaluate方法执行计算属性watcher get方法执行 计算属性watcher get方法执行计算属性watcher会被添加到保存watcher的数组中 执行getter
从数组中删除计算属性watcher 计算属性watcher获得最新的value值 dirty属性变成false 此时渲染属性还没完, 所以还得将computed函数中所用到的数据所对应的dep收集上当前的渲染watcher(数组中最后一项会作为Dep.target, 计算属性watcher删除, 那最后一个就是渲染watcher了)
此时computed函数中所使用的变量所对应的dep已经绑定了计算属性watcher和渲染watcher了
那么当依赖的数据变化之后, 执行依赖的数据的set方法, set方法dep.notify通知, notify方法就会去遍历所有绑定的watcher, 执行watcher所对应的update方法, 计算属性watcher的update方法就只是将dirty
属性变成true了, 渲染watcher则需要将其放到全局的watcher数组里面, 然后去重, nextTick执行这些(这里已经在上述地方阐述过了, 所以不再赘述), 再次执行渲染watcher, 渲染watcher的getter方法执行,
getter方法中执行计算属性的get方法, 由于当前计算属性watcher所对应的dirty已经变成true了, 所以会再次执行, 执行完之后,再次变成false(这里也是为什么数据需要收集渲染watcher的原因, 需要再次执行计算属性的get方法)
如果依赖的数据一直没有变, 在计算属性的get中只有dirty为true的话才会执行计算属性函数, 第一次执行完dirty已经变成false了, 下次再来用的时候就不会走计算属性watcher的evaluate方法, 直接返回之前保存的计算属性watcher的value属性即可
这个大概就是计算属性的源码解读
侦听器watcher
1.watch的实现本质上也是通过watcher来实现的, 这里不讨论底层实现的所有核心, 只会说明其大概内部的运行原理, 理解本质即可
2.watch与computed一致, 都是watcher, 都会在vue initState方法中判断, 如果用户传了watch, 就执行initWatch方法
3.initWatch同样也是拿到传入的watch对象, 遍历watch对象, 取出来每一个key和value(key就是监听的数据的名称, value就是数据变化后执行的回调函数), 同样也是创建每一个watch所对应的
watcher, 每次调用new Watcher, 都会将每一项的key, value还有一个选项对象{ user: true }传给Watcher, Watcher的constructor收到之后, 就会将user属性添加到watcher身上, 表示
当前是一个侦听器watcher, 如果是侦听器watcher的话, 会将传进来的key加上函数作为watcher的getter方法, this.getter = () => vm[key], (vm就是当前的vue实例, 也会在new Wacther
添加进来), 此时在constructor中会执行get方法, get方法一执行, getter方法就会执行, 通过vm[key], 就走到了数据的get方法中, get方法中执行dep.depend方法, 就会将当前的侦听器
watcher也收集到当前数据所对应的dep身上了, 到现在为止, initWatch中new Watcher为止, watch对象中要监听的数据的dep都已经收集到了当前的侦听器watcher了, 那数据变化之后自然而然
的会执行watcher的update方法了(前面文章有说watcher的一个执行过程), 我们无非就是在这个update方法中判断一下是否是侦听器watcher(根据user: true判断), 如果是我们就拿到new Watcher
中传过来的watch对象中的每一项value(遍历出来的key是数据名, value就是回调函数), 刚才忘记说了, 在constructor中会将传过来的value作为watcher的cb属性, 那在这里我们就可以直接调用
cb函数了, 这样就实现了数据变化, watch中的函数执行了
4.现在还有一个问题, 就是watch函数其实是需要两个参数的, 在上述提到关于计算属性watcher中会创建一个value来接收getter方法的返回值, 在侦听器watcher中也有这个value, 与计算属性watcher
中是一致的, 所以我们可以拿到这个值, 而这个值是不是就是数据变化之前的旧值呢? 旧值拿到了,保存在一个变量中, 新值无非也就是重新去调用get方法而已, 然后再把旧值和新值一起传给cb函数即可
保存旧值, 获取新值的操作都是在watcher的update方法中, 执行cb函数前需要做完的
5.接下来就是watch配置项的问题了 { immediate: true, deep: true }
immediate就是立即执行, 那我们就可以在initWatch中 new Watcher后直接调用的一个函数就行, 并且将watcher的value属性传进去就行了
deep表示深度监听, 深度监听的本质就是让监听的这个数据内部的对象所对应的dep都能收集到当前的侦听器watcher就可以了, 最简单的做法就是将侦听器watcher的getter方法修改成() => JSON.parse(JSON.stringify(key)),
内部JSON.parse(JSON.stringify(key))(key就是watch监听的数据名称) 监听的数据里面的对象就都会执行get, 对应的dep就会收集当前的侦听器watcher dep.depend
computed和watch的区别
1.相同点 都是通过wathcer实现的
2.不同点
(1).computed是有缓存的, 内部通过dirty属性进行控制 watch没有缓存
(2).computed不支持异步返回数据, 原因在于computed内部是执行evaluate方法, 拿到value值, 直接返回value的, 支持异步就是说computed函数中异步返回数据, 深点就是watcher中的getter方法需要异步返回数据, 这显然是不可能得, 都是同步返回, 即使是有异步操作, 也是先返回undefined的,
可以在compouted里面写异步操作, 但是不可能会异步返回数据的, watch支持异步操作, watch只是一个函数, 会在数据变化之后调用, 仅此而已, computed watch支持异步操作, computed不支持异步返回数据

2510

被折叠的 条评论
为什么被折叠?



