Obeta

我遇到的Web前端有难度的面试题

最近转移阵地到了成都,因此一个多月都没有更新博客,期间面试了多家公司,记录一下遇到的一些比较有深度的问题,也许以后用得着

大部分公司问的问题都比较广,根据你简历的内容来提问.非常多的问到了这类问题:最近你有没有遇到一些比较有难度的问题或者职业生涯中遇到过,你是怎么解决的?

这类问题其实挺多的,但是很多一下子想不起来,因为没有那么特殊到让你终生难忘的一些问题,不过还是有比较难点的问题,描述一下然后讲一下你的解决方案,就差不多了.有些公司问的并不深入,有些又面试的比较详细,没有特别难的(自我感觉).

有时候面试的东西是已经很久没有看而忘的差不多的知识点,比如 http 协议缓存(问到很细),数据结构和算法等等...这类好久没有看了,又一次就被一家公司 CTO 问了一遍才知道自己这些地方还不够好,回去后把这类复习了一遍记录到此博客中.虽然感觉很尴尬,不过我觉得一点都不亏,如果我没有被这么问过,那么我是不知道我这块需要捡一下的.

同时前几天也去了 ThoughtWorks 面试体验了一下,过程挺独特的,网上有人把这类面试称之为“史上最难面试”,其实我觉得吹的太过了,ThoughtWorks 偏好 cool 一点的开发者,比如有自己独立经营的博客,有自己的一些技术想法,热爱这个行业等等,跟我前司Ricequant类似,如果有人在深圳想去这类公司可以给我发简历,我可以内推一下 Ricequant 滴(虽然离职了没内推奖金拿哈哈).至于 TW 的结果就先等着了,我在这次面试中给自己的分数 80%吧,基本达到了自己的预期,他们要走流程讨论,估计三天内出结果.

后续:第二天就出了结果,hr 给我电话说过了面试,但是成都分部没有坑位了,需要等总部批(最近的一批名额用完了),最近的要等一两个月,等到有坑位了会第一时间联系我...虽然感觉挺失望的,不过至少说得到了认可.如果入了 TW 坑我再来更新博客.

2019-07-03 更新,已经入职 ThoughtWorks 三天了,感受颇多,活跃的 email 技术讨论,各种活动可以参加等等,也发现自己需要提升的空间还非常大,正在努力适应中~

encodeURIComponent

对统一资源标识符(URI)的组成部分进行编码的方法.它使用一到四个转义序列来表示字符串中的每个字符的 UTF-8 编码(只有由两个 Unicode 代理区字符组成的字符才用四个转义字符编码,比如汉字会被它转成三个转义字符)

类似的还有escape,encodeURI,那么为什么会出现这些方法呢?这是因为 RFC 的一个 1738 协议规定了 URL 只能使用英文还一些特殊的符号,其它的字符则需要进行转义,而协议没有规定如何编码其它字符,因此不同的浏览器不同的系统可能处理都不一样,比如对于汉字来说,不同的编码方式得到的结果是不一致的,因此为了兼容这种混乱,浏览器提供了上面这三种方法.

escape主要用于编码汉字,不能用于编码 URL,返回的结果是 Unicode 编码,并且不对+编码. encodeURI对整个 URL 编码,不编码特殊符号;/?:@&=+$,# encodeURIComponent对部分 URL 编码,通常是 URL 中带参数的部分,编码特殊符号;/?:@&=+$,#

常见 web 攻击

  1. XSS攻击:跨站脚本攻击,攻击者使一个页面加载一些恶意的 javascript 或者其他链接,诱导用户点击,防御重点在于过滤用户的输入,可以使用上面的encodeURIComponent进行转码.此攻击分为存储型(数据库中),反射型(URL 中),DOM 型(URL 中,前端责任)

反射型 XSS,当用受害者被引诱点击一个恶意链接,提交一个伪造的表单,恶意代码便会和正常返回数据一起作为响应发送到受害者的浏览器,少量用户受到攻击.一类是储存型 XSS,也就是持久型 XSS.注入的脚本永久的存在于目标服务器上,每当受害者向服务器请求此数据时就会重新唤醒攻击脚本,攻击范围是大多数用户;一类是 DOM 型 XSS, 有点类似于存储型 XSS.但存储型 XSS 是将恶意脚本作为数据存储在服务器中,每个调用数据的用户都会受到攻击.但 DOM 型 XSS 则是一个本地的行为,更多是本地更新 DOM 时导致了恶意脚本执行.

可以试试这个游戏,尝试使用 XSS 攻击.

X-XSS-ProtectionContent Security Policy(CSP,访问源白名单)可以进行一些限制防范:

X-XSS-Protection: 1; mode=block Content-Security-Policy: <polic></polic>

还有对用户输入进行过滤:

function encodeInput(val) {
	switch (val) {
		case '&':
			return '&amp;';
		case '<':
			return '&lt;';
		case '>':
			return '&gt;';
		case '"':
			return '&quot;';
		case ' ':
			return '&nbsp;';
		default:
			return val + '';
	}
}
  1. CSRF:跨站请求伪造,当用户登录 A 网站后,攻击者诱导此用户来到危险网站 B 发起针对 A 网站的攻击,利用的就是 A 网站对此用户的信任来攻击.与 XSS 攻击不同的是 XSS 利用的是站内受信任用户,CSRF 则是通过伪装成受信任用户.

一般来说 CSRF 的防御点有这些:关键请求使用验证码;表单提交这里增加一些 token 验证;验证 Request Header 中的 Referer 来源;修改数据请求用 POST;使用 custom request header 等等.

  1. DDos:分布式拒绝服务,攻击者使用大量的"肉鸡"制造大量的非法请求到目标服务器,使目标服务器无法响应正常的用户请求.是一种目前非常强大以及难以防御的攻击方式.此类攻击利用了计算机网络中各层的机制,比如 TCP 连接中需要三次握手,那么可以攻击者可以伪造 IP 发送大量的握手请求过去,造成目标服务器维护大量的半连接列表占用大量的机器资源而瘫痪.

HTTP 缓存策略

缓存是一种非常有效提高资源利用率的手段,大幅度减少请求次数以及传输资源所需要的时间.HTTP 中的资源缓存手段包括了强缓存和协商缓存.

  1. 强缓存相关的 Header 字段:expirescache-control.

这两个值是第一次请求资源 Response 中 header 里所包含的.expires是 http1.0 中使用的一个 GMT 绝对时间字符串,比如值为Mon, 10 Jun 2020 15:11:42 GMT说明此资源在明年六月 10 号之前都是有效的.

cache-control则是 http1.1 时候增加的,相对比与expires的绝对值,它是一个相对值.比如cache-control: max-age=604800说明了当从此刻开始算 604800 秒后此资源过期.解决了expires中客户端与服务器时区不一致发生的 bug.

ceche-control有许多的配置,具体查看 MDN 文档,这里介绍常见的几个:

Cache-control: must-revalidate # 缓存必须在使用之前验证旧资源的状态,并且不可使用过期资源
Cache-control: no-cache # 不使用本地缓存,也就是每次发送304查询,跳过强缓存直接去协商
Cache-control: no-store # 禁用缓存,每次下载完整资源
Cache-control: no-transform # 不得对资源进行转换或转变
Cache-control: public # 资源可以被代理服务器以及客户端缓存
Cache-control: private # 资源只能被客户端缓存
Cache-control: proxy-revalidate # 与must-revalidate作用相同,但只对代理有效(private的时候失效)
Cache-Control: max-age=<seconds> # 设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒)
Cache-control: s-maxage=<seconds> # 覆盖max-age或者Expires头,但是仅适用于代理(private时候它被忽略)

cache-controlexpires同时存在的话,cache-control的优先级高于expires

  1. 协商缓存相关 Header:Last-Modified/If-Modified-Since,Etag/If-None-Match

当第一次请求资源后会检查 Response Header 中的强缓存与协商缓存相关的 Header 并记录下来.之后的请求都会先检查强缓存,若符合强缓存则直接应用缓存(不会发送请求至服务器),否则进入协商缓存向服务器询问资源状态(带上协商缓存相关的 Header).

获取资源形式状态码发送请求到服务器
强缓存从缓存取200(from cache)否,直接从缓存取
协商缓存从缓存取304(not modified)是,询问服务器缓存是否可用

若第一次请求资源 Response Header 中没有相关的协商缓存 Header 字段EtagLast-Modified,那么协商缓存阶段不会发送配对的If-None-MatchIf-Modified-Since,也就是有哪个就带上哪个,都没有就都不带上.

Etag是为了修复Last-Modified的部分缺点:1.文件生成时间的周期性变化,但是内容不一定变化 2.短时间内变化多次的文件(比如一秒内).当它们同时存在发送到服务器时候,服务器优先判断Etag.我们可以通过command+R强制刷新使浏览器忽略上面的两种缓存,直接去获取最新的资源,而简单的F5只是让强缓存失效.

目前单页应用首页建议增加如下的 Response Header:

cache-control: private, max-age=0, no-store, no-cache, must-revalidate,
proxy-revalidate strict-transport-security:
max-age=31536000;includeSubDomains;preload x-xss-protection: 1; mode=block

HTTP 版本

一般来说有以下几点提问:

  1. http1 与 http1.1 有什么区别
  2. http1.1 与 http2 有什么区别
  3. https 如何保证安全

回答:

  1. http1.1 增加了更好的缓存控制,在 http1 的时候主要使用Last-Modified,expires控制,http1.1 的时候增加了Etag,cache-control;增加了带宽优化,请求头引入了 range 头域,它允许只请求资源的某个部分,即返回码是 206(Partial Content),方便开发者自由的选择以便于充分利用带宽和连接;增加了一些错误状态码;host 头支持;支持长连接,在一个 tcp 链接上传输多个 http 请求和响应.
  2. http2 使用二进制格式,之前基于文本格式的解析非常复杂;多路复用,不同于 http1.1 中的长连接复用一个 tcp 链路串行请求会导致阻塞,http2 复用 tcp 链路不会导致阻塞,不同的 request 有不同的 id,多个请求并行执行;http2 压缩了 header 中的信息;http2 支持服务端推送,比如请求首页的时候服务器直接把接下来的静态资源一起发送过去,不用客户端再次请求,提升速度;http2 目前在主流浏览器中只能使用 https;
  3. https 需要申请 CA 证书;http 协议在 tcp 协议之上,https 则是在 ssl/tls 之上,然后才是 tcp,也就是经过加密后在 tcp 上传输;端口不一样,http 是 80,https 是 443;https 防止运营商劫持数据

HTTP 三次握手四次挥手

  1. 握手

平常生活中我们打招呼其实包含了两种器官的功能:耳朵的听和嘴巴的说.那么沟通的前提就是 A 与 B 需要拥有耳朵和嘴巴,而握手的过程就是证明过程.

A 跟 B 打招呼:你好吗(A 证明了自己有嘴巴),这是第一次握手 B 收到了 A 的信息后立即回应:我很好,你呢?(B 证明了自己有耳朵和嘴巴),这是第二次握手 A 收到 B 的回应后再回应 B:我也很好(A 证明了自己有耳朵),这是第三次握手

三次握手
三次握手

  1. 挥手

想象你跟你基友在睡觉前聊天聊地,最后你撑不住了,打算告诉他你要准备睡了:

A 告诉 B:我累了,已经很晚了吧,明天还要早起 xxx 呢(A 这时候还是能接受 B 的消息) B 听了 A 的话就理解了 A 这是要准备睡觉的节奏,但是 B 可能还在正兴头上呢,想也没想的就回应 A:好,我知道了,等我说完先 B 说完后发消息给 A:我也累了,准备睡觉吧 A 收到想要的回答后回应 B 说:好,准备睡吧

上面关键字我累了其实是 FIN 报文段,表示数据传送完成,没有数据可以传送了.而代表了 ACK 报文段.由于 TCP 是全双工的,因此整个过程其实就是关掉各方的,功能步骤:A 发送 FIN 后 B 确认了,说明 A 关掉了数据功能,B 关掉了数据功能,而当 B 发 FIN 后 A 确认了,说明 B 关掉了数据功能,A 关掉了数据功能,下面是正常的流程.

假设 Client 端发起中断连接请求,也就是发送 FIN 报文:"我 Client 端没有数据要发给你了",如果你还有数据没有发送完成就继续发送数据",然后 Client 进入 FINWAIT1 状态.于是 Server 端接到 FIN 报文后,立即发送 ACK 告诉 Client 端:"你的请求我收到了,我可能会继续发送数据,当我准备好后再告诉你".Client 端接受到 ACK 后就进入 FINWAIT2 状态,继续等待 Server 端的 FIN 报文.当 Server 端确定数据已发送完成,则向 Client 端发送 FIN 报文告诉 Client 端:"好了,我这边数据发完了,准备好关闭连接".Client 端收到 FIN 报文后,就知道可以关闭连接了,但是他还是不相信网络,怕 Server 端不知道要关闭,所以发送 ACK 后进入 TIME_WAIT 状态,如果 Server 端没有收到 ACK 则可以重传.Server 端收到 ACK 后就知道可以断开连接了.Client 端等待了 2MSL 后依然没有收到回复,则证明 Server 端已正常关闭,那好,我 Client 端也可以关闭连接了.

其中MSL是 Maximum Segment Lifetime(最大报文生存时间)的缩写,超过这个时间的报文都会被丢弃,RFC 793 中规定 MSL 为 2 分钟,实际应用中常用的是 30 秒,1 分钟和 2 分钟不等.2MSL即两倍的 MSL,TCP 的 TIME_WAIT 状态也称为 2MSL 等待状态.

其它具体的可以看这篇文章通俗大白话来理解 TCP 协议的三次握手和四次分手

引用

个人随笔记录,内容不保证完全正确,若需要转载,请注明作者和出处.