1. 面试前夜与整体流程复盘
先说结论:字节的前端一面,并没有传说中那么恐怖,但也绝对不是随便聊聊就能过的。我是在投递简历后大约一周收到面试邀约的,中间隔了周末,所以实际准备时间大概有五天。这五天里我把自己的项目代码从头到尾重新过了一遍,把简历上写的每一个技术点都整理成了“如果被问到就怎么答”的提纲,事后证明这个动作非常关键。
一面整体流程大概是这样的:自我介绍 → 项目深挖 → 两道手写代码题 → 网络与浏览器基础 → 反问环节。总时长大约五十分钟,节奏比我想象中要紧凑。面试官是前端团队的一位资深工程师,全程没有特别刁难,但追问密度很高,一个问题会顺着你的回答继续往深处走,不会轻易放过任何一个模糊的表达。
关于面试前的准备,我强烈建议你把简历上写过的所有技术名词都过一遍“为什么”和“怎么做”。比如你写了“使用虚拟滚动优化长列表性能”,那你至少要能说清楚虚拟滚动的原理是什么、没有它之前卡在哪个环节、你的方案里可视区高度和缓冲区是怎么算的。单纯写“用过”是远远不够的,面试官只要追问一层就会露馅。
字节的面试预约系统是内部机制,一面面试官会提前看到你的简历,所以你的自我介绍实际上是在给他一个“从哪里开始问”的线索。我的经验是,自我介绍控制在两分钟以内,重点突出两个东西:一是你做过的最有技术含量的项目是什么,二是你最擅长的技术方向是什么。其余诸如学校、专业、获奖经历,一句带过就行,面试官后续要问什么,大概率就藏在你这两分钟里。
2. 项目深挖:怎么把“小项目”讲出“大亮点”
2.1 项目深挖是面试的重头戏,简历上的每一句话都可能被拆开
一面的大部分时间其实不是花在八股文上,而是花在项目上。面试官会先让你挑一个项目来介绍,然后从你的介绍里挑出他感兴趣的点继续追问。如果你简历上有两三个项目,一定要选那个你最熟悉、最能体现你思考深度的来介绍,而不是选那个看起来最复杂的。
我介绍的是一个基于 React 的中后台管理系统,这个项目是我自己从零搭的前端工程,涉及权限控制、动态路由、组件库二次封装和接口请求层设计。面试官听完我的介绍后,没有直接问权限控制怎么实现的,而是先问了一个让我有点措手不及的问题:“动态路由你们是怎么设计的?刷新页面之后权限怎么保持?”
这个问题其实是在考我对“前端状态持久化”和“路由鉴权”的理解。我当时回答的是:登录成功后后端返回当前用户的权限列表,前端把权限列表存储在 localStorage 和内存两份中,然后在全局路由守卫里根据权限列表动态注册路由。刷新页面时先从 localStorage 里读取权限列表,再动态 addRoute,这样刷新后路由不会丢失。
面试官听完又问:“那你为什么不直接把所有路由都注册了,只是在菜单里做权限过滤?两者有什么区别?”这个问题我觉得是项目深挖阶段最核心的问题。动态注册路由的好处是:用户没有权限的页面代码根本不会被加载,减少了首屏资源体积,同时在一定程度上提升了安全性。而只做菜单过滤的话,用户还是可以通过 URL 直接访问没有权限的页面,只是看不到入口而已,这在安全层面是不合格的。
2.2 项目中的“性能优化”千万别只答一个方向
项目里如果写了“性能优化”,面试官一定会追问:“你到底优化了哪几个方面?每一个具体是怎么做的?”我的项目里提到了首屏加载优化,所以被问到了这个。我当时总结了四个方向,算是给各位一个参考:
- 路由懒加载:把每个页面级别的组件用 React.lazy 包裹,配合 Suspense 做加载态,这样首屏只加载当前路由需要的代码。
- 组件库按需引入:我用的是 Ant Design,配置了 babel-plugin-import 实现按需加载,避免把整个组件库打进主包。
- 静态资源 CDN 化:把一些不大变化的第三方库通过 externals 配置从打包中剔除,改成 CDN 引入。
- 图片懒加载和体积压缩:列表里的大图统一走懒加载,同时用 WebP 格式替代 JPEG,体积能小 30% 左右。
面试官追问的是第一个方向的细节:“React.lazy 实现原理是什么?它内部是怎么做到‘只有组件真正被渲染时才加载对应代码’的?”这个问题我当时答得磕磕绊绊,只说了基于动态 import 和 Suspense 的机制,但没有说出核心的“代码分割”和“动态加载 chunk”原理。事后我专门查了一下,React.lazy 本质上是配合 webpack 的代码分割能力,返回一个 Promise 类型的组件,Suspense 会捕获这个 Promise 的 pending 状态并展示 fallback,resolve 之后再渲染真正的组件。如果当时能把这一层逻辑说清楚,这个问题的得分会高很多。
2.3 项目被追问到不会的时候,怎么体面地“接住”这个问题
项目深挖环节最怕的是被问到不会的。但说实话,不存在完全没有盲区的人,面试官也知道这一点,他们更关注的是你遇到盲区时的反应。我的建议是:绝对不要直接说“这个我没做过”,而是说“这个点我之前没有深入思考过,我目前的理解是……,如果让我来做的话,我会先去……验证一下”。
我介绍项目时提到过“使用 Web Worker 处理大数据量的导入导出”,面试官顺势问道:“Web Worker 里面能不能操作 DOM?”这个问题我知道答案,是不能,Worker 线程没有 window 和 document 对象。我答完之后他继续问:“那如果你需要在 Worker 处理完数据后更新页面上一个很复杂的图表,消息通信的数据量又很大,怎么避免界面卡顿?”这个问题其实在考传输效率优化,我答了结构化克隆的性能消耗,以及用 Transferable Objects 转交 ArrayBuffer 的所有权来避免拷贝,面试官点头表示认可,这一段就算过关了。
3. 前端基础八股:字节一面高频知识点逐个击破
3.1 JavaScript 核心:闭包、原型链、事件循环一个都跑不掉
项目深挖之后就是纯基础考察阶段,这部分其实是个“开卷考试”,题库就那么多,就看你是不是真的理解了。字节一面最常考的几个 JS 知识点,我总结下来是闭包、原型链、this 指向、事件循环和 Promise。
闭包的考点通常是这样的:“闭包是什么?实际开发中有哪些使用场景?”我答的是:闭包就是函数能够访问其外部作用域中的变量,即使外部函数已经执行完毕。使用场景包括函数柯里化、防抖节流、模块化封装中的私有变量。然后面试官追问:“闭包能造成内存泄漏吗?怎么避免?”这个问题我答得比较流畅,因为正好踩过坑:比如事件监听器里使用了闭包引用大对象,监听器没移除,对象就永远不会被回收。解决方案是在合适的生命周期里移除监听器,或者改用弱引用。
原型链的考法更为经典。面试官会问你“new 一个对象的过程中发生了什么”,然后延伸到“instanceof 的原理是什么”。标准回答是:new 操作符会创建一个新对象,将这个对象的原型指向构造函数的 prototype 属性,执行构造函数并将 this 绑定到这个新对象上,如果构造函数返回了一个对象则返回这个对象,否则返回新建的这个对象。instanceof 是在原型链上逐层查找构造函数的 prototype 是否存在于目标对象的原型链中。如果这些你都能答得上来,他可能会继续问“Symbol.toPrimitive”或者“Proxy 和 Object.defineProperty 的区别”这类偏进阶的内容,属于追加考察,答不上来一般不会一票否决,但会留下一个“深度不够”的印象。
3.2 事件循环与宏任务/微任务:一定要手写执行顺序
事件循环这个知识点,字节一面几乎是必考的,而且常考的形式是“写出下面这段代码的输出顺序”。我遇到的这道题大概是这样的:
console.log("start"); setTimeout(() => { console.log("timeout"); }, 0); Promise.resolve().then(() => { console.log("promise1"); }).then(() => { console.log("promise2"); }); console.log("end");答案输出顺序是:start → end → promise1 → promise2 → timeout。核心逻辑是:同步任务先执行,然后执行微任务队列,微任务清空之后再取宏任务执行。这里要注意的是 Promise.then 注册的回调是微任务,而 setTimeout 的回调是宏任务,所以即使 setTimeout 的延时是 0 也会在微任务之后执行。我当时把 promise1 和 promise2 的顺序说错了一次,面试官纠正我之后我又把正确逻辑复述了一遍,算是勉强扳了回来。
关于事件循环,还有一个高频追问是:“浏览器和 Node.js 的事件循环有什么区别?”这个能答上来的话会特别加分。核心区别是:浏览器环境下微任务在每一个宏任务执行完之后都会清空,Node.js 在旧版本中微任务只在事件循环的阶段切换时执行,但新版本 Node 的行为已经和浏览器趋同了。这个点了解即可,但如果能说出来,面试官对你的评价会明显提升。
3.3 CSS 和浏览器渲染:从输入 URL 到页面展示的全过程
前端基础八股的第二部分通常会考浏览器相关的知识,最经典的题目就是“从输入 URL 到页面展示,中间发生了什么”。这个问题其实是个开放题,面试官想考察你对整个链路的理解广度,从 DNS 解析、TCP 连接、HTTP 请求,到 HTML 解析、CSS 计算、布局、绘制、合成,每一个环节都可以深挖。
我的回答分了几层:首先 DNS 解析域名拿到 IP,然后建立 TCP 连接,这里是经典的“三次握手”过程,接着如果需要 HTTPS 还会进行 TLS 握手。请求发送到服务器后,服务器返回 HTML,浏览器开始解析 HTML 构建 DOM 树,同时解析 CSS 构建 CSSOM 树。DOM 树和 CSSOM 树合并成渲染树,然后进入布局阶段,计算每个节点的几何信息,再进入绘制阶段,最后通过合成器呈现到屏幕上。
面试官听完后追问了关于“CSS 中会影响首屏渲染速度的因素”以及“重绘和回流的区别及触发场景”。这里我分享一个当时说得不够好的点:回流一定会触发重绘,但重绘不一定会触发回流。回流是指元素的几何属性变化,比如宽度、高度、位置,需要重新计算布局;而重绘只是元素外观变化,比如颜色、背景。实际操作中减少回流的方法就是把 DOM 操作合并,或者使用 transform 和 opacity 这类不触发回流的属性来替代 top、left 动画。
3.4 网络协议高频点:HTTP 缓存、HTTPS 握手、状态码
网络这块字节一面也很爱考,我遇到的是一次性问了一串问题:“HTTP 状态码你熟悉哪些?304 是什么含义?强缓存和协商缓存的区别是什么?”我按实际场景答的:强缓存是浏览器直接读本地缓存,不发请求,对应的响应头是 Cache-Control 和 Expires;协商缓存是浏览器带着缓存标识去问服务器资源有没有更新,如果没更新服务器返回 304,浏览器继续用本地缓存,对应的响应头是 Last-Modified 和 ETag。
面试官还追问了:“Cache-Control 里的 no-cache 和 no-store 有什么区别?”我自信地答了 no-cache 是每次都要去服务器验证,no-store 是完全不缓存。然后他又加了一句:“那 private 和 public 呢?”这个问题我稍微卡壳了一下,后来凭印象答了 public 表示可以被任何缓存区缓存,包括代理服务器,private 表示只有浏览器可以缓存。答完之后面试官没有继续追问,估计是过关了。
关于 HTTPS,面试官问的是“HTTPS 握手过程与 HTTP 的区别”。我的回答流程是:客户端发起请求,服务器返回证书,客户端验证证书合法性,然后通过非对称加密协商出一个对称加密的密钥用于通信,后续的数据传输都是对称加密。这里有个细节容易答错:非对称加密在整个握手中只用于密钥交换和身份验证,数据传输用的是对称加密,因为对称加密性能更好,适合大数据量传输。
3.5 框架原理:一面不考源码级细节,但基础概念必须想清楚
框架这块,字节前端一面通常不会要求你深入源码级别,比如不会直接问“React Fiber 的完整架构”,但会问一些你在实际开发中会直接接触到的基础概念。我当时被问到的是:“React 中 key 的作用是什么?用数组 index 作为 key 会有什么问题?”
这个问题的核心考点是你是否理解 diff 算法。我当时回答:key 是给每个列表项一个稳定的标识,让 React 在更新列表时能够精确判断哪个元素被新增、删除或移动。用 index 作为 key 的问题在于,当列表头部插入一条数据或发生排序变化时,React 会错误地复用旧的组件实例,可能导致状态错乱,比如输入框的值挪到了别的行。面试官追问:“那如果列表数据只有读操作,没有状态,用 index 可以吗?”我答可以,因为没有状态的话复用错乱不会产生实际影响,但如果没有后端给出的唯一 id 又确实需要更新,建议还是用内容生成的 hash 或简单自增 id。
Vue 和 React 的对比也是面试官常问的:“你两个都用过,它们在数据响应式上的核心区别是什么?”我的回答是:Vue 2 用的是 Object.defineProperty,Vue 3 改用 Proxy,可以监听对象属性的添加和删除,而 React 走的是显式 setState 触发重新渲染。面试官后来追问了 Vue 3 的 Proxy 为什么能监听到数组变化而 defineProperty 不能,这个属于加分题,能答上来的话面试体验会非常顺畅。
4. 手写代码题:最常见的三道题与解题思路
4.1 防抖和节流:字节一面手写题的“性价比之王”
字节一面手写代码题通常是两道,一道是 JS 手写题,一道是算法或数据处理题。对我来说第一道就是防抖和节流一起考:手写一个防抖函数和一个节流函数,然后解释两者的适用场景。
这个题目其实很基础,但恰恰因为基础,面试官对代码规范的要求会更高,包括参数传递、this 绑定和取消功能。我当时写的防抖是这样的:
function debounce(fn, delay = 300) { let timer = null; return function (...args) { if (timer) clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, args); }, delay); }; }节流函数我写了基于时间戳的实现:
function throttle(fn, interval = 300) { let lastTime = 0; return function (...args) { const now = Date.now(); if (now - lastTime >= interval) { lastTime = now; fn.apply(this, args); } }; }面试官看完代码之后问了一个细节:“如果你需要节流函数第一次触发立即执行,怎么改?”我把代码改成if (now - lastTime >= interval || lastTime === 0)这样,First Time 就能立即执行,面试官表示可以。他还追问了如果 setTimeout 被 debounce 函数内部管理,怎么取消定时器,我补充了 cancel 方法,这个算是我准备的加分点。
4.2 深拷贝:从浅拷贝到深拷贝的层层递进
第二个手写题是“手写一个深拷贝函数”。面试官强调了不能用 JSON.parse 和 JSON.stringify 那套方案,因为会丢失 undefined、函数、Symbol 等数据类型。我写的是递归版本,并且处理了数组、Date、RegExp 和循环引用:
function deepClone(target, map = new WeakMap()) { if (target === null || typeof target !== "object") return target; if (target instanceof Date) return new Date(target); if (target instanceof RegExp) return new RegExp(target); if (map.has(target)) return map.get(target); const result = Array.isArray(target) ? [] : {}; map.set(target, result); Object.keys(target).forEach((key) => { result[key] = deepClone(target[key], map); }); return result; }关键点有两个:一个是循环引用,我用了 WeakMap 来保存已拷贝过的对象引用,遇到已经处理过的对象就把引用直接返回,不会死循环;另一个是对特殊对象类型的处理,Date 和 RegExp 如果只做普通对象的递归拷贝,拿到的是空对象,所以要做单独判断。
面试官紧接着问了一句:“WeakMap 和 Map 有什么区别?为什么这里用 WeakMap?”我回答 WeakMap 的键是弱引用,不会阻止垃圾回收机制回收这个键对象,对于深拷贝这种临时数据,用 WeakMap 可以在每次拷贝结束后让对象被回收。如果这里用 Map,键对象会一直持有引用,即使拷贝结束也无法释放,会导致内存泄漏。
4.3 算法场景题:大数据去重与排序的实战思路
字节一面不太会考特别偏门的算法题,我遇到的第二道手写题是“给定一个超大数组,用最高效的方式去重并排序”。这道题我第一反应是用 Set 去重再 sort,复杂度是 O(n log n),但面试官紧接着追问:“如果数据量是几百万级别,怎么优化?”
这个问题其实是在考你是否会想到基于数据特征的排序方案。如果数据范围有限,比如都是 0 到一百万之间的整数,可以用桶排序思路,时间复杂度能做到 O(n)。我没能用桶排序答出完美方案,只说了“如果数据范围有限,可以用计数排序”,面试官没有让我继续写,但我能感觉到他更想听的是分治思路或者利用数据特征来避免全局排序,建议大家在准备阶段把哈希表、桶排序、快速排序的时间空间复杂度都过一遍。
另外一面还可能会出“实现一个简单版本的 Promise”或者“用 reduce 实现 map”这类基础题。我虽然没有写 Promise,但身边同学反馈字节确实爱考 Promise 实现,尤其是 Promise.all 和 Promise.race,建议大家一定要重点刷这两个,尤其是要处理入参不是 Promise 的情况和异常分支。
5. 网络与浏览器基础:字节一面提问角度与答题策略
5.1 从“三次握手”到“为什么要三次”的追问逻辑
网络基础这块,字节喜欢以“为什么”为引子来考察你是否真的理解,而不只是背住了结论。比如“TCP 为什么需要三次握手,而不是两次?”这个问题我答的是:三次握手能让双方都确认自己的发送能力和接收能力是正常的。第一次握手是客户端发起 SYN,告诉服务端“我这边要连接你了”;第二次握手是服务端回 SYN + ACK,表示“我收到了你的请求,我也准备好连接”;第三次握手是客户端确认收到服务端的 SYN + ACK,表示“我知道了你也准备好了”。两次握手无法让服务端确认客户端的接收能力是否正常,无法防止历史重复连接请求造成资源浪费。
这个问题的延伸是“如果第三次握手丢失了会怎样”,属于超纲但可能被问到的问题。我当时的回答是:服务端会认为连接请求没有成功,可能会在一定时间内重发 SYN + ACK,客户端收到重复的 SYN + ACK 后会再次发送 ACK,直到连接建立或超时。
5.2 浏览器缓存:从 request header 到 response header 完整链路
关于浏览器缓存,字节一面会问到的是“你平时是怎么利用强缓存和协商缓存来做性能优化的”。这个问题结合项目来答会比较好。我当时的回答是:静态资源(JS、CSS)打包后通常文件名带 hash,服务器返回时设 Cache-Control: max-age=31536000,这样 hash 不变的文件在一年内直接走强缓存,只有 hash 改变才回源请求。而对于 HTML 这类可能需要及时更新的文档,就用 no-cache 配合 ETag 走协商缓存,每次请求都去服务器验证,服务器返回 304 就用本地缓存。
面试官追问:“你看过 devtools 里 Network 面板吗?Size 列显示 from disk cache 和 from memory cache 的区别是什么?”我解释前者是从硬盘缓存加载,后者是直接从内存缓存加载。内存缓存读取速度更快,但进程结束就会被释放,而磁盘缓存持久性更强。通常来说,当前页面中使用的资源更可能被存到内存缓存中。
5.3 跨域问题:从 CORS 配置到 JSONP 和代理的原理对比
跨域在浏览器基础里也是高频考点。面试官问的是:“你在开发中遇到过跨域吗?怎么解决的?”我回答的是:开发环境通过 webpack devServer 的 proxy 代理,把/api开头的请求转发到后端服务,这样浏览器看到的是同源请求。生产环境是后端在 Nginx 里配置反向代理。如果后端 API 直接在浏览器跨域调用,需要后端配置 CORS 响应头。
面试官顺势问:“CORS 的实际请求和预检请求有什么区别?”我答:简单请求直接发实际请求,带上 Origin 请求头,服务器返回 Access-Control-Allow-Origin;非简单请求,比如使用自定义 header 或者 Content-Type 是 application/json,浏览器会先发一个 OPTIONS 预检请求,确认服务器允许后才会发实际请求。他追问:“预检请求怎么避免?”我回答:用 GET 或 POST 配合简单请求,content-type 改成 text/plain,但这只是理论方案,实际开发中更推荐让后端把必要的 CORS 头配置完整,不要为了省一次预检去牺牲接口设计的合理性。
6. 面试中的软技能与临场发挥:这些细节直接影响评分
6.1 自我介绍怎么讲才能把节奏拉到自己准备的领域
关于面试的软技能,很多人会忽略,但它实际影响面试官对你的整体印象。一面开场自我介绍,你完全可以主动引导话题走向。比如你强项是 React,你就在自我介绍里多提 React 项目细节;你强项是网络,你就说自己在项目中深入理解过 HTTP 缓存机制。这样面试官后续问问题大概率会从这些你熟悉的方向切入。
我自己当时的自我介绍花了大约一分半钟,结构是这样的:先一句话概括我是谁,就读于哪个学校、什么专业;然后重点说自己做过什么项目,用到了什么技术栈,自己负责了哪个模块;最后收尾说自己目前在持续学习的方向是什么。收尾这个点其实很有用,因为它给了面试官一个补充提问抓手,比如我当时说了“我在学习 Node.js 的后端开发”,面试官后面就顺势问了一个“你觉得 Node.js 和前端的异同”的问题,这个问题我准备过,答得也算完整。
6.2 遇到不会的问题,别慌,先复述一遍问题
面试中一定会遇到你不会的题。我那次面试也有一道题是没答上来的,是关于“浏览器如何调度 TCP 拥塞控制”的,属于计算机网络的进阶内容,我平时确实没有深入看过。当时我的处理方式是:先复述一遍问题,确认自己是否理解正确,然后说“这个知识点我日常接触不多,但我对 TCP 的可靠传输和滑动窗口有一定了解,如果我需要去解决这个问题,我会先去查阅 RFC 文档”。面试官没有因为这个回答否定我,反而点了点头。
复盘来看,这样的回答至少传递了三个正面信息:第一,你够诚实,不会乱编;第二,你有基础认知,不是完全空白;第三,你有解决问题的思路和搜索能力。反而最危险的情况是,你明明不会却开始胡编,一旦面试官追问两层就会彻底暴露,且印象分掉得厉害。
6.3 反问环节是加分项,别只问“什么时候有结果”
很多面经都提醒过反问环节的重要性,但实际操作中还是有很多人只会问“面试结果什么时候出来”。这个问题的确不违规,但太常规了,浪费了一个展示你自己的机会。我当时的反问有两部分:第一,问面试官“你们团队目前前端主要的技术栈是什么?有没有在引入新的框架或工具?”第二,问“如果是实习生入职,会先被安排做什么类型的任务?”这两个问题都是在表达你对这份工作的真实兴趣,也能帮你判断团队实际情况和你的预期是否匹配。
面试官是我问到第二个问题的时候明显话多了起来,讲了团队分工和新人培养机制。虽然这不一定直接给我加分,但至少让我对整个面试氛围的观感好了很多。如果你准备充足,反问环节还可以问一些更细节的技术选型问题,比如“团队在微前端和性能监控上是怎么做权衡的”,这种问题能直接让面试官感受到你思考过他们的技术场景。
7. 复盘与心得:一面结束后我重新补了哪些知识短板
面试结束后我花了大概三个小时把全程问题在备忘录里逐条复盘,凡是回答得磕巴或不确定的地方都标记出来,然后逐个查资料补全。这里分享一个我的心得:面试最大的收获不是 offer 本身,而是那张“问题清单”,它能精准暴露你的知识盲区。如果一面挂了,带着这份清单把盲区补好,二面或者其他公司面试完全来得及。
我复盘之后发现自己的短板主要在三块:一是事件循环里微任务和宏任务在各种嵌套场景下的执行顺序,二是浏览器缓存中各种响应头组合搭配的实际行为,三是 TCP 拥塞控制这类偏计算机网络底层的知识。第一块我重新手写了各种 console.log 顺序的代码,在浏览器和 Node 两个环境里跑了一遍;第二块我用本地起了个简单服务器,设了不同的 Cache-Control 值,然后在 Network 面板里观察请求是否走缓存、是否发预检请求,实验验证过的记忆远比背下来的牢固;第三块我看了几篇文章和图示,能做基础概念解释,但要说深入理解还需要更多积累。
关于字节一面到底难不难的结论,我的个人体验是:难的不是问题本身,而是问题之间的串联深度。面试官不会问你“闭包是什么”就结束,他一定会追问“闭包在项目里哪里用到了”以及“如果用闭包导致内存泄漏你怎么定位”,这种连环追问会把你对知识点的理解投射到一个非常立体的层面。所以准备面试的最高效方式不是刷题背答案,而是针对每个知识点都自问三轮:是什么、为什么、项目里怎么应用。
最后分享一个小技巧:如果是视频面试,提前检查好电脑的摄像头、麦克风和 Chrome 浏览器权限。我面试当天调试设备花了五分钟,隔壁房间还在装修,隔着门传来电钻声,虽然不影响后续沟通,但确实增加了一点紧张感。提前把这些环境问题处理好,能让你从一开始就保持一个好的状态。另外准备好一张纸和一支笔,手写代码题有时候需要你推演思路,写在纸上会比干想有效率得多。