前言
笔者两年前端经验,前后大概面了一个月,期间面了很多公司,比如有赞
、涂鸦智能
、滴滴
、字节
、酷家乐
大搜车
、海康威视
、税友
等等,梳理一下基于我个人面试过程中被问的到的一些问题(包括但不限于
)
在开始面试之前,一份优秀的简历也是十分重要,推荐两篇文章:
- 如何写「前端简历」,能敲开字节跳动的大门?
- 一份优秀的前端开发工程师简历是怎么样的[1]
为了让自己拿到比较满意的 offer,前一周选了一些中小公司来练手,后面发现,太小的公司没有锻炼效果,所以一周后,就开始给规模比较大的厂投简历了。
印象比较深刻的是酷家乐
,一面是远程,然后二面约你现场,现场是三轮技术 + hr,差不多三个小时,效率很高,但是第一次面试这么久(当时也不知道要面这么久),导致后面表现的不是很好,遂,卒。涂鸦智能
的效率也很高,跟酷家乐的面试流程是一样的,都是远程一面,现场三轮技术 + hr。其他的公司基本上是一轮一轮的来,每一轮的结果一般第二天会告知你,当然,如果后面几天等不到,也是一种告知。
悄悄加一句,欢迎联系 ssh 内推或者交个朋友 😉,微信 sshsunlight
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 知识体系[2]
说一下 http 缓存
等你说完强缓存
和协商缓存
的大致流程,面试官可能基于你的答案,来深入考察你对 http 缓存的理解,比如(包括但不限于):
200 状态码一定是服务器返回的吗?
不是,命中强缓存的话,会直接从内存或者磁盘中读取资源,并返回一个 200 状态码,具体操作可以试试浏览器的前进后退键。
Expires 和 Cache-Control 的 max-age 指令分别是如何确定过期时间的?优劣势是什么?
为什么有了 Last-Modified 还要有 ETag?解决了什么问题?
no-store 和 no-cache 的意思分别是什么?
推荐一篇文章,解答了这些问题:一张图理解 http 缓存[3]
https 为什么比 http 安全
面试官会让你说下 https 做了什么,然后不排除会问你加密原理。(ps: 字节面试官问了)
主要用到了对称加密和非对称加密,推荐阅读:彻底搞懂 HTTPS 的加密原理[4]
说一下三次握手四次挥手
这个问题问的比较少,只遇到过一次。推荐一篇笔者之前写的文章: TCP 三次握手、四次挥手[5]
JS
0.1 + 0.2 !== 0.3?为什么?
二进制转换
:js 在做数字计算的时候,底层都是转二进制来计算的,0.1 转二进制是无限循环小数,0.2 也是,但是 js 采用的IEEE754 二进制浮点运算[6],小数后面只会保留 53 位有效数字,导致精度丢失。对阶运算
:阶小的尾数要根据阶差来右移,尾数位移时可能会发生数丢失的情况,影响精度。
如何解决上面说的精确度丢失问题?
- 刚开始第一反应就是,先将小数点扩大变成整数,做完加法之后,再除回去。(不妥当,因为还是可能会超过 js 最大数)
- 利用第三方库,比如
Math.js
、big.js
等 - 都转成字符串,然后对两个字符串做加法运算(手写题有实现)
闭包
- 说一下闭包的本质是什么?会造成哪些问题
- 除了函数的内部返回一个函数,还有其他的方法产生闭包吗?(有)
// 函数内部延迟调用,产生了闭包 function test() { let name = "tom"; setTimeout(() => { console.log(name); }, 2000); } test();
推荐文章:JavaScript 的静态作用域链与“动态”闭包链[7]
什么是作用域?什么是作用域链?函数执行上下文包含了哪些内容?
this 指向
面试官问:JS 的 this 指向[8]
es6 的问题
- 箭头函数和普通函数的区别
- 什么是暂时性死区,什么是变量提升
- for of 和 for in 的区别,怎么让 for of 可以遍历一个对象?
- es6 的 Map 和 WeakMap 的区别,WeakMap 解决了什么问题?
- promise 哪些方法是原型上的,哪些方法是实例上的
推荐阮一峰老师的教程:ES6 入门教程[9]
原型 + 原型链 (这个属于必问的题)
这个问题只要能描述的清楚对象如何查找它的值,基本上就理解了一大半。
解释一下上面的图:
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 执行机制[10]
垃圾回收机制
- 你刚刚提到的标记清除法有什么缺点?怎么解决?
- 你刚刚提到的引用计数法有什么缺点吗?
- v8 里面的垃圾回收机制是什么?
- v8 是怎么解决循环引用的?
推荐文章:你真的了解垃圾回收机制吗[11]
说一下数据类型,如何判断一个数组
判断数组的方法:
- Array.isArray(arr); // true
- arr instanceof Array; // true
- arr.constructor === Array; // true
- Object.prototype.toString.call(arr); // "[object Array]"
ps:通过 instanceof 和 constructor 来判断不一定准确,因为可能会被重写。
常用的设计模式?
- 什么是抽象工厂模式
- 发布订阅模式和观察者模式有什么区别
- 你项目里面都用了哪些设计模式
推荐文章:前端需要掌握的设计模式[12]
浏览器渲染过程
一般我都是根据这张图,把流程说一遍。
被面试官问到的一些问题:
- 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 解析。
推荐阅读:
浏览器的工作原理:新式网络浏览器幕后揭秘[13]、
从 8 道面试题看浏览器渲染过程与性能优化[14]
性能优化
我一般从 http 请求 + 代码层面 + 构建工具来回答。
setTimeout 和 requestAnimationFrame 做动画有区别吗?哪一个更好?为什么?
有区别:
- 准确性
setTimeout
做动画不准确,因为是宏任务,设置的延迟时间并不等于在延迟时间之后立即执行,因为需要等待同步任务和微任务执行完,才执行宏任务,容易丢帧;requestAnimationFrame
则是在每一帧绘制元素之前执行,更精确。
- 更好的性能
在隐藏元素或者元素不可见时,
setTimeout
仍然在后台执行动画任务;而requestAnimationFrame
会停止动画,这意味着更少的 CPU 和更少的内存消耗。
介绍一下同源策略?你知道那些跨域方法?cors 跨域的原理是什么有了解过吗?
CSS
介绍一下盒模型?
flex: 1 代表什么意思
用过 flex 布局吗?都有哪些属性?
说说什么是 BFC,一般你都用来干什么,解决了什么问题?
实现元素水平垂直居中?尽可能说多一些方法?
左侧固定 + 右侧自适应布局?说说几种方案?
重绘和重排?
推荐文章:重排(reflow)和重绘(repaint)[15]
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 的?
combineReducers 源码[16]
是的,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 事件系统原理[17]
设计的好处:
- 抹平浏览器差异,实现更好的跨平台
- 避免垃圾回收,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 种方式了解一下[18]
React.memo()和 React.useMemo()有什么区别吗?
接上题,然后面试官这两个有什么区别吗?
直接上官网: React.memo[19]、 React.useMemo[20]
useCallback 和 useMemo 的区别?
同接上题 官网地址 useCallback[21]
React.fiber 了解吗?造成卡顿的原因是什么?react.fiber 里面是怎么解决的?中断更新之后,下次是如何找到要更新的位置的?
推荐荒山大佬的文章:这可能是最通俗的 React Fiber(时间分片) 打开方式[22]
函数式编程
讲 hooks 很大概率问到函数式编程?(被问到过好几次)
- 说一下你理解的函数式编程?
- 有哪些库是利用了函数式编程的思想?
- lodash 是真正的函数式编程库吗?
简明 JavaScript 函数式编程——入门篇[23]
高阶组件里面如何防止 ref 丢失?
React.forwardRef
,上官网地址:React.forwardRef[24]
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 系列(优化篇)[25]
loader 和 plugin 的区别?
- loader(转换):主要是做转换功能,比如将 css、less 文件转成 js,识别不同的文件后缀名,可以拓展一下自己比较熟悉的 loader。
- plugin(插件):原理是监听 webpack 构建过程中的一些钩子,然后做一些自己的操作,更多的是丰富 webpack 的功能。
loader 的执行顺序是什么?如何写一个 loader?如何写一个 plugin?loader 有异步的吗?
执行顺序从右往左、从下到上。
loader 本质是一个函数,plugin 本质是一个类,具体如何编写,推荐文章:手把手教你写一个 loader / plugin[26]
loader 有同步的也有异步。
file-loader 返回的是什么?
返回的是一个字符串,详情见:file-loader 源码地址[27]
webpack 有几种 hash,它们有什么区别?一般你在项目里面是用哪种 hash?
hash
:是整个项目的 hash 值,其根据每次编译内容计算得到,每次编译之后都会生成新的 hash,即修改任何文件都会导致所有文件的 hash 发生改变。chunkHash
:根据不同的入口文件(Entry)进行依赖文件解析、构建对应的 chunk,生成对应的哈希值(来源于同一个 chunk,则 hash 值就一样)。contentHash
:根据文件内容生成 hash 值,文件内容相同 hash 值就相同
webpack5 有哪些新特性?
推荐文章:飞书团队 Webpack5 上手测评[28]
webpack 热更新原理?
推荐文章:轻松理解 webpack 热更新原理[29]
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()
;这个没什么好说的,输出 2getName()
;考察 var 和函数提升,函数优先级大于 var,所以输出 4Foo().getName()
;Foo()返回 this,此时 this 指向 window,Foo().getName 相当于 window.getName。但是 Foo()内部又对 window 上的 getName 重新赋值了,所以输出 1getName()
;同上,输出 1new Foo.getName()
;考察运算符优先级[30],new 无参数列表,对应的优先级是 18;成员访问操作符.
, 对应的优先级是 19。因此相当于是new (Foo.getName)()
;new 操作符会执行构造函数中的方法,因此此处输出为 2.new Foo().getName()
;new 带参数列表,对应的优先级是 19,和成员访问操作符.
优先级相同。同级运算符,按照从左到右的顺序依次计算。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.789)); // 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); }); }); }); };
(滴滴)两数之和
力扣原题:两数之和[31]
刚开始用双重循环写了一个,时间复杂度是 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));
(字节)无重复最长子串
力扣原题:无重复最长子串[32]
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();
总结
还出了很多场景题,让你给出解决方案,中间经历的太久了,面试记录做的不够好,很多问题都忘了。一面基本上都是问基础,后面几轮面试会深入项目和要你给出解决方案。这次面试也是发现了自己的不足,平时写代码的过程中,思考的不够多,希望今后能多一些思考。
参考资料
[1]
一份优秀的前端开发工程师简历是怎么样的: https://www.zhihu.com/question/23150301/answer/1229870117
[2]
HTTP 灵魂之问,巩固你的 HTTP 知识体系: https://juejin.cn/post/6844904100035821575#heading-100
[3]
一张图理解 http 缓存: https://segmentfault.com/a/1190000015816331
[4]
彻底搞懂 HTTPS 的加密原理: https://zhuanlan.zhihu.com/p/43789231
[5]
TCP 三次握手、四次挥手: https://juejin.cn/post/6844904194764177416
[6]
IEEE754 二进制浮点运算: https://www.h-schmidt.net/FloatConverter/IEEE754.html
[7]
JavaScript 的静态作用域链与“动态”闭包链: https://juejin.cn/post/6957913856488243237
[8]
面试官问:JS 的 this 指向: https://juejin.cn/post/6844903746984476686
[9]
ES6 入门教程: https://es6.ruanyifeng.com/
[10]
这一次,彻底弄懂 JavaScript 执行机制: https://juejin.cn/post/6844903512845860872
[11]
你真的了解垃圾回收机制吗: https://juejin.cn/post/6981588276356317214
[12]
前端需要掌握的设计模式: https://juejin.cn/post/6874906145463468046
[13]
浏览器的工作原理:新式网络浏览器幕后揭秘: https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/
[14]
从 8 道面试题看浏览器渲染过程与性能优化: https://juejin.cn/post/6844904040346681358
[15]
重排(reflow)和重绘(repaint): https://juejin.cn/post/6844904083212468238
[16]
combineReducers 源码: https://github.com/reduxjs/redux/blob/master/src/combineReducers.ts
[17]
一文吃透 react 事件系统原理: https://juejin.cn/post/6955636911214067720
[18]
React 性能优化的 8 种方式了解一下: https://juejin.cn/post/6844903924302888973
[19]
React.memo: https://zh-hans.reactjs.org/docs/react-api.html#reactmemo
[20]
React.useMemo: https://zh-hans.reactjs.org/docs/hooks-reference.html#usememo
[21]
官网地址 useCallback: https://zh-hans.reactjs.org/docs/hooks-reference.html#usecallback
[22]
这可能是最通俗的 React Fiber(时间分片) 打开方式: https://juejin.cn/post/6844903975112671239
[23]
简明 JavaScript 函数式编程——入门篇: https://juejin.cn/post/6844903936378273799
[24]
React.forwardRef: https://zh-hans.reactjs.org/docs/react-api.html#reactforwardref
[25]
带你深度解锁 Webpack 系列(优化篇): https://juejin.cn/post/6844904093463347208
[26]
手把手教你写一个 loader / plugin: https://juejin.cn/post/6976052326947618853
[27]
file-loader 源码地址: https://github.com/webpack-contrib/file-loader/blob/master/src/index.js
[28]
飞书团队 Webpack5 上手测评: https://juejin.cn/post/6844904169405415432
[29]
轻松理解 webpack 热更新原理: https://juejin.cn/post/6844904008432222215
[30]
[31]
两数之和: https://leetcode-cn.com/problems/two-sum/
[32]
无重复最长子串: https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/