‘壹’ 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中执行 flushSchelerQueue 方法,该方法会执行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 生命周期钩子函数。
‘贰’ vue3源码分析-实现props,emit,事件处理等
>
本期来实现, setup里面使用props,父子组件通信props和emit等 ,所有的源码请查看
在render函数中, 可以通过this,来访问setup返回的内容,还可以访问this.$el等
由于是测试dom,jest需要提前注入下面的内容,让document里面有app节点,下面测试用例类似在html中定义一个app节点哦
本功能的测试用例正式开始
上面的测试用例
解决这两个需求:
针对上面的分析,需要在setupStatefulComponent中来创建proxy并且绑定到instance当中,并且setup的执行结果如果是对象,也已经存在instance中了,可以通过instance.setupState来进行获取
通过上面的操作,从render中this.xxx获取setup返回对象的内容就ok了,接下来处理el
需要在mountElement中,创建节点的时候,在vnode中绑定下,el,并且在setupStatefulComponent 中的代理对象中判断当前的key
看似没有问题吧,但是实际上是有问题的,请仔细思考一下, mountElement是不是比setupStatefulComponent 后执行,setupStatefulComponent执行的时候,vnode.el不存在,后续mountelement的时候,vnode就会有值,那么上面的测试用例肯定是报错的,$el为null
解决这个问题的关键,mountElement的加载顺序是 render -> patch -> mountElement,并且render函数返回的subtree是一个vnode,改vnode中上面是mount的时候,已经赋值好了el,所以在patch后执行下操作
在vue中,可以使用onEvent来写事件,那么这个功能是怎么实现的呢,咋们一起来看看
在本功能的测试用例中,可以分析以下内容:
解决问题:
这个功能比较简单,在处理prop中做个判断, 属性是否满足 /^on[A-Z]/i这个格式,如果是这个格式,则进行事件注册,但是vue3会做事件缓存,这个是怎么做到?
缓存也好实现,在传入当前的el中增加一个属性 el._vei || (el._vei = {}) 存在这里,则直接使用,不能存在则创建并且存入缓存
事件处理就ok啦
父子组件通信,在vue中是非常常见的,这里主要实现props与emit
根据上面的测试用例,分析props的以下内容:
解决问题:
问题1: 想要在子组件的setup函数中第一个参数, 使用props,那么在setup函数调用的时候,把当前组件的props传入到setup函数中即可 问题2: render中this想要问题,则在上面的那个代理中,在 加入一个判断,key是否在当前instance的props中 问题3: 修改报错,那就是只能读,可以使用以前实现的 api shallowReadonly来包裹一下 既可
做完之后,可以发现咋们的测试用例是运行没有毛病的
上面实现了props,那么emit也是少不了的,那么接下来就来实现下emit
根据上面的测试用例,可以分析出:
解决办法: 问题1: emit 是setup的第二个参数, 那么可以在setup函数调用的时候,传入第二个参数 问题2: 关于emit的第一个参数, 可以做条件判断,把xxx-xxx的形式转成xxxXxx的形式,然后加入on,最后在props中取找,存在则调用,不存在则不调用 问题3:emit的第二个参数, 则使用剩余参数即可
到此就圆满成功啦!
‘叁’ 面试中的网红Vue源码解析之虚拟DOM,你知多少呢深入解读diff算法
众所周知,在前端的面试中,面试官非常爱考dom和diff算法。比如,可能会出现在以下场景
滴滴滴,面试官发来一个面试邀请。接受邀请📞
我们都知道, key 的作用在前端的面试是一道很普遍的题目,但是呢,很多时候我们都只浮于知识的表面,而没有去深挖其原理所在,这个时候我们的竞争力就在这被拉下了。所以呢,深入学习原理对于提升自身的核心竞争力是一个必不可少的过程。
在接下来的这篇文章中,我们将讲解面试中很爱考的虚拟DOM以及其背后的diff算法。 请认真阅读本文~文末有学习资源免费共享!!!
虚拟DOM是用JavaScript对象描述DOM的层次结构。DOM中的一切属性都在虚拟DOM中有对应的属性。本质上是JS 和 DOM 之间的一个映射缓存。
要点:虚拟 DOM 是 JS 对象;虚拟 DOM 是对真实 DOM 的描述。
diff发生在虚拟DOM上。diff算法是在新虚拟DOM和老虚拟DOM进行diff(精细化比对),实现最小量更新,最后反映到真正的DOM上。
我们前面知道diff算法发生在虚拟DOM上,而虚拟DOM是如何实现的呢?实际上虚拟DOM是有一个个虚拟节点组成。
h函数用来产生虚拟节点(vnode)。虚拟节点有如下的属性:
1)sel: 标签类型,例如 p、div;
2)data: 标签上的数据,例如 style、class、data-*;
3)children :子节点;
4) text: 文本内容;
5)elm:虚拟节点绑定的真实 DOM 节点;
通过h函数的嵌套,从而得到虚拟DOM树。
我们编写了一个低配版的h函数,必须传入3个参数,重载较弱。
形态1:h('div', {}, '文字')
形态2:h('div', {}, [])
形态3:h('div', {}, h())
首先定义vnode节点,实际上就是把传入的参数合成对象返回。
[图片上传失败...(image-7a9966-1624019394657)]
然后编写h函数,根据第三个参数的不同进行不同的响应。
当我们进行比较的过程中,我们采用的4种命中查找策略:
1)新前与旧前:命中则指针同时往后移动。
2)新后与旧后:命中则指针同时往前移动。
3)新后与旧前:命中则涉及节点移动,那么新后指向的节点,移到 旧后之后 。
4)新前与旧后:命中则涉及节点移动,那么新前指向的节点,移到 旧前之前 。
命中上述4种一种就不在命中判断了,如果没有命中,就需要循环来寻找,移动到旧前之前。直到while(新前<=新后&&旧前<=就后)不成立则完成。
如果是新节点先循环完毕,如果老节点中还有剩余节点(旧前和旧后指针中间的节点),说明他们是要被删除的节点。
如果是旧节点先循环完毕,说明新节点中有要插入的节点。
1.什么是Virtual DOM 和Snabbdom
2.手写底层源码h函数
3.感受Vue核心算法之diff算法
4.snabbdom之核心h函数的工作原理
1、零基础入门或者有一定基础的同学、大中院校学生
2、在职从事相关工作1-2年以及打算转行前端的朋友
3、对前端开发有兴趣人群