背景
笔者在最近的秋招中,参加了米忽悠的提前批,自我感觉面的非常不错,然后一面挂(小丑.jpg
下面是笔者的面经
面经
1. 说说 HTML 的语意化
- HTML 语意化有啥
- article
- section
- header
- footer
- nav
- sider
- 好处
- 网页加载慢导致 CSS 文件还未加载时(没有 CSS),页面依然清晰、可读、好看。
- 有利于 SEO,和搜索引擎建立良好沟通,有利于爬虫抓取更多的有效信息。
- 方便其他设备(如屏幕阅读器、盲人阅读器、移动设备)更好的解析页面。
- 使代码更具可读性,便于团队开发和维护。
1. async await 原理
本质上就是使用了 generator,可以看看下面的详细代码,本质是通过 generator 来处理实现循环,实现“卡住”的效果。
在实际面试的时候,我把这部分代码写出来了。
const getData = () => new Promise(resolve => setTimeout(() => resolve("data"), 1000))
// 这样的一个async函数 应该再1秒后打印data
async function test() {
const data = await getData()
console.log(data)
return data
}
// async函数会被编译成generator函数 (babel会编译成更本质的形态,这里我们直接用generator)
function* testG() {
// await被编译成了yield
const data = yield getData()
console.log('data: ', data);
const data2 = yield getData()
console.log('data2: ', data2);
return data + '123'
}
function generatorToAsync(generatorFunc) {
return function() {
const gen = generatorFunc.apply(this, arguments)
return new Promise((resolve, reject) => {
function step(key, args) {
let generatorResult;
try {
generatorResult = gen[key](args)
} catch(err) {
return reject(err)
}
const { value, done } = generatorResult
if(done) {
return resolve(value)
} else {
return Promise.resolve(value).then(val => step("next", val), err => step("throw", err))
}
}
step("next")
})
}
}
const testGAsync = generatorToAsync(testG)
testGAsync().then(result => {
console.log(result)
})
2. 垂直居中怎么做的,回答了五种,就比较详细
- relative + absolute
.wp {
position: relative;
}
.box {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
}
- calc + absolute
.wp {
position: relative;
}
.box {
position: absolute;
top: calc(50% - 50px); <!--50 是父盒子的高度-->
left: calc(50% - 50px);
}
- absolute + transform
.wp {
position: relative;
}
.box {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%)
}
- flex
.wp {
display: flex;
justify-content: center;
align-items: center;
}
- grid
.wp {
display: grid;
}
.box {
align-self: center;
justify-self: center;
}
3. http1.1 和 http2
- 头信息压缩: HTTP/2 实现了头信息压缩,由于 HTTP 1.1 协议不带状态,每次请求都必须附上所有信息。所以,请求的很多字段都是重复的,比如 Cookie 和 User Agent ,一模一样的内容,每次请求都必须附带,这会浪费很多带宽,也影响速度。HTTP/2 对这一点做了优化,引入了头信息压缩机制。一方面,头信息使用 gzip 或 compress 压缩后再发送;另一方面,客户端和服务器同时维护一张头信息表「HPACK 算法」,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就能提高速度了。
- HPACK 算法的优势
- 首先是在服务器和客户端之间建立哈希表,将用到的字段存放在这张表中,那么在传输的时候对于之前出现过的值,只需要把索引(比如0,1,2,...)传给对方即可,对方拿到索引查表就行了。这种传索引的方式,可以说让请求头字段得到极大程度的精简和复用。
- 其次是对于整数和字符串进行哈夫曼编码,哈夫曼编码的原理就是先将所有出现的字符建立一张索引表,然后让出现次数多的字符对应的索引尽可能短,传输的时候也是传输这样的索引序列,可以达到非常高的压缩率。
- 二进制协议:HTTP/2 是一个二进制协议。在 HTTP/1.1 版中,报文的头信息必须是文本(ASCII 编码){这个问题被字节一面面试官问过,还答错了,和数据体搞混了。} ,数据体可以是文本,也可以是二进制。HTTP/2 则是一个彻底的二进制协议,头信息和数据体都是二进制,并且统称为"帧",可以分为头信息帧和数据帧。 帧的概念是它实现多路复用的基础。
- 多路复用: 多路复用产生的原因是「HTTP队头阻塞」,而队头阻塞又由「HTTP请求-应答」模式所造成(在同一个 TCP 长连接中,前面的请求没有得到响应,后面的请求就会被阻塞。),后面我们使用了并发连接和域名分片去解决这个问题,但这并没有真正从 HTTP 本身的层面解决问题,只是增加了 TCP 连接,分摊风险而已。而且这么做也有弊端,多条 TCP 连接会竞争有限的带宽,让真正优先级高的请求不能优先处理。原来Headers + Body的报文格式如今被拆分成了一个个二进制的帧,用Headers帧存放头部字段,Data帧存放请求体数据。分帧之后,服务器看到的不再是一个个完整的 HTTP 请求报文,而是一堆乱序的二进制帧。这些二进制帧不存在先后关系,因此也就不会排队等待,也就没有了 HTTP 的队头阻塞问题。通信双方都可以给对方发送二进制帧,这种二进制帧的双向传输的序列,也叫做流(Stream)。HTTP/2 用流来在一个 TCP 连接上来进行多个数据帧的通信,这就是多路复用的概念。
所谓的乱序,指的是不同 ID 的 Stream 是乱序的,但同一个 Stream ID 的帧一定是按顺序传输的。二进制帧到达后对方会将 Stream ID 相同的二进制帧组装成完整的请求报文和响应报文。当然,在二进制帧当中还有其他的一些字段,实现了优先级和流量控制等功能
- 服务器推送: HTTP/2 允许服务器未经请求,主动向客户端发送资源,这叫做服务器推送。在 HTTP/2 当中,服务器已经不再是完全被动地接收请求,响应请求,它也能新建 stream 来给客户端发送消息,当 TCP 连接建立之后,比如浏览器请求一个 HTML 文件,服务器就可以在返回 HTML 的基础上,将 HTML 中引用到的其他资源文件一起返回给客户端,减少客户端的等待。
4. 说说 Vue2 中 computed 的原理
响应式基础:
vue是采用数据劫持结合观察者模式的方式,通过defineProperty()/Proxy来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调来渲染视图。
在Vue中,Dep是一个订阅器,它的作用是收集依赖(watcher)并在数据变化时通知它们进行更新。Dep中的subs存储的是所有依赖该Dep的watcher实例。Watcher是一个观察者,它的作用是当数据变化时,执行相应的回调函数。Watcher中的deps存储的是该watcher实例所依赖的所有Dep实例。Dep.target是一个静态属性,它的值为当前正在计算的watcher实例,该值是在watcher实例计算之前被赋值的。
回答:
- 依赖追踪:当
computed属性被访问时,Vue 会记录所有依赖于该computed属性的观察者(Watcher)。 - 缓存策略:
computed属性会缓存其计算结果。只有当依赖的数据发生变化时,才会重新计算。这是通过Watcher实例的dirty属性来控制的。只有当dirty为true时,才会调用evaluate方法进行计算。 - 惰性求值:
computed属性在首次访问时才会进行计算,之后会根据依赖数据的变化情况决定是否重新计算。
Detail:
- 使用
Object.defineProperty劫持数据,使其变为响应式数据。 - 创建
Watcher实例,用于依赖收集和更新通知。 - 通过
Dep实现依赖收集,当依赖数据变化时,触发computed属性的重新计算。
4. 手写debounce
function debounce(fn, delay) {
let timer
return function() {
clearTimeout(timer)
let args = arguments
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}
5. 手写 topK
leetcode.cn/problems/g5… 这个看 LC 官方解答就好