ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

网页访问全链路解析:从DNS解析到浏览器渲染的完整技术旅程

网页访问全链路解析:从DNS解析到浏览器渲染的完整技术旅程 在地址栏敲下一串网址按下回车到页面完整出现在屏幕上这个过程快的时候只需要几百毫秒慢的时候能让你盯着加载圈怀疑人生。作为长期跟页面性能打交道的人我一直觉得“WEB页面浏览过程”是前端、后端、运维三个方向的人都应该吃透的基础链路——你优化的每一个缓存策略、砍掉的每一次重定向、加上的每一个HTTP头本质都是在干预这条链路上的某个环节。网上讲这个主题的文章不少但大多只停留在“DNS解析—发送请求—服务器返回—浏览器渲染”这种一句话概括的层面。真正把这段路的每个步骤拆开看你会发现里面藏着大量值得深挖的细节URL里各段路径分别代表什么、DNS为什么可能查好几次、TCP握手和HTTP请求之间是什么关系、浏览器拿到HTML之后到底按什么顺序“画”出页面。这篇文章我会按一条完整的时间线把页面浏览从输入地址到渲染完成的全程展开讲同时把我在实际排查中踩过的坑一起整理出来尽量让新手能看懂每一步在做什么也让有经验的人能对照着查漏补缺。无论你是刚转行写前端、日常需要跟网络问题打交道的后端开发还是纯粹对自己每天看的网页是怎么来的感到好奇这套链路都值得花点时间认认真真过一遍。下面正式开始。1. 一次页面访问的全链路阶段划分在进入每个环节之前先把整条链路切成几个大的阶段心里有一个全局框架后面看细节才不会迷路。1.1 页面浏览的六个核心阶段一次普通的网页访问从你按下回车到页面完全呈现大致要经过六个阶段URL解析浏览器先把地址栏里的字符串拆开弄清楚协议、域名、端口、路径分别是什么。DNS域名解析把人类好记的域名翻译成服务器能识别的IP地址。建立连接通过TCP三次握手建立可靠传输通道如果是HTTPS还要叠加TLS握手。发送HTTP请求并等待响应浏览器把请求报文发给服务器服务器处理之后返回响应报文。浏览器解析与渲染拿到HTML、CSS、JavaScript之后浏览器把它们变成DOM树、样式规则、渲染树最终绘制到屏幕上。资源加载与页面交互HTML里引用的图片、脚本、样式表等子资源继续按规则加载页面进入可交互状态。用一句话类比你在一个陌生城市找一家餐厅。第一步是看懂地址条门牌号、路名、城市第二步是问路人这个位置在哪DNS第三步是走过去、推开门的这个过程TCP连接第四步是跟服务员说要吃什么、后厨做菜HTTP请求与响应第五步是菜端上来你开始吃渲染第六步是过程中加菜、要纸巾子资源加载与交互。1.2 六个阶段的耗时占比与优化方向这六个阶段并不是平均分配时间的。根据我测过的大量线上页面一个典型的首屏加载耗时分布大概是这样的阶段典型耗时占比主要优化手段DNS解析5% - 10%预解析、缓存、减少跨域请求TCP/TLS连接15% - 30%连接复用、HTTP/2多路复用HTTP请求与响应20% - 40%减少请求数、压缩、缓存、CDN浏览器渲染20% - 40%精简DOM、CSS合并、脚本延迟加载不同场景下这个比例会剧烈变化访问一个部署在海外的站点DNS和连接握手时间可能占据大半访问一个本地服务渲染时间又成了大头。理解这个分布的意义在于排查性能问题时你不会一上来就乱猜而是先用工具定位瓶颈再针对性地处理。我在后面第6节会专门讲怎么用开发者工具看这个分布。2. 输入地址以后的第一件事URL解析与DNS查询大多数人以为按下回车之后浏览器立刻就去请求网页了其实它先要做的是“阅读理解”——把你输入的字符串解析成一个结构化的地址然后才能知道该去问谁。2.1 URL的结构到底在表达什么一个完整的URL长这样https://www.example.com:443/path/to/page?id100#section拆开来看是这些部分协议Scheme这里是https告诉浏览器用哪套规则通信。常见的还有http、file等。协议不同后续连接方式完全不同https比http多一层TLS加密握手。域名Hostwww.example.com服务器的“门牌名”。注意它本身不是IP地址还需要解析。端口Port443定位服务器上具体的服务进程。HTTP默认80HTTPS默认443如果没写端口浏览器会按协议自动补默认值。路径Path/path/to/page告诉服务器你想要的资源在哪个位置。查询参数Query?id100跟在问号后面通常是传给后端的条件或参数。锚点Fragment#section用于定位页面内部的某个位置注意这个部分不会发给服务器纯属浏览器自己用的。我在实际开发中见过不少新手在这个环节犯迷糊把查询参数写进了路径里导致路由匹配不到或者在锚点后面又拼参数导致页面跳转异常。搞清楚URL每一段的语义排查这类问题会快很多。2.2 DNS解析是如何一步步查到IP的域名是为了给人看的网络层真正认的是IP地址。DNS域名系统干的事就是把www.example.com翻译成类似93.184.216.34这样的IP。整个过程是分层查询的先查浏览器缓存。你在地址栏输入过同一个域名之后浏览器会把解析结果短期缓存下来。如果命中就直接用不再继续查。浏览器缓存没命中查操作系统缓存。操作系统也维护着一张域名到IP的映射表你可以把它理解为系统的“通讯录”。系统缓存也没命中查本地网络配置的DNS服务器通常是路由器或运营商下发的。这个服务器也叫递归解析器它会替你把查询跑完。递归解析器追查根域名服务器得到顶级域比如.com服务器的地址。再向顶级域服务器查询得到该域名的权威服务器地址。最后向权威服务器查询拿到具体的IP地址并逐级缓存返回结果。这个链条看起来很长但实际因为有层层缓存大多数访问并不会真的走完全程。不过一旦某个环节的缓存失效或者权威服务器响应慢你就能明显感受到“打开网页变慢了”而且这种慢往往在开发者工具里表现为“等待DNS解析”时间异常。补充一点现代操作系统和浏览器还支持DNS预解析Preload。很多大流量的站点会在HTML里加一行link reldns-prefetch href//example.com目的就是让浏览器提前解析后续会用到的域名把这个耗时藏到用户还没点击的时间里。我自己优化页面的时候对第三方统计、字体、图片CDN这些域名都会做预解析实测首屏确实会快那么几十毫秒量不大但积少成多。2.3 一个容易被忽略的环节HSTS与端口探测除了常规的DNS解析现代浏览器还有两个隐性动作值得知道。第一个是HSTSHTTP严格传输安全。如果一个站点通过响应头Strict-Transport-Security告诉浏览器“以后只能用HTTPS访问我”浏览器会把这个策略记住一段时间。下次你即使输入的是http://开头的地址浏览器也会在内部直接改写为https://再发起请求不再先走一遍HTTP。这个机制是为了防中间人劫持但排查问题时它经常是个坑——你明明输入的是HTTP抓包看到的却是HTTPS请求容易让人一头雾水。第二个是端口探测。个别浏览器在加载过程中会尝试连接一些常用端口判断网络环境是否正常这属于浏览器自身的网络栈行为。理解这一点能避免一个误解不是所有你在抓包工具里看到的连接请求都跟你访问的页面有直接关系。DNS这个环节还有一个常态化的坑公共DNS和运营商DNS解析结果可能不一致导致同一个域名在不同网络环境下访问到不同的服务器节点。线上的CDN故障、灰度发布之类的问题经常最终定位到DNS解析结果不一致上。所以我排查线上异常的第一步往往是先在不同网络环境下分别nslookup一下域名看返回的IP是否都在预期范围内。3. 连接与传输TCP握手和HTTP请求报文DNS解析拿到了IP地址浏览器紧接着就要跟这台服务器建立连接、发送请求。这个阶段是整条链路里“物理感”最强的一部分也是很多性能问题的重灾区。3.1 TCP三次握手为什么非得握三次TCP协议是面向连接的可靠传输协议建立连接需要经典的“三次握手”客户端发送一个SYN报文表示“我想建立连接我的初始序列号是A”。服务器回复SYNACK报文表示“收到你的请求我的初始序列号是B并且确认你的序列号”。客户端再发送ACK报文表示“我收到你的确认了”连接正式建立。为什么要三次而不是两次核心原因是双方都需要确认自己和对方的收发能力都正常。第一次握手服务器确认了客户端的发送能力第二次握手客户端确认了服务器的接收和发送能力第三次握手服务器确认了客户端的接收能力。如果只有两次握手服务器无法确认客户端是否已经准备好接收数据连接就可能建立在一个“悬空”状态上。一个生活化的类比两个人约见面。A发消息“我到了”SYNB回“我也到了看到你了”SYNACKA再回“我看到你了”ACK。三次确认齐全双方才敢开始正式谈话。少一次总有一方不确定对方是否在听。实际优化中这个握手的开销在短连接场景下非常可观。HTTP/1.1时代一个页面几十个资源就要反复建立连接后来有了Keep-Alive连接复用以及HTTP/2的多路复用技术才把握手次数大幅降下来。如果站点还不支持HTTP/2在高延迟网络下会很吃亏。3.2 HTTPS多出来的一层TLS握手如果是HTTPS访问在TCP三次握手之后还要进行TLS握手用来协商加密算法、交换证书、生成会话密钥。这个握手一般还要2个RTT往返时间。也就是说一个全新的HTTPS连接建立最少要3次TCP握手加2次TLS往返才能发出第一个HTTP请求字节。这也是为什么TLS握手优化经常会用到会话恢复Session Resumption机制——通过复用之前的会话票据把后续访问的TLS握手压缩到1个RTT甚至0-RTT。你看到的很多“秒开”体验背后就有这套机制在起作用。顺带说一个我踩过的坑本地开发调试HTTPS站点时如果证书不是浏览器信任的机构颁发的会直接卡在TLS握手阶段报证书错误。很多人以为是代码问题排查半天其实只是没把自签名证书加入系统信任列表。3.3 HTTP请求报文里到底装了什么连接建立好之后浏览器开始发送HTTP请求。一个典型的GET请求报文大概是这样的GET /index.html HTTP/1.1 Host: www.example.com Connection: keep-alive User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) ... Accept: text/html,application/xhtmlxml,... Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q0.9,en;q0.8 Cookie: sessionidabc123逐行看关键信息请求行方法GET、路径/index.html、协议版本HTTP/1.1。Host字段指定目标主机。一个服务器上可能部署了多个域名靠这个字段区分。User-Agent告诉服务器客户端是什么浏览器服务器经常用它做统计和兼容性判断。Accept系列告诉服务器我接受什么格式、什么编码、什么语言。服务器可以根据这些字段返回不同版本的内容这就是内容协商的基础。Cookie浏览器自动携带的本地身份信息实现会话保持。POST请求则多了一个请求体通常携带表单数据或JSON。GET的参数放在URL查询串里POST的参数放在请求体里这是两者最直观的区别。需要提一下的是GET请求也可以带请求体但实践中几乎没人这么干协议对它的处理也各不相同建议不要踩这种不常规的用法。3.4 响应报文与状态码的现实语义服务器处理完请求后返回的响应报文开头是状态行核心是状态码。状态码的语义是排查问题的第一把钥匙状态码含义实际场景200成功资源正常返回301/302重定向域名换地址、HTTP跳HTTPS304未修改本地缓存仍有效服务器不返回内容401/403未认证/禁止访问登录失效、权限不足404未找到路径写错、资源被删除500服务器内部错误后端代码异常502/503/504网关/服务不可用/超时反向代理后上游挂掉或响应超时我在工作中遇到最多的诡异情况往往是301/302重定向链。举个例子一个页面从HTTP跳HTTPS再从无www跳有www中间还夹了一次加斜杠的跳转三个跳转下来用户感知到的延迟多了几百毫秒。这类问题在开发者工具的Network面板里看得一清二楚多个301记录排成一串每一跳都是一次完整的请求往返。优化方式很简单梳理跳转规则能一次跳到位绝不跳两次。另外一个高频坑是304和本地缓存的区别。304表示“你缓存的内容还有效我没必要再传一遍”浏览器会直接用本地副本。如果你发现页面刷新后某些资源没有重新下载先别急着骂服务器看看是不是命中缓存了。4. 服务器处理到返回响应后端到底干了多少活浏览器把请求发出去之后接下来的事情发生在你自己看不到的地方——服务器端。理解服务器做了什么有助于你判断“慢”到底是前端问题还是后端问题。4.1 Web服务器处理请求的基本流程服务器收到HTTP请求后处理的流程大致是解析请求报文取出方法、路径、头部和参数。根据路径匹配路由决定由哪个处理函数来处理。执行业务逻辑查数据库、调用文件系统、调用其他服务接口。生成响应内容通常是HTML文档、JSON数据、图片二进制流等。按响应格式设置头部字段附上状态码返回给客户端。这个流程里最耗时的部分通常是数据库查询和外部服务调用。我曾经优化过一个接口单次响应需要3秒定位后发现它串行调用了五个外部服务每个平均600毫秒。改成都并行请求之后总耗时一下子降到了800毫秒。所以拿到一个慢接口第一件事永远是看耗时构成而不是盲目加缓存。4.2 响应头里的关键字段服务端返回的响应头经常被人忽略但它是浏览器行为的重要指挥棒。几个关键字段Content-Type告诉浏览器响应体的类型。如果服务器把HTML误返回成text/plain浏览器会把页面当纯文本显示这是我们排查“页面变成一堆代码”问题时第一个检查的对象。Content-Length响应体的字节长度浏览器靠它判断数据传输是否完整。Cache-Control缓存策略指令。常见的值有no-cache需要重新验证、no-store禁止缓存、max-age3600缓存一小时。Set-Cookie让浏览器种下Cookie实现登录态、跟踪标识等。Access-Control-Allow-OriginCORS跨域策略核心字段决定了浏览器是否允许页面读取跨域接口的响应。CORS是我见过前端同学问得最多的一类问题。浏览器出于安全考虑默认禁止页面读取跨域响应只有在响应头里明确声明了允许的来源才会放行。记住一个原则CORS策略是服务器配的不是前端代码能绕过的——你不是在代码里“解决CORS”而是要让后端同学把响应头配正确。4.3 常见服务器相关的性能瓶颈结合我排查生产事故的经验服务器端的性能瓶颈通常集中在三块第一数据库慢查询。逻辑上看似简单的查询因为缺少索引或者数据量膨胀耗时会从毫秒级变成秒级。这是后端慢的首要嫌疑。第二线程池或连接池耗尽。大量请求同时进来服务器的连接池被打满后续请求排队等待表面上看就是响应时间突然飙升。这类问题往往伴随大量超时日志。第三网关超时配置不合理。反向代理比如Nginx默认的超时时间可能只有几十秒后端某个接口因为外部依赖变慢导致处理时间超过这个阈值用户就会收到502/504。这个时候不是后端真的崩了而是网关等不及了。排查这类问题我的习惯是先从响应头里的Server和Via字段看服务链路再从访问日志看耗时分布最后才进到具体业务日志里找异常栈。一层层剥比直接翻代码高效得多。5. 浏览器渲染从HTML字符串到屏幕像素服务器把HTML返回之后真正的“魔术”发生在浏览器这一侧。浏览器拿到HTML字符串要经过一套非常精巧的流水线才能把它变成你看到的样子。5.1 解析HTML构建DOM树浏览器收到HTML文档后首先进行词法分析和语法分析把标签字符串转化成内部的节点对象构建出DOM树。DOM树的节点对应页面上的每个元素树的层级对应元素的嵌套关系。这个阶段最值得注意的问题是解析是边下边解析的。浏览器并不需要等整个HTML下载完才开始解析而是收到一部分就解析一部分。这也是为什么大HTML页面也能逐步显示内容而不是一片空白——现代浏览器会边接收边渲染已经解析出来的部分。HTML解析过程中如果遇到script标签默认情况下解析会被暂停先下载并执行完脚本再继续解析后面的HTML。这就是“脚本阻塞解析”的来源。为了缓解这个问题工程上普遍用defer或async属性defer脚本会延迟到文档解析完成后执行多个defer脚本按顺序执行。async脚本下载不阻塞解析下载完立刻执行执行顺序与加载完成顺序有关。我在实际项目中经常用defer处理依赖DOM的脚本用async处理独立无依赖的统计脚本。如果无脑全用async很可能出现脚本执行时DOM还没建好的报错。5.2 解析CSS构建样式规则并计算样式CSS的解析结果被称为CSSOMCSS对象模型。浏览器会把所有样式规则整理成一颗样式树然后结合DOM树逐元素计算最终生效的样式这个阶段称为样式计算Style Calculation。这里有一个关键机制CSS不会阻塞DOM解析但会阻塞渲染。浏览器在样式规则还没就绪的时候不会轻易开始绘制页面否则可能出现“先画了一个没样式的页面再刷一下变成有样式的页面”的闪烁。所以CSS被视为渲染阻塞资源前端性能优化里有一条铁律把CSS放在head里尽早加载让渲染尽早开始且不反复。样式计算的性能跟选择器复杂度有关。我在优化一个渲染很卡的后台页面时发现全局有个深嵌套的选择器每秒都在几十个节点上重新匹配。改成给关键节点加类名之后样式计算的耗时下降非常明显。规范都推荐避免过度复杂的选择器实际收益虽然不像减少请求那样立竿见影但在复杂页面上相当可观。5.3 构建渲染树、布局与绘制样式计算完成之后浏览器会把DOM树和CSSOM树合并成渲染树Render Tree。注意渲染树只包含最终会出现在屏幕上、且能够显示的元素。display:none的元素、head里的节点都不会进入渲染树。但visibility:hidden的元素仍然占位置所以会留在渲染树里只是不绘制这两个属性在布局上的区别经常被面试题拿来考。接下来是布局Layout阶段也叫回流Reflow。浏览器计算每个可见元素的几何位置和尺寸宽高、边距、浮动、定位等。这一步计算量大而且一旦DOM或样式发生改动布局经常要重算。布局完成之后进入绘制Paint阶段浏览器把元素绘制成实际的像素可以理解为给每个元素填色、描边。最后是合成Composite阶段把各个图层按正确的层级顺序合成为最终的画面。这个流水线有一个非常实用的推论任何动画如果触发了布局或绘制性能都不会太好如果只触发合成性能就能很顺滑。这也是为什么现在前端优化动画时强调用transform而不是改top/left——前者走合成后者会强制触发布局重算。我在做页面滚动动画的时候把相关元素的位移全部改成transform: translate()之后掉帧问题基本消失。5.4 JavaScript执行对渲染的影响JavaScript能操作DOM、读样式、发请求能力很强但也带来了一个现实问题它频繁地修改DOM会导致布局和绘制被反复触发页面会变卡。为此浏览器引入了渲染队列的机制同一帧内的多次DOM修改会被合并到一次重排中处理而不是改一次重排一次。但在需要读取布局信息时这个优化会被打破。比如你在修改元素样式之后立刻调用offsetHeight或getBoundingClientRect()浏览器为了给你返回准确数值必须强制刷新渲染队列、立即执行布局计算。这种“写后读”操作一旦循环出现就会造成严重的布局抖动Layout Thrashing。我当时做个数据表格拖拽排序的功能就遇到过这个问题——每一行拖拽都触发大量强制同步布局几百行的表格卡得没法用。后来改成先批量收集需要的尺寸信息再统一修改样式流畅度立刻恢复正常。这条经验写成通用规则就是读布局信息归读写样式归写千万别交叉着来。另外还要提一下合成层和GPU加速。某些元素比如设置了opacity、transform、will-change的元素会被提升到独立的合成层绘制和合成交给GPU处理。合理使用能显著提升动画流畅度但也不能滥用——每个合成层都要占用内存图层数量过多反而会让性能变差。性能优化的核心永远是权衡而不是堆参数。6. 排查页面访问问题的实战经验前面把页面浏览的全链路讲完了最后这部分分享我在实际工作中排查问题的方法和踩过的坑。链路很长容易出错的地方也很多有一套固定的排查路径能省下大量时间。6.1 开发者工具Network面板的正确用法打开Chrome开发者工具切到Network面板刷新页面你看到的一排请求就是这次访问的完整“流水账”。点开任何一个请求重点看几个时间指标Queueing请求在队列里等待的时间通常是浏览器限制了并发连接数。Stalled请求发出前被阻塞的时间经常因为优先级调度或等待空闲连接。DNS LookupDNS解析耗时。Initial connection建立TCP连接可能含TLS握手耗时。TTFBTime to First Byte从发送请求到收到第一个响应字节的时间。TTFB长问题多半在服务器端或网络上。Content Download下载响应体耗时。这个值大说明资源本身不小或网络带宽受限。我在判断“页面为什么慢”的时候第一眼永远看TTFB。如果TTFB很大而Content Download正常说明瓶颈在服务器处理或网络传输前段如果TTFB正常而Content Download很长才考虑资源体积和带宽问题。这样一分问题的排查方向就清晰了。6.2 常见问题速查表整理一个我平时排查时会对照的表格覆盖最常见的几类问题现象可能原因排查方向输入网址后一直白屏DNS解析失败、服务器无响应、证书错误先看浏览器控制台报错再ping和解析域名页面内容出来了但样式全乱CSS没加载成功、Content-Type错误检查样式请求状态码和响应头图片加载不出来路径错误、防盗链、CDN跨域看图片请求状态码检查Referer策略接口请求失败跨域CORS未配置、后端异常、参数错误看响应头和后端日志核对请求参数页面偶尔打不开服务器超时、连接池耗尽、DNS不稳定看网关错误日志检查后端健康状态刷新后资源不更新缓存策略过强、版本号未变检查Cache-Control和文件名hash这里面有一个经验值得单独说很多人遇到“接口失败”第一反应是改前端代码实际上CORS问题、状态码问题在一分钟之内就能通过Network面板确认。先看请求状态和响应内容再动手改这是排查问题最基本也最有效的心态。6.3 我踩过的三个实际坑最后分享三个我在真实项目里踩过的坑都是那种“理论上不会出事实际上真的会”的案例。第一个坑是关于缓存策略的。某个页面更新了静态资源但用户端一直显示旧版本。排查之后发现服务器把Cache-Control设成了max-age604800且没有设置版本号浏览器在接下来一周内都会直接用本地缓存。解决方式是给静态资源文件名加上内容hash或者把响应头改成no-cache配合ETag验证。从那以后我对“静态资源长缓存”这件事特别谨慎——长缓存必须搭配文件名指纹否则别采用。第二个坑是混合内容Mixed Content拦截。页面本身是HTTPS但里面引了一个http://开头的图片浏览器直接把这个请求拦掉了导致图片一直加载不出来。控制台会明确提示“Mixed Content”但很多人不仔细看警告。解决很简单把所有子资源改成HTTPS或者用//协议相对地址让浏览器自动匹配。这个坑在新手写的页面上出现频率极高。第三个坑是CORS预检导致的“两次请求”。当跨域请求带了自定义头或以JSON方式发送时浏览器会先发一个OPTIONS预检请求确认服务器允许之后才发正式请求。如果服务器没正确处理OPTIONS就会出现奇怪的现象Network面板里明明有个OPTIONS请求失败了但正式请求也跟着失败。解决办法是让后端对OPTIONS请求直接返回成功并在响应头里明确允许的方法和头。6.4 一个完整的排查示例假设用户反馈某个页面访问特别慢我会这样走完整个排查流程先在开发者工具里加载页面看瀑布图定位耗时大头。假设看到TTFB高达3秒。确认TTFB长之后把问题分为“网络传输慢”和“服务器处理慢”。先看服务器所在节点是否距离用户太远ping一下看延迟再用curl模拟请求看直接请求的时间也是不是这么长。如果curl同样慢进服务器查访问日志和后端日志看具体哪个接口耗时高。假设发现数据库查询耗时2.5秒。查看慢查询日志定位到缺失索引的查询补上索引。再次验证TTFB降到500毫秒。如果还需要继续优化再考虑加一层缓存或CDN。整个过程的核心就是不要跳步先用工具定位再一层层缩小范围。每次只改一个变量改完立刻验证这样才能精准定位问题而不是靠运气把页面“调好”。我个人在实际操作中的体会是理解WEB页面浏览过程的最大价值不在于背书式的记住每个术语而在于你遇到“页面打不开”“加载很慢”“样式丢失”这些日常问题时能迅速判断问题出在哪一层——是URL写错了、是DNS没解析通、是服务器挂了、还是浏览器渲染环节出了岔子。链路上的每一个阶段都有对应的排查工具和排查思路掌握的阶段越多解决问题的时候就越从容。希望这篇文章能帮你把这条链路真正串起来下次再面对任何一个页面问题时能先想起这条从地址栏到屏幕的完整旅程而不是毫无头绪地瞎试。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进