热乎的字节跳动面经总结

1,917 阅读16分钟

写在前面

2022届秋招早在7月中下旬已经打响集结号,各路大厂都进行了几轮笔试面试了,已有许多学子已经拿到一些offer。由于现在内卷严重,导致现在秋招越来越早,对于应届生的技术要求也越来越高,知识面也要求愈发广。九月份十月份是秋招的黄金时段,现在快备点面试题,刷些算法题吧。下面带来的是十一同学字节跳动前端的真实面试经历。

一面

简单介绍

  • 自我介绍
  • 创业项目承担的角色
  • 创业项目的成果、流量之类的

技术问答

1. 能说说不同数据结果对应的一些场景吗?

1)数组

  • 优点:按照索引查询元素速度快,按照索引遍历数组方便
  • 缺点:数组的大小固定后就无法扩容了,数组只能存储一种类型的数据,添加、删除的操作慢,因为要移动其他的元素。
  • 应用场景:频繁查询,对存储空间要求不大,很少增加和删除的情况

2)栈

  • 应用场景:括号匹配问题;逆波兰表达式求值问题;实现递归功能方面的场景,例如斐波那契数列。

3)队列

  • 应用场景:因为队列先进先出的特点,在多线程阻塞队列管理中非常适用。

4)链表

  • 优点:链表是很常用的一种数据结构,不需要初始化容量,可以任意加减元素;添加或者删除元素时只需要改变前后两个元素结点的指针域指向地址即可,所以添加,删除很快。
  • 缺点:因为含有大量的指针域,占用空间较大;查找元素需要遍历链表来查找,非常耗时。
  • 应用场景:数据量较小,需要频繁增加,删除操作的场景;快慢指针:求中间值问题、单向链表是否有环问题、有环链表入口问题;循环链表:约瑟夫问题。

5)树

  • 应用场景:JDK1.8中 HashMap的底层源码中用到了数组+链表+红黑树;磁盘文件中使用B树做为数据组织,B树大大提高了IO的操作效率;mysql数据库索引结构采用B+树

6)哈希表

  • 应用场景:哈希表的应用场景很多,当然也有很多问题要考虑,比如哈希冲突的问题,如果处理的不好会浪费大量的时间,导致应用崩溃。解决哈希冲突问题:可以对数组扩容;优化hash计算方式。

7)堆

  • 应用场景:因为堆有序的特点,一般用来做数组中的排序,称为堆排序。

8)图

  • 应用场景:道路畅通工程,最短路径

2. 作用域的理解

作用域:指程序中定义变量的区域,它决定了当前执行代码对变量的访问权限。

javascript 中大部分情况下,只有两种作用域类型:

  • 全局作用域:全局作用域为程序的最外层作用域,一直存在。
  • 函数作用域:函数作用域只有函数被定义时才会创建,包含在父级函数作用域 / 全局作用域内。

由于作用域的限制,每段独立的执行代码块只能访问自己作用域和外层作用域中的变量,无法访问到内层作用域的变量。

/* 全局作用域开始 */
var a = 1;

function func () { /* func 函数作用域开始 */
  var a = 2;
  console.log(a);
}                  /* func 函数作用域结束 */

func(); // => 2

console.log(a); // => 1

/* 全局作用域结束 */

3. 闭包的理解

闭包:能够访问其他函数内部变量的函数,被称为 闭包。即闭包就是函数内部定义的函数,被返回了出去并在外部调用。

function foo() {
  var a = 2;

  function bar() {
    console.log( a );
  }

  return bar;
}

var baz = foo();

baz(); // 这就形成了一个闭包

闭包可以绕过作用域的监管机制,从外部也能获取到内部作用域的信息。

闭包的应用场景:闭包的应用,大多数是在需要维护内部变量的场景下。

滥用闭包容易导致内存泄漏。这种由于闭包使用过度而导致的内存占用无法释放的情况,我们称之为:内存泄露

内存泄露 是指当一块内存不再被应用程序使用的时候,由于某种原因,这块内存没有返还给操作系统或者内存池的现象。内存泄漏可能会导致应用程序卡顿或者崩溃。

内存泄露的排查手段:

  • Performance
  • Memory

如何避免内存泄漏:

  • 使用严格模式,避免不经意间的全局变量泄露
  • 关注 DOM 生命周期,在销毁阶段记得解绑相关事件
  • 避免过度使用闭包

4. 说说TCP和UDP的区别

TCP是面向连接的,需要经历三次握手建立连接,四次握手断开连接,因此具有可靠性,且不会对报文进行操作,只是报文的搬运工。TCP仅支持单播传输,面向字节流,可以提供拥塞控制。

UDP是面向无连接的,不需要进行三次握手,不具有可靠性,具有单播、多播以及广播的功能。且是面向报文的,头部开销小,传输数据报文时很高效。

简而言之:

  • TCP向上层提供面向连接的可靠服务 ,UDP向上层提供无连接不可靠服务。
  • 虽然 UDP 并没有 TCP 传输来的准确,但是也能在很多实时性要求高的地方有所作为
  • 对数据准确性要求高,速度可以相对较慢的,可以选用TCP

5. 为什么HTTP3.0要使用UDP协议

HTTP/3选择了UDP,主要是为了解决对头阻塞问题。它的底层协议,就是大名鼎鼎的QUIC,一个运行在传输层(也可以说是应用层)的协议。与TCP不同的是,QUIC代码并没有硬编码在操作系统内核中,而是完全运行在用户空间的,默认集成了TLS。

  • QUIC能够实现TCP协议的所有功能性需求,并集成了TLS,功能上赶超了TCP
  • 一条连接,多个stream并发传输,真正的多路复用,有效解决队头阻塞现象,性能上超越了TCP
  • 减少握手次数,尤其是带TSL传输的握手次数,有更低的握手延迟
  • 由于易于升级,为协议的更新和发展,提供了巨大的想象空间

6. 怎么实现一个v-model

v-model 实际上就是 $emit('input') 以及 props:value 的组合语法糖,只要组件中满足这两个条件,就可以在组件中使用 v-model。

父组件

new Vue({
 components: {
   CompOne,
 },
 data () {
   return {
     value: 'vue'
   }
 },
 template: `
   <div>
     <comp-one v-model="value"></comp-one> // 使用v-model 满足语法糖规则:属性必须为value,方法名必须为:input
   </div> 
 `,
}).$mount(root)

子组件:

// 修改子组件 
const CompOne = {
  props: ['value1'],
  model: {
      prop: "value1", // 接收的数据 value => value1
      event: "change" // $emit 需要绑定的事件 input => change
  },
  template: `
    <div>
      <input type="text" @input="handleInput" :value="value1"></input>
    </div>
  `,
  methods: {
    handleInput (e) {
      this.$emit('change', e.target.value)
    }
  }
}

7. 说说nextTick原理

nextTick中的回调,是在下次DOM更新循环之后执行的回调。在修改数据之后立即使用这个方法,获取更新后的DOM。

实现的主要思路就是:采用微任务优先的方式调用异步方法去执行nextTick中的方法。其实就是事件循环机制的。

8. 事件循环机制

JavaScript代码的执行过程中,除了依靠函数调用栈来搞定函数的执行顺序外,还依靠任务队列(task queue)来搞定另外一些代码的执行。整个执行过程,我们称为事件循环过程。一个线程中,事件循环是唯一的,但是任务队列可以拥有多个。任务队列又分为macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs。

macro-task大概包括:

  • script(整体代码)
  • setTimeout
  • setInterval
  • setImmediate
  • I/O
  • UI render

micro-task大概包括:

  • process.nextTick
  • Promise
  • Async/Await(实际就是promise)
  • MutationObserver(html5新特性)

事件循环机制

主线程从”任务队列“读取异步任务执行,这个过程是循环不断的,所以我们又称这个过程是事件循环或者Event Loop。

JavaScript 的代码执行时,主线程会从上到下一步步的执行代码,同步任务会被依次加入执行栈中先执行,异步任务会在拿到结果的时候将注册的回调函数放入任务队列,当执行栈中的没有任务在执行的时候,引擎会从任务队列中读取任务压入执行栈(Call Stack)中处理执行。

完整的事件循环机制:

  • 首先,整体的script(作为第一个宏任务)开始执行的时候,会把所有代码分为同步任务、异步任务两部分。

  • 同步任务会直接进入主线程依次执行,异步任务会再分为宏任务和微任务。

  • 宏任务进入到Event Table中,并在里面注册回调函数,每当指定的事件完成时,Event Table会将这个函数移到Event Queue中。微任务也会进入到另一个Event Table中,并在里面注册回调函数,每当指定的事件完成时,Event Table会将这个函数移到Event Queue中。

  • 当主线程内的任务执行完毕,主线程为空时,会检查微任务的Event Queue,如果有任务,就全部执行,如果没有就执行下一个宏任务

  • 上述过程会不断重复,这就是Event Loop,比较完整的事件循环

9. 先进行nextTick后修改值,nextTick取到的值是修改前的还是修改后的,为什么?

nextTick取的是修改前的值,因为每次更新数据都得在下次更新DOM才能重新渲染数据。

nextTick中的回调,是在下次DOM更新循环之后执行的回调。在修改数据之后立即使用这个方法,获取更新后的DOM。

10. 介绍一下为什么要使用mutationObserver?

Mutation observer 是用于代替 Mutation events 作为观察DOM树结构发生变化时,做出相应处理的API。

11. 实现Promise.all

Promise.all的特点在于:

  • 接收一个Promise实例的数组或具有Iterator接口的对象
  • 如果元素不是Promise对象,则使用Promise.resolve转成Promise对象
  • 如果全部成功,状态则变成resolved,返回值将组成一个数组传给回调
  • 只要有一个失败,状态就变成rejected,返回值将直接传递给回调
  • Promise.all()的返回值也是一个新的Promise对象
Promise.all = function(arr){
    return new Promise((resolve,reject) => {
        if(!Array.isArray(arr)){
            return reject(new TypeError('arguments must be an array'));
        }
        let length = arr.length;
        let resolveNum = 0;
        let resolveResult = [];
        for(let i = 0; i < length; i++){
            arr[i].then(data => {
                resolveNum++;
                resolveResult.push(data)
                if(resolveNum == length){
                    return resolve(resolveResult)
                }
            }).catch(data => {
                return reject(data)
            })
        }
    })
}

二面

简单介绍

  • 自我介绍
  • 你在创业项目中学到了什么?
  • 能说说你实习项目?(项目背景、项目功能、项目结构,你能做什么,带来了什么收益?)

技术介绍

1. 说一下打印结果

function ClassA(){
  this.x = "hello";
}

ClassA.prototype.x = "world";

const a = new ClassA();
a.x = "what";
console.log(a.x);
delete a.x;
console.log(a.x);
a.x = undefined;
console.log(a.x)

2. 说一下打印结果

function someFunction(){
  let a = 0;
  return function(){
    return a++;
  }
}

const f1 = someFunction();
const f2 = someFunction();

console.log(f1())
console.log(f2())

const f = someFunction();
console.log(f())
console.log(f())

3. 说说作用域与闭包

见一面

4. webSocket怎么去做消息确认呢?没有ajax的回调这种。

Websocket是传统TCP套接字之上的一个相当薄的抽象层,只有当对等方通过应用程序级协议明确地确认它时,远程客户端将为您将发送它的每个数据包发送TCP ACK数据包。但是这个事实很好地隐藏在应用程序中,仅接收ACK数据包意味着远程TCP堆栈已处理它,但没有说明客户端应用程序如何(以及如果)处理它。

5. 能说说你实习项目做了哪些优化呢?

6. 能说说你在实习项目中踩了那些坑?

7. BFC是什么、触发条件是什么?BFC解决了哪些问题?外边距重叠如何解决?

BFC:Block Formatting Context(块级格式化上下文),是一个独立的渲染区域,让处于 BFC 内部的元素与外部的元素相互隔离,使内外元素的定位不会相互影响。

BFC 的触发条件,即存在以下几种方案可创建 BFC:

  • 浮动元素, float 值不为 none
  • 绝对定位元素,position 属性为 absolute ,fixed
  • 非块级盒子的块级容器( display 值为 inline-blocks , table-cells , table-captions 等)
  • overflow 的值不为 visible ( visiable 是默认值。内容不会被修剪,会呈现在元素框之外)
  • 除此之外,根元素, HTML 元素本身就是 BFC( 最大的一个BFC )

BFC作用:

  • 自适应两栏布局:阻止元素被浮动的元素覆盖,自适应成两栏布局
  • 清除内部浮动:解决浮动元素不占高度的问题(浮动元素未被包裹在父容器)
  • 解决 margin 重叠:为了防止 margin 重叠, 可以使多个 box 分属于不同的 BFC 时
  • 阻止元素被浮动元素覆盖

BFC如何解决外边距重叠:

  • 内部的盒子会在垂直方向,一个一个地放置;
  • 盒子垂直方向的距离由 margin 决定,属于同一个 BFC 的两个相邻 Box 的上下 margin 会发生重叠;
  • 每个元素的左边,与包含的盒子的左边相接触,即使存在浮动也是如此;
  • BFC 的区域不会与 float 重叠;
  • BFC 就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素,反之也是如此;
  • 计算 BFC 的高度时,浮动元素也参与计算。

8. Vue有没有遇到数据改变视图没有更新的情况?

有,可以使用nexttick解决数据没有及时更新。

9. 说说UDP和TCP的区别?为什么TCP是可靠的?TCP的特性除了你说的面向连接、可靠的、字节流,还有什么?

见一面

10. 如何实现垂直水平居中?

方法一:

div.box{
  weight:200px;
  height:400px;
  <!--把元素变成定位元素-->
  position:absolute;
  <!--设置元素的定位位置,距离上、左都为50%-->
  left:50%;
  top:50%;
  <!--设置元素的左外边距、上外边距为宽高的负1/2-->
  margin-left:-100px;
  margin-top:-200px;
}

*兼容性好;缺点:必须知道元素的宽高

方法二:

div.box{
  weight:200px;
  height:400px;
  <!--把元素变成定位元素-->
  position:absolute;
  <!--设置元素的定位位置,距离上、左都为50%-->
  left:50%;
  top:50%;
  <!--设置元素的相对于自身的偏移度为负50%(也就是元素自身尺寸的一半)-->
  transform:translate(-50%,-50%);
}

*这是css3里的样式;缺点:兼容性不好,只支持IE9+的浏览器

方法三:

div.box{
  width:200px;
  height:400px;
  <!--把元素变成定位元素-->
  position:absolute;
  <!--设置元素的定位位置,距离上、下、左、右都为0-->
  left:0;
  right:0;
  top:0;
  bottom:0;
  <!--设置元素的margin样式值为 auto-->
  margin:auto;
}
*兼容性较好,缺点:不支持IE7以下的浏览器

11. 说说你对CDN的理解?CDN回源是什么?回源是协商缓存还是强缓存?

CDN (Content Delivery Network,即内容分发网络)指的是一组分布在各个地区的服务器。这些服务器存储着数据的副本,因此服务器可以根据哪些服务器与用户距离最近,来满足数据的请求。CDN提供快速服务,较少受高流量影响。

“回源”就是说 CDN 发现自己没有这个资源(一般是缓存的数据过期了),转头向根服务器(或者它的上层服务器)去要这个资源的过程。

12. 说说负载均衡?

负载均衡是高可用架构的一个关键组件,主要用来提高性能和可用性,通过负载均衡将流量分发到多个服务器,同时多服务器能够消除这部分的单点故障。

负载均衡器本身就是一个单点故障隐患,可以考虑负载均衡双机热备或其他方案消除单点故障提高可用性。

13. 算法题

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现偶数次。找出那个只出现一次的元素,请编程实现。

14. 算法题:树的层级遍历

15. https为什么是安全的?非对称加密解密的过程?

  • http是超文本传输协议,信息是明文进行传输的。https是具有安全性的ssl加密传输协议,为浏览器和服务器之间的通信加密,确保数据传输的安全。HTTP协议通常承载于TCP协议之上,在HTTP和TCP之间添加一个安全协议层(SSL或TSL),这个时候,就成了我们常说的HTTPS。

  • HTTPS选择了握手时交换密钥的方案。

  • 总的来说,握手过程中,服务器会发出一张证书(带着公钥),客户端用公钥加密了一段较短的数据S,并返回给服务器。服务器用私钥解开,拿到S。此时,握手步骤完成,S成为了一个被安全传输到对方手中的对称加密密钥。此后,服务器与我的请求响应,只需要用S作为密钥进行一次对称的加密就好。

16. 你知道中间人攻击吗?xss如何去预防?如果让你用js实现xss预防,你会如何做?

跨站脚本攻击(xss):恶意攻击者通过往web页面中插入恶意html代码,当用户浏览该页面时,嵌入web里面的html代码会被执行,从而达到恶意攻击用户的特殊目的。

xss如何进行预防:

  • 输入过滤:防止 HTML 中出现注入,防止 JavaScript 执行时,执行恶意代码。
  • 预防存储型和反射型XSS攻击:改成纯前端渲染,把代码和数据分隔开,对 HTML 做充分转义。
  • 预防 DOM 型 XSS 攻击:DOM 型 XSS 攻击,实际上就是网站前端 JavaScript 代码本身不够严谨,把不可信的数据当作代码执行了。如果不可信的数据拼接到字符串中传递给这些 API,很容易产生安全隐患,请务必避免。

hr面

  • 简单自我介绍
  • 你有对象不,未来你们是在一个城市,还是异地呢?
  • 如果你晚上工作到十点,你能接受不?你平时学习工作到几点?
  • 你对未来的职业规划是什么样的?
  • 你还有其它offer不?
  • ...

参考文章

写在最后

我是前端小菜鸡,感谢大家的阅读,我将继续和大家分享更多优秀的文章,如果有错误和纰漏,希望能给予指正,更多最新文章敬请关注笔者。