本文由 简悦 SimpRead 转码, 原文地址 mp.weixin.qq.com
(给三分钟学前端加星标,提升前端技能)
转自:掘金 - 孟祥_成都
前言
年前准备换工作,总结了一波面试最频繁的面试问题跟大家交流。此文章是关于浏览器的常见问题,大概面试 10 家遇到 6 家提问类似问题(主要是大厂和中厂)。(面试的部分内容已经忘了,为了串联成一个完整的故事,增加可读性,20% 的内容为虚构),目前入职滴滴出行成都团队。
问题: 从浏览器地址栏输入 url 到请求返回发生了什么
你一看这种烂掉牙的问题,小 case,但 996 面试大佬由此延展的问题已经远远超越了这个问题本身了,不信你就接着看。
我回答了首先会进行 url 解析,根据 dns 系统进行 ip 查找。
话音刚落,此时一位喜欢修福报的公司的大佬打断了我,说 url 为啥要解析,dns 查询规则是什么?我一听就心里想,不按套路出牌啊,网上一般都没问这两个问题,心里再一想,俗话说,万事开头难,扛过这一波,答出来,就是阳光明媚,万物骚动的春天!
先说为什么 url 要解析(也就是编码)
我回答大概内容是:因为网络标准规定了 URL 只能是字母和数字,还有一些其它特殊符号(-_.~ ! * ' () ; : @ & = + $ , / ? # [],特殊符号是我下来查的资料,实在背不住这么多,比较常见的就是不包括百分号和双引号),而且如果不转义会出现歧义,比如
http:www.baidu.com?key=value, 假如我的key本身就包括等于=符号,比如ke=y=value,就会出现歧义,你不知道=到底是连接key和value的符号,还是说本身key里面就有=。大佬接着毒打我说,那 url 编码的规则是什么呢,我说 utf-8
大佬接着穷追不舍,为啥是 utf-8 呢,所有浏览器都是这样吗?中文的话用 gb2312 编码吗,还有就是万一浏览器不是你说的这样统一用 utf-8,你怎么保证都是 utf-8 的编码?
我支支吾吾的说,我了解的大概是这样,不太清楚, 应该和 html 本身的编码格式有关,然后怎么保证 utf-8 的编码,我觉得可以用 encodeURIComponent
大佬说 encodeURIComponent 比 encodeURI 有什么区别?
区别就是 encodeURIComponent 编码范围更广,适合给参数编码,encodeURI 适合给 URL 本身(locaion.origin)编码, 当然项目里一般都是用 qs 库去处理
然后说说 dns 解析流程,并且 html 如何做 dns 优化
首先 dns 这个属于很久以前在计算机网络谢希仁版看到过了,有一些细节忘了,但是大致流程是记得的。比如说查询一个网址为:www.baidu.com
1、器中输入 https://www.baidu.com 域名,操作系统会先查 hosts 件是否有记录,有的话就会把相对应映射的 IP 返回。
2、hosts 文件没有就去查本地 dns 解析器有没有缓存。(这个我没答上来)
3、然后就去找我们计算机上配置的 dns 服务器上有或者有缓存,就返回
4、还没有的话就去找根 DNS 服务器 (全球 13 台,固定 ip 地址),然后判断. com 域名是哪个服务器管理,如果无法解析,就查找. baidu.com 服务器是否能解析,直到查到 www.baidu.com 的 IP 地址
注: 后面查资料才发现 dns 查询有两种模式,一种是转发模式,一种是非转发模式,我上面说的 4 是非转发模式。
前端的 dns 优化,可以在 html 页面头部写入 dns 缓存地址,比如
<meta http-equiv="x-dns-prefetch-control" content="on" /><link rel="dns-prefetch" href="http://bdimg.share.baidu.com" />终于抗过了第一轮的猛问,接着我继续说从浏览器地址栏输入 url 到请求返回发生了什么
查找到 IP 之后,就是 http 协议的三次握手(以及后面会涉及到四次分手)
我刚恢复节奏,准备侃侃而谈,修福报的大佬再次打断了我,说三次握手,为啥两次不行,顺便说一下 3 次握手发生了什么。
我去,大意了,没有闪,这是不是说我每说一句都要夹杂着各种问题,太难了啊!!!
没有办法,继续回答大佬,我说我先回答三次握手发生的事情吧,简答来说:
第一次握手:主机 A 发送位码为 SYN=1 的 TCP 包给服务器,并且随机产生一个作为确认号(这是 tcp 包的一部分),主机 B 收到 SYN 码后直到 A 要求建立连接;
第二次握手:主机 B 收到请求后,向 A 发送确认号(主机 A 的 seq+1),syn=1,seq = 随机数 的 TCP 包;
主机 A 收到后检查确认号是否正确,即第一次 A 发送的确认号是否 + 1 了,以及位码 ack 是否为 1,若正确,主机 A 会再发送确认号 (主机 B 的 seq+1),ack=1,主机 B 收到后确认 seq 值与 ack=1 则连接建立成功。
接着补上小问题为什么两次握手不行,因为第二次握手,主机 B 还不能确认主机 A 已经收到确认请求,也是说 B 认为建立好连接,开始发数据了,结果发出去的包一直 A 都没收到,那攻击 B 就很容易了,我专门发包不接收,服务器很容易就挂了。
接着,大佬说出个加分题,我看你不是科班出身,能答多少是多少。问题是, 从网卡把数据包传输出去到服务器发生了什么,提示我 OSI 参考模型
我一听,好嘛,这不是计算机网络的知识吗,幸亏之前看过书,但也是好久以前看过了,只能凭借自己的理解解答了。
我说,先从局域网把数据发送到公司的交换机(如果交换机没有缓存本地 mac 地址和 IP 地址的映射,此时会通过 ARP 协议来获得),交换机的好处是可以隔离冲突域(因为以太网用的是 CSMA/CD 协议, 这个协议规定网线上同一时刻只能有一台机器发送数据),这样就可以不仅仅同一时刻只有一台机器发送网络包了
然后交换机再将数据发送到路由器,路由器相当于公司网关(我们公司小),路由器具有转发和分组数据包的功能(路由器通过选定的路由协议会构造出路由表,同时不定期的跟相邻路由器交换路由信息),然后这算是经过了物理层,数据链路层(以太网), 开始到网络层进行数据转发了
然后路由器转发 IP 数据报,一般公司的 IP 地址都会经过 NAT 转换,让内网的 ip 也能够访问外网,我们公司我注意了一下是 192.168 打头的内网 ip 地址。通过路由器的分组传输,所有数据到达服务器。
然后服务器的上层协议传输层协议开始发挥作用,根据 tcp 包里的端口号,让服务器特定的服务来处理到来的数据包,并且 tcp 是面向字节流的 (tcp 有四大特性,可靠传输、流量控制、拥塞控制、连接管理),所以我们 node 的 request 对象,它的监听事件 data 事件为什么要用字符串一起拼接起来呢(buffer),就是因为 tcp 本身就是字节流,request 对象使用的 data(http 层面)是 tcp 传来的数据块。
最后数据由传输层转交给应用层,也就是 http 服务(或者 https),后端经过一系列逻辑处理,返回给前端数据。
答完这里,我说大佬我只知道大概的流程,具体细节我不是很清楚,但自己后面会补上。。。
大佬让我继续,我就接着 3 次握手之后接着说道,建立完链接,就该请求 html 文件了,如果 html 文件在缓存里面浏览器直接返回,如果没有,就去后台拿
刚说到缓存,立马就有一种不详的预感,果不其然大佬先让把缓存解释一下。缓存这种问烂的问题,本以为能轻松应对,结果还是被问了个满头包。。。。
我说的大概意思是:
浏览器首次加载资源成功时,服务器返回 200,此时浏览器不仅将资源下载下来,而且把 response 的 header(里面的 date 属性非常重要,用来计算第二次相同资源时当前时间和 date 的时间差) 一并缓存;
下一次加载资源时,首先要经过强缓存的处理,cache-control 的优先级最高,比如 cache-control:no-cache, 就直接进入到协商缓存的步骤了,如果 cache-control:max-age=xxx, 就会先比较当前时间和上一次返回 200 时的时间差,如果没有超过 max-age,命中强缓存,不发请求直接从本地缓存读取该文件(这里需要注意,如果没有 cache-control,会取 expires 的值,来对比是否过期),过期的话会进入下一个阶段,协商缓存
协商缓存阶段,则向服务器发送 header 带有 If-None-Match 和 If-Modified-Since 的请求,服务器会比较 Etag,如果相同,命中协商缓存,返回 304;如果不一致则有改动,直接返回新的资源文件带上新的 Etag 值并返回 200;
协商缓存第二个重要的字段是,If-Modified-Since,如果客户端发送的 If-Modified-Since 的值跟服务器端获取的文件最近改动的时间,一致则命中协商缓存,返回 304;不一致则返回新的 last-modified 和文件并返回 200;
果不其然,大佬问了一些缓存不常问的,首先就是问我知道什么是 from disk cache 和 from memory cache 吗,什么时候会触发?
- 我说强缓存会触发,这两种,具体什么行为不知道, 大概内容如下:
1、先查找内存,如果内存中存在,从内存中加载;
2、如果内存中未查找到,选择硬盘获取,如果硬盘中有,从硬盘中加载;
3、如果硬盘中未查找到,那就进行网络请求;
4、加载到的资源缓存到硬盘和内存;接着大佬又问知道什么是启发式缓存吗,在什么条件下触发?
这个问题给我的感觉就两个字,懵逼!然后如实回答不知道。(查了下资料大概如下)
启发式缓存:
如果响应中未显示 Expires,Cache-Control:max-age 或 Cache-Control:s-maxage,并且响应中不包含其他有关缓存的限制,缓存可以使用启发式方法计算新鲜度寿命。通常会根据响应头中的 2 个时间字段 Date 减去 Last-Modified 值的 10% 作为缓存时间。
// Date 减去 Last-Modified 值的 10% 作为缓存时间。
// Date:创建报文的日期时间, Last-Modified 服务器声明文档最后被修改时间
response_is_fresh = max(0,(Date - Last-Modified)) % 10接着回答,我说返回 html 之后,会解析 html, 这部分知识我提前准备过,但是答的不是很详细,大概意思就是 cssom + domTree = html, 然后布局和绘制
构建 DOM 树 (DOM tree):从上到下解析 HTML 文档生成 DOM 节点树(DOM tree),也叫内容树(content tree);
构建 CSSOM(CSS Object Model) 树:加载解析样式生成 CSSOM 树;
执行 JavaScript:加载并执行 JavaScript 代码(包括内联代码或外联 JavaScript 文件);
构建渲染树 (render tree):根据 DOM 树和 CSSOM 树, 生成渲染树 (render tree);
渲染树:按顺序展示在屏幕上的一系列矩形,这些矩形带有字体,颜色和尺寸等视觉属性。
布局(layout):根据渲染树将节点树的每一个节点布局在屏幕上的正确位置;
绘制(painting):遍历渲染树绘制所有节点,为每一个节点适用对应的样式,这一过程是通过 UI 后端模块完成;
接着面试官问我一些页面渲染层的一些优化手段,大概如下:
页面渲染优化
HTML 文档结构层次尽量少,最好不深于六层;
脚本尽量后放,放在前即可;
少量首屏样式内联放在标签内;
样式结构层次尽量简单;
在脚本中尽量减少 DOM 操作,尽量缓存访问 DOM 的样式信息,避免过度触发回流;
减少通过 JavaScript 代码修改元素样式,尽量使用修改 class 名方式操作样式或动画;
动画尽量使用在绝对定位或固定定位的元素上;
隐藏在屏幕外,或在页面滚动时,尽量停止动画;
尽量缓存 DOM 查找,查找器尽量简洁;
涉及多域名的网站,可以开启域名预解析
最后面试官问我,如何诊断页面渲染时各个性能指标,我大概说了,通过 chrome 浏览器的工具,比如看网络请求情况的 network,还有看页面渲染情况的 perfermance,以后有机会自己总结一篇。
最后
欢迎关注「三分钟学前端」,回复「交流」自动加入前端三分钟进阶群,每日一道编程算法面试题(含解答),助力你成为更优秀的前端开发!
》》面试官也在看的前端面试资料《《
“在看和转发” 就是最大的支持