前言
笔者两年前端经验,前后大概面了一个月,期间面了很多公司,比如有赞
、涂鸦智能
、滴滴
、字节
、酷家乐
大搜车
、海康威视
、税友
等等,梳理一下基于我个人面试过程中被问的到的一些问题(包括但不限于
)。
在开始面试之前,一份优秀的简历也是十分重要,推荐两篇文章: 一份优秀的前端开发工程师简历是怎么样的、 如何写「前端简历」,能敲开字节跳动的大门?
为了让自己拿到比较满意的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状态码,具体操作可以试试浏览器的前进后退键。
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.js
、big.js
等 - 利用ES2020新增的基础数据类型BigInt
- 都转成字符串,然后对两个字符串做加法运算(手写题有实现)
闭包
- 说一下闭包的本质是什么?会造成哪些问题
- 除了函数的内部返回一个函数,还有其他的方法产生闭包吗?(有)
// 函数内部延迟调用,产生了闭包
function test () {
let name = 'tom'
setTimeout(() => {
console.log(name)
}, 2000)
}
test();
推荐文章: JavaScript 的静态作用域链与“动态”闭包链
什么是作用域?什么是作用域链?函数执行上下文包含了哪些内容?
this指向
es6的问题
- 箭头函数和普通函数的区别
- 什么是暂时性死区,什么是变量提升
- for of 和for in的区别,怎么让for of可以遍历一个对象?
- es6的Map和WeakMap的区别,WeakMap解决了什么问题?
- promise哪些方法是原型上的,哪些方法是实例上的 推荐阮一峰老师的教程: ES6 入门教程
原型 + 原型链 (这个属于必问的题)
这个问题只要能描述的清楚对象如何查找它的值,基本上就理解了一大半。
解释一下上面的图:
prototype
是函数特有的属性,__proto__
是每个对象都有的属性,而prototype
本身也是一个对象- 当我们去获取
a.name
的时候,会先从对象的自身属性开始查找,如果没有的话,就会从a.__proto__
上找 - 对象
a.__proto__
又指向构造器函数test
的prototype
(原型),所以从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来判断不一定准确,因为可能会被重写。
常用的设计模式?
- 什么是抽象工厂模式
- 发布订阅模式和观察者模式有什么区别
- 你项目里面都用了哪些设计模式 推荐文章:前端需要掌握的设计模式
浏览器渲染过程
一般我都是根据这张图,把流程说一遍。
被面试官问到的一些问题:
- 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解析。
推荐阅读:
浏览器的工作原理:新式网络浏览器幕后揭秘、
性能优化
我一般从http请求 + 代码层面 + 构建工具来回答。
setTimeout和requestAnimationFrame做动画有区别吗?哪一个更好?为什么?
有区别:
- 准确性
setTimeout
做动画不准确,因为是宏任务,设置的延迟时间并不等于在延迟时间之后立即执行,因为需要等待同步任务和微任务执行完,才执行宏任务,容易丢帧;requestAnimationFrame
则是在每一帧绘制元素之前执行,更精确。
- 更好的性能
在隐藏元素或者元素不可见时,
setTimeout
仍然在后台执行动画任务;而requestAnimationFrame
会停止动画,这意味着更少的CPU和更少的内存消耗。
介绍一下同源策略?你知道那些跨域方法?cors跨域的原理是什么有了解过吗?
CSS
介绍一下盒模型?
flex: 1代表什么意思
用过flex布局吗?都有哪些属性?
说说什么是BFC,一般你都用来干什么,解决了什么问题?
实现元素水平垂直居中?尽可能说多一些方法?
左侧固定 + 右侧自适应布局?说说几种方案?
重绘和重排?
React
面了一圈下来,发现react问的都差不多。
都用过那些版本的react,分别介绍一下区别?
说一下前端路由,他们的实现原理分别是什么?
hash路由:通过监听hashchange
事件来捕捉url的变化,来决定是否更新页面。
history路由:主要监听popState
、pushState
、replaceState
来决定是否更新页面。但是要注意,仅仅调用pushState
方法或replaceState
方法 ,并不会触发popState
事件,只有用户点击浏览器倒退按钮和前进按钮,或者使用 JavaScript 调用back
、forward
、go
方法时才会触发,想要pushState
、replaceState
的时候触发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的?
是的,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.memo、 React.useMemo
useCallback和useMemo的区别?
同接上题 官网地址useCallback
React.fiber了解吗?造成卡顿的原因是什么?react.fiber里面是怎么解决的?中断更新之后,下次是如何找到要更新的位置的?
推荐荒山大佬的文章:这可能是最通俗的 React Fiber(时间分片) 打开方式
函数式编程
讲hooks很大概率问到函数式编程?(被问到过好几次)
- 说一下你理解的函数式编程?
- 有哪些库是利用了函数式编程的思想?
- lodash是真正的函数式编程库吗? 简明 JavaScript 函数式编程——入门篇
高阶组件里面如何防止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转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会
- 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。
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();
总结
还出了很多场景题,让你给出解决方案,中间经历的太久了,面试记录做的不够好,很多问题都忘了。一面基本上都是问基础,后面几轮面试会深入项目和要你给出解决方案。这次面试也是发现了自己的不足,平时写代码的过程中,思考的不够多,希望今后能多一些思考。