玩转Vue面试题系列结合源码分析(2)
文章目录`Vue`中如何检测数组变化?`Vue`中如何进行依赖收集?如何理解`vue`中模板编译原理`Vue`生命周期钩子是如何实现的生命周期的价值`Vue`的生命周期方法有哪些?一般在哪一步发送请求及其原因上文:vue面试题及其结合源码分析Vue中如何检测数组变化?vue中检测数组的变化并没有使用defineProperty,因为修改索引的情况不多(且直接使用defineProperty会浪费大
文章目录
Vue
中如何检测数组变化?
vue中检测数组的变化并没有使用defineProperty,因为修改索引的情况不多(且直接使用defineProperty会浪费大量性能,如果数组一万项,我们需要拦截一万次?)所以在vue中是重写数组的变异方法来实现的响应式(函数劫持)
流程:
- _init -> initState -> initData -> observe -> defineProperty -> new Observer -> 发现value是数组
- 对传入的数组进行原型链修改,后续调用的7个数组变异方法可以达到响应式变化
- 但是,修改数组索引,修改长度是无法观测到的(不会响应式触发更新)
- 此外,对于数组元素还是数组或者对象的,会再次观测的
源码和第一个问题所在的源码位置是一样的,只是针对数组做了特殊处理。
Vue
中如何进行依赖收集?
-
所谓的依赖收集(观察者模式),被观察者指代的数据(dep收集watcher),观察者有三种(watcher):
- 渲染watcher
- 计算属性watcher
- 用户watcher
-
一个watcher可能对应着多个数据,所以watcher也需要保存dep
-
重新渲染的时候,可以让属性重新记录watcher,
-
计算属性也会用到
-
一个dep对应多个watcher(一个属性可以用在多个组件上)
-
一个watcher可以有多个dep(一个组件可以使用多个数据)
-
默认渲染的时候,会进行依赖收集
模板取值就会来到getter方法:
来到getter,表示当前watcher用到了此属性,那么属性的dep就会提醒watcher去收集此属性对应的dep,然后收集完毕后又让dep把watcher也收集了。当然在收集过程中做过去重操作。
在把依赖都收集完毕后,render函数也执行完毕了。此时清除无效的dep和watcher之间的关系。如果上次的dep在本次调用render函数时,并没有用到该属性的dep,那么就会把watcher和dep之前的关系清理掉。
如何理解vue
中模板编译原理
我们用户传递的是template属性,我们需要把这个template编译成render函数。
- template -> ast语法树
- 对语法树进行标记(可以理解为优化,有些静态节点压根没有用到data数据,也就是节点本身是不会发生改变的)。递归标记,深度优先,先标记子节点,然后标记父节点。子节点不是静态节点,那么父节点很明显也不是静态的。最后还会标记一次当前节点的根节点是否是静态的。
- 将ast语法树生成render函数
最终每次渲染可以调用render函数返回对应的虚拟节点。
Vue
生命周期钩子是如何实现的
就是内部利用了一个发布订阅模式,将用户写的钩子维护成一个数组,后续在特殊时间点,一次调用callHook。
- 在调用_init方法中,会合并选项,使用的就是mergeOptions方法
- 在该方法内进行父和子选项的属性合并
- 对于有的策略,我们会使用对应的策略方式进行合并,策略模式在这里可以减少if-else的使用。
- 钩子合并完成后,会在特殊的时间段调用,调用时使用的就是callHook方法.
- callHook函数就是取出该生命周期的函数,依次执行,但是实际执行钩子的方法是
invokeWithErrorHandling
:该方法会捕获执行时的异常,也会绑定上下文,也可以在promise的情况下把错误给用户,不是promise的情况,会调用handleError方法帮助处理异常。
- 此外,对于异常的捕获,也是有生命周期钩子的。在生命周期钩子执行报错后,默认执行此方法
内部实现原理就是发布订阅模式。主要就是靠mergeOptions方法
生命周期合并后,会在特殊时间点调用。比如状态state初始化前后就会调用 beforeCreate和created两个钩子
问题来了,为什么有些钩子是先执行子的,在执行父的钩子;但是有些又是先父后子?还有组件渲染是如何渲染的?
- 在组件渲染过程中,遇到父组件就渲染父组件,遇到子组件就渲染子组件。
<!--渲染父-->
<div id="app">
<!--开始渲染子-->
<my-btn/>
<!--子渲染完毕-->
<!--继续渲染父-->
</div>
<!--渲染完毕-->
对于有些生命周期,肯定需要先执行父组件的,才能执行子组件的,比如created;但是像mounted等生命周期,肯定是先把子组件渲染出来,才能继续渲染父组件,因此肯定是先需要执行子组件的mounted等生命周期钩子。
生命周期的价值
Vue
的生命周期方法有哪些?一般在哪一步发送请求及其原因
-
beforeCreate:这里还没有实现响应式数据。该生命周期是在initState方法之前执行的。其实没什么特别大的用。(Vue3中已经用不到这玩意了)
-
created:这里已经进行了数据劫持,可以拿到响应式数据(不涉及dom渲染)。这个api可以在服务端渲染中使用。(vue3的setup取代了该钩子)
// init.js // 初始化生命周期 组件父子关系 $parent $children 等 initLifecycle(vm) // 初始化事件 $on $emit $once $off ... initEvents(vm) // 声明变量 slot等 initRender(vm) callHook(vm, 'beforeCreate') // inject initInjections(vm) // resolve injections before data/props // TODO init data method computed ... initState(vm) // provide initProvide(vm) // resolve provide after data/props callHook(vm, 'created')
-
beforeMount:没有实际的价值
-
mounted:组件已经挂载完毕,第一次渲染完毕。在这个钩子中可以获取真实dom($el)。要明确一下,不一定mounted执行了,就代表组件全都挂载到页面上了,可能是子组件渲染完毕,挂载到父组件上,此时父组件不一定也渲染完毕了。只是代表我们在这个钩子中可以拿到当前组件对应的dom元素而已。
-
beforeUpdate:页面更新会调用该钩子
-
updated:组件更新后调用。依赖更新导致视图刷新(非同步)
-
activated:组件正在激活(未激活->激活)会被调用
-
deactivated
-
beforeDestory:手动调用移除后会触发。此时我们的watcher还在,还有响应式数据。(属性,方法等都还在)
-
destoryed:销毁组件后触发。所有属性和方法均已经被移除后触发。所以对于不涉及组件数据/方法的清理工作,这两个钩子都可以。
-
errorCaptured:捕获错误
那我们应该在哪个钩子发起请求?
一般最多的是在mounted中。(created不是比mounted早吗?要明确一下,代码是同步执行的,请求是异步的,就算请求的发出去的早这么一点点,也是等待同步代码执行完毕才能执行异步代码)
虽然服务端渲染都是在created中发起请求获取数据,但是即使是服务端渲染,也很少使用这个钩子。服务端没有dom,也没用mounted钩子。
在哪里发请求,主要看你要做什么事情,什么需求。(请求后获取最新dom做一些事情,就在mounted中)
有人说:created执行完再执行mounted,这个时候异步的created已经执行完了。
这种说法是错误的,因为生命周期是顺序调用的(同步执行),请求是异步的,所以最终获取到数据肯定是在mounted之后的。
另外,我们说的created中拿不到dom,(因为此时还没开始解析模板,生成render,然后执行render函数生成虚拟dom,最后渲染成真实dom。)不能拿dom只是代表不能同步拿dom,但是如果你在created中发起了异步请求,在异步请求中的代码(比如请求成功的回调函数)是可以拿到dom元素了。vm.$el
… 持续更新中…
更多推荐
所有评论(0)