☰
前端面试核心链路:TCP握手、浏览器缓存、渲染与CSS布局
2026/11/3 4:58:19 网站建设 项目流程

简介:这份PDF资料面向前端初学者与有一定经验的开发者,系统梳理面试中高频出现的基础知识点,帮助读者在准备面试或团队培训时快速补齐网络与页面构建方面的短板。内容覆盖HTTP与HTTPS的区别及SSL加密工作原理、TCP三次握手与四次挥手、TCP与UDP差异、数据包有序可靠传输机制、跨域成因与JSONP、代理等解决思路,并延伸到粘包问题、缓存机制、页面加载全过程与性能优化技巧;DOM操作、CSS盒模型与BFC、Flex、Rem、百分比及浮动等布局方案也有专门解析,配有实例讲解。压缩包仅含1个PDF文件,约1020KB,单文件结构便于通读与随时检索。该资源已有686人学习,适合作为面试前的速查手册与日常开发中的知识对照材料。

1. 从一道追问开始:为什么三次握手不能是两次

前端面试里真正拉开差距的,往往不是「HTTP 和 HTTPS 有什么区别」这种直球,而是紧跟着那句「那你说说为什么三次握手不能改成两次」。八股文背得滚瓜烂熟,一被追问因果就哑火,这是最常见的一幕。这场面试考的东西其实很集中:HTTP/HTTPS 和 TCP 撑起网络层,DOM 与 CSS 撑起渲染和布局层,中间夹着浏览器缓存、重排重绘、跨域这些高频串联点。拿分靠的不是名词数量,而是能不能把一条链路从头讲到尾,把每个设计选择的原因说出来。下面按考点模块拆,每一块都给能直接背、能直接练的答题骨架。

2. 网络层考点:HTTP/HTTPS 与 TCP 握手的必答结构

网络题在前端面试里的出场率极高,原因不在于它难,而在于它能把「背过」和「懂了」一眼分开。面试官问 HTTPS,不是想听「加密的 HTTP」,而是想看你能不能把证书、公钥、会话密钥这条换钥匙的链路说清楚;问三次握手,也不是想听状态名,而是想看你对「连接建立要解决什么问题」有没有概念。这一章把这条链路上的四个必考点摊开:HTTPS 的握手步骤、三次握手四次挥手的状态迁移、TCP 保证可靠的机制、以及 TCP 与 UDP 的取舍。

2.1 HTTP 与 HTTPS:六步链路和五条硬差异

HTTP 是客户端和服务器之间请求与应答的标准,建立在 TCP 之上,负责把超文本从服务器传到本地浏览器,特点是简单、无状态,默认端口 80。HTTPS 则是以安全为目标的 HTTP 通道,在 HTTP 和 TCP 之间加了一层 SSL,作用是建立信息安全通道,保证传输过程不被窃取和篡改、保证网站的真实性,默认端口 443。这段概念两句话说完就够,多说不加分。

真正要背熟的是握手六步,面试时按顺序讲,一步不落:

第一,客户端用 https 的 URL 访问服务器,要求与 Web 服务器建立 SSL 链接;第二,服务器收到请求后,把网站证书(证书里含公钥)传给客户端;第三,双方协商 SSL 链接的安全等级,也就是加密等级;第四,客户端按协商好的安全等级生成会话密钥,再用服务器公钥加密这个会话密钥,传回服务器;第五,服务器用自己的私钥解密,拿到会话密钥;第六,之后双方就用这个会话密钥加密通信内容。

这六步的底层逻辑只有一句:用非对称加密安全地换一把对称加密的钥匙,之后全程用对称加密跑数据。非对称加密安全但慢,对称加密快但钥匙没法裸传,所以拿它来传钥匙。把这句话补在六步后面,这道题就从及格变优秀。

差异和缺点要能一口气报出来,尤其是缺点部分,很多人只会说「HTTPS 更安全」,一问代价就卡壳:

对比项HTTPHTTPS
传输方式明文SSL 加密传输
默认端口80443
连接特性简单、无状态握手阶段费时
证书成本无需要 CA 证书,功能越强费用越高
缓存效率高不如 HTTP 高效,增加数据开销

HTTPS 的具体代价有四条:握手阶段比较费时,会使页面加载时间延长约 50%;增加约 10%~20% 的耗电;缓存不如 HTTP 高效,会增加数据开销;需要 CA 证书且费用较高,SSL 证书还需要绑定 IP,同一个 IP 上不能绑定多个域名,IPv4 资源支撑不了这种消耗。最后这条是加分项,能说出「证书要绑 IP」的候选人不多。

提示:回答「HTTPS 是否绝对安全」时不要拍胸脯说绝对安全,加一句「安全性并非绝对,证书信任链被攻破或用户忽略证书告警时同样有风险」,显得有边界感。

2.2 TCP 三次握手与四次挥手:状态名怎么串起来

三次握手的三个包,关键是把 SYN、ACK、seq、ack 四个符号的位置摆对。第一次握手,客户端发送 SYN 包(syn=j)到服务器,进入 SYN_SENT 状态,等待服务器确认,SYN 是同步序列编号。第二次握手,服务器收到 SYN 包并确认客户的 SYN(ack=j+1),同时自己也发一个 SYN 包(syn=k),也就是 SYN+ACK 包,此时服务器进入 SYN_RECV 状态。第三次握手,客户端收到 SYN+ACK 包,向服务器发送确认包 ACK(ack=k+1),发送完毕客户端和服务器都进入 ESTABLISHED 状态,连接建立完成。握手过程中传送的包里不包含数据,三次握手完毕后双方才正式开始传数据。

四次挥手的难点不在步骤,在状态名和「谁先关」。客户端进程发出连接释放报文并停止发送数据,FIN=1,序列号 seq=u(等于前面已传送数据的最后一个字节序号加 1),此时客户端进入 FIN_WAIT_1 状态;服务器收到释放报文后发出确认报文,ACK=1、ack=u+1,带上自己的 seq=v,进入 CLOSE_WAIT 状态,此时是半关闭状态——客户端没有数据要发了,但服务器若发数据,客户端依然要接收;客户端收到确认后进入 FIN_WAIT_2,等待服务器发连接释放报文;服务器把最后的数据发完,向客户端发连接释放报文,FIN=1、ack=u+1,假定此时 seq=w,服务器进入 LAST_ACK;客户端收到后必须发出确认,ACK=1、ack=w+1、自己的 seq=u+1,进入 TIME_WAIT,此时连接还没释放,必须经过 2MSL(最长报文段寿命)后才撤销 TCB 进入 CLOSED;服务器只要收到确认就立即进入 CLOSED,所以服务器结束连接的时间比客户端早一些。

记住一个规律:客户端多走一个 TIME_WAIT,服务器多走一个 CLOSE_WAIT,剩下两个 WAIT 各归一边。把它写成两行对照,考场上不会乱:

  • 客户端路径:FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED
  • 服务端路径:CLOSE_WAIT → LAST_ACK → CLOSED

TCP 保证数据包有序可靠,靠的是「分段编号 + ACK 回复 + 超时重发」三件套。发送方必须把已发送的数据包保留在缓冲区,并为每个包启动超时定时器;定时器超时前收到应答(可能是对本包的应答,也可能是对后续包的累计应答),就释放该包占用的缓冲区;否则重传,直到收到应答或超过最大重传次数。接收方收到包后先做 CRC 校验,正确则交给上层协议,并回一个累计应答包表明已收到,如果接收方正好也有数据要发,应答可以捎带在数据包里一起走。

TCP 和 UDP 的区别按四点答:TCP 面向连接,UDP 面向无连接;TCP 仅支持单播,UDP 支持单播、多播、广播;TCP 靠三次握手保证连接可靠性,UDP 无连接、不确认、不保证送达;UDP 头部开销更小、传输速率更高、实时性更好。

粘包是流式传输的固有现象:发送方发的若干包到达接收方时粘成一包,从接收缓冲区看,后一包的头紧接前一包的尾。UDP 不会粘包,因为它有消息边界。粘包有两种情况,一种是粘在一起的包都是完整包,另一种是粘在一起的包里有不完整的包。

常见三种对策各有短板:发送方用 push 指令强制立即发送,能避免发送方引起的粘包,但关闭了优化算法,降低发送效率,一般不建议;接收方优化程序设计、精简接收进程工作量、提高接收进程优先级,只能减少可能性,高并发时仍会来不及收;接收方按结构字段人为分多次接收再合并,虽然避免了粘包,但应用效率低,不适合实时场景。比较周全的做法是接收方建一个预处理线程,把收到的数据包先拆开再交给业务。

import struct def split_frames(buffer: bytes): """按「4 字节长度头 + 消息体」的协议格式切分粘包与半包""" frames = [] while len(buffer) >= 4: # 大端序解析长度头,约定头 4 字节,长度只算消息体 (body_len,) = struct.unpack(">I", buffer[:4]) if len(buffer) < 4 + body_len: break # 半包:数据还没收全,留着等下次 frames.append(buffer[4:4 + body_len]) buffer = buffer[4 + body_len:] # 已消费的部分从缓冲区移除 return frames, buffer # 完整包列表 + 剩余未成包的数据

这段代码体现的就是「长度前缀」协议:发送方每次都在消息体前写 4 字节的长度,接收方先读长度、再按长度切。参数上,">I"里的>表示大端序,I表示 4 字节无符号整数,收发两端必须约定一致,否则长度会解析成天文数字。break那一行是半包处理的核心——数据没到齐就中断循环、保留buffer,等下一批数据拼上来再继续,千万别丢弃。返回值拆成「已完成帧」和「残余缓冲」两部分,这样调用方可以无状态地反复喂数据。

2.3 真题演练:三个必问的握手与传输问题

题一:为什么是三次握手,不能是两次?

答题骨架是「一次不够、两次不够、三次刚好、四次浪费」。一次不够,因为客户端发请求、服务器确认、客户端再确认,只有三次才能让双方都确认「自己的发送能力和对方的接收能力」都正常。两次的问题在于:服务器发出 SYN+ACK 后无法确认客户端是否收到,网络上滞留的过期 SYN 包可能让服务器建立一条无人使用的连接,白占资源。四次则没必要,服务器把 ACK 和 SYN 合并在一个包里发出去,效率最高。这类题面试官最想听的是「防止历史连接」,把「过期 SYN 报文导致服务器空等」这个场景说出来,比背状态名有效得多。

题二:为什么挥手要四次,TIME_WAIT 为什么要等 2MSL?

四次的原因在于 TCP 是全双工的,两个方向要分别关闭。服务器收到 FIN 后可能还有数据没发完,所以先回 ACK 表示「我知道你要关了」,等自己的数据发完再发自己的 FIN,ACK 和 FIN 不能合并,于是多出一次。TIME_WAIT 等 2MSL 有两个作用:一是保证客户端最后发出的 ACK 能到达服务器,如果这个 ACK 丢了,服务器会重发 FIN,客户端还在 TIME_WAIT 状态下可以再补一个 ACK;二是让本次连接产生的所有报文段在网络中消散,避免影响之后复用同样端口的新连接。这两条说全,基本就是满分答法。

题三:一个实时对战游戏和一个文件下载,分别选 TCP 还是 UDP?

场景题不要直接答协议名,先答特征匹配。实时对战选 UDP:帧率优先、允许丢几帧但不能卡,UDP 无连接、头部开销小、实时性好;代价是丢包和应用层自己补,通常要在业务层做序列号和插值。文件下载选 TCP:完整性优先,丢一个字节文件就坏了,必须靠分段编号、ACK、超时重传保证有序可靠。补一句「现在很多实时场景会在 UDP 之上自己实现轻量可靠层」,比单纯二选一更有工程感。

3. 浏览器层考点:从输入 URL 到渲染的六道关卡

这一章是前端面试的主战场,因为它同时考网络、缓存、渲染和性能优化,一道题能问二十分钟。很多人栽在「说不全」,答案像散落的珠子。真正稳的答法是把它当成一条流水线,按关卡推进:查缓存、解析域名、建连接、发请求、收响应、渲染。每一关里再挂一个高频追问,形成树状结构,被追问哪一关都不慌。

3.1 缓存:强缓存、协商缓存与 304 的判定顺序

浏览器每次发起请求,都会先在浏览器缓存里查找该请求的结果和缓存标识;每次拿到返回结果,也会把结果和标识存进缓存。这两句话是所有缓存题的根。

强制缓存是第一步:向浏览器缓存查找结果,并按缓存规则判断是否直接用。规则放在响应头的Expires和Cache-Control里,Cache-Control优先级高于Expires。三种情况——没有缓存结果和标识,直接向服务器请求;有结果但已失效,强制缓存失效,走协商缓存;有结果且未失效,直接返回,浏览器网络面板里能看到 from cache 字样。Expires的坑是受本地时间影响,用户改了本地时间就可能让缓存失效,所以要用Cache-Control: max-age指定最大生命周期。

协商缓存是第二步:强制缓存失效后,浏览器带着缓存标识向服务器请求,由服务器判断能不能继续用。控制字段是Last-Modified/If-Modified-Since和ETag/If-None-Match,其中ETag的优先级更高。ETag能保证每个资源唯一,资源一变ETag就变,服务器靠客户端带上来的If-None-Match判断是否命中。Last-Modified是资源最后修改时间,第一次请求时服务器在响应头里加上它,再次请求时请求头带If-Modified-Since。命中返回 304,不命中返回 200 和新资源。

把判定顺序背成一条链:Cache-Control→Expires→ETag/If-None-Match→Last-Modified/If-Modified-Since。面试时能主动说出「为什么 ETag 优先于 Last-Modified」更好答:Last-Modified只精确到秒,一秒内改两次文件识别不出来;文件内容没变但修改时间变了也会误判失效。这就是 ETag 存在的理由。

提示:304 不是错误码,它表示「你本地那份还能用」,响应体是空的,别答成「请求失败」。

3.2 重排与重绘:触发条件和优化手段怎么对应

重排(回流)指 DOM 的变化影响了元素的几何信息,浏览器要重新计算元素的几何属性、重新生成布局、重新排列元素。重绘指元素外观改变但布局没变,重新把外观画出来。两者的关系必须答准:重绘不一定出现重排,重排必然出现重绘。代价上,重排的性能影响更大,两者无法避免时宁可选择代价更小的重绘。

触发场景要背具体:添加、删除、更新 DOM 节点会触发重排重绘;display: none隐藏节点触发重排和重绘;visibility: hidden隐藏节点只触发重绘,因为没有几何变化;移动节点或给节点加动画、添加样式表调整样式属性、用户调整窗口大小或改变字号或滚动,都会触发。display: none和visibility: hidden这一对是高频对比题,务必答出「一个不占空间、一个仍占空间」。

优化手段要说成「减少重排次数」而不是「消除重排」:集中改变样式,不要一条一条改 DOM 样式;不要把 DOM 节点的属性值放在循环里当循环变量;动画元素使用fixed或absolute定位,脱离文档流后修改 CSS 不会引发 reflow;不用 table 布局,很小的改动都可能造成整个 table 重新布局;尽量只修改position为absolute或fixed的元素;动画用transform和3D变化走 GPU 加速。再补一句「把元素提升为合成层」:合成层的位图交给 GPU 合成,比 CPU 处理快;需要 repaint 时只重绘本身,不影响其他层;对transform和opacity不会触发 layout 和 paint。提升合成层最直接的方式是will-change。

.card { /* 提前告诉浏览器这个属性要变,让它单独提层,避免动画期间反复重排重绘 */ will-change: transform; transition: transform .2s ease; } .card:hover { transform: translateZ(0) scale(1.02); /* 3D 变换走合成层,不触发 layout */ }

这段样式的关键是两个参数:will-change: transform是提前声明,让浏览器在动画开始前就准备好图层,不要等到动画中途才临时提层;translateZ(0)是一个常用的触发 3D 变换的写法,目的是让元素进入合成层,代价是额外显存占用。所以will-change不能滥用,只能加在真正要动的少量元素上,加了不删反而拖累内存。

3.3 真题演练:输入 URL 后发生了什么,跨域方案怎么选

题一:从输入 URL 到页面加载完成,中间发生了什么?

答题分六段,每段一句主干加一个细节。第一段查缓存:浏览器先看浏览器缓存、系统缓存、路由缓存里有没有该页面,有就直接显示。第二段域名解析:浏览器缓存里有 DNS 记录就直接用,没有就调系统、查路由器缓存、再向 ISP 的 DNS 搜索,DNS 服务器基于 UDP,所以解析过程用到 UDP 协议。第三段建连接:解析出 IP 后,按 IP 和默认端口与服务器建立 TCP 连接,走三次握手。第四段发请求:浏览器发起读取文件的 HTTP 请求,这个请求报文作为三次握手的第三次数据发给服务器。第五段收响应:服务器返回对应内容,比如 HTML 文件,随后四次挥手释放连接。第六段渲染,也是最容易讲浅的一段。

渲染流程按顺序背:构建 DOM 树(词法分析后解析成 DOM 树,由 DOM 元素及属性节点组成,根是 document 对象)→ 构建 CSS 规则树(CSSOM)→ 把 DOM 和 CSSOM 结合构建渲染树(render tree)→ 布局 Layout,计算每个节点在屏幕中的位置 → 绘制 Painting,遍历渲染树用 UI 后端层把节点画出来。之后 JS 引擎介入:创建 window 对象作为全局执行环境,所有全局变量和函数都是 window 的属性和方法,DOM 树也映射在 window 的 document 上;检查语法与词法是否合法;进入预编译,把全局变量声明挂到 window 上并赋值为 undefined,把全局函数声明挂上去并赋值函数体,匿名函数不参与预编译,因为它是变量;最后解释执行,变量逐个赋值。这里可以顺带提一句变量提升:ES5 非严格模式下未声明就赋值的变量会成为 window 的属性,也就是全局变量,这个问题在 ES6 里用 let/const 解决了,但函数提升依然存在。

题二:跨域是怎么产生的,你们项目里怎么解决?

原理一句话:跨域是浏览器不能执行其他网站的脚本,由同源策略造成;同源策略是浏览器对 JavaScript 实施的安全限制,协议、域名、端口任何一个不同都算不同域。跨域方案的本质是「避开浏览器的这条限制」,不是「绕过安全」。

方案按场景选,答题时给取舍而不是罗列。JSONP 利用 script 标签的 src 可以访问跨域脚本这一特性,服务端不再返回 JSON,而是返回一段调用某个函数的 JS 代码,在 src 里调用,从而拿到数据。

// 1. 动态创建 script 标签 var script = document.createElement('script'); // 2. 定义回调函数,函数名要通过参数传给后端,后端才能拼出调用语句 function getData(data) { console.log(data); // 这里拿到的就是后端返回的业务数据 } // 3. 设置 src,callback 参数的值必须和上面的函数名完全一致 script.src = 'http://localhost:3000/?callback=getData'; // 4. 挂到文档上才会真正发起请求 document.body.appendChild(script);

这段代码的两个约定要讲清楚:callback是自定义参数名,值必须等于前端已定义的全局函数名,后端靠它拼出getData(...)这样的返回体;script标签只能发 GET,所以 JSONP 只支持 GET,携带数据量也小,且必须后端配合返回指定格式。这也是它现在基本被淘汰的原因。

其他方案:document.domain要求基础域名相同、子域名不同才可用;window.name利用同一个浏览器窗口内载入的所有域名共享一个 window.name;CORS 是服务器设置Access-Control-Allow-Origin响应头后,浏览器允许跨域请求,这是现在的主流;代理是目前最常用的工程方案,开发环境用 devServer 的 proxy,线上用 nginx 反向代理,前端请求同源地址、由服务器转发;window.postMessage利用 H5 新特性做跨窗口通信。

实际项目里的答法可以参考这个套路:早期用 JSONP,但只支持 GET、不安全、携带数据小;也试过 iframe,但要求主域相同,问题不少;后来改成开发环境用 proxy、线上用 nginx 代理,前后端配置一次,开发和生产都顺。这段「血泪经验」比背七种方案更能证明你干过活。

4. CSS 与 DOM 考点:盒模型、BFC 与居中的标准答法

CSS 题的特点是「人人都会,人人答不全」。面试官问盒模型,想要的是你能不能把两套宽度算法讲清楚;问 BFC,想要的是触发条件和它能解决什么实际问题;问居中,想要的是你知道每种方案的适用边界,而不是背一段 flex。这一章把 HTML 语义化、选择器优先级、position、盒模型、BFC、居中和隐藏元素这几块串起来,每块都给能直接说的版本。

4.1 盒模型与 box-sizing:两套宽度算法和选择器优先级

CSS 盒模型本质是一个盒子,包括边距、边框、填充和实际内容,分为 IE 盒模型和标准盒模型。标准盒模型里width指 content 部分的宽度;IE 盒模型里width表示 content + padding + border 三部分之和。计算方式因此不同:标准盒模型下,一个块的总宽度 = width + margin(左右)+ padding(左右)+ border(左右);怪异盒模型下,总宽度 = width + margin(左右),因为 width 已经包含 padding 和 border。

box-sizing就是切换这两套算法的开关。content-box是默认值,宽高只作用于内容框,内边距和边框在宽高之外另画;border-box下设定的宽高决定边框盒,包含 padding 和 border;inherit表示继承父元素的值。工程上最常见的一句是「全局设border-box,让写宽度时不用再心算 padding」,说出这句就说明你写过布局。

选择器优先级按权重排:!important最高;行内样式(1000);ID 选择器(0100);类选择器、属性选择器、伪类选择器(0010);元素选择器、伪元素选择器(0001);关系选择器和通配符选择器(0000)。来源相同时的比较顺序是:!important> 行内样式 > ID 选择器 > 类选择器 > 标签 > 通配符 > 继承 > 浏览器默认属性。这里容易被追问「继承的样式为什么优先级最低」,因为继承压根不参与优先级计算,没有命中任何选择器时才轮到它。

4.2 BFC:五条布局规则、五个触发条件和四个使用场景

BFC 是 Block Formatting Context 的缩写,即块级格式化上下文,是 CSS 布局的一个概念,指一个独立的渲染区域,规定了内部 box 如何布局,并且这个区域的子元素不会影响到外面的元素。

布局规则五条:内部的 Box 会在垂直方向一个接一个地放置;垂直方向的距离由 margin 决定,属于同一个 BFC 的两个相邻 Box 的 margin 会发生重叠;每个元素的 margin box 左边与包含块 border box 的左边相接触(从左往右的格式化下);BFC 的区域不会与 float box 重叠;BFC 是一个独立容器,容器里的子元素不会影响外面的元素;计算 BFC 高度时,浮动元素也参与计算。第三条和第四条是解决实际问题的钥匙。

触发条件五个:根元素即 HTML 元素本身;float不为 none;position为 absolute 或 fixed;display为 inline-block、table-cell、table-caption;overflow不为 visible。这五个要能倒背,因为题目经常反过来问「下面哪个属性不会创建 BFC」。

使用场景四条,每条对应一个具体 bug:去除边距重叠现象;清除浮动,让父元素的高度包含子浮动元素;避免某元素被浮动元素覆盖;避免多列布局因宽度计算四舍五入而自动换行。答题时把「规则」和「场景」对应起来讲,比如「相邻 margin 重叠是因为它们在同一个 BFC 里,给其中一个包一层触发 BFC 的容器就能隔开」,比干背五条规则有说服力。

4.3 真题演练:居中、隐藏元素和 CSS 三角

题一:水平垂直居中,你能说出几种?

按「元素类型」分类答,不要按「我知道几种」答。水平居中方面:行内元素用text-align: center;宽度确定的块级元素用margin: 0 auto,或用绝对定位加margin-left: (父width - 子width) / 2,前提是父元素position: relative;宽度未知的块级元素可以用 table 标签配合左右 margin auto,或把块级元素设成display: table再左右 margin auto;inline-block配text-align: center;绝对定位加transform: translateX(-50%),可以移动自身宽度的 50%;flex 布局用justify-content: center。

垂直居中方面:纯文字类用line-height等于容器高度;父相子绝加 margin 自适应;flex 布局下父级display: flex、子级margin: auto;父级相对定位、子级绝对定位加transform位移;table 布局下父级转成表格形式、子级用vertical-align: middle,注意vertical-align: middle的前提是内联元素或display值为table-cell的元素。最省事的写法是 flex 一行搞定:

.parent { display: flex; justify-content: center; /* 主轴居中:水平 */ align-items: center; /* 交叉轴居中:垂直 */ height: 300px; /* 没有高度时垂直居中看不出来,别漏了 */ }

参数说明:justify-content管主轴,align-items管交叉轴,默认flex-direction: row时主轴就是水平方向,所以这两个属性一组合就是水平垂直居中。height必须有确定值,否则父容器高度由内容撑开,align-items看不出效果,这是最常见的翻车点。

题二:隐藏一个元素有哪几种方式,区别是什么?

三种,区别在于「占不占空间」和「触不触发事件」。opacity: 0把元素隐藏起来,但不改变页面布局,如果元素绑了 click 事件,点击该区域依然能触发。visibility: hidden也隐藏元素、不改变布局,但不会触发已绑定的事件,文档布局里仍保留原来的空间,触发重绘。display: none把元素隐藏起来并且改变布局,可以理解成页面里不再显示该元素、文档布局中不再分配空间,触发回流加重绘。这三条答完,顺手带一句「所以做淡入淡出用 opacity,做彻底移除用 display」,就是工程视角。

题三:用 CSS 画一个三角形,原理是什么?

原理是把盒子的宽高设为 0,四个边框各自向中心收缩成三角形,把其中三个边框设成透明,剩下的那个边框就是可见的三角。

.triangle::after { content: ""; /* 伪元素必须给 content 才渲染 */ position: absolute; width: 0; height: 0; /* 宽高归零,边框才会收成三角 */ border-top: 100px solid #ff0; /* 可见的那条边,决定三角形朝向 */ border-right: 100px solid transparent; border-left: 100px solid transparent; border-bottom: 100px solid transparent; }

参数说明:四条边框宽度相等时得到等腰直角三角形,改某一条的宽度就能得到不同形状;border-top有颜色、其余透明,三角形就朝下,因为可见的那条边是「底」;content: ""不能省,伪元素没有内容不渲染。记忆口诀是「盒子宽高均为零,三面边框皆透明」。

顺带把position五个值在这道题里串一遍:static是默认值,没有定位,元素出现在正常流中,忽略 top/bottom/left/right 和 z-index;relative相对自身起点移动,无论是否移动都仍占据原来的空间;absolute相对最近已定位的父元素定位,没有则相对根元素,脱离文档流、不占空间、会和其他元素重叠;fixed相对浏览器窗口固定,窗口滚动也不动,脱离文档流;sticky先按普通文档流定位,跨越特定阈值前表现为相对定位、之后表现为固定定位。

5. 失分点排查:五个最容易翻车的高频错答

这一章专门收那些「自觉答对了、其实被扣分」的地方。它们的共同点是:结论没错,但方向、顺序或边界错了,面试官一听就知道是背的。下面五条按「错在哪 → 为什么错 → 怎么答对」拆开,考前过一遍比再刷十道新题有用。

5.1 把 HTTPS 说成「绝对安全」

错在哪:被问「HTTPS 一定安全吗」,直接答「安全,因为加密了」。

为什么错:HTTPS 解决的是传输链路的加密和网站身份真实性,解决不了服务端被攻破、证书本身是伪造的、用户手动忽略证书告警这些情况。把「加密」等同于「安全」,暴露的是只背了结论没想过边界。

怎么答对:先说「HTTPS 相比 HTTP 显著更安全,但不是绝对安全」,再补一层「它的安全性建立在证书信任链上,证书签发环节或客户端信任配置出问题,加密通道照样可能指向错误的对象」,最后落回工程「所以还要配合 HSTS、内容安全策略这些手段」。三段话,层次就出来了。

5.2 挥手写成「四次都是客户端发起」

错在哪:把四次挥手说成客户端发四次包,或者把服务端的 CLOSE_WAIT 说成客户端的。

为什么错:没理解全双工。TCP 两个方向要分别关闭,主动关闭方发 FIN、被动关闭方先 ACK 再发自己的 FIN,所以是「两个方向各一次 FIN + 各一次 ACK」。方向搞混,状态名必然跟着错。

怎么答对:先给一句总纲「四次挥手是两个方向分别关闭,每个方向一次 FIN、一次 ACK」,再按主动关闭方和被关闭方两条路径分别报状态。补一句「主动关闭方会多一个 TIME_WAIT,被动关闭方会多一个 CLOSE_WAIT」,这是一句话记住两条路径的锚点。

5.3 缓存字段优先级答反

错在哪:说Expires优先于Cache-Control,或者说Last-Modified优先于ETag。

为什么错:两组字段里,被淘汰的、精度低的那一个都被排在了后面,记混就是把「新旧」记反了。Cache-Control是 HTTP/1.1 的字段,用相对时间max-age表达,不受本地时钟影响;Expires是 HTTP/1.0 的字段,用绝对时间,改本地时间就可能失效。ETag按内容算,Last-Modified只精确到秒。

怎么答对:给一句口诀「新的优先、精确的优先」,然后展开成完整链条:强缓存阶段Cache-Control优先于Expires,协商缓存阶段ETag/If-None-Match优先于Last-Modified/If-Modified-Since。被追问「为什么」时,答本地时间可改、秒级精度不够这两条。

5.4 BFC 触发条件背成「overflow: hidden 就能创建」

错在哪:只记住overflow: hidden,被问「overflow: visible能不能创建 BFC」时愣住,或者把float: left记成float: none。

为什么错:记忆时记了具体值而没记「哪个值不算」。BFC 的五个触发条件里有三个是「不等于某个值」:float不为 none、position为 absolute 或 fixed、overflow不为 visible。记成「等于某个值」,反向一问就崩。

怎么答对:把五个条件按「值域」说——根元素;float不为 none;position为 absolute 或 fixed;display为 inline-block、table-cell、table-caption;overflow不为 visible。再补一句「所以overflow: visible是默认值,反而不创建」,用反向确认自己的记忆。

5.5 重排重绘方向搞反

错在哪:说「重排一定会引起重绘,重绘也一定会引起重排」,或者把visibility: hidden说成触发重排。

为什么错:重排是几何变化,几何变了当然要重画,所以重排必然带重绘;重绘只是外观变化,几何没变就不需要重排,方向是单向的。visibility: hidden隐藏后仍占原来的空间,几何没变,所以只重绘。

怎么答对:先给单向结论「重绘不一定重排,重排必然重绘」,再用两个例子验证:visibility: hidden只重绘,display: none重排加重绘。最后补一句「所以优化时优先选只触发重绘的手段」,把知识点落到决策上。面试官追问「怎么减少重排」时,把集中改样式、动画用 absolute/fixed、用 transform、不用 table 布局这几条报出来即可。

6. 答题技巧:把八股文讲成因果链的三段式

考场上的问题从来不是「不知道」,而是「知道一堆,说出来是散的」。我后来固定用一个三段式:先给结论,再走链路,最后讲边界。结论一句话,让对方知道你没跑偏;链路按顺序推,每步只讲「这一步解决什么问题」;边界讲取舍和代价,证明你不是背的。以「为什么三次握手不能两次」为例:结论是「三次才能让双方都确认收发能力正常,两次会留下历史连接」;链路是「客户端发 SYN、服务器回 SYN+ACK、客户端再 ACK,每一步确认一个方向」;边界是「四次可以但没必要,因为服务器的 ACK 和 SYN 能合并」。三段讲完,一分钟左右,比背两分钟状态机得分高。

不同题型对应的三段内容不一样,可以照着下面这张表换:

题型第一段:结论第二段:链路第三段:边界或取舍
原理题(三次握手、HTTPS)一句话给机制名按步骤推,每步说解决什么问题为什么不是两次/四次,代价是什么
对比题(TCP/UDP、强缓存/协商缓存)先给最核心的一条差异逐条展开,每条配一个场景什么场景选哪个,为什么不选另一个
流程题(输入 URL、渲染)先报总关卡数按关卡推进,每关一句主干哪一关最容易出性能问题
方案题(跨域、居中)先给当前主流方案再列备选方案和原理各自的限制条件,项目里怎么选

这张表的用法是考前拿真题套一遍,每道题只写三个关键词,写完对照表格检查有没有缺段。缺结论的答案是「散」,缺链路的答案是「虚」,缺边界的答案是「背的」——三种都会被追问。

还有一个反直觉的技巧:主动暴露一个限制,比把话说满更安全。比如答 HTTPS,主动说「握手会延长加载时间、缓存效率不如 HTTP」;答 JSONP,主动说「只支持 GET、需要后端配合」。面试官听到你主动说缺点,会默认你后面说的优点也是真的。反过来,一路只讲优点的答案,追问一句就塌。

代码题也一样,别急着写完再解释,边写边说参数含义。比如写 flex 居中,写到align-items时顺口说「这个管交叉轴,父容器没给高度就看不出来效果」——这一句就把「你真的写过」和「你抄过」分开了。

最后说个习惯。我以前答这类题喜欢一口气把知道的都倒出来,结果面试官抓不住重点,追问也追得零散。后来每次开口前先在脑子里过一遍那张三段表:结论是什么、链路几步、边界在哪,答完就停,把追问的空间留给对方。这个习惯改完之后,同一套知识点,反馈完全不一样。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询