前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >全球顶级交易所前端二面

全球顶级交易所前端二面

作者头像
Peter谭金杰
发布2022-06-05 10:36:30
1.1K0
发布2022-06-05 10:36:30
举报
文章被收录于专栏:跨平台全栈俱乐部

背景

今天早上在脉脉上看到一个关于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的时间切片思想

代码语言:javascript
复制
可以结合我三年前文章 手写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

代码语言:javascript
复制
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注入数据

例如装饰器

代码语言:javascript
复制
@Foo()

智能合约内部也有修饰器,例如access control里面的

代码语言:javascript
复制
modifier onlyOnwer(){
  require(msg.sender == onwer,'msg.sender not onwer');
  __;
}
function _mint () public onlyOnwer(){
    //dosomething
}

依赖注入,本质上帮助简化组装依赖过程。

asyncpool实现

前端并发控制的库 asyncpol ES7实现版本

代码语言:javascript
复制
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实现版本:

代码语言:javascript
复制
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软件工程师

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-06-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 为什么会有这套面试题
  • 假设一个场景
  • 大概讲讲题目
    • 1.React的时间切片思想
      • useMemo之类的原理和优化原理
        • react的副作用
          • vue的nextTick
            • 什么是控制反转和依赖注入
              • asyncpool实现
              • 总结
              相关产品与服务
              容器服务
              腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档