2020 字节跳动前端实习生面经

今年春招战绩算是惨败吧,纷纷挂在二、三面上。字节客户端实习毙掉不久,就被 hr 捞到了前端实习岗。挺困惑,一个完全没有 React 或 Vue 项目开发经验,对 JavaScript 也不熟悉的人怎么会符合前端岗…不过远程实习听起来很赞啊,还是决定硬上了。

一面

熟悉的牛客网,熟悉的 UI 味道,熟悉地被挂掉,唉。

一面面试官给人的感觉很好,先是让自我介绍,简单列了一下自己学过的 related courses,随后就进入到提问环节。看在我基础薄弱的份上,问题很是直接:

  1. 你对 HTTP 知道多少?

这个答案的粒度不太好掌控,太细又像是背题,太粗又没啥营养。虽然很久前看过《图解 HTTP》( [日] 上野宣),但是今天没有讲这些细节,而是从性能优化角度挑了感兴趣的 HTTP/3 的 QUIC,Preload / Prefetch 以及 HTTP/2 的二进制流。值得注意的是,自 SPDY 即后来的 HTTP/2 开始,TCP 连接复用已经被标准版实现。

  1. 大致讲一讲浏览器页面渲染的流程。

这个问题挺细,可以从 DNS 查询 -> 建立 TCP 连接 -> HTTP 请求与响应 -> DOM 树构建 -> CSSOM 树构建 -> 渲染 来讲。往细的话其实可以考到绘制 DOM 树时所涉及的编译原理的词法分析(Scanning)、语法分析(Parsing)。

除此之外还问了一些 JavaScript 基础。可能由于前端不涉及底层,这次没考 TCP/IP 以及操作系统的内容。最后是两道笔试题。

  1. 对浅拷贝、深拷贝的理解,以及手写一个 JavaScript 的深拷贝。

问题看着挺陌生的,但是在过去的开发经历中也遇到过这样的问题。当处理 reference type,尤其是 Object 的复制时,深拷贝是个绕不过去的坎。对于 Shallow Copy,原本与副本的内存地址指向一处;而 Deep Copy 将重新分配内存空间,类似于 C 中的 memcpy(),将内容完整的复制一份。对于 JavaScript 的深拷贝,可以通过递归遍历 Object.keys() 的方式实现。需要注意的是,ArrayFunction 都属于 reference type。

  1. 手写快排。

距离上次手写快排已经过去了整整一年,模模糊糊只记得“荷兰国旗问题”以及 pivot 的选取,至于两个下标 i, j 的移动细节则忘却了。事后想起自己之前还写过一篇关于 3-way quicksort 的博文,很是惭愧。

整体感觉很 nice,面试官会引导我的解题思路,答不上来时会安抚被试者情绪。很希望这样的人能当自己的 mentor。

面试结束之后,我提了一些比较发散的问题。

  1. 如果看待大规模使用 NPM 包管理后所不得不重视的供应链安全?

A: 一般项目依赖的版本都是固定的,部分企业内部也会有私有的 mirror 或者内部维护的依赖,因此一般情况下不用担心此问题。

  1. 如何看待 PWA App、原生 App 以及 Flutter 和 React Native 这种前端驱动的开发模式?

A: React Native 这样的开发方式不太适合工业环境,因为这要求开发人员不仅了解移动端技术,还得掌握前端技术。目前来说,绝大部分 App 的内容都是 Webview,例如 Amazon 和 Taobao,这也是个大趋势。

  1. 如何看待很多企业用腾讯 X5 Webview?

A: 国产 Android 的原装 WebView 实现层次不齐,腾讯 X5 还是有优势的(对于低端机)。

二面

字节的效率挺高的,一面结束后立刻就安排了二面。

二面给人感觉很差,面试官上来就问我为什么投递前端岗,然后直接进入笔试环节了。

  1. 不允许使用类和全局变量,写一个 getPrime() 函数,返回下一个质数。

看题面可知用闭包来处理,可是我说过没有系统性学过前端欸,为什么上来就考我闭包(逃。想到一个骚操作:在计算完当前一轮质数后,将本函数序列化,将变量的初始化语句用正则表达式替换,再反序列化回函数,不过面试不是日常开发,不能查 JavaScript API,所以没能写出来。面试结束之后经过一番讨论,得到了一个比较 tricky 的答案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function getPrime() {
let num = 2;
function isPrime(n) {
let res = true;
for (let i = 2; i <= Math.sqrt(n) ; i++) {
if (n % i == 0) res = false;
}
return res;
}

// 这个函数太巧妙了
return function() {
while (!isPrime(num)) {
num++;
}
return num++;
};
}

let t = getPrime();
console.log(t());
console.log(t());

注意最后这个部分,需要先通过一个变量接下 getPrime() 所返回的函数体,再执行,而不能直接 getPrime()(),否则会得到两个独立的函数实例,均返回第一个结果。

面试小插曲。

1
2
3
4
我:(有思路后开始敲测试样例)
面试官:你页面切换出去我是看得到的啊。
我:?我在写测试样例,没有切出去
面试官:哦,我就是提醒你一下。
  1. 手动实现一个 forEach(),使能原本不支持 forEach 的浏览器。

不会写。事后发现可以用 Callback 来实现,非常巧妙。首先找到 ECMAScript 2015 - Array.prototype.forEachforEach 函数将对于每一个数组中的元素执行一次传入的 callback 函数,然后返回一个三元组 $<f_j, j, f>$。

1
2
3
4
5
6
7
8
9
10
forEach = function (arr, callback) {
if (!Array.isArray(arr)) {
throw new Error("Not an array!");
}

const len = arr.length;
for (let i = 0; i < len; ++i) {
callback(arr[i], i, arr);
}
};

从二面面试官的角度看来,国内互联网企业关于实习生选择,更多的还是倾向于招“准-校招应届生”的入职即用的劳动力。

后续

跟一位在外企实习的同学交流了一番,很是羡慕:

每周聊天老大就要提醒我一次,来这里不是让我当工具人的。
我反而有些为难…我知道他是想早点让我悟出软件设计的精髓。