今天参加了字节跳动的一面,面试岗位是前端开发实习生。主要是在飞书的线上面试,虽然出现了耳机没带,电脑检测不到摄像头,网络卡等问题,不过其他都挺顺利的。
本次也算是一次试水,看看自己掌握的技能和那些大厂要求的差距,补足一下自己的短板,下面打算记录一下本次面试。
面试流程
自我介绍
中规中矩,简单介绍了一下我为什么选择前端开发,是因为出于兴趣和爱好。
聊了聊我的学习历程,在大学学习了Vue(不小心说成”系统性的学习了Vue“,为后面拷打Vue的底层原理埋下了伏笔)。
还在此期间学了前后端的相关知识,另外做了一些项目,包括自己做的和老师做的。
介绍一下自己的项目经历
简单介绍了下自己的个人开发的网站,接着拿了一个Uniapp的项目举例,介绍了一下自己如何做技术选型,如何开发,如何部署。
小程序开发和网页开发有什么不一样
我主要回答了两点:
- 响应式布局
- 性能差异
围绕响应式布局问了我一些css的问题:
问:如果我要根据手机屏幕来调整显示的字体大小,该怎么做?
答:使用rem做字体单位。
问:rem具体是怎么实现的,或者说我该怎么确定rem的值?
答:应该是基于根节点的字体大小计算,具体怎么计算的我忘记了。(后期:基于根节点 font-size的比例)
问:你知道rpx吗?
答:了解过一些,是一种像素单位吧,好像是根据不同的界面比例来确定像素大小,但具体的用法不太了解。(后期:1rpx = 屏幕宽度 / 750)
问:说一说display的value有哪些?
答:block、inline、inline-block、none、flex、inline-flex、grid、inline-grid(只回答了一部分)
问:你用过flex布局吗?
答:用过,可以用来调整子级元素的布局方式。
问:那它有那些属性,或者说有哪些操作方法?
答:能再问详细点吗?(写代码的时候只知道怎么写,描述的话脑子是蒙的)
问:你知道主轴和副轴吗?
答:justify-content什么的?(忘了怎么答的了,反正好像没怎么答出来)。
问:你还了解过其他什么响应式布局的方法?
答:媒体查询吧,我大部分是用的这个,其他的没怎么了解。
围绕性能差异又问了些:
问:那你觉得小程序性能方面有什么问题?
答:因为是面向移动端,所以在资源使用方面会被平台本身限制。(显然没答在点上)(后期:通信限制、渲染层-逻辑层分离)
问:这是小程序本身平台的限制,有没有什么性能上的不足?
答:有些计算性能会变慢。(举了个手机端展示地图大量标签元素的例子)
问:那怎么解决的?
答:一个是标记点位分级,根据缩放来显示不同分级标记。
问:地图是使用的小程序原生地图吗?
答:不是,因为我们业务涉及到地图的更加细致的操作,所以原生提供的地图组件不满足业务需求。使用的webview来另起一个地图业务。
问:怎么添加标记的?
答:我们使用的openlayers做的地图底层,调用的openlayer自带的api,先通过点击事件获取坐标,再填写表单,再在相应位置添加元素。
接着问了些Vue相关的问题:
问:你觉得和Vue2相比Vue3有哪些优点?
答:性能更快,支持组合式API,开发更加灵活,对ts的支持也更完善。
问:谈一谈你对Vue的认识。
答:(直接懵了,不知道从哪方面开始答)首先是采用组件开发,更加方便。
问:描述一下双向绑定。
答:数据更新会让视图更新,视图更新也会让数据同步更新。Vue2是Object.defineProperty,Vue3是使用的Proxy。(说出来当时就后悔了)
问:那你说说defineProperty底层是怎么实现的?
答:不太清楚。(-_-)
问:那说说你开发Vue的时候,编译器是怎么吧Vue代码编译成原生代码的。
答:应该不同的编译器方法不同吧,我也不太清楚。(当时已经不知道说什么了)(后期:template → AST → render function → 虚拟DOM →真实DOM)
问一些前后端的问题
问:了解过跨域问题吗?
答:了解过,浏览器请求页面的地址和服务端响应的源地址不同的话,响应就会被浏览器拒绝。
问:浏览器URL有哪些部分组成?
答:协议类型、请求主机、端口、资源路径、请求参数
问:那怎么解决跨域呢?
答:可以在前端的config.js中配置proxy代理,也可以在后端的响应头添加可接受源字段。
问:前端代理是怎么实现的?
答:前端也是一个简单的node服务嘛,浏览器请求node服务,node再把请求代理至服务器,这样浏览器始终就是从nodejs服务发送和接收请求的。
问:那部署之后也是用node服务吗?
答:部署之后不会使用node,那时就是用nginx这类工具做代理了。
问:那后端响应头这个方法,加一个响应头就行了吗?
答:?(疑惑了一下)
问:这个响应头的内容是什么?
答:就是允许跨域访问的源地址。
问:加一个这个内容,所有请求都能全部work了?
答:不能(既然都这样问了,不能)
问:那哪些请求不能呢?
答:不知道(噗)
问:那img和静态js请求受同源策略限制吗?
答:受限制的。(后期:不受限制,img/script 标签加载是例外,img、script 标签可跨域加载,但不能访问响应内容)
问:同源策略的目的是什么?
答:为了更加安全吧,不然任何请求都能成功通信。
代码题
1,将 [‘a’, ‘b’, ‘c’, ‘d’] 转换成嵌套对象:{a: {b: {c: ‘d’}}}。
当时写了半天没写出来(数组方面都没怎么准备),说了下大致思路:做一个嵌套函数,每一层数组取最后两位转为对象。
后期:
// 输入:['a','b','c','d'] → 输出:{a: {b: {c: 'd'}}}
function buildObject(arr) {
return arr.reverse().reduce((acc, cur) => ({ [cur]: acc }))
}
2,写一个倒计时组件(Vue)要求:显示 60 秒,有暂停按钮,有重置按钮。
啪啪啪用setInterval写了一个倒计时组件。之后追问:
问:你觉得还有什么地方能够优化?
答:在暂停功能上吧,我这里暂停是直接把计时器删除了,感觉会有一些不妥。但又说不上来能怎么优化。
问:如果我要显示毫秒怎么办?
答:setInterval的时间间隔改为10,再改一下起始值和时间格式化部分。
问:那 setInterval 10ms 和 1ms 有什么区别?
答:1ms 误差更大一点,浏览器调度不能精确到 1ms
问:如何减小误差?
答:使用时间戳差值来解决。
后期:
<!-- 基础代码 -->
<script setup>
import { ref } from 'vue'
const totalTime = 60 // 秒
const timeLeft = ref(totalTime)
let timer = null
const start = () => {
if (timer) return
timer = setInterval(() => {
timeLeft.value--
if (timeLeft.value <= 0) {
clearInterval(timer)
timer = null
}
}, 1000)
}
const pause = () => {
clearInterval(timer)
timer = null
}
const reset = () => {
clearInterval(timer)
timer = null
timeLeft.value = totalTime
}
</script>
<template>
<div>
<h2>{{ timeLeft }} 秒</h2>
<button @click="start">开始</button>
<button @click="pause">暂停</button>
<button @click="reset">重置</button>
</div>
</template>
// 显示毫秒
function formatTime(ms) {
const sec = Math.floor(ms / 1000)
const ms2 = Math.floor((ms % 1000) / 10).toString().padStart(2, '0')
return `${sec}.${ms2}`
}
<!-- 使用时间戳差值 -->
<script setup>
import { ref } from 'vue'
const total = 60000 // ms
const timeLeft = ref(total)
let timer = null
let startTime = 0
let pauseOffset = 0
const format = (ms) => {
const sec = Math.floor(ms / 1000)
const ms2 = Math.floor((ms % 1000) / 10).toString().padStart(2, '0')
return `${sec}.${ms2}`
}
const tick = () => {
const elapsed = Date.now() - startTime
timeLeft.value = Math.max(total - elapsed - pauseOffset, 0)
if (timeLeft.value <= 0) {
clearInterval(timer)
timer = null
}
}
const start = () => {
if (timer) return
startTime = Date.now() - (total - timeLeft.value) // 恢复用
timer = setInterval(tick, 16) // 60fps
}
const pause = () => {
clearInterval(timer)
timer = null
pauseOffset += Date.now() - startTime
}
const reset = () => {
clearInterval(timer)
timer = null
timeLeft.value = total
pauseOffset = 0
}
</script>
<template>
<div>
<h2>{{ format(timeLeft) }} 秒</h2>
<button @click="start">开始</button>
<button @click="pause">暂停</button>
<button @click="reset">重置</button>
</div>
</template>
反思
知识盲区
知识点 | 暴露问题 |
---|---|
Vue 响应式原理 | 仅知 Proxy,不清楚 defineProperty 的底层 |
Vue 编译流程 | 完全不了解模板编译流程 |
CSS 响应式布局 | 了解 rem、rpx 用法,但不能解释细节 |
跨域 CORS | 能答出头部字段,但不知道“加一个响应头并不够” |
暴露出的问题
- 基础知识不够系统、容易被“连环追问压垮”
- 项目成果略模糊,缺乏“结果导向表达”
- 临场描述卡顿,表达略缺乏术语/层次
下一次准备
- Vue 源码概览
- CSS 响应式布局语法梳理(rem/rpx/media query/flex/grid)
- 跨域 CORS 原理总结(理解预检请求、浏览器限制对象)
- 准备 Vue 常见高频问题(响应式、生命周期、组件通信、diff 算法、双向绑定)
- reduce 类结构题多练(嵌套构造、对象映射)
- 组件类题目(计时器、列表渲染、滑动窗口、表单绑定、节流防抖)