记「字节跳动首次实习」面试 - 20.08.06

610 阅读16分钟

前记

自暑假以来,不明而来的焦虑感,总想做点什么,便在做完实验室项目后开始规划自己实习之路
在期间一直很喜欢字节跳动这个氛围,便一直朝着方向努力
每天刷刷算法题,系统规划知识点,再看看学习视频扩展一下

在一个星期前忍不住投了一个实习的简历,昨天就加上hr的微信
然后今天下午5点半就开始面试(天呐阿巴阿巴阿巴第一次面试超级紧张的好吧!)
这两个刷了很多面经和整理自己做过项目的经历(还设计了个人介绍反复反复练呀)

然后面试就开始了

面试

网络基础/游览器

  1. IOS模型
  2. IP传输在哪层
  3. TCP在哪层
  4. HTTP在哪层
  5. UDP和TCP的区别
  6. HTTP的状态码
  7. 304请求服务器如何知道本地已缓存
  8. 哈希值放在哪
  9. 存储方式的区别(Cookie、Session Storage、Local Storage)

大概记得这一些

js

  1. js基础类型
  2. 判断数组和对象的类型
  3. Object.prototype.toString.call()中的参数
  4. 你知道的数组的方法
  5. a = [1, 2, 3] a.splice(1, 1, 2)返回的内容
  6. flat的原生方法

css

  1. box-sizing的值和代表的意思
  2. 三栏布局
  3. 让第三栏自适应

Vue

  1. vue-router的mode值
  2. vue双向绑定原理
  3. Object.defineProperty有什么值

其他

  1. 博客中使用的萌萌哒插件有没有看过源码
  2. 它是怎么跟着鼠标移动
  3. 两个链表有相同的一个节点,找出该节点
  4. 反问

解答

网络基础/游览器

一.IOS模型

IOS七层模型(很熟的好不啦!)

  1. 物理层
  2. 数据链路层
  3. 网络层
  4. 传输层
  5. 会话层
  6. 表示层
  7. 应用层

然后我在第五层拉垮了,突然脑子空白为什么呢!!为什么就紧张了呢!是因为小姐姐的颜嘛!!没道理阿...我平时不吃这一套
第五层第六层要注意了

二.IP传输在哪层

网络层

我说了数据链路层,对不起计算机网络老师,我忘了

三.TCP在哪层

传输层

四.HTTP在哪层

应用层

五.UDP和TCP的区别

  1. TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接
  2. TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
  3. TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的,UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
  4. 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
  5. TCP首部开销20字节;UDP的首部开销小,只有8个字节
  6. TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道

我记得我曾经在哪看过,可是面试时就是忘了,查了一下又有印象了,自己有点糟糕

六.HTTP的状态码

点这里这里!!记录常见的Http状态码

七.304请求服务器如何知道本地已缓存

浏览器缓存分为强缓存和协商缓存

强缓存

服务端第一次响应请求时,告知浏览器还存在本地,设定时间,时间之内还获取该资源就从本地获取。

Cache-Control

简单地说,该字段用于控制浏览器在什么情况下直接使用本地缓存而不向服务器发送请求。一般具有以下值:

  • public: 所有内容都将被缓存
  • private: 内容只缓存到似有缓存中
  • no-cache: 所有内容都不会被缓存
  • no-store: 所有内容都不会被缓存到缓存或者internet临时文件中
  • must-revalidation/proxy-revalidation: 如果缓存的内容失效,请求必须发送到服务器/代理以进行重新验证
  • max-age=xxx( xxx is numeric ): 缓存的内容将在 xxx 秒后失效, 这个选项只在HTTP 1.1可用, 并如果和Last-Modified一起使用时, 优先级较高
  • 其中最常用的属性便是 max-age, 这个字段很简单,就是浏览器在资源成功请求后的制定时间内,都将直接调用本地缓存和不会向服务器去请求数据。
Expires
  • Expires 头部字段提供一个日期和时间,在该日期前的所有对该资源的请求都会直接使用浏览器缓存而不用向服务器请求(注意:cache-control max-age 和 s-maxage 将覆盖 Expires 头部。)
  • Expires 字段接收以下格式的值:“Expires: Sun, 08 Nov 2009 03:37:26 GMT”。
  • 但是使用Expires存在服务器端时间和浏览器时间不一致的问题。

Cache-Control优先级大于Expires

协商缓存

  • 文件最后修改时间,服务器判断资源是否更新,未更新返回304表示not modified,浏览器从缓存加载。
  • LastModified:资源最后更新时间,随服务器返回。
  • if-modified-Since请求首部字段,通过比较两个时间判断资源是否修改。没有修改则协商缓存。

流程:

  1. 浏览器第一次请求资源,服务器响应,返回资源,响应头加Last-Modified。
  2. 浏览器再次请求资源,在请求头加上if modified since,该值为上次Last-Modified的值
  3. 服务器接受请求,将if modified since值和资源最后修改值做对比,若一致则返回304,协商缓存。

Etag周期性重写资源,但资源没变化,加注释等无关紧要信息。
用Etag区分两个资源是否一致,随response返回和请求头的if-none-match相比较,判断资源在两次请求中是否修改,未修改则协商缓存。

八.哈希值放在哪

因为上一个问题我答利用哈希值来判断是否需要读取缓存

放在服务器中

九.存储方式的区别(Cookie、Session Storage、Local Storage)

共同点

都是保存在浏览器端、且同源的

区别

  1. cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递,而sessionStorage和localStorage不会自动把数据发送给服务器,仅在本地保存。cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下
  2. 存储大小限制也不同,cookie数据不能超过4K,同时因为每次http请求都会携带cookie、所以cookie只适合保存很小的数据,如会话标识。sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大
  3. 数据有效期不同,sessionStorage:仅在当前浏览器窗口关闭之前有效;localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie:只在设置的cookie过期时间之前有效,即使窗口关闭或浏览器关闭
  4. 作用域不同,sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;localstorage在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的
  5. web Storage支持事件通知机制,可以将数据更新的通知发送给监听者
  6. web Storage的api接口使用更方便

js

一.js基础类型

点这里这里!!ES6 随笔【赋值解构】

二.判断数组和对象的类型

instanceof

let arr = [1, 2, 3]
let obj = {
  name: '大大',
  age: 18,
  1: 'name'
}
console.log(arr instanceof Array); // true
console.log(obj instanceof Array); // false
console.log(obj instanceof Object); // true

constructor

let arr = [1, 2, 3];
let obj = {
  name: '大大',
  age: 18,
  1: 'name'
}
console.log(arr.constructor === Array); // true
console.log(obj.constructor === Array); // false
console.log(obj.constructor === Object); // true

Object.prototype.toString.call

let arr = [1, 2, 3];
let obj = {
  name: '大大',
  age: 18,
  1: 'name'
}
console.log(Object.prototype.toString.call(arr) === '[object Array]'); // true
console.log(Object.prototype.toString.call(obj) === '[object Array]'); // false
console.log(Object.prototype.toString.call(obj) === '[object Object]'); // true

优先使用: Object.prototype.toString.call > instanceof > constructor

三.Object.prototype.toString.call()中的参数

需要判断的参数变量

我记得昨天我才刚理过这个方法,结果我还是忘了阿阿阿阿阿

四.常见数组的方法

参考了该文章 数组的常用方法大全

数组的增删操作(原数组不受影响)

join

该方法可以将数组里的元素,通过指定的分隔符,以字符串的形式连接起来。
返回值:返回一个新的字符串

//将数组用 - 符号连接起来
let arr = [1,2,3,4,5];
let str = arr.join('-');
console.log(str)  //str = 1-2-3-4-5;
split

该方法是用过指定的分隔符,将字符串分割成数组。
返回值:返回一个新的数组

let str = 'wqz-ttj';
let arr = str.split('-');
console.log(arr); // arr=['wqz','ttj'];

数组的增删操作(原数组受影响)

push

该方法可以在数组的最后面,添加一个或者多个元素
结构: arr.push(值)
返回值:返回的是添加元素后数组的长度.

pop

该方法可以在数组的最后面,删除一个元素
结构: arr.pop(值)
返回值:返回的是刚才删除的元素.

unshift

该方法可以在数组的最前面,添加一个或者几个元素
结构: arr.unshift(值)
返回值: 返回的是添加元素后数组的长度

shift

该方法可以在数组的最前面,添加一个元素
结构: arr.shift(值)
返回值: 返回的是刚才删除的元素.

数组的翻转和排序(改变数组)

reverse 翻转数组

结构:arr.reserse()

sort

该方法可以对数组进行排序.

let arr = [1,3,5,2,4,23,122,34];
//没有参数:时按照首字符的先后排序
arr.sort()//arr=[1,122,2,23,3,34,4,5];
//有参数
arr.sort(function(a,b){
	return a-b;//从小到大排序
	return b-a;//从大到小排序
})

数组的拼接与截取(原数组不受影响)

concat

该方法可以把两个数组里的元素拼接成一个新的数组
返回值: 返回拼接后的新数组

let arr1 = [1,2,3];
let arr2 = [4,5,6];
let arr = arr1.concat(arr2);  //arr = [1,2,3,4,5,6];
arr1.push(arr2);  //arr1 = [1,2,3,[4,5,6]];
slice

该方法可以从数组中截取指定的字段,返回出来
返回值:返回截取出来的字段,放到新的数组中,不改变原数组

// 结构1:arr.slice(start,end) ;从start下标开始截取,一直到end结束,不包括end
let arr = [0,1,2,3,4,5,6,7];
let newArr = arr.slice(0,3)   //newArr = [0,1,2];    

// 结构2:arr.slice(start) ;从start下标开始截取,一直到最后
let arr = [0,1,2,3,4,5,6,7];
let newArr = arr.slice(2)//newArr = [2,3,4,5,6,7];

// 结构3:arr.slice( ) ;全部截取
let arr = [0,1,2,3,4,5,6,7];
let newArr = arr.slice(2)//newArr = [0,1,2,3,4,5,6,7];

删除或增加元素(任意在任何位置,直接改变原数组,没有返回值)

splice
// 结构1: arr.splice(start,deletedCount) 纯删除
// 从start下标开始,删除几个
let arr1 = [1,2,6,7,8];
arr1.splice(2, 1);  //arr = [1,2,7,8];

// 结构2: arr.splice(start,deletedCount,item) 替换
// 从start下标开始,删除几个,并在该位置添加item
let arr2 = [1,2,6,7,8];
arr2.splice(2, 1, 4);  //arr = [1,2,4,7,8];

// 结构3: arr.splice(start,0,item) 纯添加
// 从start下标开始,删除0个,并在该位置添加item,start开始全部往后移动
let arr3 = [1,2,6,7,8];
arr3.splice(2,0,3,4,5);  //arr = [1,2,3,4,5,6,7,8];

查找元素在数组中出现的位置

indexOf

该方法用来查找元素在数组中第一次出现的位置
结构: arr.indexOf(元素)

// 用来判断元素是否存在于数组中!
let arr = [1, 2, 3]
let ele = 1
if (arr.indexOf(ele) === -1){ //说明元素不存在!!
	console.log('元素不存在!')
} else {
	console.log('元素存在!')
}
// '元素存在!'

ES5新增的遍历数组方法

forEach()

该方法等同于for循环,没有返回值
item: 数组中的每一项;
index: item 对应的下标索引值
arr: 就是调用该方法的数组本身

arr.forEach((item,index,arr) => {})

map()

映射,该方法使用和forEach大致相同,但是该方法有返回值,返回一个新数组,新数组的长度和原数组长度相等
item: 数组中的每一项;
index: item 对应的下标索引值
arr: 就是调用该方法的数组本身

let arr = [1,32,54,6,543];
let res = arr.map(function(item,index,arr){
	return item*2;
})
// res = [2, 64, 108, 12, 1084]
filter()

有返回值, 过滤出符合条件的元素
过滤出布尔类型为true的项

let arr = [1, 3, 5, 2, 4, 6];
let res3 = arr.filter(function(item, index) {
  return item % 2 === 0;
});
console.log(res3);  // [2, 4, 6]

let arr2 = [0, "", false, 1, 3, 4];
let res4 = arr2.filter(function(item, index) {
  return item;
});
console.log(res4);  // [1, 3, 4]

some

判断数组中有没有符合条件的项(只要有,就返回true),如果一个都没有,才返回false

let arr3 = [
  { name: "zs", age: 18, done: "notYet" },
  { name: "ls", age: 20, done: true },
  { name: "ww", age: 22, done: true }
];
let res5 = arr3.some(function(item) {
  return item.done;
});
console.log(res5);  // true

every

判断数组中所有的项是否满足要求,如果全都满足,才返回true,否则返回false

let arr3 = [
  { name: "zs", age: 18, done: "notYet" },
  { name: "ls", age: 20, done: true },
  { name: "ww", age: 22, done: true }
];
let res6 = arr3.every(function(item) {
  return item.done;
});
console.log(res6);    // true

find

找到符合条件的项,并且返回第一项

let arr4 = [
  { id: 3, name: "ls", done: false },
  { id: 1, name: "zs", done: true },
  { id: 2, name: "ww", done: true }
];
let res7 = arr4.find(function(item) {
  return item.done;
});
console.log(res7);  // [{ id: 1, name: "zs", done: true }]
findIndex

找到符合条件的项的下标,并且返回第一个

let arr4 = [
  { id: 3, name: "ls", done: false },
  { id: 1, name: "zs", done: true },
  { id: 2, name: "ww", done: true }
];
let res8 = arr4.findIndex(function(item) {
  return item.done;
});
console.log(res8);  // 1

reduce

挺重要的方法!!

//  1.求和计算
let arr1 = [1,2,3,4,5] ;
let new1 = arr1.reduce((pre,next,index) => {
  return pre+next ;
})
console.log(new1);  // 15
//​ 第一次:pre–>1 next–>2 index–>1
// pre+next=1+2=3
// 第二次:pre–>3 next–>3 index–>2
// pre+next=3+3=6
// 第三次:pre–>6 next–>4 index–>3
// pre+next=6+4=10
// 第四次:pre–>10 next–>5 index–>4


// 2.扁平化数组(拼接数组) -> 等效第二个参数为1的flat方法
let arr2 = [[1,2,3],[4,5],[6,7]] ;
let new2 = arr2.reduce((pre,next,index) => {
  return pre.concat(next);	//前数组拼接后数组 .concat()
})
console.log(new2);  // [1,2,3,4,5,6,7]


// 3.对象数组叠加计算
let arr3 = [
  {price:10,count:1},
  {price:15,count:2},
  {price:10,count:3}
];
let new3 = arr3.reduce(function(pre,next,index){
  return pre + next.price * next.count;
},0)	//在原数组第一项添加为0,不改变原数组,则可不操作第一项
console.log(new3);  // 70


// 4.计算数组中每个元素出现的次数
let names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice'];
let countedNames = names.reduce(function (allNames, name) {
  if (name in allNames) {
    allNames[name]++;
  } else {
    allNames[name] = 1;
  }
  return allNames;
}, {});
console.log(countedNames);  // { Alice: 2, Bob: 1, Tiff: 1, Bruce: 1 }

五.a = [1, 2, 3] a.splice(1, 1, 2)返回的内容

[1, 2]

由于忘了第三个参数是替换参数...唉

六.flat的原生方法

const arr = [[0,[19,42,3],2],[3,4,[[9,5],10,12],6],7,8];

Array.prototype._flat = function(num = 1){
    if(Array.isArray(this)){
        let _this = this;
        for(let i = 0;i < num;i++){
            _this = [].concat(..._this);
        };
        return _this;
    }
    return this;
};
console.log(arr._flat(3));

我写的很简陋

css

一.box-sizing的值和代表的意思

两个值

  1. content-box
  2. border-box

主要就是width和height的不同

content-box

W3C盒子模型

border-box

IE盒子模型

三栏布局

我是在这里学习的 面试之道之 CSS 布局

让第三栏自适应

这个我不太清楚 我答的是vh
help~ 小伙伴们可以跟我说一说

vue

一.vue-router的mode值

hash

  1. url地址中带有#
  2. 不能随意的修改path地址
  3. 在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。

history

  1. 页面刷新重新请求接口
  2. 可以随意修改path地址,可以进入404

vue-router的mode值我之前看过相关的文章,结果忘了
面试官还很友好的提示我页面跳转后url会在哪个地方
默认的明显在#后边 -> 对应mode值就是hash了
哎...年少易忘事

二.vue双向绑定原理

可以看看这里 面试官: 实现双向绑定Proxy比defineproperty优劣如何?

我要好好学习下这篇文章

三.Object.defineProperty有什么值

MDN文章

其他

一.博客中使用的萌萌哒插件有没有看过源码

live2d-widget源码

二.它是怎么跟着鼠标移动

这个我也不知道难受了,查找好久都找不着,估计还得看源码
我感觉是Canvas + 动画

三.两个链表有相同的一个节点,找出该节点

我自己的思路是:

  1. 遍历第一个链表,设置一个特殊值
  2. 遍历第二个链表,当找到这个特殊值时 -> 返回该节点
    感觉不太行

从leetcode找到类似的题目

// 双指针每个都判断一遍
var getIntersectionNode = function(headA, headB) {
    let node1 = headA, node2 = headB;
    while(node1 != node2) {
        node1 = node1 !== null ? node1.next : headB;
        node2 = node2 !== null ? node2.next : headA;
    }
    return node1;
};

四.反问

弱弱地问了两个问题

Q1

1.字节跳动的产品都是以APP为主,假如我去实习的话具体体现的工作在哪儿?

A1

  1. 在平时的业务中也会有Web前端的相关业务开发的
  2. APP中的页面也是基于html前端开发的
  3. 还会做一些后端相关的业务开发

Q2

2.转正机会,该如何转正呢,会不会有什么流程?

A2

转正好像没有特殊的形式,是一种自然而然的结果
当你成长了,经历了,有一定能力和担当足以面对业务时
你自然也就获得转正的机会了

后记

总体感觉不得不说字节跳动的面试官人儿很Nice
!important -> 面试官是一个很好看的小姐姐(天呐,更想去字节了!说不定可以找命中归宿)
而且很温柔,很有耐心~就是那种你说啥都会给你反馈的(简称让你安心一下)
在面试过程一开始不紧张(毕竟在校我也是干部)
后面答不上来就开始紧张了,心里想完蛋了完蛋了完蛋了

首次实习面试估计以凉经告终
有点小失望...对自己的小失望
很多题目自己都学习过,看过相关文章,可惜没答上来

不管了~ 希望自己耐得住寂寞,下次还投字节实习 给总结未来两点方向

  1. 字节很重视基础知识 -> 要下功夫
  2. 根据简历来进行考察的 -> 所以简历要自己好好再琢磨琢磨
  3. 面试题都是根据你回答的内容进行下一问 -> 所以很多问题学习还得深入学习

最后最后最后
加油~ ✨
好累...