从八月中开始,面试至今已经面过四十多家公司,参加的面试场数也有五六十场,终于在国庆节后的字节面试中成功上岸。
从面试开始到现在我都有记录自己面试过的公司的习惯,可以看到我是在第二次面试字节的时候才成功,第一次是商业产品与技术部门的二面挂的。本人是一所双非的大四学生,字节对我们真的很友好,不仅给到面试机会还可以允许在之前面评比较好的前提下给到更多的机会。
当然机会是留给有准备的人的,从面试至今,不断失败,不断爬起,从很细小的知识点到没有掌握的知识盲区,总之就是一直坚持。这个过程中,最难受的就是在网易有道和字节商业产品与技术的二面挂了,面了那么久还是失败,心都是拔凉的,身边的朋友都陆陆续续实习去了,我也想过去一个中厂实习再跳槽去大厂,但是真的不甘心。还好功夫不负有心人,在前11号收到了字节发来的offer,也刚好是我的生日前一天,开心坏了。
接下来给大家分享一下我的字节面试题,解答的话给家人们推荐一些文章。
一面
1. keepalive的原理和使用
参考文章彻底揭秘keep-alive原理。
2. 图片懒加载
参考文章这件事确实需要懒一下!懒加载!。
3. scoped的作用和原理
参考文章刨析Scoped原理。
4. 0.1+0.2为什么不等于0.3
这是由于计算机无法直接对十进制的数字进行运算, 需要先对照 IEEE 754 规范转换成二进制,然后对阶运算。而二进制最多64位,并且0.1和0.2转成二进制是无线循环,所以会出现精度丢失的情况,即0.1+0.3不等于0.3。
这题之后面试官就直接深入实现小数相加。
5. 实现小数相加
我的方法是将小数转换成整数进行运算,然后再转换回小数。这种方法适用于已知小数位数的情况。
function add(num1, num2) {
let decimalPlace1 = (num1.toString().split('.')[1] || '').length;
let decimalPlace2 = (num2.toString().split('.')[1] || '').length;
let maxDecimalPlace = Math.max(decimalPlace1, decimalPlace2);
let factor = Math.pow(10, maxDecimalPlace);
return (Math.round((num1 * factor) + (num2 * factor)) / factor);
}
console.log(add(0.1, 0.2)); // 输出 0.3
6. vue的生命周期
7. vue3的响应式
vue3的响应式采用的是proxy,也可以归根为ref和reactive的原理,推荐文章30分钟手写一遍源码带你搞明白vue3中的reactive和ref的本质区别。
8. vue3的diff算法
vue3的diff算法重点是对最长递增子序列的处理,可以看看这篇认识 React、Vue2、Vue3 三者的 diff 算法与对比。
9. 箭头函数和普通函数的区别
箭头函数和普通函数的主要区别有三点:1. this的指向;2. arguments; 3. 实例化;
10. new的过程
这道题是由上一题引出的,因为new的过程主要是:1. 创建空对象;2. 改变this指向;3. 让新对象的隐式原型指向对象的显式原型;4. 返回新对象;在这个过程中涉及this的指向,所以箭头函数无法被实例化。
function myNew(Fn,...arg){
const obj={}
Fn.apply(obj,arg)
obj.__proto__=Fn.prototype
return obj
}
11. cookie、localstorage、sessionstorage的区别
文章推荐cookie、localStorage和sessionStorage 三者之间的区别以及存储、获取、删除等使用方式。
12. 垃圾回收机制
-
引用计数(Reference Counting):
每个对象都有一个引用计数器,用于记录有多少个引用指向该对象。 当对象被创建或者被引用时,引用计数增加。 当引用失效或者被删除时,引用计数减少。 如果一个对象的引用计数变为0,那么它就可以被回收。
缺点:无法处理循环引用的情况。
-
标记-清除(Mark-Sweep):
从根对象开始,递归地标记所有可达的对象。 未被标记的对象被视为垃圾,并释放其内存。
缺点:可能会产生内存碎片。
-
标记-整理(Mark-Compact):
类似于标记-清除,但多了整理内存的步骤。 整理过程会将所有活动对象移动到内存的一端,然后清理掉边界以外的内存。
优点:解决了内存碎片问题。
-
分代回收(Generational Collection):
基于对象存活周期的不同,将对象分为不同的代(如新生代和老生代)。 新生代中的对象生命周期短,经常被回收;老生代中的对象生命周期长,回收频率较低。 这种策略可以优化垃圾回收的性能,因为回收新生代通常比回收老生代更快。
13.算法:判断数组是否是二叉搜索树后序遍历
leetcode:LCR 152. 验证二叉搜索树的后序遍历序列。
二面
1. 输入url后到看到页面经历了什么
推荐文章 史上最详细的经典面试题 从输入URL到看到页面发生了什么?。
2. 使用koa实现静态服务器返回html
上来就是手写题给我整懵了都,一开始都没理解面试官的意思,问了好几遍理解了然后开始动手。
const Koa = require('koa');
const fs = require('fs').promises; // 使用 promises 版本的 fs 模块
const app = new Koa();
// 中间件,用于从文件系统读取HTML并返回
app.use(async ctx => {
try {
// 读取HTML文件
const html = await fs.readFile('path/to/your/index.html', 'utf8');
// 设置响应类型为HTML
ctx.type = 'html';
// 返回读取的HTML内容
ctx.body = html;
} catch (err) {
// 如果发生错误,设置状态码为500并返回错误信息
ctx.status = 500;
ctx.body = 'Internal Server Error';
console.error(err);
}
});
// 启动服务器
const port = 3000;
app.listen(port, () => {
console.log(`正在允许`);
});
3. 使用vue3实现一个百度搜索的功能
这道题很巧,学了几遍也比较熟练。
<template>
<div>
<input
type="text"
v-model="searchQuery"
@input="onSearchInput"
placeholder="输入搜索内容"
/>
<ul v-if="searchResults.length > 0">
<li v-for="(result, index) in searchResults" :key="index">
{{ result }}
</li>
</ul>
</div>
</template>
<script setup>
import { ref, watch } from 'vue';
const searchQuery = ref('');
const searchResults = ref([]);
// 模拟搜索函数,实际使用时应替换为百度搜索API的调用
const search = async (query) => {
// 这里使用 fetch API 发送请求,百度搜索API的URL可能不同,以下为示例
const response = await fetch(`https://www.baidu.com`);
const data = await response.json();
return data.results || []; // 假设返回的数据中包含搜索结果数组
};
// 防抖函数
function debounce(func, delay) {
let timer;
return function(...args) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// 使用防抖包装搜索函数
const debouncedSearch = debounce(async (query) => {
if (query.trim() === '') {
searchResults.value = [];
return;
}
const results = await search(query);
searchResults.value = results.map(item => item.title); // 假设每个结果项有一个title属性包含搜索词
}, 300); // 设置300毫秒的延迟
// 监听搜索框输入
const onSearchInput = () => {
debouncedSearch(searchQuery.value);
};
// 使用watch来监听searchQuery的变化
watch(searchQuery, (newValue, oldValue) => {
if (newValue.trim() === '') {
searchResults.value = [];
}
});
</script>
4. 手写发布订阅模式
发布订阅模式是一种消息通信模式,发布者将消息发布到特定主题,订阅了该主题的订阅者会自动收到消息,实现了解耦和异步通信。这种方式允许多个订阅者同时接收相同的消息,非常适合一对多的消息传播场景。发布订阅模式常用于事件驱动系统和实时数据流处理中。
// EventEmitter
class EventEmitter {
constructor() {
this.cache = {}
}
on(name, fn) {
if (this.cache[name]) {
this.cache[name].push(fn)
} else {
this.cache[name] = [fn]
}
}
emit(name, once = false, ...args) {
if (this.cache[name]) {
// 执行 但不要影响订阅者
let tasks = this.cache[name].slice()
for (let fn of tasks) {
fn(...args)
}
if (once) {
delete this.cache[name]
}
}
}
off(name, fn) {
let tasks = this.cache[name]
if (tasks) {
let index = tasks.findIndex(f => f === fn || f.callback === fn)
if (index >= 0) {
tasks.splice(index, 1)
}
}
}
}
5. 最小堆
对于这题一开始我也懵了,面试的时候对他都没太多的印象,只记得是棵完全二叉树,最小值是根节点。 推荐文章最小堆最大堆了解吗?一文了解堆在前端中的应用。
商业产品与技术面经
这是我第一次面字节的题目,就不再详细讲解了,希望可以帮到有需要的友友们。
总结
到此我的面试之旅就画上了句号,全身心投入的去做一件事的感觉真的“很爽”,过程充满坎坷与挑战,好在结果很满意,还在努力的家人们加油,祝你们可以拿到心仪的offer,毕竟有志者事竟成。18号入职,我也准备迎接下一阶段的挑战了。