我在9家大厂的前端校招offer心得

27,650 阅读40分钟

本文由 杨珏成 首发于 SegmentFault,未经许可请勿转载
原文链接:segmentfault.com/a/119000002…

0x00 作者是谁

我就读于北京理工大学软件工程专业,是一名大四学生,现已入职字节跳动。从大一开始投入以前端为主的全栈开发,独立开发过多个中型和小型项目,是 佬铁|宿舍市集 小程序的社区创始人及独立项目负责人。

在学校里读书的时候,我就是一个闲不住的人。最近因为一个偶然的契机,接触到了校招季,最后定下了本科毕业就工作的规划目标。

于是我在一个月的时间里参加了9家国内大厂校招

一个月的时间里,我拿到了5家大厂的offer(含意向),包括字节跳动

利益相关:我已此文在掘金发布后入职字节跳动,字节跳动春招2月24日启动!
想要春招内推码的小伙伴直接看这里【字节跳动】部门直推&简历优化!~先到先得哦

0x01 为什么要写这篇文章

从一开始手指冒汗被怼的说不出话,到最后和面试官侃侃而谈游刃有余,我发现:同样的能力水平,在不同的面试表现下,反馈到面试官眼中的结果可以有着天壤之别

因此,如果你希望把自己的真实水平展示给面试官,那么掌握一些合适的方法是非常有必要的。

正文脉络

正文的内容聚焦于应聘大厂校招所需具备的能力,以及分析各个大厂具体的校招策略。分为两个部分:

  • 如何进入面试
  • 如何通过面试

希望能为第一次走上职场的同学们提供参考,也是对自己过去数周奔波忙碌的一个总结。

0x02 大厂前端校招:如何进入面试?

2.1 通过简历初筛

2.1.0 内推

这里一定要在开头强调一下内推,引用一下知乎@Tony的回答:

内部推荐很可能会加速你的申请过程,但很小可能增加你被录取的成功率。

每天投简历到Google的人是很多很多的。HR每天要看成千上万份通过网上递交的简历,看到你的那份时都不知道猴年马月了。何况,你真的确定你的简历能在上万份简历里stand out?

这时内推就能帮你了。内部规定HR必须在收内推的一周内查看你的简历,必须在两周内给出答复。换句话说,你算是插队了。

可是这不能提高你被录取的成功率,实力不行的人也只是插队被拒。整个面试和普通流程都是一样的。最终决定录取与否的始终是你的背景,能力,和面试表现。

2.1.1 布局与层次

不要用网上花里胡哨的模板!好的简历应该具有最合理的信息密度。hr一秒就要看到关键信息。除了文字和照片不要有其他的东西,应该是一片黑白,不多一点冗余信息。那些花花绿绿的信息只能降低你简历的信息密度。

我的建议:直接在word里写简历,不要用彩色模板。所有层次关系用字号,粗细进行区分。用竖线划分行内信息,横线划分块级内容。顶部两到三行交代清楚个人信息,右侧放职业照。

作为前端,对布局有自己的敏感度是最基本的要求。我把我的经验都运用在了自己的简历里,出于个人隐私保护,需要的同学欢迎私聊联系。

2.1.2 取舍和顺序

写简历切忌把自己的优点全部铺上去,这样只会让面试官看不懂你的简历。记住一点,不是所有的优点在别人眼里都是优势,不是所有优点都适合你所应聘的岗位

先把自己的全部简历素材整理成一个文本库,再根据应聘的不同职位精心挑选出与岗位匹配的简历内容,其中以相关性最高,最能突出个人能力的经历为先。注重语言的精炼和准确!不偏不倚的强调你的亮点才是最能吸引人的,至于亮点怎么选出来,这就要靠你自己了。

不要写自我评价!

2.2 通过笔试

笔试没有什么窍门,我个人会刷Leetcode。要是能刷100道基本上面试题就不用愁,要是刷到200道就不用惧怕校招笔试了。

我在附录2中记录了自己遇到的一些笔试题,仅供参考。

0x03 大厂前端校招:如何通过面试?

3.1 克服紧张

任何人在第一次面对面试官的时候都会紧张,即使参加了多场面试,依然会在有的时候感觉控制不住自己的状态。克服这种紧张感,你就已经比多数面试者成功了一小半。

面试官永远都喜欢你临危不乱,面对难题依然能够掌握大局的样子。并且在未来任何场合都是如此。

如何克服紧张,我这里提供两点建议

  1. 尽可能多的参加你所能参加的一切 优质 面试。好的面试可以帮助你进一步巩固自己的知识系统,最重要的是培养你的临场反应能力。
  2. 尽可能少的在内心期待面试官对你的评价。把重心放在解决问题上,不要把自己的实力依赖于面试官的认可,更要学会自我评估。

3.2 完备的逻辑思维

面试的时候,面试官总是喜欢给你几分钟的时间解决一个复杂问题:自己实现一个Vue双向绑定,写一个公平的洗牌算法,写一个css走马灯样式,获取某个字符串的全排列,写一个class的polyfill。或者是深挖你的项目经历一步步追问技术细节,让你现场考虑如何实现更好的效果。

如果没有准备,可能一下子思路就乱了,然后你后面的思路可能就完全跟不上来,可能会直接导致你挂掉这场面试。

这种时候,你要意识到面试官是在考验你的逻辑思维,面试官的目的不是要你给出一个绝对完美的解决方案,而是想看到你如何运用一套系统的逻辑思维来调用自己的知识库,最终给出你自己的思考路径。最重要的是这个过程中间的思考,一定要阐述给面试官

3.3 扎实的技术储备与工程能力

接下来是整个面试中90%时间在干的事情,也就是对你的技术储备与工程能力的考核。

一般来说,大厂的前端校招会比其他中小企业更看重对面试者的全方位考核,如果你是科班出身,校招的技术考核会包括且不限于:

  • 计算机专业基础(数据结构,算法,计算机网络,操作系统,数据库)
  • 职位相关基础(JS/ES知识体系,浏览器渲染与缓存,前后端通信,Web安全)
  • 工程实践经验(性能优化,依赖管理,依赖打包,模块化,组件化,用户鉴权,版本管理,包管理,服务器基础)
  • 主流框架理解(Vue,React二选一)
  • 部分要求极高的大厂还会考核你的理科基础(线性代数,高等数学)

另外,不同的大厂也有不同的侧重点。

技术实力最顶尖的阿里淘系FED会对你的基础知识体系以及你简历上写到的技能展开一场惨绝人寰的刨根问底,而字节跳动则更看重你的实际工程经验以及对于软件系统架构的理解。

通过每家大厂的面试策略,你也可以侧面观察出这家企业的团队技术实力和业务发展方向。

我将技术面中遇到的所有知识点整理成了一张思维导图。建议一条一条仔细查阅,对于任何没有百分百把握的知识点,我都建议你把它整理到一个列表里,逐条梳理。

PS.标星的是非必须知识,可以略过:

前端知识体系

对于不想看图的同学,我把它导出成了一份大纲,可以收藏到备忘录里。

大厂前端校招 - 知识体系

3.4 职业规划

如果你通过了以上所有考核的话,恭喜你,你离获得offer基本只剩一步之遥了。

一般到这个时候,面试官会问你对自己的职业规划。

这个问题其实也是需要自己问题自己的,最好在投递简历的时候就想清楚:

  • 为什么要去这家企业,看中它哪方面的优势(业务,技术,平台,前景)
  • 我为什么要应聘这个岗位,我对自己未来的发展方向有什么样的期许(能力,职位,技术栈,行业)

还是那句话 —— 不想当架构师的前端不是好程序员(雾)。

3.5 每次结束后一定要复盘总结

如果面试没有复盘,你参加再多的面试也和没有参加一样。不要叹息面试中出的洋相或者咒骂面试官有多么反人类,不管多么受伤都要在面试结束后立刻复盘。

现场面试最好一出场就开始回忆面试流程&写备忘录,如果是电话面试可以录音下来重听一边,捋一捋面试官的问题和自己的回答,看看自己答得如何(答成了什么b样),有没有可能答得更好。

我在附录1中记录了每次参加校招时的复盘。

0x04 各家大厂的面试体验

这里本来想仔细量化评测一下各家大厂面试流程中的面试体验,遗憾的是有些面试已经过去一段时间了,印象不是那么清晰,我担心我的评价会有失偏颇。所以就简单说一下在各家面试过程中让我印象深刻的一些事情吧。

  • 字节会给参加面试的每个人发一个包含抖音帽子在内的小礼包,还有一袋早餐,中午还有免费的自助午餐供应,非常贴心了
  • B站的老哥在问我为什么选择B站的时候,补充了一句“除了二次元情怀之外”
  • 阿里淘系FED的每一轮面试都是突袭电话面试,分别在我睡觉、飞翔、吃饭的时候打了进来,每一轮平均时间90分钟,而且会提前告诉你面试的目录……(具体可以看附录1里的 # 6.8 阿里校招二面
  • 骑着摩拜单车去美团摩拜总部的大楼参加了摩拜单车小程序开发的面试
  • 在字节认识了一个很逗的学弟和一个很可爱的负责飞聊的小姐姐
  • 腾讯的内推还是挺重要的,不然面试官可能看不到你
  • 上面这条对于多数大厂都适用
  • 上面这条不适用于字节
  • 非常希望腾讯和阿里的小哥哥看到这里不会挂掉我

0x05 写在最后:如何成为一个优秀的前端工程师

最近看到一篇文章《前端深水区》感触颇深,技术岗的最终出路一定是建立技术壁垒和影响业务决策。

在面试B站的时候,也遇到了一个让我陷入了思考的问题,面试官当时问我:“我对你的职业规划印象很好,你打算怎样去实现它呢?我给你一分钟的时间仔细思考这个问题。”

最后我回答了三句话:


保持进取,
保持谦虚,
每天坚持探索最佳实践。

与大家共勉!

附录1:大厂面试题整理

6.1 符号约定

符号 含义
现场回答正确
× 现场回答错误
? 现场无法判断是否答对
追问 对于父问题的追问
() 我在面试现场的回答思路
()右边 最合适的回答(或对现场回答的补充)
补充 面试官亲自对现场回答的补充
我向面试官提出的问题

PS. 并不是所有试题都包含以上符号,因为都是现场回忆记录,所以有很多不全的地方。

6.2 阿里实习一面

  1. 用js描述一棵树 √
  2. 非递归遍历树 ×
  3. 详述js new操作 √
  4. 方法调用模式this指向 ×
    • 追问:函数调用模式this指向 √
  5. 什么是js闭包 √
  6. 如何跨域访问 √
  7. vue的父子组件之间如何通信 ×
  8. 用css写无限循环动画 ×
  9. 如何响应式布局 √
  10. 如何清除float √
  11. 手写jsonp √
  12. 为什么禁止跨域 ×
  13. osi七层 × 漏说了
  14. tcp三次握手 √ 四次挥手 ×
  15. setTimeout为何能在单线程的js里异步执行 ×
  16. 进程和线程的区别 ?
  17. 写一个数字转中文的程序 √
  18. js函数中如何绑定this到新对象上 √
  19. bind和call有什么区别 ×
  20. 手写快排 ×
  21. 讲一下http状态码 √,其中303 304代表什么 ×
  22. 死锁的原理和四要素 √
  23. cookie和session的区别 √
  24. 同源的定义 √

6.3 Bilibili校招一面

  1. 详述es5 es6中的作用域和闭包 √(es5全局+函数级,函数化闭包,es6块级)
  2. 详述输入url到页面渲染完成 √(域名解析-TCP分包-IP寻路-握手-滑动窗口传输-持久化连接-挥手-解析-构建dom树与cssom-构建渲染树-回流-重绘-渲染)
  3. 详述js异步机制Event Loop,MacroTask和MicroTask √(1个主线程+n个任务队列,浏览器异步处理后推入队列,循环处理,一个macroTask后跟全部microtask)
  4. Promise.all的用法 √(在所有all中的promise结束后再执行)
  5. 如何让Promise.all在抛出异常后依然有效 ×(正确答案:主动reject)
  6. 什么是VueX √(状态量跨组件管理)
    • VueX中action和mutation的区别 ×(正确答案:同步和异步)
  7. 详述Vue的双向数据绑定原理 √(语法糖,dom监听+模型监听)
  8. Vue的优势 √(virtual dom,数据绑定,视图与模型分离,隐去冗杂的dom操作)
  9. 如何实现SEO优化 ?(只答了服务器端伪静态)
  10. 详述回流和重绘优化 √(回流是对物理尺寸的变更,回流一定会重绘,重绘不一定回流,因此尽量减少回流次数,将元素移出dom树再变更)
  11. 详述防抖和节流优化 √(状态锁/同步锁)
  12. 简述ES6新特性 √(块级作用域,变量不提升,let, const,箭头函数,模板字符串,promise,async)
  13. 简述箭头函数特性 ×
  14. webpack打包如何优化 ×

6.4 阿里校招一面

  1. 列举几个css中可继承和不可继承的元素 √
  2. 用css选中列表第二项 √
  3. 伪类和伪元素的区别 √
  4. h5字体如何自适应屏幕 √
  5. rpx是什么 √
    • 追问:rem是什么 ?
    • 追问:vw是什么 √
    • 追问:vw和rem区别 ×(vw根据屏幕宽度,rem根据根元素确定font-size换算比例)
  6. 什么情况下css会使用gpu加速 √
  7. css filter是什么 ×(元素的可视效果例如:模糊与饱和度)
  8. 网页如何去适配不同宽度 √
  9. 详述meta标签的作用 √
  10. position默认值和所有可能值 √
  11. 什么是sass和less √
  12. css动画最小间隔 √
  13. shadow dom是什么 ×
  14. svg和canvas的概念和区别 √
  15. canvas图层怎么用 ×
  16. dom渲染的性能损耗在哪里 √
  17. 如何高效地从1000个div中删除10个div √
  18. 如何监听img加载完成 √
  19. 浏览器里除了js还能运行什么 × (webAssembly和actionscript)
  20. promise有几种状态 × (fufilled, rejected,  pending)
  21. 如何捕获promise错误 √
  22. promise可以串联吗 ?答的不清晰
  23. 详述vue都能解决什么问题 √
  24. virtual dom如何进行diff操作 ×
  25. vue中data为什么是函数不是对象 √
  26. 为什么需要深拷贝 √
  27. 简述指针是什么 √
  28. node.js了解吗 × 不了解
  29. 进程和线程的区别 √
  30. 你自己开发的平台有多少用户和访问量 √
  31. 如何监控未处理的异常 ?(只说了监听console error)
  32. 5G是什么,为什么要用5G(开放题目)
  33. 想象一下5G的应用场景(开放题目)
  34. http和https的区别 √
  35. 为什么https不会被截获 √
  36. 量子计算机能否破、解非对称加密 √(附加)
  37. 量子计算机的原理 √(附加)
  38. 浏览器如何缓存 ?(答的不好,设置http头部)
  39. 详述http 1.0, 1.1, 2.0的区别 √
  40. 详述TCP如何保证传输完整性 √
  41. UDP和TCP有什么区别 √
  42. 为什么使用UDP × 答的不好
  43. WebSocket是基于什么协议连接 √
  44. 冒泡算法和快排时间复杂度 √
  45. 分布式系统用什么算法排序 ?半对
  46. 广搜和深搜的应用 √
  47. 广搜的数据结构 √
  48. 链式求导是什么 ×
  49. 矩阵的秩是什么 ?半对
  50. 梯度和导数,偏导 √
  51. 信息熵 √
  52. 编译原理 ?半对
  53. sql如何获取当前时间 √
  54. char和varchar区别 √
  55. drop, delete, truncate ?(半对,truncate可以持久化)
  56. python用过吗 ?做过爬虫

6.5 小米实习电话面试

  1. 作为全栈为什么报前端 √
  2. js用的哪个版本 √
  3. es6新特性 √
  4. promise有几种状态 √
  5. promise如何满足多个异步进程的同步顺序 √
  6. promise.race和promise.all的区别 ×
  7. 怎么设计页面布局(开放题目)
  8. 如何用flex让8个图标排成两行 ×(flex-wrap)
  9. 垂直居中和水平居中 ×(说反了)
  10. 你的页面是用一套界面响应不同宽度吗 √
  11. 自己如何调前后端api √
  12. vue中的fetch和axios是什么×
  13. 跨域问题怎么解决 √
  14. 跨域怎么做post ×(用iframe)
  15. 详述git操作 √
  16. vue.js 加载完成前隐藏花括号 ×(v-clock)
  17. 开发主要用react,有兴趣转技术栈吗(有)
  18. 可以实习多久

6.6 Bilibili校招二面

  1. 纯js如何获取scrolltop值 √
  2. 详述js闭包原理和意义 √
  3. 深拷贝 浅拷贝是什么 √
  4. arguments如何转数组 √
  5. 移动端和pc端click事件为什么差了300毫秒 ×(因为iphone可以双击缩放)
  6. flex布局用法 √
  7. 如何实现移动端响应式布局 √
  8. ES6的作用域 √
  9. async await是什么 √
  10. 块级作用域有哪些 √
  11. 详述promise异步机制 √
  12. 如何实现跨域访问 √
  13. http通信如何设置缓存 √
  14. 详述http状态码 √
  15. 如何实现vue组件通信 √
  16. 简述VueX的作用 √
  17. 如何实现一个swiper √
  18. hybrid是什么 √
  19. hybrid js如何调用native接口 ×
  20. 为什么要做前端
  21. 对于自己的发展规划
  22. 上海怎么样

6.7 网易校招一面

  1. 块状元素 行内元素 √
  2. 标签语义化是什么 √
  3. css清除浮动 √
  4. 什么是盒模型 √
  5. css优先级 √
  6. position属性 √
  7. 移动端适配?(媒体查询,flex,rem)还有viewport
  8. px em rem √
  9. ==和===的区别 √
  10. 原型和原型链是什么 √
  11. 什么是深拷贝 √
  12. 什么是同步 什么是异步 √
  13. 如何顺序执行10个异步任务 ?(答的不全)
  14. es6 proxy是什么 ?(不了解,说了下代理模式的概念)
  15. 题目:遍历一个任意长度的list中的元素并依次创建异步任务,如何获取所有任务的执行结果 ?(用promise.all,感觉面试官不是很满意,应该是用proxy代理)
  16. 对一个对象数组排序 ?(增加原型方法)
  17. 乱序一维数组排序
  18. 数组去重 ?(map.set,键值对象)
  19. 如何进行git的分支管理 ?(答的不全)
  20. 浏览器有哪些缓存 √
  21. 什么是跨域 √
  22. 如何解决跨域 √(jsonp,代理,白名单)
  23. 不考虑还有别的办法 √
    • 补充 本地存储,window.name,form.message
  24. 页面性能的优化 √(重绘,回流,防抖,节流
  25. 还有吗 √(懒加载,预加载)还有base64,压缩,骨架屏
  26. 浏览器安全处理 √(xss,数据库注入)还有csrf,文件上传漏洞
  27. 有没有真机浏览器debug经验(开放题)

6.8 阿里校招二面

  1. 一分钟介绍一下你的项目经历 √
  2. 是否了解淘系FED这个部门 √

一.基础部分

  1. html

    1. 如何在移动端处理兼容性 ?(css前缀)
      • 追问 工程中遇到过类似的问题(svg)
      • 追问 还有补充吗(没了…)还有meta viewport http-equiv
    2. vw em rem的区别和使用场景 √
    3. 不同定位模式之间的区别 √
    4. fix的显示问题?(宽度塌陷)没答全,还有z-index覆盖
    5. canvas如何使用图层 √
      • 追问 如何避免图层覆盖 ?(答的封装)
  2. css

    1. 所有绝对居中的实现 ?( flex, text, padding: auto)没答全,还有translate
    2. sass和less有什么区别 √
    3. 实现响应式布局的方法 √ (flex 媒体查询 rem)
      • 追问 还有吗 √ (grid)
    4. 了解过BFC吗 ×(不了解)
  3. js

    1. 用三句话概括所有值传递类型,所有引用传递类型,以及如何用引用的方式传递值类型 ?
    2. js所有基础类型 ?(布尔值,数字,字符串)还有null和undefined,symbol
      • 追问 null和undefined的区别 √(未定义和赋空值)
      • 追问 怎么比较 ×
    3. 指针和引用的区别 √(地址和别名)
    4. js当中对于不同环境的变量什么时候释放 √(标记清除和引用计数)
      • 追问 在非闭包的情况下变量什么时候会被回收?(不确定)
    5. js的作用域你怎么理解 √
    6. js里的多重继承怎么实现 √(call,es6 extend)
      • 追问 还有吗?(不知道)

二、工程部分

  1. React 和 Vue 生命周期有什么区别?(答了vue,react不了解)
  2. Vue如何监听数据的变化 √ (defineProperty,订阅者模式)
  3. Vue里如何实现父子组件之间的通信 √
  4. 了解过高阶组件吗(不了解)
  5. 看过Vue的源码吗(目前主要在理解原理阶段)
  6. 有没有使用工具构建过工程项目(vue cli+webpack)
  7. webpack编译和构建原理(分析依赖,chunk)没有说loader
  8. 平时使用什么工具转换es6(babel)
  9. babel转码流程(配置.babelrc,解析语法数,改变块级变量名等等)

三、算法部分

  1. js哈希存储结构的构成方式 √(哈希值,哈希表,哈希冲突)
  2. js当中如何实现某一个数的阶乘 ?(只答了for循环)
  3. 设计一个算法找到乱序数组中相加等于指定值的所有数对 √(快排+两端查询 / 两层for)

6.9 阿里校招三面

  1. 介绍一下你的项目(中大型小程序系统,企业控制台,Vue CLI单页面web应用等)
  2. 你的小程序是怎样一个软件(校内交易社区)
    • 追问 为什么不通过闲鱼去卖呢(解决楼内交易)
  3. 你的小程序用openid去登陆,可以讲一下OAuth流程吗(可信平台对前端发放token,后端处理敏感信息)
    • 追问 OAuth有什么优势(避免前端直接接触敏感信息)
  4. 你的小程序Websocket通信系统是做什么的(各种类型消息的实时通信)
    • 追问 你的实时语音通信是怎么做的(接口鉴权->手势状态机->本地持久化->上传服务器->缓存管理)
    • 追问 微信的录音返回数据是传回base64吗(不是 是返回tmp协议路径)
    • 追问 你的录音本地持久化的目的是什么(减轻服务器负载,减少冗余的资源重传)
    • 追问 你的本地持久化是怎么做的(用local storage)
    • 追问 你的一条语音大概有多大(几十k到几百k)
    • 追问 小程序的localstorage有多大(10-20M)
    • 追问 你的websocket为什么要做心跳(避免网络环境变化带来的通信中断)
    • 追问 你的心跳机制是怎么做的(计时器控制 超时重连 网络状态监听)
  5. 你是自己买的服务器吗(阿里云)
  6. 你的CDN服务是怎么做的(阿里云)
  7. 你的SSL是怎么做的(配置ssl证书链,非对称加密)
  8. 你的搜索为什么用Elastic Search(中文分词,倒排索引,高效)
    • 追问 你的中文分词搜索是怎么做的(IK分词器)
  9. 你的数据库用的是什么引擎(INNODB)
    • 追问 为什么用INNODB引擎(外键,索引类型,utf8mb4)
    • 追问 INNODB的锁是什么粒度级别(答的表级,不太确定)
    • 追问 对于事务的原子性有了解吗(不了解)
  10. 你的单页面web应用是怎么做的(Vue CLI+Webpack自动构建,Vue Router路由)
  11. 你的用户密码是怎么存在数据库里的(PASSWORD函数)
    • 追问 用户密码在前端传输的时候有做加密吗(有了ssl不需要)
  12. 你有没有做登陆态持久化,怎么做的(设置cookie过期时间)
    • 追问 你的服务器端怎么管理session登陆态(php自动分发)
    • 追问 怎么在多台服务器同步session数据(数据库或者分布式系统)
    • 追问 分布式怎么做(hbase或者es)
    • 追问 存储完以后怎么用php获取呢(不知道)
  13. 在中航通用实习的主要成果(独立开发web系统,数据控制台,后端服务器)
    • 追问 你的控制台管理什么数据(产品,新闻,职位,简历)
    • 追问 你的WYSIWYG编辑器是自己做的吗(基于summernote二次开发)
    • 追问 你的异步交互和事物存储是什么(AJAX+PDO)
  14. 你对于自己未来发展的规划是什么样(读框架源码-写自己的框架-掌握前后端深层知识-掌握整个软件架构)
  15. XSS,CSRF,数据库注入怎么防范(控制前端渲染,控制后端处理,预编译)
  16. 解释一下深拷贝和浅拷贝(引用传递和值传递blablabla)
  17. 平时自己是怎么关注前端领域的知识的(工具书,技术博客,官方文档,交流群)

6.10 美团校招一面

  1. 介绍一下你的项目经历
  2. 影响页面加载性能的主要因素 √
  3. 你是怎么统计页面数据的 √
    • 补充 其实这些数据可以用小程序控制台查看
  4. 你的微信OAuth登陆怎么做的 √
  5. 你的微信模板消息怎么做的 √
  6. 小程序的分包原理是什么×(用户点击时加载对应包)
  7. 如何自动构建前端项目并自动部署?(webpack+第三方插件自动化)
  8. 视差屏原理 √
    • 追问 用absolute和translate做视差哪个好 √
    • 追问 你的vue项目里为什么用了jquery,用在哪 √
  9. 数组有哪些方法 √
  10. 函数bind方法会接收什么,返回什么 √
  11. 哪些静态资源会阻塞页面渲染,怎么解决,有什区别 √
  12. 如何跨域访问 √
  13. JSONP原理 √
  14. 事件代理原理 √
  15. 你现在的实习主要做什么
  16. 能接受加班吗 以及能接受的加班时间
  17. 为什么选择美团

6.11 字节跳动校招一面

  1. 你的项目经历
  2. js有哪些基础类型√
  3. 闭包是什么√
  4. 如何循环间隔1秒输出数组元素√
  5. 如何实现事件监听√ (callback,addEventListener)
    • 追问 两者有什么区别√ (后者会被覆盖)
  6. Vue生命周期√
  7. BFC了解吗× 块级格式化上下文
  8. 画一个盒模型√
    • 追问 box-sizing√
  9. 实现一个三栏布局√
  10. websocket原理√
  11. 登陆的cookie怎么存的√
  12. www.toutiao.com转为com.toutiao.www

6.12 字节跳动校招二面

  1. 介绍一下你的项目经历
  2. 你的php包管理用什么√ composer
  3. composer的autoloader怎么实现的?
  4. php fast-cgi是什么?并发管理
  5. php set_cookie是否会改变$_COOKIE数组√不会
  6. 你的MYSQL Procedure是干什么的√函数交互
  7. 跨域请求怎么设置header字段√
  8. Vue Router原理√
  9. VueX具体应用在哪些场景内√
  10. 用过哪些Ajax组件√ Axios
  11. Axios怎么实现拦截√
  12. js二维数组反向合并√
输入:[1, 2, [3, 4], 5, 6, [7, 8], 9] 
输出: [[3, 4, 1, 2, 5, 6][7, 8 ,9]]
  1. js驼峰转换√
输入:contentType
输出:content_type

6.13 字节跳动校招三面

  1. 介绍一下你的项目经历
  2. 给我看看你上线的小程序√
    • 追问 这里瀑布流不平衡怎么回事√(用10px显示误差换取预加载带来的性能提升)
    • 追问 服务器用的什么√(阿里云腾讯云都用)
    • 追问 服务器运维了解吗√
    • 追问 服务器宕机以后怎么解锁mysql×(工程中没有遇到,不确定)
    • 追问 cpu使用率异常升高怎么解决√
  3. 描述一下你的小程序开发流程√
  4. 你的websocket是干什么的√
    • 追问 你的websocket是怎么通信的√
  5. 离线怎么获取消息√
  6. Vue Router原理√
  7. 你的发展规划(前端工程-前端架构-系统架构)
  8. 你的意向部门(C端)
  • 问 有可能去哪个部门(不确定,双向选择)

6.14 网易校招二面

坑点:没有准备耳机,视频面试官声音比较小,一开场乱了节奏

  1. 性能上面做过优化效果最好的×××(懒加载,预加载)
    • 追问 在什么情况下判断预加载(点击时利用150ms延迟进行预加载)
    • 追问 还有其他情况会用预加载吗(没有用过) 这两个是你认为最明显的吗×××(严重失误,忘记说重绘和回流以及防抖和节流,浏览器缓存,代码压缩,异步加载等等)
  2. 其他方面比如构建 组件化的拆分做过吗

后续问题整理中...

6.15 Bilibili校招三面

6.16 腾讯校招一面

6.17 小米校招一面

6.18 小米校招二面

附录2:大厂笔试题整理

7.1 腾讯校招笔试

1. 字符串解码

小明和小红用字符串压缩通信。 字符串压缩规则是:如果有连续重复的字符串比如ABCABCABC就缩写成[3|ABC]。 现有压缩后的字符串,设计一个解压程序还原字符串。

样例:
输入:
HG[3|B[2|CA]]F
输出:
HGBCACABCACABCACAF
坑点:

需要优化内存,我之所以87.5就是因为内存溢出MLE了,正在考虑用栈结构重写一次。

思路(87.5/100分):
 string decode(string s) {
    string res = "", ans = "";
    int len, start , end;
    int time, counting;
    time = 0, counting = 1;
    len = s.size();
    for (int i = 0; i < len; i++)
    {
        if (s[i] == '[')
        {
            start = i;
            for (i = len; s[i] != ']'; i--);
            end = i;
            res += decode(s.substr(start + 1, end - start - 1));
            i++;
        }
        if (counting && s[i] >= '0' && s[i] <= '9')
        {
            time = time * 10 + (s[i] - '0');
        }
        else if (s[i] == '|')
        {
            counting = 0;
        }
        else
        {
            res += s[i];
        }
    }
    char tmp = res[res.size() - 1];
    if (tmp == '\0')
    {
        res = res.substr(0, res.size() - 1);
    }
    if (time > 0)
    {
        for (int i = 0; i < time; i++)
        {
            ans.append(res);
        }
    }
    else
    {
        ans = res;
    }
    return ans;
}

int main()
{
    string s;
    cin >> s;
    cout << decode(s) << endl;
    return 0;
} 

2.识别IP

判断一个ip地址是不是私有的 已知私有IP范围是:

10.0.0.0 - 10.255.255.255
172.16.0.0-172.16.255.255
192.168.0.0-192.168.255.255
127.0.0.0/8 # 注意!这里是一个巨坑,0/8的意思代表子网掩码255.255.255.0,也就是最后8位可以有动态范围,这是一种简写方法,但是腾讯并没有说明其含义,可能也是一处考察。

样例:

输入:
0.0.0.0
输出:
false
思路(100/100分):
function isPrivate(ip){
    // TODO
    let ipVal = ip.split('.');
    ipVal[0] = Number(ipVal[0]);
    ipVal[1] = Number(ipVal[1]);
    ipVal[2] = Number(ipVal[2]);
    ipVal[3] = Number(ipVal[3]);
    if (ipVal[0] == 10) {
        if (ipVal[1] >= 0 && ipVal[1] <= 255) {
            if (ipVal[2] >= 0 && ipVal[2] <= 255) {
                if (ipVal[3] >= 0 && ipVal[3] <= 255) {
                    return true;
                }
            }
        }
    }
    if (ipVal[0] == 172) {
        if (ipVal[1] >= 16 && ipVal[1] <= 31) {
            if (ipVal[2] >= 0 && ipVal[2] <= 255) {
                if (ipVal[3] >= 0 && ipVal[3] <= 255) {
                    return true;
                }
            }
        }
    }
    if (ipVal[0] == 192) {
        if (ipVal[1] == 168) {
            if (ipVal[2] >= 0 && ipVal[2] <= 255) {
                if (ipVal[3] >= 0 && ipVal[3] <= 255) {
                    return true;
                }
            }
        }
    }
    if (ipVal[0] == 127) {
        if (ipVal[1] == 0) {
            if (ipVal[2] == 0) {
                if (ipVal[3] >= 0 && ipVal[3] <= 8) {
                    return true;
                }
            }
        }
    }
    return false;
}

3. 驼峰转换

把一个由 - 或 _ 或 @ 连接的变量词组转换成驼峰写法

样例:
输入:
content-type
输出:
contentType
思路(100/100分):
function camel(str) {
    // TODO
    let ans = "";
    let upper = false;
    for (let index = 0; index < str.length; index++) {
        const element = str[index];
        if (element == '_' || element == '-' || element == '@') {
            upper = true;
        } else {
            if (upper) {
                ans += element.toUpperCase();
            } else {
                ans += element;
            }
            upper = false;
        }
    }
    return ans;
};

4.星球开会

企鹅星球上一天有N(<200000)个小时(时间不包含0点),对应N个时区,当第1时区一点的时候第2时区已经两点了,以此类推 每个时区有Ai个人,每个时区上的人只有在[u,v)时间内有空,现在想要让尽可能多的人开会,给出开会时第一时区的时刻

样例:
输入:
3
2 5 6
1 3
输出:
3
坑点:

时区的对应有一点绕,我一开始理解成后一个时区比前一个时区落后,实际上是超前的,每后一个时区比前一个时区快1个小时,解决掉这个问题就没有大问题了。 另外要考虑一下时间复杂度的问题,我的优化比较差,最坏复杂度是O(n2/2)

思路(80/100分):
int main() {
    int n, u, v, len, pos;
    long long ans, tmp;
    cin >> n;
    vector<int> a(n, 0);
    for (int i = 0; i < n; i++)
    {
        cin >> a[i];
    }
    cin >> u >> v;
    u--;
    v--;
    len = v - u;
    pos = 0;
    if (len < n / 2)
    {
        ans = 0;
        for (int i = 0; i < n; i++)
        {
            tmp = 0;
            for (int j = 0; j < len; j++)
            {
                tmp += a[(i + j) % n];
            }
            if (tmp > ans || (tmp == ans && ((n + u - pos) % n < (n + u - pos) % n)))
            {
                ans = tmp;
                pos = i;
            }
        }
    }
    else
    {
        ans = INF;
        for (int i = 0; i < n; i++)
        {
            tmp = 0;
            for (int j = 0; j < n - len; j++)
            {
                tmp += a[(i + j) % n];
            }
            if (tmp < ans || (tmp == ans && ((n + u - pos) % n < (n + u - pos) % n)))
            {
                ans = tmp;
                pos = i;
            }
        }
    }
    cout << (n + u - pos) % n + 1 << endl;
    return 0;
}

7.2 网易校招笔试

1.超大数和一个长整型的最大公约数。

第一题的思路比较简单,就是辗转相除法,用字符串存储大数,然后分段辗转相除

辗转相除法:

假如有两个正整数p1,p2,其中p1>p2,那么必然存在两个自然数k,b,使得p1=k*p2。 如果p1,p2的最大公约数是p3,那么p2,b的最大公约数也是p3。 例如gcb(55,30)=gcb(25,30)=gcb(25,5)

2.一个数组中长度从1到n的子序列中最大值的最小值。

题目:在一个最大长度200000的数组中,分别求出长度从1到n的子序列中最大值的最小值

样例:
输入:
6
1 8 7 5 4 2
输出:
1 4 5 7 8 8

简单来说,就是把一个数组进行连续子序列的划分,从长度为1的子序列开始划分,每次划分子序列后,求出每个子序列的最大值,再求出所有这些最大值中最小的那个,一直到长度为n的子序列(序列本身)。

这题一开始把我看绕了,其实就是一道标准的DP题,然而我最后做的这题,考完才写出来。。。这次笔试基本是按照最差的答题顺序来的,估计跪了。

状态转移方程可以这样想出来:

dp[j][i]是从数组第j个数字开始的长度为i的子序列的最大值,当长度i=0(实际长度应该为1,从0开始方便些)时,dp[j][0]等于数字本身num[j],从i=1开始,dp[j][i]的长度等于MAX(dp[j][i-1], dp[j+1][i-1])也就是前后相邻的两个长度为i-1的子序列最大值中的最大值。

这题要求的是同一划分长度下所有最大值的最小值,所以在计算dp数组的同时还要计算这个值是否为当前划分长度的最小值,于是定义一个min数组,长度100000,先初始化成最大数值,每次计算dp[j][i]的时候与min[i]比较哪个值更小,一趟下来就能得到最小值了。

思路:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <algorithm>
#define MAX(x,y) ((x) > (y) ? (x) : (y))
#define INF 0x7FFFFFFF
using namespace std;
int num[100000] = { 0 };
int (*dp)[100000];
int main()
{
	int n;
	int min[100000] = { 0 };
	scanf("%d", &n);
	dp = (int (*)[100000])malloc(n * 100000 * sizeof(int));
	for (int i = 0; i < n; i++)
	{
		scanf("%d", &num[i]);
		min[i] = INF;
	}
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n - i; j++)
		{
			if (i == 0)
			{
				dp[j][0] = num[j];
			}
			else
			{
				dp[j][i] = MAX(dp[j][i - 1], dp[j + 1][i - 1]);
			}
			if (dp[j][i] < min[i])
			{
				min[i] = dp[j][i];
			}
			i = i;
		}
	}
	for (int i = 0; i < n; i++)
	{
		if (i>0)
		{
			printf(" ");
		}
		printf("%d", min[i]);
	}
	printf("\n");
	return 0;
}

3.奇偶互换

一个数组中,奇偶数可互换,求任意次互换后字典序最小的数组序列。

个人思路:没有特别好的想法

4.数组减一

给定一个长度M(<=100000)的数组,然后输入N(<=100000)个整数,每次将数组中所有大于等于该整数的元素减一,并输出改变了多少个元素,要求时间性能小于1s。

个人思路:

用二分查找结果70%结果都TLE了,经过分析认为主要是遍历数组进行减一的操作太费时间(O(n^2)的复杂度)后来考虑用一个数组储存更新过的下标分界位置来绕过遍历减一的环节,然而没写完。

7.3 大疆校招笔试

1. 玩游戏

给定暑假时间X天(<=1000),游戏数量N个(<=11),接下来N行给定每种游戏需要花费的天数(Ai),以及通关该游戏带来的成就点数(Bi),求:在暑假X天里能够达成的最高成就点数。

个人思路:
#include <iostream>
#include <vector>
#include <cassert>
#include <algorithm>
using namespace std;
 
// 需要填充一个容量为X的背包,使得成就点数最大
class Knapsack01 {
 
private:
    vector<vector<int>> memo;
 
    // 用 [0...index]的物品,填充容积为c的背包的最大价值
    int bestValue(const vector<int> &w, const vector<int> &v, int index, int c) {
 
        if (c <= 0 || index < 0)
            return 0;
 
        if (memo[index][c] != -1)
            return memo[index][c];
 
        int res = bestValue(w, v, index - 1, c);
        if (c >= w[index])
            res = max(res, v[index] + bestValue(w, v, index - 1, c - w[index]));
        memo[index][c] = res;
        return res;
    }
 
public:
    int knapsack01(const vector<int> &w, const vector<int> &v, int C) {
        assert(w.size() == v.size() && C >= 0);
        int n = w.size();
        if (n == 0 || C == 0)
            return 0;
 
        memo.clear();
        for (int i = 0; i < n; i++)
            memo.push_back(vector<int>(C + 1, -1));
        return bestValue(w, v, n - 1, C);
    }
};
 
int main() {
 
    // X为暑假天数,N为游戏数量
    int X, N;
    cin >> X >> N;
 
    int w, v;
    // vs存的是价值(成就点数)
    // ws存的是每一件物品的重量(天数)
    vector<int> vs, ws;
    for (int i = 0; i < N; i++) {
        cin >> w >> v;
        vs.push_back(v);
        ws.push_back(w);
    }
 
    cout << Knapsack01().knapsack01(ws, vs, X) << endl;
 
    return 0;
}

PS.这题我特么写成完全背包了,其实是01背包,结果只对50%。

2.输入指令:

输入指令集长度M和指令操作长度N 接下来输入M个指令(字符串)=》指令值(字符串)的映射关系 然后随机输入N个指令,要求输出对应指令值。

个人思路:

最简单的用c++ map容器,然而忘记map写法,耽误大量时间,超级遗憾。

#include <iostream>
#include <string>
#include <map>
using namespace std;

int main()
{
	map<string, string> ops;
	int x, y;
	cin >> x >> y;
	for (int i = 0; i < x; i++)
	{
		string a, b;
		cin >> a >> b;
		ops[a] = b;
	}
	for (int i = 0; i < y; i++)
	{
		string op;
		cin >> op;
		cout << ops[op] << endl;
	}
}

3. 买水果

给定N块钱,M种水果,每种水果价格Pi,其中有X种特别喜欢的水果,给定不同水果喜欢程度的排序,并要求排序靠前的水果购买量不得小于靠后的,求所有把钱花光的可能性,结果对10000007取模。

个人思路:

跪了...

7.4 字节跳动校招笔试

1. 上学闹钟O(nlogn)

小明定了n个闹钟,他只能在闹钟响起时出发去学校,每个闹钟时间分别为hi点mi分,小明家到学校要x分钟,学校上课时间a点b分 (0-24小时,0-59分钟),求他最晚几点起

输入:
3 //定了几个闹钟
5 0 //第1个闹钟的小时数和分钟数
6 0 //第2个闹钟的小时数和分钟数
7 0 //第3个闹钟的小时数和分钟数
59 //到学校要多少分钟
6 59 //上课的小时数和分钟数
输出:
6 0 //最晚的起床时间
思路(80/100分):

纯智障思路,自定义结构体存储闹钟时间,全部输入后对闹钟时间从晚到早排序,接下来从前往后遍历闹钟时间,计算从当前时刻出发到学校的时间,输出第一个能够到达学校的,由于算法很粗劣,很明显被卡边界了,没时间管了直接看下一题。

代码:
struct Time
{
    int h;
    int m;
    friend bool operator < (Time a, TIme b){
        if(a.h == b.h){
            return a.m > b.m;
        }
        return a.h > b.h;
    }
}
int main()
{
	int n, x, a, b, rest;
	cin >> n;
	Time* time = (Time*)malloc(n * sizeof(Time));
	for (int i = 0; i < n; i++)
	{
		cin >> time[i].h >> time[i].m;
	}
	sort(time, time + n);
	cin >> x;
	cin >> a >> b;
	for (int i = 0; i < n; i++)
	{
		rest = 0;
		if (time[i].h < a || time[i].h == a && time[i].m < b)
		{
			rest = (a - time[i].h) * 60 + b - time[i].m;
			if (rest >= x)
			{
				cout << time[i].h << ' ' << time[i].m << endl;
				break;
			}
		}
	}
	return 0;
}

2. 加密通信 O(n)

小明和小红采用密码加密通信,每次通信有固定的明文长度n和加密次数k。 比如:密码二进制明文是1001010,加密次数是4,则每次将密文右移1位与明文做异或操作,总共位移3次(k=4, 所以k - 1 = 3)

输入:
7 4 // n k
1110100110 //密文
输出:
1001010 //明文

解释:
1001010---
-1001010--
--1001010-
---1001010

加密次数为4,故对于明文右移4-1=3轮,每轮与当前密文进行一次异或,故1001010对应密文为1110100110

思路(100/100分):

一道标准的异或数学题,不知道该怎么归类,有一点考数学的感觉,看几眼就能看出规律了直接上代码

简单讲一下思路:

首先密文和明文第1位是一样的,看一下上方样例里的解释就懂了。 然后考虑第2到k-1位,可以发现这一段的每一位都是由前一位密文的异或结果再与当前位明文异或得到的。

接下来考虑第k到n-1位,观察规律可以发现这一段的每一位都是由前一位密文与第i-k位明文异或得到的结果再与当前位明文异或得到的。 如何消除异或影响大家应该都能理解,因此只要把参与异或的部分再与密文异或一下即可得到明文。

int main() {
	int n, k, tmp;
	string s,ans="";
	cin >> n >> k;
	cin >> s;
	ans += s[0];
	for (int i = 1; i < k; i++)
	{
		tmp = (int)(s[i] - '0') ^ (int)(s[i - 1] - '0');
		ans += tmp + '0';
	}
	for (int i = k; i < n; i++)
	{
		ans += (int)(s[i] - '0') ^ (int)(s[i - 1] - '0') ^ (int)(ans[i - k] - '0') + '0';
	}
	cout << ans;
	return 0;
}

3.发工资O(n)

王大锤要给员工发工资,员工从左到右坐成一排,每个员工知道彼此的资历,每个员工只知道自己左右员工的工资,如果某员工比左边或右边的人资历老,那他一定比这个人工资高100元,每个人最低工资100元,求王大锤最低给多少工资。

样例
输入:
4 //几个员工
3 9 2 7 //员工顺序以及对应的资历
输出:
600 //100元,200元,100元,200元

6
1 2 3 4 5 6
2100 //100,200,300,400,500,600

5
1 1 1 1 1
500 //100,100,100,100,100

8
1 2 3 4 3 2 3 4
1800 //100 200 300 400 200 100 200 300
8
3 4 3 4 3 4 3 4
1200 //100 200 100 200 100 200 100 200
5
1 2 3 4 1 
1100 //100 200 300 400 500
思路(100/100分):

广度优先搜索,可以把员工序列看作一棵多根树,每个工资最低的员工就是根节点,一个员工的工资其实就是他在多根树里的深度,

首先在输入的时候找到比左右资历都年轻的员工入队,每次从队列pop一个员工,然后判断该员工的最小工资,然后判断左右员工是否可以入队,直到所有员工出队

int main() {
	int n, now;
	long long ans = 0;
	cin >> n;
	if (n == 0)
	{
		cout << 0 << endl;
		return 0;
	}
	vector<int> epy(n, 0), depth(n, 0);
	queue<int> sal;
	for (int i = 0; i < n; i++)
	{
		cin >> epy[i];
		if (i > 1 && epy[i - 1] <= epy[i - 2] && epy[i - 1] <= epy[i])
		{
			depth[i - 1] = 1;
			sal.push(i - 1);
		}
	}
	if (epy[0] <= epy[1])
	{
		depth[0] = 1;
		sal.push(0);
	}
	if (epy[n - 1] <= epy[n - 2])
	{
		depth[n - 1] = 1;
		sal.push(n - 1);
	}
	while (!sal.empty())
	{
		now = sal.front();
		int left = (now > 0 && epy[now-1] < epy[now]) ? depth[now - 1] : 0;
		int right = (now < n - 1 && epy[now + 1] < epy[now]) ? depth[now + 1] : 0;
		sal.pop();
		if (depth[now] == 0)
		{
			depth[now] = max(left, right) + 1;
		}
		//left
		if (now > 0 && depth[now - 1] == 0 && (now == 1 || epy[now - 2] > epy[now - 1] || depth[now - 2] > 0))
		{
			sal.push(now - 1);
		}
		//right
		if (now < n - 1 && (depth[now + 1] == 0) && (now == n - 2 || epy[now + 2] > epy[now + 1] || depth[now + 2] > 0))
		{
			sal.push(now + 1);
		}
	}
	for (auto salary : depth) {
		ans += salary;
	}
	cout << ans * 100 << endl;
}