记录下遇到的一些面试题
今年辞职后面试遇到的一些面试题,在这里记录下,放便查阅学习
一些面试题目
题目并不是所有都给出了答案,请自行总结~;另,感谢各位大佬的面经收获颇多;题目可能出现记忆偏差,答案如有错误或者描述不准确,欢迎指正
js是单线程语言,所有任务只能在一个线程上完成,一次只能做一件事。js通过
eventLoop
来实现异步。
为什么js设计成单线程?
JavaScript 的主要用途是与用户互动,以及操作 DOM。多线程操作DOM的话容易造成混乱(比如一个线程要修改这个DOM,而另一个线程则要删除这个DOM…)。
js 中的 EventLoop
主要需要理解
任务(task)
和微任务(micro task)
常见的任务(task): setTimeout、setInterval、 setImmediate、script(整体代码)、 I/O 操作、UI 渲染等
常见的微任务(micro task): process.nextTick、new Promise().then(回调)、MutationObserver(html5 新特性) 等
Node 中的 EventLoop
Node.js 采用 V8 作为 js 的解析引擎,而 I/O 处理方面使用了自己设计的 libuv,事件循环在其中实现。与浏览器中的事件循环是不同的
在 libuv 中,将事件循环分为六个阶段:
- timers 阶段:这个阶段执行 timer(setTimeout、setInterval)的回调
- pending callbacks 阶段:该阶段对某些系统操作(如TCP错误类型)执行回调。处理一些上一轮循环中的少数未执行的 I/O 回调
- idle, prepare 阶段:仅 node 内部使用
- poll 阶段:获取新的 I/O 事件,执行与 I/O 相关的回调,适当的条件下 node 将阻塞在这里
- check 阶段:执行 setImmediate() 的回调
- close callbacks 阶段:执行 socket 的 close 事件回调
Node 事件循环执行顺序:外部输入 -> poll -> check -> close callbacks -> timers -> I/O callbacks -> idle,prepare -> poll -> …(循环)
重点阶段详解:
timers:Node 会检查有无过期的timer,如果有,则把他的回调加入到timer的任务队列中等待执行「
这个过期检测不一定靠谱,受机器上其它运行程序影响,或者那个时间点主线程不空闲
」。1
2
3
4
5
6
7
8
9
10// 下列代码中 settimeout和setimmediate的执行顺序不确定
// 但是如果将他们放入到I/O回调中
// 则 setImmediate会先执行
// 请参看前面Node中事件循环执行顺序
setTimeout(() => {
console.log('timeout')
}, 0)
setImmediate(() => {
console.log('immediate')
})poll:该阶段系统会做两件事:
- 计算应该阻塞和轮询 I/O 的时间(控制timer阶段的执行)
- 处理 poll 队列里的事件
进入本阶段,同时未设定 timer 时,会发生以下两种情况:
- 如果 poll 队列不为空,会遍历回调队列并同步执行,直到队列为空或达到系统限制。
- 如果 poll 队列为空:
- 如果有预设的
setimmediate()
,则poll阶段会结束并进入执行 check 阶段任务队列 - 如果没有预设的
setimmediate()
,则EventLoop会被阻塞在该阶段
。为防止一直阻塞,该阶段event loop会有一个检查机制,检查timer队列是否为空,如果timer队列非空,event loop就开始下一轮事件循环,即重新进入到timer阶段。
- 如果有预设的
check:setImmediate()的回调会被加入 check 队列中。check 阶段的执行顺序在 poll 阶段之后。
process.nextTick
这个函数其实是独立于 Event Loop 之外的,它有一个自己的队列,当每个阶段完成后,如果存在 nextTick 队列,就会清空队列中的所有回调函数,并且优先于其他 microtask 执行。递归的调用process.nextTick()
会导致I/O starving,官方推荐使用setImmediate()
参考文章
- https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/
- https://www.jianshu.com/p/5f1a8f586019
- https://blog.csdn.net/u014465934/article/details/89176879
- https://www.jianshu.com/p/7430a4c494e6
Node中和浏览器中EventLoop的差异:
区别:microtask 队列的执行时机不同
- Node.js 中 EventLoop 分为六个阶段
- 浏览器环境下:microtask 队列是在每个 macrotask 队列执行之后执行。
- Node.js 中:一个阶段执行完毕就会去执行 microtask 队列的任务。
Vue3.0 中的 Proxy
和 Vue2.x 中的 defineProperty
的优劣对比
结论:
- Proxy 监听是的对象,而不是对象上的属性(defineProperty 监听的是对象上的属性)
- Proxy 除了拦截 set 和 get 还能拦截 apply、ownKeys、deleteProperty、has等等是Object.defineProperty不具备的。
- Proxy返回的是一个新对象,可以只操作新的对象达到目的,而Object.defineProperty只能遍历对象属性直接修改
- Object.defineProperty 支持 IE9,Proxy的存在浏览器兼容性问题,而且无法用polyfill磨平。
Vue2.x 的核心函数是 Object.defineProperty
,通过给对象属性添加一些配置,拦截属性的 get
和 set
方法来实现数据的响应式绑定,但是对对象新增的属性无能为力,对数组也需要拦截它的原型方法来实现响应式,不能通过 arr[1]=xx 方式来修改数组
。此时,vue 提供了一个api:this.$set
使新增属性也能实现响应式。
1 | // vue2中是根据具体的 key 去进行拦截的,需要知道拦截的 key 是什么, |
Vue3 中使用 Proxy
代替了 Object.defineProperty
1 | // 这种拦截方式不需要知道具体的 key |
参考文章
Vue3 的响应式和以前有什么区别
: https://juejin.im/post/5e92d863f265da47e57fe065ES6 Proxy
:https://es6.ruanyifeng.com/#docs/proxy
TCP三次握手以及HTTP位于那一层?(计算机网络)
TCP/IP
协议族分为 4 层:
- 应用层
- 传输层
- 网络层
- 数据链路层
三次握手:
- 第一次握手:发送端将
SYN=1
同步序列号和初始化序列号seq=x
发送给接收端 - 第二次握手:接收端收到
SYN
请求报文之后,如果同意连接,会以自己的同步序列号SYN(服务端) = 1
、初始化序列号seq = y
和确认序列号(期望下次收到的数据包)ack = x+ 1
以及确认号ACK = 1
报文作为应答 - 第三次握手:接收端接收到服务端的
SYN + ACK
之后,发送同步序列号ack = y + 1
和数据包的序列号seq = x + 1
以及确认号ACK = 1
,握手结束。
四次挥手:
- 第一次挥手:客户端
发送一个FIN=M
,用来关闭客户端到服务器端的数据传送,客户端进入FIN_WAIT_1状态 - 第二次挥手:服务器端收到FIN后,
发送ack=M+1
,服务端进入CLOSE-WAIT(关闭等待)状态,客户端进入FIN_WAIT_2 状态 - 第三次挥手:服务器端确定数据已发送完成,则向客户端
发送FIN=N报文
,服务器端进入LAST_ACK状态 - 第四次挥手:客户端收到FIN=N报文后,
发送ack=N+1
后进入TIME_WAIT状态,服务器端收到ACK后断开连接,客户端2SML(最长报文段寿命)后未收到回复则关闭连接
,完成四次挥手
为什么是4次挥手?
服务端是分开发送 ack 和 FIN 信息的,所以会多一次。
HTTP 协议属于应用层
HTTP
与 Websoket
联系与区别
HTTP 与 Websocket 的相同点
- 位于应用层,基于TCP,需要三次握手四次挥手
- 使用Request/Response模型进行连接的建立
- 在连接的建立过程中对错误的处理方式相同(可能返回相同的错误码)
HTTP 与 Websocket 的不同点
- HTTP 是单向的(客户端发送请求,服务端发送响应);Websocket 是双向的(服务端客户端都可以主动发起请求);
- HTTP 是无状态协议(每次请求都会新建与服务器的连接,获得响应后自动终止);Websocket 是有状态协议(客户端和服务器之间的连接将保持活动状态,直到被任何一方(客户端或服务器)终止);
webpack 的 loader 和 plugin 的区别
- Loader:加载器、转换器。因为 webpack 只能解析 js 文件,loader 能让webpack 拥有加载和解析非 js 资源的能力。
- Plugin:插件、扩展器。扩展 webpack 功能,Plugin 可以监听 webpack 运行期间广播出的一些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。
websoket 同时接入的数据量大时性能问题
这个还真不清楚,公司这个项目用户数不多,所以根本就不存在高并发的情况。。个人觉得,这种并发问题和前端关系应该不大,是需要后端处理的。唯一可能与性能有关的前端问题应该是大量、频繁更新前端界面的时候引起的。
这个问题感觉可以和性能优化放在一起
性能优化
HTTP 优化
两个大方向,就是我们通常所说的资源的压缩与合并。这里就需要知道webpack了。
- 减少请求次数
- 减少每次请求花费的时间
webpack性能优化:主要两个方面:
- webpack 构建过程时间过长
- webpack 打包结果体积过大
针对构建时间过长:
- 不要让loader做太多事
- 使用
include
或者exclude
来避免不必要的转译 - 开启缓存,将转译结果缓存至文件系统
1
2// 以babel-loader为例
loader: 'babel-loader?cacheDirectory=true'
- 使用
- 第三方库单独打包
- 处理第三方库的包有:
Externals
、CommonsChunkPlugin
、DllPlugin
等(详细情况自己再下去研究),这样的第三方库就不会随业务代码被重新打包,只有第三方库自身版本发生变化时才会重新打包
- 处理第三方库的包有:
针对打包结果体积过大:
- 拆分资源。使用
DllPlugin
等 - 删除冗余代码
Tree-Shaking
:是基于 import/export 语法,没有被真正使用的模块在打包的时候会被删除。UglifyJsPlugin
:在压缩过程中对碎片化的冗余代码(如 console 语句、注释等)进行自动化删除
- 按需加载
图片优化
JPEG/JPG:
特点:有损压缩、体积小、加载快、不支持透明,24位的JPG可存储1600万种颜色
使用场景:适用于呈现色彩丰富的图片。常见的大背景图、轮播图、banner图等
PNG-8 与 PNG-24:
PNG 图片具有比 JPG 更强的色彩表现力,弥补了 JPG 的不足,但是体积较大
特点:无损压缩、质量高、体积大、支持透明,8位支持256中颜色,24位支持1600万中颜色
使用场景:Logo、背景简单且对比强烈的图片或背景
SVG:
SVG 对图像的处理不是基于像素点,而是是基于对图像的形状描述,与其他图片有本质的不同。可以被非常多的工具读取和修改,具有较强的灵活性。
特点:文本文件、体积小、不失真、兼容性好
Base64:
作为小图标解决方案而存在的,作用和雪碧图类似
特点: 文本文件、依赖编码、小图标解决方案
存储优化
浏览器缓存机制:
- Memory Cache
- Service Woker Cache
- HTTP Cache
- Push Cache
HTTP Cache
分为
强缓存
和协商缓存
,强缓存优先级较高,强缓存命中失败才会走协商缓存
强缓存:由 HTTP 请求头中的 Expire
和 Cache-Control
两个字段控制。浏览器根据这两个字段判断是否命中强缓存,若命中,则直接从缓存中获取,**不与服务器发生请求
**
- Expire 是一个时间戳,浏览器会
对比本地时间和 Expire 时间戳
(如果改掉客户端本地时间, expire 将不能达到预期,这是 expire 的局限性)1
expires: Wed, 11 Sep 2019 16:12:18 GMT
- HTTP1.1新增了
Cache-Control
来替代 Expire 。(该字段是一个时间长度,避免了时间戳带来的问题。相比 expire,更准确,优先级更高,两个字段同时存在时,以 Cache-Control 为准)1
cache-control: max-age=31536000
协商缓存:由 Last-Modified 和 Etag 实现,两个同时存在时以 Etag 为准
, 需要浏览器向服务器询问缓存信息,来决定是发起请求还是从本地缓存获取。
响应会返回 Last-Modified
,请求会携带 If-Modified-Since
。
1 | Last-Modified: Mon, 01 Jun 2020 06:34:26 GMT |
Last-Modified
常见问题:
If-Modified-Since
只能检查以秒为单位的时间差,修改时间过短会出问题- 如果修改了文件但是没改文件内容,会被当作新资源请求,所以出现了
Etag
。
Etag:基于文件内容,编码生成唯一标志字符串,避免了 Last-Modified
的两个问题。响应会返回 ETag
,请求会携带 If-None-Match
。
1 | ETag: W/"2a3b-1602480f459" |
Memory Cache
内存缓存,优先级最高,速度快,进程结束后即被清空。常缓存 base64 格式的图片、体积较小的 js 和 css 文件,体积较大的会被存进磁盘。
Service Woker Cache
独立于主线程之外的 Javascript 线程。脱离于浏览器窗体,因此无法直接访问 DOM。
用来实现离线缓存、消息推送和网络代理等功能
,**仅支持 HTTPS 协议
**
Push Cache
HTTP2 中的新知识
特点:
- 在其他缓存均未命中的情况下,才会查找该缓存
- 是一种存在于会话阶段的缓存,当 session 终止时,缓存也随之释放
- 不同的页面只要共享了同一个 HTTP2 连接,那么它们就可以共享同一个 Push Cache。
渲染优化
参考文章
推荐一本掘金小册:
常用的设计模式(然后说话你的项目中用到过的)
推荐一本掘金小册和一篇文章,然后结合自己的项目总结下:
JavaScript 设计模式核⼼原理与应⽤实践(掘金小册):https://juejin.im/book/5c70fc83518825428d7f9dfb/section/5c70fc845188256282697b96
前端渣渣唠嗑一下前端中的设计模式(真实场景例子):https://juejin.im/post/5e0eaff4e51d45413b7b77f3
Vuex的原理,怎么实现状态管理的
Vue双向绑定如何实现的,get和set进行了哪些操作
Vue生命周期
this.$nexttick原理
说下你了解的async和await?await的使用条件是什么?
前端请求的封装是如何做的?
遍历数组你常用的方法,有何区别
watch和computed相关用法以及区别
组件传参
深拷贝浅拷贝
做过公共组件抽离吗?
vue权限路由实现?如果后续路由层级加深,该如如何处理?
mixin在项目中有用到嘛?
你所做项目中觉得哪个是比较有的亮点的?是什么?
vue-router原理(怎么根据url找到我们对应的组件的)
数组去重
写一个数组排序方法(数组可能是简单数组,也可能是复杂数组)
项目打包后文件过大你是怎么优化的
页面加载时间过长你是怎么优化的
vue双向绑定原理
vue中computed与watch
介绍下协商缓存与强制缓存
项目中,关闭页面(或者浏览器后)未提交的数据如何保存与回填。你怎么确定存储的数据是这个页面的
js数据类型转换
js中类型转换有3种:
- 转换为布尔型(调用
boolean()方法) - 转换为数字(调用Number()、parseInt()和parseFloat()方法)
- 转换为字符串(调用.toString()或者String()方法)
除以上三种外,还有一些操作符会存在隐式转换
- 对象和布尔值比较:
对象先转换成字符串然后转成数值,布尔值转成数值,然后两者进行比较
- 对象和字符串比较:
对象转成字符串,然后和字符串进行比较
- 对象和数字比较:
对象先转换成字符串然后转成数值,然后进行比较
- 字符串和布尔值比较:
两者先转成数值进行比较
- 布尔值和数字比较:
布尔值先转成数值进行比较
Number
方法的参数是对象时
第一步,调用对象自身的valueOf方法。如果返回原始类型的值,则直接对该值使用 Number函数,不再进行后续步骤。(默认情况下,对象的valueOf方法返回对象本身)
第二步,如果valueOf方法返回的还是对象,则改为调用对象自身的toString方法。如果toString方法返回原始类型的值,则对该值使用Number函数,不再进行后续步骤。
第三步,如果toString方法返回的是对象,就报错。
1 | var obj = {x: 1}; |
js隐式转换原理
Vue 父子组件生命周期执行顺序
- 加载渲染过程
- 父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
- 子组件更新过程
- 父beforeUpdate->子beforeUpdate->子updated->父updated
- 父组件更新过程
- 父beforeUpdate->父updated
- 销毁过程
- 父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
三栏布局
bfc
清除浮动
声明一个变量实现:a===1&&a===2&&a===3
方法一:
1 | let val = 1 |
方法二:利用 toString
方法
1 | class A { |
拓展:
1 | // 设置一个函数输出以下的值 |
事件触发顺序
- NetSpace:事件捕获先触发
- MicroSoft: 事件冒泡先触发
- W3C模型中:事件先捕获,在冒泡
js 基础数据类型与复杂数据类型的区别
7种基础数据类型:
undefined
,null
,number
,boolean
,string
,Symbol(ES6新增,表示独一无二的值)
和BigInt(ES10)
1种引用数据类型(复杂数据类型):
Object
,包含function、Array、Date等
- 基本数据类型
把数据名和值直接存储在栈当中
,占据空间小、大小固定,属于被频繁使用数据
。 - 复杂数据类型
在栈中存储数据名和一个在堆(占据空间大、大小不固定)中的地址
,在堆中存储属性及值
,访问时先从栈中获取地址,再到堆中拿出相应的值
vue页面级的拆包
闭包及其作用
闭包就是能够读取其他函数内部变量的函数。闭包是将函数内部和函数外部连接起来的桥梁
如何从外部读取函数内部的局部变量?可通过闭包实现
- 作用
- 封装私有变量
- 读取函数内部变量
- 变量始终保存在内存中(滥用闭包会造成内存泄漏)
原型、原型链
每个对象都有
__proto__
属性,但只有函数对象才有prototype
属性
所有的普通的原型链最终都会指向Object.Prototype
。
箭头函数与普通函数的区别
- 箭头函数是匿名函数,不能作为构造函数,不能使用new
- 箭头函数不能绑定arguments,取而代之用rest参数…解决
1
2
3
4
5
6
7
8
9function A(a){
console.log(arguments);
}
A(1,2,3,4,5,8);
// [1, 2, 3, 4, 5, 8, callee: ƒ, Symbol(Symbol.iterator): ƒ]
let C = (...c) => {
console.log(c);
}
C(3,82,32,11323); - 箭头函数没有原型属性
1
2let arrow = () => {}
arrow.prototype // undefined - 箭头函数的this永远指向其上下文的this,没有办改变其指向,
普通函数的this指向调用它的对象
vue中 v-if 和 v-for 同时出现,那个优先级高,同时存在时怎么优化性能?
- v-for优先于v-if被解析
- 如果同时出现,每次渲染都会先执行循环再判断条件,无论如何循环都不可避免,浪费了性能
- 优化一:要避免出现这种情况,则在外层嵌套template,在这一层进行v-if判断,然后在内部进行v-for循环
1
2
3<template v-if="...">
<p v-for="item in itemArrValid" :key="item.id">{{item}}</p>
</template> - 优化二:如果在循环内部进行if判断,我们可以先用computed过滤出有效的展示数据,再进行渲染
1
<p v-for="item in itemArrValid" :key="item.id">{{item}}</p>
1
2
3
4
5
6computed:{
itemArrValid(){
// return ...
// 处理 itemArr 重的数据
}
}
v-for 绑定 key 的作用
v-for 的默认行为会尝试原地修改元素而不是移动它们。要强制其重新排序元素,你需要用特殊 attribute key 来提供一个排序提示。———— 来源:Vue官网
key 的特殊属性主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试修复/再利用相同类型元素的算法。使用 key,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。
key是给每一个vnode的唯一id,可以依靠key,更准确, 更快的拿到oldVnode中对应的vnode节点。
更准确
因为带key就不是就地复用了,在sameNode函数 a.key === b.key对比中可以避免就地复用的情况。所以会更加准确。
更快
利用key的唯一性生成map对象来获取对应节点,比遍历方式更快。(这个观点,就是我最初的那个观点。从这个角度看,map会比遍历更快。)
vue中的data
data可以是一个 Object,也可以是一个 Function。但是组件的定义只接收 function(因为组件可能被用来创建多个实例。如果 data 仍然是一个纯粹的对象,则所有的实例将共享引用同一个数据对象!通过提供 data 函数,每次创建一个新实例后,我们能够调用 data 函数,从而返回初始数据的一个全新副本数据对象)
1 | var data = { a: 1 } |
Vue 组件间传值
一、父子组件传值
- 父组件 传给 子组件
- 父组件 通过
v-bind
绑定动态数据传给子组件 - 子组件 通过
props
接收 - 使用
$ref
获取指定的子组件 - 使用
$children
- 父组件 通过
- 子组件 传给 父组件
- 子组件 用
this.$emit('fnName', data)
触发 `` - 父组件 用
@fnName="fn"
接收,注意,@的名称 和子组件this.$emit名称相同 - 使用
$parent
- 子组件 用
二、组件传值
eventBus
,利用bus.$emit(‘eventName', sendData)
触发,通过bus.$on(‘eventName', (getData) => {})
监听接收- 使用
Vuex
$sttrs/listeners
用于多级组件间传值的问题1
2
3
4// 一般在 main.js中引入
Vue.prototype.bus=new Vue()
// 或者
window.bus = new Vue()
webpack 热更新原理
推荐文章:https://zhuanlan.zhihu.com/p/30669007
vue 组件封装
全局主题切换思路
vue 中怎么定义一个全局方法
方法一:
1 | // 在 main.js 中 |
方法二:
1 | // 一个单独的js文件 |
如何保证API调用时数据的安全性?
- 通信使用https
- 请求签名,防止参数被篡改
- 身份确认机制,每次请求都要验证是否合法
- 对所有请求和响应都进行加解密操作
V8垃圾回收机制
下列代码输出什么
滑慢点,下面挨着的就是答案
1 | var a = 1; |
解析:
本题个人看到有两个知识点:
1、this指向问题
;
2、let 声明的变量不会挂在window上,var 声明的会挂在 window 上
1 | obj.fn() 输出: 11,22 |