说在前面:
- 本面试题是本人在工作和找工作中总结出来的面试题,主要用于上家的新人招聘。因此:
- 问的问题与业务关联程度比较高,更偏向于前端基础、性能优化、故障定位等。
- 上家要招的职级并不是很高,因此面试题难度不算高,大致相当于前端1-3年经验的水平
- 部分与我另一篇博客相同或类似,但毕竟随着成长以及技术的发展重视的东西也在改变。
- 答案仅供参考,部分答案待补充
基础篇
语义化
问:HTML语义化是什么意思?有什么作用?
参考答案:
简单的来说,语义化就是让该做某件事的东西来做那件事。比如,HTML中的各级标题用H1等表示。同样的例子还有header,footer,site等标签。
作用:
- 首先是对维护者友好,维护你代码的人,能通过你的HTML代码轻松理解你的意图;
- 其次是对搜索引擎友好,搜索引擎不会抓取你的CSS属性,所以,语义化能让搜索引擎更好的抓到你想表达的东西,更容易让搜索引擎理解你的网站架构;
- 另外就是对用户友好,当然大部分用户都只是用眼睛看你的网站,所以可以通过CSS样式来达到这个目的。但是,盲人是没法看到的,他们只能通过辅助设备来实现,但同样的,这些设备只能识别语义化的HTML。
HTTP状态码
提问1:HTTP常见的状态码有哪些?分别表示什么意思?
参考答案1:(以下必答。能讲到301、405、503等更佳)
- 200:成功
- 302:重定向
- 304:未修改
- 403:服务器禁止访问
- 404:找不到请求的资源
- 500:服务端错误
提问2: HTTP状态码分为哪五类?各表示什么意思?
- 1xx:信息获取
- 2xx:成功
- 3xx:重定向
- 4xx:错误访问
- 5xx:服务端错误
提问3:HTTP状态码中,4xx和5xx有什么不同?
- 4**是请求错误,例如未经授权的请求(403),错误的请求地址(404),错误的请求方法(405)
- 5**是服务端错误,例如脚本运行出错(500)
缓存
提问: 说一下浏览器缓存策略
参考答案:讲到浏览器本地缓存、根据Last-Modified和Etag去请求判断是否304就差不多了。能讲到缓存/代理服务器、cdn更佳。
- 请求资源前,先查看缓存中是否有未过期且未修改的相同资源,如果有,直接在缓存中获取而不是向服务器索求;如果没有,并且服务器允许缓存,则将资源缓存在本地。
- 相关字段及作用:
- Expires:服务器允许浏览器在这个时间前使用该资源缓存
- Cache-control:作用和Expires类似,但优先级更高,且可选值更多。值可以是public、private、no-cache、no-
store、no-transform、must-revalidate、proxy-revalidate、max-age。其中,max-age值最常用。
- Public指示响应可被任何缓存区缓存。
- Private指示对于单个用户的整个或部分响应消息,不能被共享缓存处理。这允许服务器仅仅描述当用户的部分响应消息,此响应消息对于其他用户的请求无效。
- no-cache指示请求或响应消息不能缓存
- no-store用于防止重要的信息被无意的发布。在请求消息中发送将使得请求和响应消息都不使用缓存。
- max-age指示客户机可以接收生存期不大于指定时间(以秒为单位)的响应。
- min-fresh指示客户机可以接收响应时间小于当前时间加上指定时间的响应。
- max-stale指示客户机可以接收超出超时期间的响应消息。如果指定max-stale消息的值,那么客户机可以接收超出超时期指定值之内的响应消息。
- Last-Modified / If-Modified-Since:配合Cache-Control使用。
- Last-Modified:标示这个响应资源的最后修改时间。web服务器在响应请求时,告诉浏览器资源的最后修改时间。
- If-Modified-Since:当资源过期时(使用Cache-Control标识的max-age),发现资源具有Last-Modified声明,则再次向web服务器请求时带上头
If-Modified-Since,表示请求时间。web服务器收到请求后发现有头If-Modified-Since则与被请求资源的最后修改时间进行比对。若最后修改时间较新,说明资源又被改动过,则响应整片资源内容(写在响应消息包体内),HTTP 200;若最后修改时间较旧,说明资源无新修改,则响应HTTP 304 (无需包体,节省浏览),告知浏览器继续使用所保存的cache。
- Etag /
If-None-Match:也要配合Cache-Control使用。注意,Etag优先级比Last-Modified高,服务器会优先比对Etag。
- Etag:web服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器觉得)。Apache中,ETag的值,默认是对文件的索引节(INode),大小(Size)和最后修改时间(MTime)进行Hash后得到的。
- If-None-Match:当资源过期时(使用Cache-Control标识的max-age),发现资源具有Etage声明,则再次向web服务器请求时带上头If-None-Match
(Etag的值)。web服务器收到请求后发现有头If-None-Match 则与被请求资源的相应校验串进行比对,决定返回200或304。
性能
提问:首页白屏时间如何判断?
答案:三种场景都可近似模拟,但越标准越接近最正确答案。不同标准对应不同水平
答案1:window.onload事件中使用 requestAnimationFrame 函数,用当前时间减去 navigationStart。具体代码:
window.onload = () => {
requestAnimationFrame(() => {
const time = Date.now() - window.performance.timing.navigationStart;
console.log(time);
})
}
答案2:windows.onload中直接使用当前时间减去 navigationStart。
答案3:js末尾执行时间减去第一个js执行时间
如果回答了答案3,可以追问:
追问1:第一个js执行前还有哪些时间是没考虑进去的?
- 浏览器解析时间
- DNS查询时间
- 重定向时间
- HTML下载时间
- 等等。言之有理即可
追问2:js末尾执行时间在什么情况下会出现不准确情况?
- 异步函数比较多的时候。例如接口,或者事件回调
- 其他有理答案
提问2:如何减少这个时间?
- 参照后面进阶性能优化问题的答案,基本通用
- 后端首屏直出(对react和vue则是同构)
- 其他有理答案
https
提问1:https有什么作用?
参考答案:
服务端客户端双向认证,防止数据被窃听或篡改。
说浏览器有安全锁让用户放心也算,但不是主要。
提问2:怎么实现这些作用的?
参考答案:
在http层之外增加了一层TLS层,对数据进行加解密。客户端服务端生成对称加密公私钥,交换公钥,用自己的私钥解出内容。中间人无法得到双方公钥,故而无法加解密,也就无法窃听和篡改。
提问3:有什么弊端?
参考答案:
1. 握手次数增加,计算需要时间,导致响应时间变长
2. 客户端服务端都需要计算公私钥,和加解密,需要消耗大量的资源(尤其是服务端)
3. 服务端的并发数减少(由前两点得出的结论)
4. 其他有理有据的答案
提问4:如何优化?
参考答案:
- 配置服务器,缩短握手轮数
- 升级成http2.0
- 其他有理有据的答案
http2
提问:http2有什么优化点?
参考答案:
- 头部压缩——减少包大小(对带有大量cookie的http请求尤为有效)
- 服务端推送(减少HTTP请求数)
- 管道复用(同域请求只需要一遍三次握手和一次慢启动,大大减少响应时间)
网络安全
提问:有哪些常见的网络攻击方式?攻击手段具体是怎样的?如何防御?(社工手段不考虑)
- csrf
- 攻击手段:浏览器在访问一个域的时候,会自动带上这个域的cookie,从而在用户不知情的情况下伪造用户完成敏感操作
- 防御手段
- Referer白名单判断
- 全站post
- CORS策略控制
- csrf token
- xss
- 攻击手段:提交一段js代码,或者欺骗用户点击带有js代码的链接,从而获取用户信息或伪造用户进行操作
- 防御手段
- 输入的表单进行标签转义
- 输出到页面时过滤标签
- 去除jsonp
- DDos
- 攻击手段:制造大量无意义请求,使服务器无暇处理真正的请求
- 防御手段:
- 调整服务器配置,提升服务器并发容量
- 对服务器添加防刷模块,对恶意请求ip的请求进行过滤
- 应用对请求进行识别,无意义请求直接终止
- 其他有理有据答案,如增加服务器
前端进阶
请求
提问:前端发起一个请求,从发起请求到返回结果发生了什么?
答案:不要求全答,但越详细越好。可看情况追问
- 浏览器
- 判断链接是否合法
- 将链接解析成URI各个部分,如协议,域名,端口,路径,参数,锚点等
- DNS
- 如果请求的域名不是ip形式,需要去查找该域名的DNS信息
- 查找路径为 浏览器缓存-主机-DNS服务器-更上层DNS服务器……找到即返回,没找到则直到最顶部DNS服务器,再找不到则返回报错
- 查找的方式为所处局域网内广播寻找
- 握手
- TCP三次握手四次挥手
- TLS握手、协商密钥
- 包传递
- HTTP、TLS、TCP、IP等各层依次封包
- 路由、交换机寻址
- 慢启动、拥塞避免、快速重传、快速恢复
- 网络节点
- 正向代理服务器
- 缓存服务器
- 反向代理服务器
- CDN
- 源站
- 服务器处理
- 路径寻找
- 应用处理
- 返回数据
- 原路返回
埋点
提问1:有什么埋点方案?
参考答案:
1. 被动埋点——所有节点统一加事件委托,用户点击(或其他交互)节点都会发送带有相应信息的埋点
2. 主动埋点——需要时再调用埋点发送函数
3. 自动埋点——检测事件,如onload等事件(一般用于pv、uv的统计)
提问2:各有什么优缺点?
提问3:如何实现埋点?
性能优化
提问1:前端常用的性能优化方案有哪些?越多越好。
参考答案:
- 压缩(js、css、图片、混淆)
- gzip压缩
- 合并文件(如雪碧图)
- data image
- 异步加载(js、css)
- 懒加载(图片)、预加载
- 其他有道理的优化方案
提问2:以上方案可能会带来什么问题
- 压缩(js、css、图片、混淆)
- 图片压缩之后导致模糊
- 追问:如何取舍;
- 尽量无损压缩;
- 控制压缩率[一般60%-70%]);
- 追问:如何取舍;
- 混淆之后线上难以定位问题
- 图片压缩之后导致模糊
- gzip压缩
- 服务器CPU压力
- 合并文件
- 原子性变差(改一处地方得更新整个大文件)
- 如何优化
- dll包(或common chunk),即公共部分或极少改动部分单独打包
- 如何优化
- 原子性变差(改一处地方得更新整个大文件)
- data image
- 无法缓存;
- 客户端CPU压力
- 增加包体积
- 异步加载(js、css)
- 异步加载过程中页面交互可能不及时
- 懒加载(图片)
- 用户等待时间加长(可用默认图片或加载图提升用户体验)
- 其他有道理的优化方案
框架
提问1:虚拟DOM的实现原理
提问2:vue和react有何不同
代码题
安全加固
已知jq的ajax函数,对其进行csrf和重放攻击安全加固。(假设后端可以满足所需任意要求)
// 源码已做脱敏处理
import Cookies from 'js-cookie';
// 客户端和服务端的时间偏差
let timeOffset = 0;
const timeOffsetCookieKey = 'timeOffset',
timeCookieKey = 'time',
csrfCookieKey = 'csrfToken';
// 毫秒转成秒
function getSecondTimestamp () {
return Math.floor(Date.now() / 1000)
}
if (Cookies.get(timeOffsetCookieKey)) {
timeOffset = +Cookies.get(timeOffsetCookieKey);
} else if (Cookies.get(timeCookieKey)) {
timeOffset = Cookies.get(timeCookieKey) - getSecondTimestamp();
Cookies.set(timeOffsetCookieKey, timeOffset);
}
// 获取服务端的实时时间计算偏差,假设接口返回了服务端时间
$.ajax({
url: '//www.test.com/t',
success: function (timestamp) {
timeOffset = timestamp - getSecondTimestamp();
Cookies.set(timeOffsetCookieKey, timeOffset);
}
})
// 自定义加密函数。可用加盐哈希,或其他加密方式。此处涉敏不列
function encrypt () {
}
// 生成随机串
function getRandStr (data) {
return data
? data.toString(16)
: Math.random().toString(16).replace('0.', '');
}
function generateRandStr (length) {
let randStr = getRandStr() + getRandStr(Date.now())
while (randStr.length < length) {
randStr += getRandStr()
}
return randStr.substr(0, length)
}
// 生成签名
function generateSign (data) {
const keyArr = Object.keys(data)
keyArr.sort()
let signStr = '';
for (let i = 0; i < keyArr.length; i++) {
// undefined会导致生成串错误
if (typeof(data[keyArr[i]]) !== 'undefined') {
signStr += keyArr[i] + data[keyArr[i]];
}
}
return encrypt(signStr)
}
const fn = jQuery.ajax;
jQuery.ajax = (arg) => {
const setting = Object.assign({}, arg);
setting.data = setting.data || {};
setting.type = setting.type || 'get';
const queryUrl = setting.url || ''
// csrf token
const csrfToken = Cookies.get(csrfCookieKey);
if (csrfToken) {
setting.data[csrfCookieKey] = csrfToken;
}
if (setting.dataType !== 'script') {
// 全站POST,原先的请求类型放到参数中
setting.data.method = setting.type
setting.type = 'post';
setting.data.nonce = generateRandStr(32);
setting.data.timestamp = getSecondTimestamp() + timeOffset;
setting.data.sign = generateSign(setting.data);
setting.dataType = '';
// 添加withCredentials支持
setting.xhrFields = {
withCredentials: true
};
setting.beforeSend = function (xhr) {
xhr.withCredentials = true;
}
}
fn(setting);
};
lazyman实现
实现一个LazyMan,可以按照以下方式调用:
LazyMan(“Hank”)输出:
Hi! This is Hank!
LazyMan(“Hank”).sleep(10).eat(“dinner”)输出
Hi! This is Hank!
//等待10秒..
Wake up after 10
Eat dinner~
LazyMan(“Hank”).eat(“dinner”).eat(“supper”)输出
Hi This is Hank!
Eat dinner~
Eat supper~
LazyMan(“Hank”).sleepFirst(5).eat(“supper”)输出
//等待5秒
Wake up after 5
Hi This is Hank!
Eat supper
以此类推。
参考答案:见LazyMan