关于javascript:全球顶级交易所前端二面

26次阅读

共计 5407 个字符,预计需要花费 14 分钟才能阅读完成。

背景

明天早上在脉脉上看到一个对于 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 软件工程师

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

正文完
 0