vue的watcher的源码(vue watcher源码)
本文目录一览:
- 1、Vue源码系列之生命钩子beforeCreate&created
- 2、vue的watcher
- 3、【手把手教你搓Vue响应式原理】(五) Watcher 与 Dep
- 4、vue生命周期详解
- 5、vue watch原理
Vue源码系列之生命钩子beforeCreate&created
在实习面试中,Vue相关被问到最多的就是围绕生命周期函数内部所作的一些操作,很多朋友可能了解的就是官网的下图,有时候倒霉遇上硬核点的面试官,深入问些东西就会被怼的哑口无言,本文就是在之前源码的基础上,分析一下,生命周期函数究竟发生了什么
先上图
在分析每个钩子之间究竟干了什么之前,先来看看钩子是怎么触发的,以第一个钩子为例
调用callHook函数并向其传入this和'beforeCreate'字符串,那来看看callHook函数究竟是何方神圣
注意到,该函数一开始,也就是钩子函数进入准备触发前,进行了一个pushTarget()的操作,注释写的是“在钩子函数触发时,禁用依赖收集”,那这个操作是干嘛呢
言归正传,禁用了依赖收集后,创建handlers数组存入合并后options的hook,本例中就是找自定义或者继承来的beforeCreate钩子,然后在invokeWithErroeHandling函数中以此触发,顺序是先触发父级,后自己定义的
而这个invokeWithErroeHandling函数如下
该函数直接就在内部call了钩子,所以钩子内部的this指向vm实例
以上就是钩子函数的触发过程,下面来看不同的生命周期之间究竟干了什么
beforeCreate之前
beforeCreate之后到created
1.对于props:合法化,缓存key进数组方便下次迭代,defineReactive
2.对于data:检验props,methods中是否有重名属性,defineReactive
3.对于methods:代理到vm实例上,方便使用this.method.name调用
4.对于computed:封装成watcher并用该watcher的value缓存该计算属性的value,再在每个计算属性上劫持一层getter和setter,在第一次调用getter的时候,取得最新的value,并将依赖缓存下来,之后再依赖不变的前提下,getter只返回watcher的value而不是又去取一遍值,再依赖发生变化的时候,通知watcher更新,watcher取的最新值作为value,从而实现依赖更新计算属性才更新
5.对于watch:调用$watch封装成一个user watcher,如果有immediate options传入,就在封装的时候就调用一遍callback,有deep options传进来的话就将该属性的所有嵌套属性记为依赖
beforeCreate之前主要是做准备工作,将该实例的options合并整理出来,再把$那些初始话
created之前就是对options做操作,data,props设置数据劫持,methods代理在vm实例上,computed,watch封装成不同类型的watcher
vue的watcher
在页面中使用vue的watcher的源码的watcher,即用户定义的watcher,用于观察一个属性的更新,支持数组定义多个,对象定义单个的形式,在initWatcher中进行watcher的初始化之后,在渲染函数进行数据的读取,触发依赖收集时会将user-watcher的依赖收集进去,data属性set更新时会被触发user-watcher所定义的回调函数(将新旧值传入),支持异步操作
可选项vue的watcher的源码:immediate deep sync
每一个组件都有一个render-watcher当data/computed中的有被依赖的属性改变的时候会触发render-watcher的更新,表达式为function(){vm._update(vm.render(), hydrating)}
这三种watcher执行顺序为computed watcher = user watcher = render watcher,这样做 尽可能的保证vue的watcher的源码了视图更新时数据是最新的
收集依赖是在render tree渲染时读取render tree的结构,触发依赖的收集,每一次都只会对一个观察者进行操作,所以一个时间点只有一个Dep.tatget,说明这个观察者是依赖于当前的数据,就会把这个观察者添加到该数据的subs里面,会通过id来防止重复添加,同时会将依赖添加到自身的deps中以便通过set调用dep.notify()
实现vue的watcher的源码:
【手把手教你搓Vue响应式原理】(五) Watcher 与 Dep
【手把手教你搓Vue响应式原理】(一)初识Vue响应式
【手把手教你搓Vue响应式原理】(二)深度监测对象全部属性
【手把手教你搓Vue响应式原理】(三)observe 以及 ob
【手把手教你搓Vue响应式原理】(四) 数组的响应式处理
之前已经将数据劫持已经全部完成vue的watcher的源码了。
那么,接下来,主要的要点就是在于两点,依赖收集和触发依赖更新。
它的意义主要在于控制哪些地方使用了这个变量,然后,按照最小的开销来更新视图 。
首先,要先明白,依赖是什么,比方说在vue的watcher的源码我们的模板中有 {{a}} ,那么,这个地方就有对于变量 a 的依赖。
在模板编译的时候,就会触发 a 变量的 getter 。
然后,当我们执行 a++; 的时候,那么,我们就要触发依赖的更新,当初模板中 {{a}} 的地方,就要更新,是吧!
所以,我们都是 在 getter 中收集依赖,在 setter 中触发依赖更新 。
这一节的内容,主要就是用来专门讲清楚这两件事情。
依赖收集和触发依赖更新主要由两个类来完成, Dep 和 Watcher 。
Dep 和 Watcher 在设计模式中,就是 发布-订阅者 的模式。
而依赖,你可以理解为所谓的订阅者。
Dep 说白了就是发布者,它的工作就是依赖管理,要知道哪些地方用到了这个变量,可能用到这个变量的地方有很多,所以,它会有多个订阅者。
然后,每个变量都应该有属于自己的 Dep ,因为每个变量所在的依赖位置是不一样的,所以vue的watcher的源码他们的订阅者也不一样。
然后在变量更新之后,就去通知所有的订阅者(Watcher),我的变量更新了,你们该触发视图更新了。
Watcher 说白了就是订阅者,它接受 Dep 发过来的更新通知之后,就去执行视图更新了。
它其实就是所谓的 watch 监听器,变量改变之后,执行一个回调函数。
我们先按照图例来创建我们的 Dep 类
根据我们的需求:
Dep 我们在前面也说了,每个属性都应该有它自己的 Dep ,用来管理依赖。
所以,首先,如果我们在 Observer 中创建 Dep,那不就可以了。毕竟 Observer 会遍历到每一个对象。
所以,很明显,我们可以在 defineReactive 的 get 中收集依赖
因为有了 if(Dep.target) 的判断,所以, 只有绑定 Watcher 的变量触发 getter 时,才会添加依赖 。
这个 Dep.target 其实就是 Watcher 的实例
所以,很明显,我们可以在 defineReactive 的 set 中收调用 notify() 方法告知 Watcher 实例,数据更新了。
至此, Dep 的所有职责,我们已经帮它完成了。
其实照道理应该有一个删除依赖,我们这里就不再扩展了。
首先, Watcher 实例应该大家会相对而言更加好理解点,因为,我们有一个 watch 侦听器,大家一定都很熟悉,这两个其实一样。
我们先按照图例来创建我们的 Watcher 类
根据我们的需求:
这个 parsePath 需要单独拎出来说一下,比方说我们现在有这么一个对象
我们要监听到 a.b.c.d ,所以,我们需要下面的这种格式
所以,这个 get 很明显就有点难度了。 我们需要通过循环 拿到 a.b 然后 .c 然后 .d。
我们将这个方法命名为 parsePath 。
入参接受我们的 b.c.d ,我们可以看到 第一句执行之后 segments=['b','c','d'] ,然后进行第二层,这是返回了一个方法,按照循环,那就是 obj=obj.b = obj=obj.c = obj=obj.d ,所以,就是返回一个对象的 obj.b.c.d,相当于是遍历字符串中的属性树。
在执行 a.b.c.d=55; 的同时,我们的控制台就会输出 ok 55 10 。
【尚硅谷】Vue源码解析之数据响应式原理
vue生命周期详解
vue源码中最终执行生命周期函数都是调用 callHook 方法, callHook 函数的逻辑很简单,根据传入的生命周期类型 hook ,去拿到 vm.$options[hook] 对应的回调函数数组,然后遍历执行,执行的时候把 vm 作为函数执行的上下文。
1. new Vue(options) :创建一个vm实例;
2. mergeOptions(resolveConstructorOptions(vm.constructor), options, vm) :合并Vue构造函数里options和传入的options或合并父子的options。比如:在mergeOptions函数中会调用mergeHook方法合并生命周期的钩子函数,mergeHook方法原理是只有父时返回父,只有子时返回数组类型的子。父、子都存在时,将子添加在父的后面返回组合而成的数组。这也是父子均有钩子函数的时候,先执行父的后执行子的的原因;
3. initLifecycle(vm)、initEvents(vm)、initRender(vm) :在创建的vm实例上初始化生命周期、事件、渲染相关的属性;
4. callHook(vm, 'beforeCreate') :调用beforeCreate生命周期钩子函数;
5. initInjections(vm)、initState(vm)、initProvide(vm) :初始化数据:inject、state、provide。initState 的作用是初始化 props、data、methods、watch、computed 等属性;
6. callHook(vm, 'created') :调用created生命周期钩子函数;
7. vm.$mount(vm.$options.el) : $mount 方法在多个文件中都有定义,如"src/platform/web/entry-runtime-with-compiler.js"、"src/platform/web/runtime/index.js"、"src/platform/weex/runtime/index.js"。因为 $mount 方法的实现是和平台、构建方式相关的。以"entry-runtime-with-compiler.js"为例,关键步骤是查看 vm.$options 中是否有render方法,如果没有则会根据el和template属性确定最终的template字符串,再调用 compileToFunctions 方法将template字符串转为render方法,最后,调用原先原型上的$mount方法,即开始执行"lifecycle.js"中 mountComponent 方法;
8. callHook(vm, 'beforeMount') :调用beforeMount生命周期钩子函数;
9. vm._render() = vm._update() = vm.__patch__() :先执行vm._render方法,即调用createElement生成虚拟DOM,即VNode ,每个VNode有children ,children 每个元素也是⼀个 VNode,这样就形成了⼀个 VNode Tree;再调用vm._update方法进行首次渲染,vm._update方法核心是调用vm. patch 方法,这个方法跟vm.$mount一样跟平台相关;vm. patch 方法则是根据生成的VNode Tree递归createElm方法创建真实Dom Tree挂载到Dom上;
10. callHook(vm, 'mount') :调用mount生命周期钩子函数:VNode patch 到 Dom 之后会执行 'invokeInsertHook'函数,把 insertedVnodeQueue 中保存的mount钩子函数执行一遍,insertedVnodeQueue队列中的钩子函数是在根据VNode Tree递归createElm方法创建真实Dom Tree过程生成的钩子函数顺序队列,因此mounted钩子函数的执行顺序是先子后父;
11. data changes :数据更新,nextTick中执行 flushSchedulerQueue 方法,该方法会执行watcher队列中的watcher;
12. callHook(vm, 'beforeUpdate') :执行watcher时会执行watcher的before方法,即调用beforeUpdate生命周期钩子函数;
13. Virtual DOM re-render and patch :重新render生成新的Virtual DOM,并且patch到DOM上;
14. callHook(vm, 'updated') :调用updated生命周期钩子函数;
15. vm.$destroy() :启动卸销毁过程;
16. callHook(vm, 'beforeDestroy') :调用beforeDestroy生命周期钩子函数;
17. Teardown watchers, childcomponents and event listeners :执行一系列销毁动作,在 $destroy 的执行过程中,它又会执行 vm.__patch__(vm._vnode, null) 触发它子组件的销毁钩子函数,这样一层层的递归调用,所以 destroyed 钩子函数执行顺序是先子后父,和 mounted 过程一样。
18. callHook(vm, 'destroyed ') :调用destroyed 生命周期钩子函数。
vue watch原理
1、很多情况下vue的watcher的源码,computed和watch可以实现相同的功能;
2、当需要在数据变化时执行异步或开销较大的操作时vue的watcher的源码,使用watch会更好一些。因为computed会立即返回,此时异步操作可能还没有完成;
3、因为数据是响应式的,使得watch有意义。并不是因为watch了才使得数据是响应式的。
4、使用immediate:true,会在初始化watch时就立即执行handler回调函数,而不用等下一次数据更新。
5、使用deep:true,才会递归监听对象的属性(如果监听的是对象或数组)。
在created函数调用之前,调用了initWatcher方法,(调用该方法时,若immediate为真,则会立即执行回调函数),为每一个watcher属性实例化了一个Watcher,new Watcher ,会传入监听的属性key、回调函数和options,包括handler、deep和immediate的值,实例化的结尾会调用watcher.prototype.get方法,该方法会获得值并返回,值存在watcher.value属性上。
Get函数(即autorun)的执行即导致了watcher被收集为依赖。至此成功的监听了属性。
Get时,如果deep为真,则会递归监听所有的属性。
在数据发生变化时,调用watcher.prototype.update方法,最终会执行第三步的get。
watch的watcher中的lazy和sync都为false,所以会执行queueWatcher.
第一步
第二步
第三步 watch的get方法
第四步 更新数据
第五步
Q:哪些对象是Watcher?
A :在源码中,看到三个地方会初始化Watcher对象。挂载组件(mountComponent方法)、初始化watch(initWatch方法)和初始化computed(initComputed方法)。