复盘最近一个月的中大厂面试经历(附加答案)

8,667 阅读22分钟

前言

 笔者两年前端经验,前后大概面了一个月,期间面了很多公司,比如有赞涂鸦智能滴滴字节酷家乐 大搜车海康威视税友等等,梳理一下基于我个人面试过程中被问的到的一些问题(包括但不限于)。

  在开始面试之前,一份优秀的简历也是十分重要,推荐两篇文章: 一份优秀的前端开发工程师简历是怎么样的如何写「前端简历」,能敲开字节跳动的大门?

  为了让自己拿到比较满意的offer,前一周选了一些中小公司来练手,后面发现,太小的公司没有锻炼效果,所以一周后,就开始给规模比较大的厂投简历了。

  印象比较深刻的是酷家乐,一面是远程,然后二面约你现场,现场是三轮技术 + hr,差不多三个小时,效率很高,但是第一次面试这么久(当时也不知道要面这么久),导致后面表现的不是很好,遂,卒。涂鸦智能的效率也很高,跟酷家乐的面试流程是一样的,都是远程一面,现场三轮技术 + hr。其他的公司基本上是一轮一轮的来,每一轮的结果一般第二天会告知你,当然,如果后面几天等不到,也是一种告知。

HTTP

  http的问题算是面试热点问题,在一些交叉面或者一面里面很喜欢问,是比较考验你对基础的掌握。

get和post有什么区别?

请求参数:get请求参数是通过url传递的,多个参数以&连接;POST请求放在request body中。
请求缓存:get请求会被缓存,而post请求不会,除非手动设置。
相对的安全性:get是将参数通过url传递的,会被浏览器缓存,容易被他人获取,post相对来说,比较安全。
请求参数长度限制:get通过url传参,浏览器会限制url的长度(http不会)。
编码方式:GET请求只能进行url编码,而POST支持多种编码方式。

http1.1和http2.0有什么区别?

(包括但不限于1.1和2.0的版本对比)

http1.1 

  • 引入了持久链接,即TCP默认不关闭,可以被多个请求复用
  • 引入管道机制,一个TCP连接,可以同时发送多个请求
  • 新增了一些缓存的字段
  • 新增了一些方法,PUT、DELETE、OPTIONS、PATCH
  • 支持断点续传,通过请求头字段Rang来实现

http2.0

  • 头部压缩
  • 多路复用
  • 二进制传输,头信息和数据体都是二进制
  • 请求优先级, 设置数据帧的优先级,让服务器优先处理
  • 服务器主动推送消息

被问到的一些问题:

  • 管道机制会造成什么样的问题,http2.0是怎么解决的
  • 头部压缩的原理是什么
  • options方法的作用
  • http2.0允许服务器主动推送消息,那跟WebSocket有什么区别吗?

推荐一篇神三元大佬的文章:HTTP灵魂之问,巩固你的 HTTP 知识体系

说一下http缓存

等你说完强缓存协商缓存的大致流程,面试官可能基于你的答案,来深入考察你对http缓存的理解,比如(包括但不限于):

200状态码一定是服务器返回的吗?

不是,命中强缓存的话,会直接从内存或者磁盘中读取资源,并返回一个200状态码,具体操作可以试试浏览器的前进后退键。 image.png

Expires和Cache-Control的max-age指令分别是如何确定过期时间的?优劣势是什么?

为什么有了Last-Modified还要有ETag?解决了什么问题?

no-store和no-cache的意思分别是什么?

推荐一篇文章,解答了这些问题:一张图理解http缓存

https为什么比http安全

面试官会让你说下https做了什么,然后不排除会问你加密原理。(ps: 字节面试官问了)

主要用到了对称加密和非对称加密,推荐阅读:彻底搞懂HTTPS的加密原理

说一下三次握手四次挥手

这个问题问的比较少,只遇到过一次。推荐一篇笔者之前写的文章: TCP三次握手、四次挥手

JS

0.1 + 0.2 !== 0.3?为什么?

  • 二进制转换:js在做数字计算的时候,底层都是转二进制来计算的,0.1转二进制是无限循环小数,0.2也是,但是js采用的IEEE754二进制浮点运算,实际最大可以存储53位有效数字,所以导致精度丢失。
  • 对阶运算:阶小的尾数要根据阶差来右移,尾数位移时可能会发生数丢失的情况,影响精度。

如何解决上面说的精确度丢失问题?

  • 刚开始第一反应就是,先将小数点扩大变成整数,做完加法之后,再除回去。(不妥当,因为还是可能会超过js最大数)
  • 利用第三方库,比如Math.jsbig.js
  • 利用ES2020新增的基础数据类型BigInt
  • 都转成字符串,然后对两个字符串做加法运算(手写题有实现)

闭包

  • 说一下闭包的本质是什么?会造成哪些问题
  • 除了函数的内部返回一个函数,还有其他的方法产生闭包吗?(有)
// 函数内部延迟调用,产生了闭包
function test () {
  let name = 'tom'
  setTimeout(() => {
    console.log(name)
  }, 2000)
}
test();

推荐文章: JavaScript 的静态作用域链与“动态”闭包链

什么是作用域?什么是作用域链?函数执行上下文包含了哪些内容?

this指向

面试官问:JS的this指向

es6的问题

  • 箭头函数和普通函数的区别
  • 什么是暂时性死区,什么是变量提升
  • for of 和for in的区别,怎么让for of可以遍历一个对象?
  • es6的Map和WeakMap的区别,WeakMap解决了什么问题?
  • promise哪些方法是原型上的,哪些方法是实例上的 推荐阮一峰老师的教程: ES6 入门教程

原型 + 原型链 (这个属于必问的题)

这个问题只要能描述的清楚对象如何查找它的值,基本上就理解了一大半。

image.png 解释一下上面的图:

  • prototype是函数特有的属性,__proto__是每个对象都有的属性,而prototype本身也是一个对象
  • 当我们去获取a.name的时候,会先从对象的自身属性开始查找,如果没有的话,就会从a.__proto__上找
  • 对象a.__proto__又指向构造器函数testprototype(原型),所以从a.__proto上找属性其实就是在test.prototype找属性,但是prototype(原型)本身又是一个对象,这样的话,会重复上面两个步骤,最终都是找到了Object这个构造器函数,而Object.__proto是一个null值,如果中间有值就返回,没有就赋值undefined
  • 这样的链式结构就是原型链 因为构造器函数原型上的constructor是指向构造器函数自身的,所以
a.constructor === test // true
a.__proto__.constructor === test // true
a.__proto__.constructor === test.prototype.constructor// true

有一道面试题可以测试一下,说出打印内容,并且说明原因。

function test () {

}
test.prototype.then = function () {
  console.log('test => then')
}
Function.prototype.mythen = function () {
  console.log('Function => mythen')
}
test.mythen();
test.then();

eventLoop

面试官会基于你的回答来提问,比如:

  • 你刚刚说到js是单线程,那线程跟进程有什么区别?
  • 浏览器新开了一个页面,有几个线程?
  • 为什么要设计成微任务先执行,宏任务后执行。

推荐一篇酷家乐大佬的文章:这一次,彻底弄懂 JavaScript 执行机制

垃圾回收机制

  • 你刚刚提到的标记清除法有什么缺点?怎么解决?
  • 你刚刚提到的引用计数法有什么缺点吗?
  • v8里面的垃圾回收机制是什么?
  • v8是怎么解决循环引用的? 推荐文章:你真的了解垃圾回收机制吗

说一下数据类型,如何判断一个数组

判断数组的方法:

  • Array.isArray(arr); // true
  • arr instanceof Array; // true
  • arr.constructor === Array; // true
  • Object.prototype.toString.call(arr); // "[object Array]" ps:通过instanceof和constructor来判断不一定准确,因为可能会被重写。

常用的设计模式?

  • 什么是抽象工厂模式
  • 发布订阅模式和观察者模式有什么区别
  • 你项目里面都用了哪些设计模式 推荐文章:前端需要掌握的设计模式

浏览器渲染过程

image.png 一般我都是根据这张图,把流程说一遍。

被面试官问到的一些问题:

  • link标签会不会阻塞页面的渲染?说一下原因?
  • 为什么css推荐放上面,js推荐放下面?
  • js会阻塞页面的渲染吗?说一下原因?
  • css会阻塞html的解析吗?为什么?
  • css会阻塞html的渲染吗?为什么?

  js执行是单独的线程,浏览器渲染是GUI渲染线程负责的,两个线程是互斥关系,所以很好理解,js会阻塞页面的渲染。

  css不会阻塞html的解析,解析html和解析css是并行的,但是css会阻塞html的渲染,因为页面渲染的时候,需要style Rules + DOM Tree一起合成Render Tree

  正常来说,解析页面过程中如果遇到一个script标签,会停止html解析(如下图),去下载script脚本,下载完毕之后立即执行脚本,然后接着解析html,所以如果script下载速度很慢,会造成页面白屏。

defer:html解析和脚本下载并行(异步),等html解析之后,DOMContentLoaded触发之前执行脚本。
async:html解析和脚本下载并行(异步),下载后立即执行脚本,且中断html解析。

image.png 推荐阅读:
浏览器的工作原理:新式网络浏览器幕后揭秘

从 8 道面试题看浏览器渲染过程与性能优化

性能优化

我一般从http请求 + 代码层面 + 构建工具来回答。

setTimeout和requestAnimationFrame做动画有区别吗?哪一个更好?为什么?

有区别:

  • 准确性

setTimeout做动画不准确,因为是宏任务,设置的延迟时间并不等于在延迟时间之后立即执行,因为需要等待同步任务和微任务执行完,才执行宏任务,容易丢帧;requestAnimationFrame则是在每一帧绘制元素之前执行,更精确。

  • 更好的性能

在隐藏元素或者元素不可见时,setTimeout仍然在后台执行动画任务;而requestAnimationFrame会停止动画,这意味着更少的CPU和更少的内存消耗。

介绍一下同源策略?你知道那些跨域方法?cors跨域的原理是什么有了解过吗?

CSS

介绍一下盒模型?

flex: 1代表什么意思

用过flex布局吗?都有哪些属性?

说说什么是BFC,一般你都用来干什么,解决了什么问题?

实现元素水平垂直居中?尽可能说多一些方法?

左侧固定 + 右侧自适应布局?说说几种方案?

重绘和重排?

推荐文章:重排(reflow)和重绘(repaint)

React

面了一圈下来,发现react问的都差不多。

都用过那些版本的react,分别介绍一下区别?

说一下前端路由,他们的实现原理分别是什么?

hash路由:通过监听hashchange事件来捕捉url的变化,来决定是否更新页面。

history路由:主要监听popStatepushStatereplaceState来决定是否更新页面。但是要注意,仅仅调用pushState方法或replaceState方法 ,并不会触发popState事件,只有用户点击浏览器倒退按钮和前进按钮,或者使用 JavaScript 调用backforwardgo方法时才会触发,想要pushStatereplaceState的时候触发popState事件,需要自定义事件。

你能手写个简单的redux吗?

面试官说完之后,给我递过来笔和纸......(内心崩溃) 写完了之后,面试官让我给他讲一遍。

function createStore(reducer) {
  let listeners = [];
  let currentState;
  function getState() {
    return currentState
  }
  function dispatch(action) {
    currentState = reducer(currentState, action);
    listeners.forEach(l => l());
  }
  function subscribe(fn) {
    listeners.push(fn);
    return function unsubscribe() {
      listeners = listeners.filter(l => l !== fn);
    }
  }
  return {
    getState,
    dispatch,
    subscribe
  }
}

redux里面dispatch是如何正确找到reducer的?

combineReducers源码

是的,redux它不找,一把梭,每次dispatch都要遍历一遍所有的reducer...

redux怎么挂载中间件的?它的执行顺序是什么样的?

核心函数compose

function compose(middlewears) {
  return middlewears.reduce((a, b) => (...argu) => a(b(...argu)))
}

执行顺序:从后往前执行redux中间件。

除了redux,还用过其他的状态管理库吗?

没有。

redux的缺点?

react生命周期?

setState什么情况下同步,什么情况下异步?

讲一下react的事件机制,为什么这么设计?react17里面有什么变化吗?

推荐文章:一文吃透react事件系统原理

设计的好处

  • 抹平浏览器差异,实现更好的跨平台
  • 避免垃圾回收,React引入事件池,在事件池中获取或释放事件对象,避免频繁地去创建和销毁
  • 方便事件统一管理和事务机制

class组件跟函数组件有什么区别?

能在if判断里面写hooks吗?为什么不能?

Hooks是用链表保存状态的,每次渲染的时候,必须要保证hooks的长度和顺序是一样的,如果不一致,react无法获取正确状态,会报错。

HOC和hooks的区别?

hooks实现原理?不用链表可以用其他方法实现吗?

基于链表来实现的,也可以用数组来模拟。

useEffect依赖传空数组和componentDidMount有什么区别吗?

useeffect和useLayouteffect区别

  • useEffect不会阻塞浏览器渲染,而 useLayoutEffect 会阻塞浏览器渲染。
  • useEffect会在浏览器渲染结束后执行,useLayoutEffect 则是在 DOM 更新完成后,浏览器绘制之前执行。

介绍一下react dom diff

介绍一下Vdom?

在React中,有做过什么性能优化吗?

按照自己脑袋瓜里面的想法,说了几种。

推荐文章:React性能优化的8种方式了解一下

React.memo()和React.useMemo()有什么区别吗?

接上题,然后面试官这两个有什么区别吗?

直接上官网: React.memoReact.useMemo

useCallback和useMemo的区别?

同接上题 官网地址useCallback

React.fiber了解吗?造成卡顿的原因是什么?react.fiber里面是怎么解决的?中断更新之后,下次是如何找到要更新的位置的?

推荐荒山大佬的文章:这可能是最通俗的 React Fiber(时间分片) 打开方式

函数式编程

讲hooks很大概率问到函数式编程?(被问到过好几次)

高阶组件里面如何防止ref丢失?

React.forwardRef,上官网地址:React.forwardRef

webpack && node

  • 都用过node干了什么?用过node框架吗?
  • node一些基本api,如何删除文件,创建文件,写入文件。
  • 用过node的哪些模块?

进程和线程的区别

介绍一下模块发展史

node_modules问题

假如npm安装了一个模块A、依赖c的0.0.1版本,又安装了一个模块B,依赖c的0.0.2版本。请问node_module是怎么保证A、B正确的找到对应的c版本的包的?

webpack打包原理

  • 初始化参数:从配置文件和Shell语句中读取与合并参数,得出最终的参数;
  • 开始编译:用上一步得到的参数初始化Compiler对象,加载所有配置的插件,执行对象的run方法开始执行编译; 确定入口:根据配置中的entry找出所有的入口文件
  • 编译模块:从入口文件出发,调用所有配置的Loader对模块进行编译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
  • 完成模块编译:在经过第 4 步使用Loader翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个Chunk转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会
  • 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。

image.png

webpack性能优化你是怎么做的?

推荐文章:带你深度解锁Webpack系列(优化篇)

loader和plugin的区别?

  • loader(转换):主要是做转换功能,比如将css、less文件转成js,识别不同的文件后缀名,可以拓展一下自己比较熟悉的loader。
  • plugin(插件):原理是监听webpack构建过程中的一些钩子,然后做一些自己的操作,更多的是丰富webpack的功能。

loader的执行顺序是什么?如何写一个loader?如何写一个plugin?loader有异步的吗?

执行顺序从右往左、从下到上。

loader本质是一个函数,plugin本质是一个类,具体如何编写,推荐文章:手把手教你写一个 loader / plugin

loader有同步的也有异步。

file-loader返回的是什么?

返回的是一个字符串,详情见: file-loader源码地址

webpack有几种hash,它们有什么区别?一般你在项目里面是用哪种hash?

  • hash:是整个项目的hash值,其根据每次编译内容计算得到,每次编译之后都会生成新的hash,即修改任何文件都会导致所有文件的hash发生改变。
  • chunkHash:根据不同的入口文件(Entry)进行依赖文件解析、构建对应的chunk,生成对应的哈希值(来源于同一个chunk,则hash值就一样)。
  • contentHash:根据文件内容生成hash值,文件内容相同hash值就相同

webpack5有哪些新特性?

推荐文章:飞书团队Webpack5 上手测评

webpack热更新原理?

推荐文章:轻松理解webpack热更新原理

tree-shaking原理?

利用ES Module做静态分析,通过分析ast语法树,对每个模块维护了一个作用域,收集模块内部使用的变量,然后分析作用域,将import进来未被使用的模块删除,最后递归处理文件。

babel转换代码的过程,箭头函数转普通函数的时候,是如何处理this的?

过程:parser => transfrom => generator,可以根据自己的理解,展开说说。

箭头函数转普通函数如何处理this:就近找上一层作用域里面的this,用一个唯一变量名that缓存一下this,然后将之前箭头函数里面的this替换成that即可。

手写题

节流和防抖

(某独角兽公司)面试官:你能手写个节流函数吗?然后递过来了笔跟纸......

函数防抖

定义:在n秒时间内,函数只会触发一次,如果期间被触发,则重新计时。

场景input框实时搜索浏览器的resize、和scroll

  function debounce (fn, delay) {
    let timer;
    return function (...argu) {
      let that = this;
      if (timer) clearTimeout(timer);
      timer = setTimeout(() => {
        fn.apply(that, argu);
      }, delay);
    }
  }

函数节流

定义:在n秒内,事件只执行一次,如果期间被触发,也不会响应事件。

场景表单重复提交滚动加载

// 利用时间戳实现
function throttle(fn, delay) {
  let previous = new Date();
  return function (...argu) {
    let nowDate = new Date();
    if (nowDate - previous > delay) {
      fn.apply(this, argu);
      previous = nowDate;
    }
  };
}

// 利用定时器实现
function throttle(fn, delay) {
  let timer;
  return function (...argu) {
    let that = this;
    if (timer) return false;
    timer = setTimeout(() => {
      fn.apply(that, argu);
      timer = null; // 释放timer变量,让下一次的函数接着执行
    }, delay);
  };
}

写一种你熟悉的排序?

没错,也是用笔写,写完了然后给他讲思路。我选了一个快速排序

// 先选一个数当作基点,一般选择最后一个数
// 然后遍历arr, 找出这个基点数的比它大的数组集合和比它小的数组集合
// 递归此步骤
function quickSort(arr) {
  if (arr.length < 2) {
    return arr;
  }
  const cur = arr[arr.length - 1];
  let left = [];
  let right = [];
  for (let i = 0; i < arr.length - 1; i++) {
    if (arr[i] >= cur) {
      right.push(arr[i])
    } else {
      left.push(arr[i])
    }
  }
  return [...quickSort(left), cur, ...quickSort(right)];
}
console.log(quickSort([1, 3, 3, 6, 2, 4, 1]));

(字节)说出打印顺序

默认非严格模式

function Foo() {         
  getName = function () { alert(1); }; 
  return this;
}
Foo.getName = function () { alert(2); };
Foo.prototype.getName = function () { alert(3); };
var getName = function () { alert(4); }; 
function getName() { alert(5); }

// 请写出以下输出结果:
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();

正确输出顺序: 2 4 1 1 2 3

  • Foo.getName();这个没什么好说的,输出2

  • getName();考察var和函数提升,函数优先级大于var,所以输出4

  • Foo().getName();Foo()返回this,此时this指向window,Foo().getName相当于window.getName。但是Foo()内部又对window上的getName重新赋值了,所以输出1

  • getName();同上,输出1

  • new Foo.getName();考察运算符优先级,new 无参数列表,对应的优先级是17;成员访问操作符 . , 对应的优先级是 18。因此相当于是 new (Foo.getName)();new操作符会执行构造函数中的方法,因此此处输出为 2。

  • new Foo().getName();new 带参数列表,对应的优先级是18,和成员访问操作符.优先级相同。同级运算符,按照从左到右的顺序依次计算。new Foo()先初始化 Foo 的实例化对象,实例上没有getName方法,因此需要原型上去找,即找到了 Foo.prototype.getName,输出3。

instanceof原理,能尝试手写一个instanceof函数吗

function myInstanceOf(left, right) {
  if (left === null || typeof left !== 'object') return false;
  if (typeof right !== 'function') throw new Error('right must be function')
  let L = left.__proto__;
  let R = right.prototype;
  while(L !== null) {
    if (L === R) return true;
    L = L.__proto__;
  }
  return false;
}

实现new关键字

  • 创建一个空的简单javaScript对象,即{}
  • 将创建对象的__proto__指向构造函数的prototype
  • 修改this指向
  • 如果构造函数没有返回值,就返回创建的对象。
      function myNew (context, ...argu) {
         let obj = Object.create(null);
         obj.__proto = context.prototype;
         let res = context.apply(obj, argu);
         return typeof res === 'object' ? res : obj;
      }

大数相加

let a = "123456789012345678";
let b = "1";

function add(a ,b){
   //...
}
add(a, b) // '123456789012345679'

思路: 模拟加法运算,但是需要用'0'补齐长度,对于整数,向前补0。

let a = "123456789012345678";
let b = "1";
// 1. 先找出最大的长度的数
// 2. 给较小的数填充向前填充0
function add(a ,b){
  let maxLength = Math.max(a.length, b.length);
  a = a.padStart(maxLength, '0');
  b = b.padStart(maxLength, '0');
  // 123456789012345678
  // 000000000000000001
  let res = ''; // 返回的值
  let sum = 0; // 同位相加的和
  let t = 0; // 同位相加和的十位数
  let r = 0; // 同位相加和的个位数
  for(let i = maxLength - 1; i >= 0; i--) {
    sum = parseInt(a[i]) + parseInt(b[i]) + t;
    t = Math.floor(sum / 10) // 拿到十位数的值
    r = sum % 10; // 拿到个位数的值
    res = r + res;
  }
  return res;
}

console.log(add(a, b)); // 123456789012345679


(滴滴)扁平化数组

实现一个flat函数,接收arr和depth参数

function flat(arr, depth = 1) {
  return depth > 0 ?
    arr.reduce((pre, cur) => {
      return pre.concat(Array.isArray(cur) ? flat(cur, depth - 1) : cur);
    }, []) :
    arr.slice();
}

实现一个event类

class EventEmitter {
  constructor(){
  }
  // 监听事件 
  on(){
  }
  // 触发事件
  emit(){
  }
  // 只监听一次,下次emit不会触发
  once(){
  }
  // 移除事件
  off(){
  }
}
const events = new EventEmitter();
events.on('hobby', (...argu) => {
  console.log('打游戏', ...argu);
})
let eat = () => {
  console.log('吃');
}
events.once('hobby', eat);
events.on('hobby', (...argu) => {
  console.log('逛街', ...argu);
})

events.off('hobby', eat);
events.emit('hobby', 1,2,3);
events.emit('hello', 'susan')
//打游戏 1 2 3
// 逛街 1 2 3

答案:

class EventEmitter {
  constructor() {
    this.events = {}; // 存放着所有的事件{eventName: [callback, ...]}
  }
  on(eventName, callback) {
    if(!this.events[eventName]) {
      this.events[eventName] = [callback];
    } else {
      this.events[eventName].push(callback);
    }
  }
  emit(eventName, ...argu) {
    if(this.events[eventName]) {
      this.events[eventName].forEach(fn => fn(...argu))
    }
  }
  off(eventName, callback) {
    if(this.events[eventName]) {
      this.events[eventName] = this.events[eventName].filter(fn => callback !== fn && fn.l !== callback);
    }
  }
  once(eventName, callback) {
    const _once = () => {
      callback();
      this.off(eventName, _once)
    }
    _once.l = callback;
    this.on(eventName, _once)
  }
}

实现千分位format函数

   // 接收一个number,返回一个string
   function format(number) {
    
   }
  console.log(format(12345.7890)); // 12,345.789,0
  console.log(format(0.12345678));// 0.123,456,78
  console.log(format(123456)); // 123,456

思路

  • 基于小数点切分,对于整数部分,从后往前遍历,隔3加,
  • 小数点部分,从前往后便利,隔3加,
  function format (number) {
     let str = number.toString();
     let [int, dec = ''] = str.split('.');
     let intStr = ''
     for (let i = int.length - 1; i >= 0; i--) {
        if ((int.length - i) % 3 === 0 && i !== 0) {
             intStr = ',' + int[i] + intStr;
        } else {
           intStr = int[i] + intStr;
        }
     }
    let decStr = '';
    if (dec.length > 0) {
       for (let i = 0; i < dec.length; i ++) {
          let sum = decStr + dec[i];
          if ((i + 1 )% 3 === 0) {
             decStr = sum + ',';
          } else {
            decStr = sum;
          }
       }
     }
   return decStr.length > 0 ? `${intStr}.${decStr}` : `${intStr}`;
};

(字节、滴滴)根据传入的姓名权重信息,返回随机的姓名(随机概率依据权重)

第一次没看懂题目,面试官解释了一下。

/**
 * @description: 根据传入的姓名权重信息,返回随机的姓名(随机概率依据权重)
 * @param {Array} personValue
 * @returns {String} personName 姓名
 */
var getPersonName = function (personValue) {
   
}
const person = [
  {
    name: '张三',
    weight: 1
  },
  {
    name: '李四',
    weight: 10
  },
  {
    name: '王五',
    weight: 100
  }
]
function getResult(count){
  const res = {}
  for(let i = 0; i < count; i++){
    const name = getPersonName(person)
    res[name] = res[name] ? res[name] + 1 : 1
  }

  console.log(res) 
}
getResult(10000)

答案:

var getPersonName = function (personValue) {
  // 标记区间,并且获得weight的总数
  let sum = personValue.reduce((pre, cur) => {
    cur.startW = pre;
    return cur.endW = cur.weight + pre
  }, 0);
  let s = Math.random() * sum; // 获得一个 0 - 111 的随机数
  // 判断随机数的所属区间
  let person = personValue.find(item => item.startW < s && s <= item.endW);
  return person.name;
}

(字节)实现一个promise.all


Promise.all = function (promises) {
  let result = [];
  let count = 0;
  return new Promise((resolve, reject) => {
    promises.forEach((p, index) => {
      // 兼容不是promise的情况 
      Promise.resolve(p).then((res) => {
        result[index] = res;
        count++;
        if(count === promises.length) {
          resolve(result)
        }
      }).catch((err) => {
        reject(err);
      })
    })
  });
}

(滴滴)两数之和

力扣原题: 两数之和
刚开始用双重循环写了一个,时间复杂度是O(n^2),面试官问你能优化到O(n)吗?然后有了下面这个。

var twoSum = function(nums, target) {
  let len = nums.length;
  let map = new Map();
  for(let i = 0; i < len; i++) {
      if (map.has(target - nums[i])) {
          return [map.get(target - nums[i]), i]
      } else {
          map.set(nums[i], i)
      }
  }
};

console.log(twoSum([2,7,11,15], 9))

(字节)无重复最长子串

力扣原题:无重复最长子串

var lengthOfLongestSubstring = function(s) {
  let arr = [];
  let max = 0;
  for (let i = 0; i < s.length; i++) {
    let index = arr.indexOf(s[i])
    if (index !== -1) {
      arr.splice(0, index + 1);
    }
    arr.push(s.charAt(i));
    max = Math.max(arr.length, max);
  }
  return max;
};

实现new Queue类

new Queue()
.task(1000,()=>console.log(1))
.task(2000,()=>console.log(2))
.task(3000,()=>console.log(3)).start();
实现一个Queue函数,调用start之后,1s后打印1,接着2s后打印2,然后3s后打印3

答案

function sleep(delay, callback) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      callback();
      resolve()
    }, delay);
  })
}
class Queue {
  constructor() {
    this.listenser = [];
  }
  task(delay, callback) {
    // 收集函数
    this.listenser.push(() => sleep(delay, callback));
    return this;
  }
  async start() {
    // 遍历函数
    for (let l of this.listenser) {
      await l()
    }
  }
}

new Queue()
.task(1000,()=>console.log(1))
.task(2000,()=>console.log(2))
.task(3000,()=>console.log(3)).start();

总结

还出了很多场景题,让你给出解决方案,中间经历的太久了,面试记录做的不够好,很多问题都忘了。一面基本上都是问基础,后面几轮面试会深入项目和要你给出解决方案。这次面试也是发现了自己的不足,平时写代码的过程中,思考的不够多,希望今后能多一些思考。