
2023年腾讯音乐秋招前端开发岗第二批笔试我是在国庆假期后第三天晚上收到的邮件通知。当时正在改一个Vue组件的样式看到邮件标题心里咯噔一下——毕竟第一批笔试的讨论帖里有人抱怨过难度。不过我想既然能进入第二批还是值得拼一把当天晚上就把秋招以来整理的笔记过了一遍。现在回头看这次笔试整体感受是题量不小覆盖面广难度属于中上水平。不像有些大厂笔试纯粹考算法腾讯音乐的题目明显更侧重工程能力和实际业务场景这跟他们主App的H5页面、用户量级相匹配。如果准备过一段时间的项目开发再针对性刷过一些前端模块的面试题拿到面试机会的概率还是很大的。先说结论这次笔试考察内容和其他大厂前端笔试有明显区别重点聚焦在前端工程化使用规范、数据绑定实现原理、组件通信单测方案以及移动端兼容适配这几个方向。相比平铺直叙的知识点填空腾讯音乐更倾向于把知识点放进一个实际业务场景里考察看你碰到问题时有没有系统性的解决思路。1. 笔试整体框架与题型分布1.1 首批到第二批的难度变化第二批笔试相比第一批主观题占的比例明显升高。第一批还有不少选择题能靠背概念得分第二批直接取消了一部分纯理论单选把分数挪到了大题的场景设计上。收到的邮件里写得很清楚笔试时间120分钟满分100分选择题30分编程题40分问答题30分。整体时间不算宽裕特别是编程题部分如果对常用方法不够熟悉很容易被卡住。从后来和一起笔试的同学交流以及论坛上对答案的情况来看第二批的题目确实更看重“是否真正写过代码”而不是“是否背过面试八股”。比如编程题里有一道处理深层对象属性访问的题目题目表面考察的是函数传参与默认值逻辑但内里实际考的是对解构、rest操作符以及引用类型特性的理解。1.2 选择题模块占比不高但非常致命选择题一共15道每道2分覆盖了常规八股和部分偏门考点。其中印象比较深的有Vue响应式原理在新版本中的差异、浏览器事件循环中宏任务与微任务的执行顺序、CSS选择器优先级计算、HTTP缓存中强缓存与协商缓存的应用场景、ES6模块与CommonJS的差异等。选择题虽然分值不高但恰恰是拉开差距的地方。因为大题大家都能写出一些思路而选择题是客观的错一道就丢2分。我的策略是遇到拿不准的题目先标记全部写完再回头检查不在单选上纠结太久。事后证明这个策略是有效的因为后面编程题的耗时比预计长很多。1.3 笔试环境的体验这次笔试使用的在线平台支持代码高亮和自动保存整体体验还不错。需要注意的一点是平台切屏会记录超过一定次数会被警告。我隔壁宿舍有同学因为不小心切出去看了IDE里的代码片段他习惯本地写代码结果被系统标记了一次虽然没直接取消资格但心理上还是会受影响。建议模拟测试时一定走一遍环境尤其是代码编辑器部分——它支持几种语言默认的缩进是两格还是四格能否看到控制台的输出。这些细节看似不重要但真到笔试的时候手感会很不一样。2. 前端核心基础你不能只会“用”2.1 手写Promise与异步控制流不只是默写这次笔试编程题第一道就是手写一个符合Promise/A规范的简化版Promise核心要求是支持异步链式调用、值穿透以及状态不可变。看到这道题的压力点在于如果平时只是调用promise.then接口而没有真正理解then回调的返回值如何驱动链式调用大概率会在resolvePromise这部分卡住。这道题的正确解法是基于状态机的思路——pending、fulfilled、rejected三个状态每个状态只能单向迁移。代码里最关键的部分是then方法中onFulfilled和onRejected的返回值处理如果返回值是一个Promise外层状态要等待内层状态落定如果返回值是普通值则直接作为下一个then的入参。这里需要处理的核心逻辑是什么时候把当前Promise标记为resolved什么时候继续等待。我自己的实现思路是用函数递归来展开嵌套的Promise同时用定时器确保回调异步执行。有一个细节值得注意Promise的构造函数执行器是同步执行的但回调是通过queueMicrotask或Promise内部的微任务机制异步调度的。这一块如果你理解不到位后续写出错误执行顺序的代码就非常容易。做完这道题我最大的感受是手写Promise不是为了让你在生产环境造轮子而是检验你是否理解“回调地狱为何产生”以及“异步代码如何被优雅管理”。如果只刷“Promise用法面试题”而没理解状态机本质这类题很难拿到高分。2.2 函数柯里化与...args那点事编程题第二道是要求实现一个通用柯里化函数传入一个多参数函数返回一个可以分批次接收参数的函数。题目特意提到要用到rest参数也就是热搜里那个常见的关键字。这道题考察的核心点有两个函数参数个数如何获取fn.length可以得到函数声明中的形参个数但这个属性对默认值参数和rest参数的处理有坑。如果形参中使用了默认赋值或者逗号表达式fn.length的行为会发生变化需要特别留意。收集参数并判断是否执行原函数每次接收新参数时都要把当前参数合并到之前累积的参数数组中当累积个数达到fn.length时通过fn.apply(this, args)调用原函数否则继续返回一个能接收新参数的函数。function curry(fn) { const arity fn.length; const collected []; return function _curry(...args) { collected.push(...args); if (collected.length arity) { return fn.apply(this, collected); } return _curry; }; }这里有一个容易踩的坑如果直接用let或const声明collected数组它会在闭包内共享累积状态导致多次调用结果串掉。笔试完成后我复盘应该在一开始将collected放在_curry函数参数默认值中而不是闭包外层共享状态——不然第二次调用时仍然带着第一次的参数。如果你没有仔细读过函数式编程中柯里化的实现多写几个用例就能发现这些细节。2.3 Vue组件通信与数据流旧问题新问法问答题部分有一道关于Vue组件通信的题但和网上的通用面试题不同题目给了三个业务场景父子组件实时同步一个搜索框的值兄弟组件之间共享一份筛选状态跨多层级的组件向最外层触发一个事件。要求针对每个场景选择最合适的通信方案并说明理由。这里我写了三个方案v-model加emit(update:的语法糖组合适合父子同步一个简单的事件总线或者提一个状态到共同的父组件适合兄弟组件provide/inject加emit组合适合跨层级场景。但题目还追问了一个关于响应式丢失的问题——如果直接解构props给子组件是否会影响响应式更新为什么。参考答案是当从父组件传入一个普通对象子组件采用const { list } props这种方式解构新创建的局部变量本质上是一次值拷贝如果该值在父组件中发生了替换而不是原地修改子组件的视图不会自动更新。正确做法是使用computed或直接在模板中访问props.xxx。这个问题在实际项目开发中非常常见尤其是从后端接口拿数据渲染列表的时候。另外值得提醒的是题目明确要求“不要使用Vuex/Pinia”这就意味着考察的不是状态管理库的API熟练度而是对组件自身数据流机制的掌握程度。我猜他们想考察的是你是否理解组件树的通信本质而不是靠外部库来解决所有通信问题。3. 框架与工程化这是区分经验深浅的分水岭3.1 Vue2到Vue3迁移中的响应式陷阱有一道选择题非常有意思问的是在Vue3中为什么reactive只接受对象类型而不能用于基础类型。选项里有一个非常具有迷惑性的描述是“为了兼容Vue2的Object.defineProperty”但正确答案是“因为基础类型无法被Proxy直接代理需要用ref包裹一层对象”。Proxy可以代理引用类型但不能直接作用于字符串、数字这种原始值所以Vue3设计了ref这个机制。当你用ref声明一个数字可以用newProxy包装成{ value: 1 }从而让响应式系统能追踪到变化。这个设计细节如果你只看官方文档而没看源码可能理解不深。这提醒我们准备大厂笔试时不能只看怎么用更要看框架为什么要这么设计。框架的官方文档不会告诉你“Proxy不能代理基础类型”这种底层问题但你一旦理解了答起各种响应式题目就会顺很多。3.2 Vue项目规范的意义热搜词里频繁出现“前端开发规范vue”这个词我觉得很能说明问题。现在的前端笔试不再满足于问“你用过哪些生命周期”而是喜欢问“在团队多人协作的Vue项目中你会制定哪些规范来保证代码可维护性”。这个趋势在这次笔试中也有所体现。问答题的最后一题就是假设一个项目有20个人同时开发你如何保证代码风格统一、组件库使用规范、以及联调时接口定义的一致性。我的回答思路是分层规范编码规范ESLint配置共享统一使用单引号、无分号、缩进两格通过pre-commit钩子强制校验。提交规范使用Commitizen或者单写脚本约束提交信息的格式比如功能、修复、样式、重构等分类前缀。路由与组件规范页面级文件放在views目录可复用组件放在components目录并约定每个组件导出名称和文件名保持一致。接口规范使用TypeScript对接口返回数据类型做统一管理后端契约通过Swagger自动生成或者手工维护一份api.d.ts。3.3 从“会用框架”到“会写插件”选择题中有一道关于Vue自定义指令的题要求在v-model对应的输入框上同时挂载一个自动聚焦逻辑你会怎么实现。四个选项中有mounted钩子里写ref到元素也有通过directives注册自定义指令的选项。这个题目考察的是对生命周期钩子时机和DOM元素是否真实挂载的理解。在mounted中获取ref当然是可行的但如果同一个组件中有多个输入框手动在多个mounted中写重复代码显然不够优雅。用自定义指令的方式可以复用且代码更内聚。我在答题时选的是自定义指令并顺便在问答题中补充了指令的钩子函数签名。这类题目不是单纯考API记忆而是考你在项目中的“代码组织能力”。4. 网络、性能与移动端适配基础但容易丢分4.1 HTTP缓存与首屏加载优化选择题里有两道关于HTTP状态的题一道问强缓存与协商缓存分别由哪个响应头控制、命中后状态码是多少另一道结合具体场景问页面首次加载后再次刷新哪些资源会直接从缓存中读取哪些需要向服务器发请求校验。第一道题还算基础Cache-Control控制强缓存Last-Modified/ETag控制协商缓存强缓存命中状态码200from memory cache/disk cache协商缓存命中返回304。第二道题容易出错的地方在于当强缓存的有效期没过时浏览器根本不会发起请求而很多经验不足的同学误以为“刷新会让强缓存失效”。实际上只有强制刷新CtrlF5才会绕过强缓存普通刷新还是会带着缓存信息去请求服务器。另外还有一道关于Bundle体积优化的题给定一个Vue项目首屏JS体积达到2MB你会从哪些方向进行优化。常规答案是路由懒加载、第三方库CDN引入、Tree-shaking。但我额外写了splitChunks分离公共依赖、图片压缩和首屏骨架屏这样组织答案会比较全面。4.2 移动端1px边框与适配方案腾讯音乐的主场景一定在移动端所以笔试出现移动端适配题算是预期内。有一道问答题给出了一个设计稿宽度750px要求在iPhone 6/7/8375px上实现一个常见的栏底部边框视觉上等于1px考察如何写样式。这道题我看到时心里松了一口气因为这种场景我确实在项目里遇到过。方案通常是三种利用transform: scale(0.5)配合媒体查询针对dpr缩放使用border-image或者通过viewport设置rem单位换算。实际工程中用transform方案最多因为兼容性好。.border-bottom { position: relative; } .border-bottom::after { content: ; position: absolute; left: 0; bottom: 0; width: 100%; height: 1px; background-color: #e5e5e5; transform: scaleY(0.5); transform-origin: 0 0; }我还在答案中补充了在2倍屏和3倍屏上scale的取值要相应调整严谨的说法是除以设备的dpr。如果题目要求用rem适配还需要把根目录字号和测量尺寸的比例写清楚。4.3 事件循环与微任务不只是考察概念有一道代码阅读题给了一段混合了setTimeout、Promise和async/await的代码要求写出最终打印顺序。这类题如果只看面试攻略很多人会背一个“同步任务大于微任务大于宏任务”的粗略结论但实际上遇到底层细节还是会出错。关键坑点有两个await右侧的函数执行是同步的但await之后的代码会进入微任务队列。Promise构造函数内部的代码是同步的但then回调是异步的。如果题目里还有finally它的执行时机也要额外考虑。这类题目建议多刷实际运行代码用浏览器控制台跑一跑把每次输出顺序都记录一遍比背口诀有用得多。5. 编程题复盘我踩过的几个坑5.1 深拷贝的“可扩展性”陷阱有一道编程题要求实现一个深拷贝函数但输入数据不仅包含普通对象和数组还可能包含Date、RegExp、Map和Set类型同时要求不能改变原有对象。如果只写一个递归遍历然后简单赋值那对于引用类型嵌套的拷贝会出错尤其无法处理循环引用。常规实现核心思路是用一个WeakMap记录已经被拷贝过的原对象与拷贝后对象的对应关系当再次遇到同一引用时直接返回之前拷贝的结果避免死循环。对于特殊类型需要判断其构造器名或通过instanceof进行分支处理。这里有个更高级的考察点当遇到一个对象拥有不可枚举的属性或带symbol键时for...in或Object.keys都无法覆盖全部属性Reflect.ownKeys才是完整方案。我们小组后来讨论这道题发现它真正想考察的是对“可扩展性”的理解——你做的深拷贝能不能在数据格式变化后依然适用而不是只能处理简单对象。5.2 数据大列表渲染优化的“分批渲染”思路还有一道综合题假设后端一次性返回10万条数据渲染到页面上会导致卡顿要求给出至少三种优化思路。这不是经典虚拟滚动问题题目还加了“不能使用虚拟滚动库”的限制。我给出的方案是使用requestAnimationFrame和DocumentFragment分批次把数据追加到页面中每次只渲染一小部分避免主线程长时间阻塞。将数据流式切块配合setTimeout或requestIdleCallback在主线程空闲时继续任务把单次大量DOM操作拆成多次小操作。针对静态列表考虑使用content-visibility或contain: layout style来跳过屏外元素的渲染绘制。这个题目不只是考虚拟滚动原理还考察事件循环、浏览器渲染流程、长任务优化等综合知识储备。即使没有真正在项目中处理过10万条数据只要理解浏览器渲染机制也能提出合理方案。5.3 防抖节流的场景化理解防抖节流几乎是大厂笔试标配这次也不例外。腾讯音乐的题目没有直接要求手写而是让你结合搜索框联想的场景说明是选择防抖还是节流并给出代码实现。我写的是防抖因为搜索请求应该在用户停止输入300ms后再发送防止每敲一个字母就触发一次接口请求。但如果场景改为“用户持续滚动列表时按一定频率上报位置信息”这时候应该选择节流而不是防抖。实现上防抖的核心是每次触发都清除上一次的定时器重新计时节流的核心是限制在一个时间窗口内只能执行一次。两道代码都很短但如果对this指针的处理不严谨在事件监听中调用时会出现指向错误的问题。所以在回答中我一直强调用function而不是箭头函数保存原this并在闭包中用let保存定时器ID。6. 常见问题排查与备赛建议6.1 拿到题目如何逐层拆解复盘整场笔试我发现最实用的方法是拿到题目先花30秒快速把问题拆解成三层——需求层、原理层、实现层。需求层要明确题目到底让你干什么有没有隐藏条件原理层要考虑这个问题牵涉到哪些核心机制比如响应式、异步、缓存策略实现层才是用代码或者方案把前两步串起来。不要一上来就开始写代码。笔试时间虽然紧张但思考的几分钟能避免写到一半发现方向错了的情况。我第二道编程题其实一开始用闭包共享状态的方式很快就写完了结果测试用例输出了错误的结果回头才意识到collected数组状态被复用了。如果先在脑中过一遍边界条件这个问题完全可以提前发现。6.2 时间分配与做题顺序策略结合我这次笔试的经历建议把时间按照2:4:4整体分配选择题最多25分钟编程题控制在50分钟左右问答题留下40分钟。如果一道编程题卡了15分钟没有进展先跳过做问答题最后再回头补。编程题的在线判题平台通常每道题都有若干测试用例但所有用例不会一次性给出。大部分得分来自基础用例边界用例是拉分的。即使某个高级特性没写完只要基础功能正确依然能拿到不少分。我前两道题都写了完整的函数但有一道数据列表渲染优化题因为时间不够只写了方案描述把具体的优先级和代码框架写出来多少也拿了一部分分。6.3 复盘哪些准备有效哪些无效笔试结束到现在过去一周回头复盘我觉得最有效的准备有三件事一是把Vue响应式源码的底稿跑了一遍包括scheduler、effect和reactive的关系理解了之后再做框架相关选择题得心应手。二是背了常用CSS动画和移动端适配方案的通用代码实战时直接套用大幅节省时间。三是刷完了一套GitHub上整理的前端常见手写题列表其中就包含深拷贝、防抖节流、Promise、柯里化等题目。虽然不是每道都考到了但手写代码的熟练度确实靠平时积累考场上状态会稳很多。不太有效的准备也有花了很多时间在“设计模式”的八股上但这次几乎没有考到。不是说设计模式不重要而是互联网大厂前端笔试在大概率上更倾向于工程技能和底层原理记忆设计模式更多会在面试环节被追问。如果你正在准备秋招建议把时间优先分配给JavaScript核心机制、框架数据流、网络缓存这三大块。7. 给正在备赛的同学一些个人体会关于这场笔试我还有一些更主观的感受想分享。腾讯音乐这个岗位的方向和业务关联度很高从题目的场景设计能明显看出来搜索联想、列表加载、组件状态共享、接口容错全都是我在真实业务开发中会碰到的需求。这意味着他们并不希望招一个只会做“管理后台”的工程师而是期待你拥有较完整的Web前端开发技能并能理解业务交互逻辑。另外笔试的细节题非常能体现一个人是否在实际环境中落地过代码。比如编程题里的rest参数用法如果你只是看过教程但没有在自己的代码中用...args处理过不确定数量的参数那么你在面试时不一定会记得处理参数边界情况。所以准备大厂笔试不要只依赖刷那些大而全的学习资料尽量多把手边的小工具、小脚本用原生JS或Vue写出来反复迭代几次很多知识点会变得特别牢固。最后再说一点如果你接到第二批笔试通知不用因为“第二批”就觉得自己是备选或不重要。从题目难度和考点来看第二批和第一批是平行试卷各有侧重。考场上尽力发挥就好把自己积累的知识完整地展示出来整个过程本身就是很好的成长。