框架相关
原生JS虽能实现绝大部分功能,但要么就是过于繁琐,要么就是存在缺陷,故绝大多数开发者都会首选框架开发方案。现阶段较热门是React、Vue两大框架,两者工作原理上存在共通点,也存在一些不同点,对于校招来说,不需要两个框架都学得特别熟,一般面试官会针对你简历中写的框架进行提问。
在框架方面,生命周期、钩子函数、虚拟DOM这些基本知识是必须要掌握的,在学习的过程可以结合框架的官方文档
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
Vue框架
知识要点:
1. vue-cli工程
2. vue核心知识点
3. vue-router
4. vuex
5. http请求
6. UI样式
7. 常用功能
8. MVVM设计模式
React框架
知识要点:
1. 基本知识
2. React 组件
3. React Redux
4. React 路由
任务调度的实现
在理解任务调度之前我们先来了解一些重要的概念:
- JS是单线程的,所有的JS代码执行在
JS引擎线程
中; - 浏览器是多线程的,除了
JS引擎线程
还有UI渲染线程
,网络IO线程
等; - 由于JS执行在一个线程中,所以一次只能执行一个任务,如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务;
- 同步任务 是指主进程中一个个按顺序执行的任务,如果某个任务执行时间久,那后面的任务就等待执行;
- 异步任务 先不进入主线程,而先进入任务队列,只有主线程空闲了,且异步任务可以执行了,这些任务才会进入主线程,也是按照先后顺序执行;
- JS操作DOM是同步任务,浏览器渲染DOM是异步任务(因为
js引擎线程
与GUI渲染线程
线程间是互斥的); - 异步任务分为 微任务 和 宏任务, 微任务优先执行,所有的微任务执行完成后再执行宏任务;
- 微任务 有
promise
等,宏任务有setTimeout
等;
触发组件渲染的入口逻辑
const effect = new ReactiveEffect(
componentUpdateFn,
() => queueJob(instance.update),
instance.scope
)
const update = (instance.update = effect.run.bind(effect) as SchedulerJob)
前面提到过 组件的副作用渲染函数 是基于
ReactiveEffect
:
组件模板的数据变化后,会触发() => queueJob(instance.update)
函数(本质就是ReactiveEffect的run
方法),然后最终会调用componentUpdateFn
函数执行组件的挂载或者更新,从而更新DOM。
queueJob
的逻辑
<!-- scheduler.ts -->
// 组件渲染任务数组
const queue: SchedulerJob[] = []
// 组件渲染任务队列执行函数
export function queueJob(job: SchedulerJob) {
// 省略其他
if (job.id == null) {
queue.push(job)
} else {
queue.splice(findInsertionIndex(job.id), 0, job)
}
queueFlush()
}
Vue 维护了一个
queue
队列,用于保存需要执行的 副作用渲染函数 即ReactiveEffect的run
方法。
queueJob
就是将 副作用渲染函数 添加到队列中合适的位置,然后执行queueFlush
方法。
queueFlush
方法我们先忽略,我们回到侦听器的相关逻辑中。
侦听器的侦测的数据变化后的的逻辑
我们继续看上面提到的一段代码。
let scheduler: EffectScheduler
if (flush === 'sync') {
scheduler = job as any // the scheduler function gets called directly
} else if (flush === 'post') {
scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
} else {
// default: 'pre'
scheduler = () => {
if (!instance || instance.isMounted) {
queuePreFlushCb(job)
} else {
// with 'pre' option, the first call must happen before
// the component is mounted so it is called synchronously.
job()
}
}
}
我们可以通过flush
参数来指定侦听器的执行顺序,有sync
,post
和pre
(默认) 这三种方式。同步很好理解我们不讨论,我们主要来研究queuePostRenderEffect
和 queuePreFlushCb
这两个方法。
queuePostRenderEffect
在大多数情况下等同于queuePostFlushCb
函数:
<!-- render.ts -->
export const queuePostRenderEffect = __FEATURE_SUSPENSE__
? queueEffectWithSuspense
: queuePostFlushCb
所以我们来看看queuePostFlushCb
和queuePreFlushCb
的逻辑:
<!-- scheduler.ts -->
// 更新DOM前的两个callback队列
const pendingPreFlushCbs: SchedulerJob[] = []
let activePreFlushCbs: SchedulerJob[] | null = null
// 更新DOM前的两个callback队列
const pendingPostFlushCbs: SchedulerJob[] = []
let activePostFlushCbs: SchedulerJob[] | null = null
export function queuePreFlushCb(cb: SchedulerJob) {
queueCb(cb, activePreFlushCbs, pendingPreFlushCbs, preFlushIndex)
}
export function queuePostFlushCb(cb: SchedulerJobs) {
queueCb(cb, activePostFlushCbs, pendingPostFlushCbs, postFlushIndex)
}
function queueCb(
cb: SchedulerJobs,
activeQueue: SchedulerJob[] | null,
pendingQueue: SchedulerJob[],
index: number
) {
// 省略其他
pendingQueue.push(cb)
queueFlush()
}
- Vue 维护了DOM更新前需要执行的回调函数执行队列
pendingPreFlushCbs
和activePreFlushCbs
;- Vue 维护了DOM更新后需要执行的回调函数执行队列
pendingPostFlushCbs
和activePostFlushCbs
;queuePostFlushCb
和queuePreFlushCb
分别是吧对应的回调方法加到pendingPostFlushCbs
和pendingPreFlushCbs
队列中;- 然后执行
queueFlush
函数。
queueFlush
执行异步调用
不管是DOM更新还是监听器的监听到数据后的回调都是进入了queueFlush
,我们来看看它的实现逻辑。
const resolvedPromise: Promise<any> = Promise.resolve()
function queueFlush() {
if (!isFlushing && !isFlushPending) {
isFlushPending = true
currentFlushPromise = resolvedPromise.then(flushJobs)
}
}
queueFlush
使用了微任务的Promise
执行异步执行flushJobs
。且用isFlushPending
控制flushJobs
的执行时机。
flushJobs
清空所有任务
function flushJobs(seen?: CountMap) {
isFlushPending = false
isFlushing = true
// 1. 依次执行所有的所有的回调函数
flushPreFlushCbs(seen)
// 2. 对副作用渲染函数排序,然后依次执行所有的副作用渲染函数
queue.sort((a, b) => getId(a) - getId(b))
try {
for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
const job = queue[flushIndex]
callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)
}
} finally {
// 3. 依次执行所有的所有的回调函数
flushPostFlushCbs(seen)
isFlushing = false
currentFlushPromise = null
// 4. 如果有新的回调函数添加进来,继续一个 1,2,3 的执行流程
if (
queue.length ||
pendingPreFlushCbs.length ||
pendingPostFlushCbs.length
) {
flushJobs(seen)
}
}
}
flushJobs
的作用是清空所有任务:
flushPreFlushCbs
依次清空所有DOM更新前的回调函数:1). 先将pendingPreFlushCbs
中的所有数据拷贝到activePreFlushCbs
中,pendingPreFlushCbs
置空等待新的回调函数加入;2). 依次执行activePreFlushCbs
中的回调函数;callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)
依次清空所有更新DOM的副作用渲染函数;flushPostFlushCbs
依次清空所有DOM更新后的回调函数:1). 先将pendingPostFlushCbs
中的所有数据拷贝到activePostFlushCbs
中,pendingPostFlushCbs
置空等待新的回调函数加入;2).依次执行activePostFlushCbs
中的回调函数;
神秘的nextTick
nextTick
异常神秘,遇到DOM的操作问题可能就想到它了。其实非常简单
export function nextTick<T = void>(
this: T,
fn?: (this: T) => void
): Promise<void> {
const p = currentFlushPromise || resolvedPromise
return fn ? p.then(this ? fn.bind(this) : fn) : p
}
异常简单,其实就是对Promise调用then方法。
Promise.resolve().then(() => {
// 清空任务(包括更新DOM)
}).then(() => {
// 是不是可以获取到更新后的DOM了??
})
不那么神秘的forceUpdate
总结
-
框架原理真的深入某一部分具体的代码和实现方式时,要多注意到细节,不要只能写出一个框架。
-
算法方面很薄弱的,最好多刷一刷,不然影响你的工资和成功率😯
-
在投递简历之前,最好通过各种渠道找到公司内部的人,先提前了解业务,也可以帮助后期优秀 offer 的决策。
-
要勇于说不,对于某些 offer 待遇不满意、业务不喜欢,应该相信自己,不要因为当下没有更好的 offer 而投降,一份工作短则一年长则 N 年,为了幸福生活要慎重选择!!!
喜欢这篇文章文章的小伙伴们点赞+转发支持,你们的支持是我最大的动力!
到公司内部的人,先提前了解业务,也可以帮助后期优秀 offer 的决策。
-
要勇于说不,对于某些 offer 待遇不满意、业务不喜欢,应该相信自己,不要因为当下没有更好的 offer 而投降,一份工作短则一年长则 N 年,为了幸福生活要慎重选择!!!
喜欢这篇文章文章的小伙伴们点赞+转发支持,你们的支持是我最大的动力!