2024年Web前端最新从源码看Vue生命周期(2),2024年最新阿里+头条+抖音+百度+蚂蚁+京东面经

总结

=============================================================

从转行到现在,差不多两年的时间,虽不能和大佬相比,但也是学了很多东西。我个人在学习的过程中,习惯简单做做笔记,方便自己复习的时候能够快速理解,现在将自己的笔记分享出来,和大家共同学习。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

个人将这段时间所学的知识,分为三个阶段:

第一阶段:HTML&CSS&JavaScript基础

第二阶段:移动端开发技术

第三阶段:前端常用框架

  • 推荐学习方式:针对某个知识点,可以先简单过一下我的笔记,如果理解,那是最好,可以帮助快速解决问题;如果因为我的笔记太过简陋不理解,可以关注我以后我还会继续分享。

  • 大厂的面试难在,针对一个基础知识点,比如JS的事件循环机制,不会上来就问概念,而是换个角度,从题目入手,看你是否真正掌握。所以对于概念的理解真的很重要。

  • 根据AST,生成render函数。

对应代码如下:

const ast = parse(template.trim(),options)

if(options.optimize !== false){

optimize(ast,options)

}

const code = generate(ast,options)

总之,在有了render函数之后,就可以进行渲染步骤了,执行beforeMount钩子函数。

beforeMount执行完成之后接着往下走:

//…

callHook(vm,‘beforeMount’)

let updateComponent

/* istanbul ignore if */

if (process.env.NODE_ENV !== ‘production’ && config.performance && mark) {

updateComponent = () => {

const name = vm._name

const id = vm._uid

const startTag = vue-perf-start:${id}

const endTag = vue-perf-end:${id}

mark(startTag)

const vnode = vm._render()

mark(endTag)

measure(vue ${name} render, startTag, endTag)

mark(startTag)

vm._update(vnode, hydrating)

mark(endTag)

measure(vue ${name} patch, startTag, endTag)

}

} else {

updateComponent = () => {

vm._update(vm._render(), hydrating)

}

}

//…

定义一个渲染组件的函数updateComponent

updateComponent =  () => {

vm._update(vm._render,hydrating)

}

vm._render就是调用render函数生成一个vnode,而vm._update方法则会对这个vnode进行patch操作,帮我们把vnode通过cleateElm函数创建新节点,并且渲染到dom中。

//src/core/instance/lifecycle.js :59

Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {

//…

vm.KaTeX parse error: Expected group after '_' at position 9: el = vm._̲_patch__(vm.el, vnode, hydrating, false /* removeOnly */)

//…

}

看完updateComponent方法里面的具体实现之后

接下来就是要执行,updateComponent方法了。

执行是由Watcher类负责执行的。

//src/core/instance/lifecycle.js :197

new Watcher(vm, updateComponent, noop, {

before () {

if (vm._isMounted && !vm._isDestroyed) {

callHook(vm, ‘beforeUpdate’)

}

}

}, true /* isRenderWatcher */)

为什么要用Watcher来执行呢?因为在执行过程中,需要去观测这个函数依赖了哪些响应式的数据,将来在数据更新的时候,我们需要再重新执行updateComponent函数。

如果是更新后调用updateComponent函数,updateComponent内部的patch就不再是初始化的时候创建节点,而是通过diff算法将差异的地方找到,以最小化的代价更新到真实dom上。

Watcher中有一个before方法,逻辑是当vm._isMounted,也就是第一次挂载完成之后,再次更新视图之前,会先调用beforeUpdate钩子函数。

注意:如果在render过程中有子组件的话,此时子组件也会有一系列的初始化过程,也会走之前所说的所有过程,因此这是一个递归构建过程。

当有子组件时生命周期执行过程:

  • 父 beforeCreate

  • 父 created

  • 父 beforeMount之后—>render

  • 子 beforeCreate

  • 子 created

  • 子 beforeMount之后—>render

  • 子 mounted

  • 父 mounted

//src/core/instance/lifecycle.js :208

if (vm.$vnode == null) {

vm._isMounted = true

callHook(vm, ‘mounted’)

}

最终mounted生命周期钩子函数触发

更新流程

当一个响应式属性被更新,触发了Watcher的回调函数,也就是updateComponent 方法

updateComponent = () => {

vm._update(vm._render(), hydrating)

}

new Watcher(vm, updateComponent, noop, {

before () {

if (vm._isMounted && !vm._isDestroyed) {

callHook(vm, ‘beforeUpdate’)

}

}

}, true /* isRenderWatcher */)

在更新之前会先进行判断,是否是更新vm._isMounted如果是更新那么就会直接执行beforeUpdate生命周期钩子。

Vue 异步执行 DOM 更新

由于Vue组件的异步更新机制,当响应式数据发生变化,根据数据劫持此时会调用dep.notify(src/core/observer/index.js : 191),之后会在Dep中去遍历所有watcher进行更新subs[i]update()(src/core/observer/dep.js : 47),之后在Watcher中会发现,在调用update方法后,会调用Watcher中的update方法()会发现它执行了一个叫queueWatcher的方法,具体可以看下代码。

// src/core/observer/scheduler.js :164

export function queueWatcher (watcher: Watcher) {

const id = watcher.id

if (has[id] == null) {

has[id] = true

if (!flushing) {

queue.push(watcher)

} else {

let i = queue.length - 1

while (i > index && queue[i].id > watcher.id) {

i–

}

queue.splice(i + 1, 0, watcher)

}

// queue the flush

if (!waiting) {

waiting = true

nextTick(flushSchedulerQueue)

}

}

}

从代码中可以看到,queueWatcher方法主要做了几件事

将watcher存到一个队列queue中, 在nextTick方法中执行flushSchedulerQueue方法

function flushSchedulerQueue () {

currentFlushTimestamp = getNow()

flushing = true

let watcher, id

queue.sort((a, b) => a.id - b.id)

for (index = 0; index < queue.length; index++) {

watcher = queue[index]

if (watcher.before) {

watcher.before()

}

id = watcher.id

has[id] = null

watcher.run()

}

在flushSchedulerQueue方法中主要干的事情就是,遍历执行存放watcher的queue,并且判断如果当前watcher有before方法,那么就先执行watcher.before,此时就会触发之前的callHook(vm, ‘beforeUpdate’)方法,触发beforeUpdate生命周期钩子函数(src/core/instance/lifecycle.js)。而nextTick方法就是将flushSchedulerQueue方法存到一个数组callbacks中

// src/core/util/next-tick.js :87

export function nextTick (cb?: Function, ctx?: Object) {

callbacks.push(() => {

if (cb) {

try {

cb.call(ctx)

} catch (e) {

handleError(e, ctx, ‘nextTick’)

}

} else if (_resolve) {

_resolve(ctx)

}

})

if (!pending) {

pending = true

timerFunc()

}

}

最终会执行timerFunc方法

//src/core/instance/util/next-tick.js :33

let timerFunc

if (typeof Promise !== ‘undefined’ && isNative(Promise)) {

const p = Promise.resolve()

timerFunc = () => {

p.then(flushCallbacks)

if (isIOS) setTimeout(noop)

}

isUsingMicroTask = true

} else if (!isIE && typeof MutationObserver !== ‘undefined’ && (

isNative(MutationObserver) ||

MutationObserver.toString() === ‘[object MutationObserverConstructor]’

)) {

let counter = 1

const observer = new MutationObserver(flushCallbacks)

const textNode = document.createTextNode(String(counter))

observer.observe(textNode, {

characterData: true

})

timerFunc = () => {

counter = (counter + 1) % 2

textNode.data = String(counter)

}

isUsingMicroTask = true

} else if (typeof setImmediate !== ‘undefined’ && isNative(setImmediate)) {

timerFunc = () => {

setImmediate(flushCallbacks)

}

} else {

timerFunc = () => {

setTimeout(flushCallbacks, 0)

}

}

这段代码就是nextTick的核心代码了,Vue响应式更新为什么是异步的也在这里能找到答案,首先判断运行环境是否支持Promise,如果不支持就判断是否支持MutationObserver,如果不支持,就去判断是否支持setImmediate,如果还不支持就用setTimeout大法。最终最终会将callbacks数组中的方法在异步方法中执行,到此,Vue响应式异步更新也差不多梳理了一遍。beforeUpdate生命周期执行完之后,就开始一系列的patch、diff流程后组件渲染完毕之后就会调用updated生命周期钩子函数。

//src/core/observer/scheduler.js :130

function callUpdatedHooks (queue) {

let i = queue.length

while (i–) {

const watcher = queue[i]

const vm = watcher.vm

if (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {

callHook(vm, ‘updated’)

}

}

}

注意:这里watcher中的updated是倒序调用的,所以当同一个属性在父-子组件中都有使用,收集依赖是按照父->子收集,但是触发updated钩子函数却是子->父。updated生命周期钩子函数执行完成之后渲染更新流程也就到此结束。

销毁流程

在更新流程中,如果发现有组件在下一轮渲染中消失,比如v-for对应的数组中少了数据,或者v-if控制的组件由true变为false,那么就会调用removeVnodes进入组件的销毁流程。

removeVnodes会调用vnode的destroy生命周期,而destroy内部则会调用vm.destroy.(keep-alive包裹的子组件除外)

这时就会调用callHook(vm,‘beforeDestroy’)

//src/core/instance/lifecycle.js :97

Vue.prototype.$destroy = function () {

const vm: Component = this

if (vm._isBeingDestroyed) {

return

}

callHook(vm, ‘beforeDestroy’)

vm._isBeingDestroyed = true

const parent = vm.$parent

if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {

remove(parent.$children, vm)

}

最后

其实前端开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

这里再分享一个复习的路线:(以下体系的复习资料是我从各路大佬收集整理好的)

《前端开发四大模块核心知识笔记》

最后,说个题外话,我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在IT学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值