背景
明天早上在脉脉上看到一个对于 BN
的前端二面分享,作者出于纯正的目标分享了一下最近的面试题。
我感觉这是一套不错的面试题,于是分享给了大家。
为什么会有这套面试题
前端界,到底什么样子的我的项目,会用到这类型的面试题背地蕴含的常识?
我有幸从 0 - 1
参加过几个我的项目,例如:
- 桌面端 IM 我的项目(Electron、React、Node.js), 端到端加密,主打 20 万人群聊性能
- 几个大的 SAAS 零碎(React)
- 小程序(Taro)
- 混合 APP
- 微信公众号
- 一些 web3 我的项目(流动池几千万,solidity React TypeScript Node.js)
等等..
外面有些须要肯定技术深度背地蕴含的常识有:
- 通信,基于 TCP 的端到端加密长链接通信,
- 平安,用户隐衷,平安,像 Telegram 一样的方向
- 性能:数据量大的解决与展现,前端任务调度,re-render 管制等
- 设计模式的了解与实际和面向对象编程:例如单例模式,管制反转,依赖注入
- 对 react 和 Vue 要害节点源码的浏览与了解
- 对 ES6 异步实现的了解
- 浏览器的渲染原理
- Node.js
- Linux、docker、K8s、nginx 等根底运维常识
等等 … 这里不开展是因为写这篇文章时候中午还没吃饭。很饿,况且大部分人基本用不到其余冷门的常识
假如一个场景
例如每秒同时有两个人给你发消息,你的客户端(前端)是不须要做任务调度。
如果每秒同时有一千个人给你发很多音讯,这个时候就要做任务调度了,因为这外面波及到网络层、DB 层、缓存层(前端内存,例如 redux 等),以及数据流向、更新频次与机会管制。
交易,同理。例如一个币价一秒钟内稳定激烈,因为是 IM 场景,双工通信,可能一秒你接管到屡次推送。这个频次如果依据用户理论场景拆解做精细化,是一个极度简单的需要。这里就不开展讲了
那么这个时候,你就会用到我在下面提到的大部分常识,在做性能优化的时候,当你的常识足够全面丰盛,其实更像是在下棋,子落后不可反悔。有利有弊
随着互联网的推动,我认为前端会越来越像是一个残缺的客户端,当初有 webContainer 技术和 webasm 等技术,只有谷歌解决 native-socket 和平安的一些要害节点问题, 就是残缺的客户端。不再须要 Electron 之类的
大略讲讲题目
1.React 的工夫切片思维
能够联合我三年前文章 手写 mini-react 源码看看
https://github.com/JinJieTan/Peter-/tree/master/mini-React
- 先看看
cpu
调度工夫片
工夫片即 CPU 调配给各个程序的工夫,每个线程被调配一个时间段,称作它的工夫片,即该过程容许运行的工夫,使各个程序从外表上看是同时进行的。如果在工夫片完结时过程还在运行,则 CPU 将被剥夺并调配给另一个过程。如果过程在工夫片完结前阻塞或完结,则 CPU 当即进行切换。而不会造成 CPU 资源节约。在宏观上:咱们能够同时关上多个应用程序,每个程序并行不悖,同时运行。但在宏观上:因为只有一个 CPU,一次只能处理程序要求的一部分,如何解决偏心,一种办法就是引入工夫片,每个程序轮流执行
- 那么 react 的工夫切片思维是什么呢?
两年前,咱们公司一个我的项目从 react0.14 版本升级上来 react16, 记得过后给公司一些共事科普过一次。react16 引入了 fiber, 其实这个工夫切片思维,就是 react16 的 fiber。
过后 react0.14 版本的我的项目有一个问题,就是会呈现卡顿, 因为 react16 版本之前,是一口气实现更新。如果这个过程很长,就会导致期待(卡顿)的工夫很长
react16 版本后,react 更新,会有一个 Reconcilation 阶段,这个阶段是会遍历虚构 dom 树,找出更新的节点,实现一系列操作。这个阶段计算比拟多,就会长工夫占用 cpu. 而这个 Reconcilation 阶段是能够中断的(临时挂起),让浏览器先响应高优先级事件,例如用户交互等。这就是所谓的工夫切片思维,实质上是任务调度
- 2. 为什么不必
requestIdleCallback
在代码外面我有备注过,我测试过requestIdleCallback
,过后我在做 1 秒钟 1000 集体频繁发消息的性能优化,就在联合手写 react 做任务调度。
起因是:requestIdleCallback 的兼容性不好,对于用户交互频繁屡次合并更新来说,requestAnimation 更有及时性高优先级,requestIdleCallback 则适宜解决能够提早渲染的工作
咱们能够发现,很多优化思维,来自于对操作系统自身的认知,对事物的自身认知决定了倒退的天花板。
useMemo 之类的原理和优化原理
背地应用了 Object.js 办法遍历浅比照了传入的 dependencys 的 prev 和 current 值。
应用简略的比拟,省去不必要的 render
react 的副作用
比拟抽象的问题,这个问题我就不答复了
vue 的 nextTick
vue2 有一个优雅降级的过程
- 先是 promise.then
- 而后是 MutationObserver
- 而后是 setImmediate
-
最初是 setTimeout
let timerFunc // nextTick 异步实现 fn if (typeof Promise !== 'undefined' && isNative(Promise)) { // Promise 计划 const p = Promise.resolve() timerFunc = () => {p.then(flushCallbacks) // 将 flushCallbacks 包装进 Promise.then 中 } isUsingMicroTask = true } else if (!isIE && typeof MutationObserver !== 'undefined' && (isNative(MutationObserver) || MutationObserver.toString() === '[object MutationObserverConstructor]' )) { // MutationObserver 计划 let counter = 1 const observer = new MutationObserver(flushCallbacks) // 将 flushCallbacks 作为观测变动的 cb const textNode = document.createTextNode(String(counter)) // 创立文本节点 // 观测文本节点变动 observer.observe(textNode, {characterData: true}) // timerFunc 扭转文本节点的 data,以触发观测的回调 flushCallbacks timerFunc = () => {counter = (counter + 1) % 2 textNode.data = String(counter) } isUsingMicroTask = true } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { // setImmediate 计划 timerFunc = () => {setImmediate(flushCallbacks) } } else { // 最终降级计划 setTimeout timerFunc = () => {setTimeout(flushCallbacks, 0) } }
出这个问题,是想晓得面试者对 Vue 框架的数据更新 – 渲染异步是否真的了解,并非只是这个 nextTick 而已。
剩下的宏工作和微工作,能够跟第六题一起答复。
什么是管制反转和依赖注入
出这个题目,阐明面试官比拟崇尚这种格调模式,不然不会问这个非凡问题,然而要留神的是,既然问了这方面的,必定会拓展发散,问你理论的应用和其余设计模式等。所以反面试题,对于略微上点品位的面试,是不靠谱的。
我集体拥护反面试题,更看重过往我的项目教训和基础知识把握与实际思考
- 管制反转(IoC):
在繁多职责准则的设计下,很少有独自一个对象就能实现的工作。大多数工作都须要复数的对象来合作实现,这样对象与对象之间就有了依赖。一开始对象之间的依赖关系是本人解决的,须要什么对象了就 New 一个进去用,控制权是在对象自身。然而这样耦合度就十分高,可能某个对象的一点小批改就会引起连锁反应,须要把依赖的对象一路批改过来。
经典的管制反转(IoC)准则:
下层模块不应该依赖于上层模块,他们独特依赖于一个形象,形象不可能依赖于具体,具体必须依赖于形象。
放在 TypeScript 中,下面这句话能够了解为,多个 class 遵循一个 interface, 这些 class 的对应数据值不同,然而字段和类型都是一样的。
当须要被独自、组合应用时,间接应用这些 class 即可
管制反转此时的益处:如果前面要更新进化,只有新的 interface 兼容现有的 interface 即可, 不须要改变现有 class 代码去做兼容。这波及到 Ts 的协变和逆变,感兴趣的去理解下
- 依赖注入(DI—Dependency Injection):
把对象之间的依赖关系提到内部去治理,可是还如果组件之间依赖关系由容器在运行期决定,形象的说,即由容器动静的将某个依赖关系注入到组件之中
例如 react 的 Context, 应用 Context.Provider 注入数据
例如装璜器
@Foo()
智能合约外部也有润饰器,例如 access control
外面的
modifier onlyOnwer(){require(msg.sender == onwer,'msg.sender not onwer');
__;
}
function _mint () public onlyOnwer(){//dosomething}
依赖注入,实质上帮忙简化组装依赖过程。
asyncpool 实现
前端并发管制的库 asyncpol
ES7 实现版本
async function asyncPool(poolLimit, array, iteratorFn) {const ret = []; // 存储所有的异步工作
const executing = []; // 存储正在执行的异步工作
for (const item of array) {
// 调用 iteratorFn 函数创立异步工作
const p = Promise.resolve().then(() => iteratorFn(item, array));
ret.push(p); // 保留新的异步工作
// 当 poolLimit 值小于或等于总任务个数时,进行并发管制
if (poolLimit <= array.length) {
// 当工作实现后,从正在执行的工作数组中移除已实现的工作
const e = p.then(() => executing.splice(executing.indexOf(e), 1));
executing.push(e); // 保留正在执行的异步工作
if (executing.length >= poolLimit) {await Promise.race(executing); // 期待较快的工作执行实现
}
}
}
return Promise.all(ret);
}
ES6 实现版本:
function asyncPool(poolLimit, array, iteratorFn) {
let i = 0;
const ret = []; // 存储所有的异步工作
const executing = []; // 存储正在执行的异步工作
const enqueue = function () {if (i === array.length) {return Promise.resolve();
}
const item = array[i++]; // 获取新的工作项
const p = Promise.resolve().then(() => iteratorFn(item, array));
ret.push(p);
let r = Promise.resolve();
// 当 poolLimit 值小于或等于总任务个数时,进行并发管制
if (poolLimit <= array.length) {
// 当工作实现后,从正在执行的工作数组中移除已实现的工作
const e = p.then(() => executing.splice(executing.indexOf(e), 1));
executing.push(e);
if (executing.length >= poolLimit) {r = Promise.race(executing);
}
}
// 正在执行工作列表 中较快的工作执行实现之后,才会从 array 数组中获取新的待办工作
return r.then(() => enqueue());
};
return enqueue().then(() => Promise.all(ret));
}
总结
面试题出得比拟贴近理论,看中对框架原理和前端异步以及根底的考查,这些知识点跟框架开发中简单性能的 debug 非亲非故。学习源码是必不可少的进阶过程,有可能过后学了没用,然而真的了解精华当前你会发现,大部分优良的框架源码都差不多,包含他们的应用,思路和理念等,源码最重要的是帮忙你在将来做简单场景需要 debug 时应用。
当然,这些都是基于我很久没有更新的前端常识的认知根底写的,如果有问题,欢送你指出。
写于 2022 年 5 月 31 日
一个写智能合约的 web2.5 软件工程师
如果感觉写得不错,能够点个赞,帮忙关注下公众号:前端巅峰