胡老板已经提桶跑路了,面上了字节!One night in Beijin, 他会留下许多情....
前言
胡老板非常强,代码和表达能力俱佳!前几天直接连投影,现场直播面试,面试官当场给offer, 老板娘都希望他赶快进京,不服不行。先把胡老板留下之面试遗产,字节、快手、蔚来面经给大家慢慢汇报,以疗我吃了两周挂面之伤。
对于双非的我们,胡总此时便是面朝大海,春暖花开吧。房补1500, 爽。
字节一面
1. AI在项目和学习中的使用
字节推出了豆包 - 字节跳动旗下 AI 智能助手 (doubao.com)和扣子 - AI 智能体开发平台 (coze.cn), 面试当然要吹下他们家的AI产品了。谈了对coze的以下几点理解:
- 通过扣子 - 开发指南 (coze.cn),学习了如何快速开发AI Agent 和chat bot。并结合自己的需求和创意,参加了coze比赛。
- 了解coze AI 工作流。将多个大模型prompt任务细化,组合插件等,完成较复杂AI应用搭建。
- 了解知识库的概念,通过coze解析上传文档,瞬间得到特定内容专家客服。
- 了解卡片的概念,定制AI输出格式,优化界面和体验。
- 了解coze 数据库的概念,只不过还有点不靠谱
建议大家都去可以玩下豆包 - 字节跳动旗下 AI 智能助手 (doubao.com), 掘金有课程:豆包 - 字节跳动旗下 AI 智能助手 (doubao.com)。非广告,没拿平台一分钱。今年不管前后端,多了解点AI知识,面试很加分。
Prompt Engineering 提示词工程
LLM
是金矿,Prompt
是打开它的钥匙。我看了下ChatGPT提示工程师&AI大神吴恩达教你写提示词|prompt engineering【完整中字九集全】_哔哩哔哩_bilibili
,大概了解其规则
- 给它一个身份,比如 "你是一位大厂前端面试官,你希望面试同学了解哪些AI方面的知识"
- 清晰明确的描述
- 提供上下文或给一些示例
few shot
### 情感分析示例
请对下面的评论进行情感分类,情感类别包括:积极、消极、中立。
#### 示例
输入:
- "这家餐厅的食物美味极了,服务也很周到。"
输出: 积极
输入:
- "我对这次购物体验非常失望,商品质量远低于我的期望。"
输出: 消极
输入:
- "今天天气不错,适合出去散步。"
输出: 积极
输入:
- "这部电影的评价褒贬不一,有人喜欢,有人不喜欢。"
输出: 中立
#### 任务
请根据以上示例对下列评论进行情感分类:
输入:
- "虽然交通有些拥堵,但是我们还是按时到达了会议地点。"
输出: ___________
输入:
- "新产品发布后,客户反馈非常热烈,大家都表示愿意再次购买。"
输出: ___________
输入:
- "这本书的内容对我来说太专业了,难以理解。"
输出: ___________
- 避免歧义 告诉
LLM
不要做什么 - 逐步引导 如果任务复杂,可以逐步引导AI完成各个部分,而不是一次性要求完成所有工作。这也是工作流的前身
- 反馈机制 告诉大模型哪些好哪些不好,让它再做一次。
AI code copilot
目前在用通义灵码,code copilot 是这个阶段,AIGC提升开发效率最有效的产品。极大的鼓励自己多学习各种知识(AI全栈), 各种代码写起来飞快。
AIGC 带来新的前端用户体验和交互
- 比如coze的应用logo AIGC生成功能。以前前端交互只有文件上传或拖拽上传,现在因AIGC,可以交给AI生成图片。我觉得这是前端必须学习AI的原因,因为AIGC带来了全新的交互方式。
OpenAI 接口的理解
- 基于completion 和chat completion 接口,完成chatbot 功能
- 基于
embedding
实现AI全栈语义搜索功能。可以参考代码 ai_lesson/openai/embedding at 899c734ddf9fced3322777f617cac424f28812b9 · shunwuyu/ai_lesson (github.com) - 基于多模态能力 上传界面截图,直接生成vue 代码
- 基于coze api 调用, 完成各种AIGC功能。
学习了transformer.js 对端模型充满期待
当直接在手机端或浏览器端,可以交给js直接调用的LLM成熟的那一刻,将是JSer 大量AI应用开发的时刻。来自Hugging Face
推出的机器学习库Transformer
的js版本,调试了机器翻译demo, 建议大家也试试。
未来有时间,会继续学习AI,ALL in AI。
以上就是对AI相关认识的回答。
2. 项目中的难点
写项目必须安排些亮点、难点、性能优化点,安排!
- transformer.js 实习翻译AI功能
- web worker 多线程,将AI功能独立到web worker 线程工作,并通过消息机制通信
- 单例模式 transformer 实例化特别耗时且复杂,使用了单例模式封装
- 使用了typescript
- 封装了自定义hooks useIntersectionObserver.ts
import { Ref, onUnmounted, ref, watch } from "vue" const useIntersectionObserver = (NodeRef: Ref<HTMLElement | null>, loadMore: () => void) => { // 是否有下一页 const hasMore = ref(true) // 用于监听节点 let observer: IntersectionObserver // 监听节点变化 watch(NodeRef, (newNodeRef, oldNodeRef) => { // 取消监听旧节点 if (oldNodeRef) { observer.unobserve(oldNodeRef) } // 监听新节点 if (newNodeRef) { observer = new IntersectionObserver(([entry]) => { if (entry.isIntersecting) { // 该节点出现在视图中时触发回调,加载更多 loadMore() } }) observer.observe(newNodeRef) } }) // 组件卸载时,取消监听 onUnmounted(() => { observer.disconnect() }) // 监听hasMore变化,如果有下一页,则继续监听,否则取消监听 watch(hasMore, (value) => { value ? observer.observe(NodeRef.value!) : observer.disconnect() }) return { hasMore, setHasMore: (value: boolean) => { hasMore.value = value }, } } export default useIntersectionObserver
-
2D/3D 数据可视化及hooks封装
shunwuyu/three-echarts-show: echarts 和 three.js 2D/3D 数据可视化代码。 (github.com)
-
node blog 项目
- 使用express + ts 开发
- 优秀的架构, 每个功能模块划分为以下几个子模块:
- 高级中间件
举例 :
router.post('/users', validateUserData, hashPassword, userController.store);
根据中间件洋葱模型 将注册功能,细分为参数校验、密码加密、存储等中间件。
错误处理中间件:
export const defaultErrorHandler = (
error: any,
request: Request,
response: Response,
next: NextFunction,
) => {
if (error.message) {
console.log('🚧', error.message);
}
let statusCode: number, message: string;
/**
* 处理异常
*/
switch (error.message) {
case 'NAME_IS_REQUIRED':
statusCode = 400;
message = '请提供用户名';
break;
case 'PASSWORD_IS_REQUIRED':
statusCode = 400;
message = '请提供用户密码';
break;
case 'USER_ALREADY_EXIST':
statusCode = 409;
message = '用户名已被占用';
break;
case 'USER_DOES_NOT_EXIST':
statusCode = 400;
message = '用户不存在';
break;
case 'PASSWORD_DOES_NOT_MATCH':
statusCode = 400;
message = '密码不对';
break;
case 'UNAUTHORIZED':
statusCode = 401;
message = '请先登录';
break;
case 'USER_DOES_NOT_OWN_RESOURCE':
statusCode = 403;
message = '您不能处理这个内容';
break;
case 'FILE_NOT_FOUND':
statusCode = 404;
message = '文件不存在';
break;
case 'TAG_ALREADY_EXISTS':
statusCode = 400;
message = '标签已存在';
break;
case 'POST_ALREADY_HAS_THIS_TAG':
statusCode = 400;
message = '内容已经有这个标签了';
break;
case 'UNABLE_TO_REPLY_THIS_COMMENT':
statusCode = 400;
message = '无法回复这条评论';
break;
case 'FILE_TYPE_NOT_ACCEPT':
statusCode = 400;
message = '不能上传此类型文件';
break;
case 'NOT_FOUND':
statusCode = 404;
message = '没找到 ~~ 🦖';
break;
case 'USER_NOT_FOUND':
statusCode = 404;
message = '没找到这个用户 ~~';
break;
case 'PASSWORD_IS_THE_SAME':
statusCode = 400;
message = '要修改的密码不能与原密码一样';
break;
default:
statusCode = 500;
message = '服务暂时出了点问题 ~~ 🌴';
break;
}
response.status(statusCode).send({ message });
};
3.父子组件该如何封装
这道题很多朋友会当作八股文来回答,面小公司的时候无所谓。当是字节面试时,这道题其实更应该从组件设计的角度去回答。
- 单向数据流和职责
父组件负责管理业务逻辑和数据流,子组件负责渲染特定的视图或处理特定的交互。
父组件通过ref/reactive 或pinia/vuex 管理数据
父组件通过 props
向子组件传递数据。props
是一种单向的数据流,即数据从父组件流向子组件。
使用事件进行子组件与父组件的通信
子组件通过emit 调用父组件函数,通知数据更新,各个子组件同步更新
- 使用插槽 (
slots
) 提供灵活性 默认插槽和具名插槽 - 对于嵌套深度比较大的,可以使用 provide/inject
- 根据组件的功能,将复杂的父组件拆分成多个子组件,以便于维护和复用。
- expose 向父组件暴露自组件方法
<template>
<div v-if="isVisible" class="modal-overlay">
<div class="modal-content">
<slot></slot>
<button @click="close">Close</button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const isVisible = ref(false);
// Function to open the modal
const open = () => {
isVisible.value = true;
};
// Function to close the modal
const close = () => {
isVisible.value = false;
};
// Expose the open and close methods to the parent component
defineExpose({
open,
close,
});
</script>
<style scoped>
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
}
.modal-content {
background: white;
padding: 20px;
border-radius: 5px;
text-align: center;
}
</style>
4.父子组件传值
在 Vue 3 中,父子组件传值主要通过 props
和事件 ($emit
) 机制。父组件可以通过 props
传递数据给子组件,子组件则可以通过 $emit
事件将数据传递回父组件。此外,Vue 3 还支持通过 v-model
实现父子组件之间的双向绑定。了解这些机制,能够帮助你在不同场景下灵活地进行组件间的数据交互。
父组件
// 父组件
<template>
<div>
<ChildComponent v-model="parentMessage" />
<p>Updated message: {{ parentMessage }}</p>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';
const parentMessage = ref('Hello from parent!');
</script>
// 子组件
<template>
<input type="text" v-model="internalMessage" />
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import { defineProps, defineEmits } from 'vue';
const props = defineProps({
modelValue: String,
});
const emit = defineEmits(['update:modelValue']);
const internalMessage = ref(props.modelValue);
watch(internalMessage, (newVal) => {
emit('update:modelValue', newVal);
});
</script>
5.diff 算法
建议大家可以看看 聊聊 Vue 的双端 diff 算法Vue 和 React 都是基于 vdom 的前端框架,组件渲染会返回 vdom,渲 - 掘金 (juejin.cn)
6.项目的性能优化
图片懒加载
- 使用
IntersectionObserver
API - scroll + getBoundingClientRect
- const scrollTop = window.pageYOffset || document.documentElement.scrollTop; const viewportHeight = window.innerHeight; lazyImages.forEach(img => { if (img.offsetTop < scrollTop + viewportHeight) 要使用节流
- v-lazy 指令封装
Vue.directive('lazy', {
inserted: (el, binding) => {
const loadImage = () => {
el.src = binding.value;
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
loadImage();
observer.unobserve(el);
}
});
});
observer.observe(el);
}
});
`inserted` 钩子在指令绑定的元素插入父节点时触发。这意味着当元素被插入到 DOM 中,并且已经出现在页面上时,`inserted` 钩子会被调用。在这个阶段,你可以安全地操作 DOM 元素,因为它已经存在于文档中。
图片优化
选择合适的格式:针对不同类型的图片选择合适的格式,例如使用 WebP 替代 JPEG/PNG 以减小文件体积。
WebP 格式通过提供更高的图像压缩率(无损和有损压缩)来减少图像文件大小,同时保持较好的画质,从而加快网页加载速度。
上图在淘宝的地址为
https://img.alicdn.com/imgextra/i2/3024481173/O1CN01aNSEqF1KXHVDFrF2T_!!3024481173.jpg_.webp
小图标使用base64 格式, 放在css中,减少http请求
将静态资源部署到CDN
将静态资源部署到CDN节点,缩短资源加载的物理距离,加快资源加载速度。
防抖节流
useResize
scroll 节流
按需加载
vite 比webpack 快, 因为按需加载
路由懒加载,减少首屏加载时间。
UI组件按需加载, 减少包体积
7.实现一个横向可下拉的列表
// hook
import { onMounted, onUnmounted, ref } from 'vue';
export function useIntersectionObserver(callback, options) {
const observerElement = ref(null);
const observer = ref(null);
onMounted(() => {
observer.value = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
callback();
}
});
}, options);
if (observerElement.value) {
observer.value.observe(observerElement.value);
}
});
onUnmounted(() => {
if (observer.value && observerElement.value) {
observer.value.unobserve(observerElement.value);
}
});
return observerElement;
}
<template>
<div class="horizontal-scroll-container">
<ul class="item-list">
<li v-for="item in items" :key="item" class="item">{{ item }}</li>
<!-- 这里是观察者元素 -->
<li ref="observerElement" class="observer-element"></li>
</ul>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { useIntersectionObserver } from './useIntersectionObserver';
const items = ref(['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5']);
const loadMoreItems = () => {
// 模拟加载更多数据
const nextItems = items.value.length + 5;
for (let i = items.value.length + 1; i <= nextItems; i++) {
items.value.push(`Item ${i}`);
}
};
// 使用 IntersectionObserver 进行加载更多的监控
const observerElement = useIntersectionObserver(loadMoreItems, {
root: null, // 使用视口作为根
threshold: 0.1, // 当观察者元素进入视口 10% 时触发
});
</script>
<style>
.horizontal-scroll-container {
overflow-x: scroll;
white-space: nowrap;
width: 100%;
}
.item-list {
display: flex;
list-style-type: none;
padding: 0;
margin: 0;
}
.item {
min-width: 150px;
margin-right: 10px;
background-color: #ddd;
padding: 20px;
text-align: center;
border-radius: 8px;
}
.observer-element {
min-width: 1px;
height: 1px;
}
</style>
8.算法题 求字符串里的最长不重复子字符串
function lengthOfLongestSubstring(s) {
let map = new Map();
let maxLen = 0;
let start = 0;
for (let end = 0; end < s.length; end++) {
if (map.has(s[end])) {
// 移动开始位置到重复字符的位置之后
start = Math.max(map.get(s[end]) + 1, start);
}
// 更新字符位置
map.set(s[end], end);
// 计算最大长度
maxLen = Math.max(maxLen, end - start + 1);
}
return maxLen;
}
leetcode 第3题 滑动窗口,双指针
二面
9.react和vue的区别
-
模板 react JSX, vue 自定义模板语法(template) JSX 使得 React 组件的结构更加清晰和直观,因为它允许在 JavaScript 中直接嵌入 HTML 语法,从而提高了代码的可读性和组件的表达力。而vue需要通过指令、动态绑定等语法来实现
-
学习曲线
vue 好入门, react 学习曲线陡峭
-
文档和社区支持
react 大企业用的比较多,vue拥有更广泛的下沉市场。vue 文档更友好,两者生态都很好。
-
生命周期
react 倾向于使用useEffect hooks 来实现生命周期,类式组件也支持生命周期钩子函数。vue主要通过生命周期钩子函数实现。
-
状态管理
react 使用的是redux/mobx等, vue是vuex/pinia
-
hooks 函数式编程
react 全面激进支持hooks编程,直接提供十几个hooks api, vue支持hooks, 但框架本身没有那么多原生hooks 函数
-
组件化 React 组件主要通过 JavaScript 定义。通常使用函数式组件(Function Components)或者类组件(Class Components)来定义组件。组件使用 JSX 语法来编写模板,这种方式将 HTML 和 JavaScript 结合在一起,使其更像是写原生 JavaScript。
Vue 组件主要通过单文件组件(Single File Components, SFC)来定义,通常使用
.vue
文件。一个 Vue 组件由三部分组成:<template>
(模板)、<script>
(逻辑)和<style>
(样式),使其结构化更清晰。 -
react 单向数据流,通过 props 传递数据 vue 单向数据流,支持双向数据绑定
-
diff 算法 vue 使用双端比对的方式来加速 Diff 算法
10.聊聊tailwindcss
Tailwind CSS 是一个原子化的、实用优先的 CSS 框架,它通过提供大量的低级实用类(即原子化 CSS)来直接在 HTML 中快速构建响应式、定制化的界面,而无需编写自定义 CSS。
它有以下优点:
- 快速 Tailwind 提供了大量的实用类,可以快速搭建页面布局和样式,提高开发效率。
- 高度定制化 Tailwind 允许开发者通过配置文件轻松调整默认样式,以满足具体项目的需求,从而实现高度定制化的设计。
- 实用类优先:通过使用短小精悍的实用类,Tailwind 使得 HTML 代码更加简洁,减少了冗余的 CSS 代码。
- 可维护性:由于 Tailwind 使用实用类而非特定的样式类,因此更容易维护和重构代码,降低了 CSS 的复杂性。
- 性能优化:Tailwind 可以按需打包,仅包含实际使用的样式类,从而减小最终生成的 CSS 文件大小,提高加载速度。
- 响应式设计:内置了响应式设计的支持,可以通过简单的实用类实现不同屏幕尺寸下的样式调整。
- 社区支持:Tailwind 拥有活跃的社区和丰富的插件生态系统,提供了许多额外的功能和工具。
- 代码生成 结合Gpt4o, promot 将html5,tailwindcss,react/vue,提供界面截图,就可以得到AIGC生成的高质量代码。因为tailwindcss 原子类名自带语义化。
11. webpack 和 vite
推荐大家看看 这篇文章一定能让你搞懂vite为什么比webpack快!运行原理、构建方式等方面到底有什么不同!1.前端常见工具之间的定 - 掘金 (juejin.cn)
12. 假设我们有一个二维数组 arr
,我们要生成一个数组,包含所有可能的组合,其中每个组合由 arr
的每个子数组中的一个元素组成。
let arr = [ [1, 2], [3, 4], [5, 6] ];
输出
[ [1, 3, 5], [1, 3, 6], [1, 4, 5], [1, 4, 6], [2, 3, 5], [2, 3, 6], [2, 4, 5], [2, 4, 6] ]
- 看题目这是一道
排列组合
。 - 想到
递归
递归可以自然地将复杂问题分解为更小的相似子问题,逐层组合,直至得到最终结果。
-
将一个大问题拆分为相似的小问题
大问题: 所有的组合
相似小问题:从第一个子数组 [1, 2]开始,对于第一个子数组中的每个元素,我们将其与其余子数组中的所有可能组合进行组合。当我们处理完所有子数组时,我们会得到最终的所有组合。
递归的想法是,要想得到第一行的所有组合,拿到第二行的所有组合就好.....
function combine(arr) {
// 结果数组,用来保存所有可能的组合
const result = [];
// 递归函数
function backtrack(index, path) {
// 如果到达数组的末尾,将当前路径 path 加入结果数组
if (index === arr.length) {
result.push([...path]);
return;
}
// 遍历当前行的所有元素
for (let num of arr[index]) {
// 将当前元素加入路径
path.push(num);
// 递归处理下一行
backtrack(index + 1, path);
// 回溯,移除最后一个元素,准备尝试下一个可能的组合,匹配完最后一行
path.pop();
}
}
// 从第 0 行开始递归 0 行, 为空
backtrack(0, []);
return result;
}
// 示例数组
const arr = [
[1, 2],
[3, 4],
[5, 6]
];
// 获取所有组合
const combinations = combine(arr);
console.log(combinations);
13. 最长递增子序列
function lengthOfLIS(nums) {
if (nums.length === 0) return 0;
const dp = Array(nums.length).fill(1);
for (let i = 1; i < nums.length; i++) {
for (let j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
}
return Math.max(...dp);
}
14. 单例模式
设计模式是面向对象开发(OOP)中一种可重复使用的代码设计模板,帮助开发者解决常见的软件设计问题。它正对接口(interface)编程,而不是具体细节。
常用的设计模式:工厂模式、单例模式、代理模式(Proxy)、观察者模式(Observer)等
单例模式 单例模式是一种设计模式,确保一个类只有一个实例,并提供一个全局访问点来访问该实例。
实现方法:
// 传统面向对象写法
class Singleton {
static getInstance() {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}
}
// 闭包写法
function singleton() {
let instance;
return {
getInstance: function () {
if (!instance) {
instance = new Singleton();
}
return instance;
}
};
}
相较于一面的全方位考察,二面相对轻松和随意。大场面试一般一面是该岗位的直属leader面。招进来是要给自己干活的,所以会全方位考察,甚至苛刻。二面要不是隔壁部门leader交叉面(避险),要不就是一面面试管的leader帮把把关,有一面通过的背书,二面一般不会太难。二面挂人,难道是说一面不准确吗?所以,过了大厂一面的宝子们二面不用慌,不出错就好了。
总结
字节的好,是只要努力就可以面,加油,再搞两个月。