背景

明天早上在脉脉上看到一个对于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异步实现fnif (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 = 1const observer = new MutationObserver(flushCallbacks) // 将flushCallbacks作为观测变动的cbconst textNode = document.createTextNode(String(counter)) // 创立文本节点// 观测文本节点变动observer.observe(textNode, {  characterData: true})// timerFunc扭转文本节点的data,以触发观测的回调flushCallbackstimerFunc = () => {   counter = (counter + 1) % 2  textNode.data = String(counter)}isUsingMicroTask = true} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {// setImmediate计划timerFunc = () => {  setImmediate(flushCallbacks)}} else {// 最终降级计划setTimeouttimerFunc = () => {  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软件工程师

如果感觉写得不错,能够点个赞,帮忙关注下公众号:前端巅峰