前言
春招的号角已经吹响,我也义无反顾地投身其中,经历了一场堪称“灵魂拷问”的技术面试。今天,我就来和大家分享这场面试的全过程,顺便聊聊我在其中学到的东西。如果你也正在求职的路上摸爬滚打,希望这篇文章能给你带来一些启发和帮助!
自我介绍
各位可以根据这篇文章去组合一下自己的自我介绍,这个还是很重要的面试自我介绍5句话公式马上进入三月份,各个大厂的春招也陆续开了,正是大家所谓的“金三银四”的时期。 想进入大厂必不可少的 - 掘金
项目难点
首先面试官开始对着我的简历上的项目技术点开始拷打,建议大家自己可以先花时间回顾一下自己的项目,然后把技术点喂给AI,拓展一下,看能不能加一点亮点上去!
接下来是本次面试题目分享
设计模式相关
面试官先问到了单例模式和工厂模式。
- 单例模式:确保一个类仅有一个实例,像在 JavaScript 里,能用来创建全局唯一对象,保障应用生命周期内,某个类的实例唯一性。
class Singleton {
constructor() {
if (!Singleton.instance) {
this.message = '这是一个单例对象';
Singleton.instance = this;
}
return Singleton.instance;
}
showMessage() {
console.log(this.message);
}
}
// 使用单例对象
const singleton1 = new Singleton();
const singleton2 = new Singleton();
console.log(singleton1 === singleton2); // 输出: true
singleton1.showMessage(); // 输出: 这是一个单例对象
在这个 ES6 类的实现中,构造函数会检查 Singleton.instance 是否已经存在,如果不存在则创建一个新的实例并将其赋值给 Singleton.instance,如果已经存在则直接返回该实例。
- 工厂模式:把对象创建和使用分开,通过工厂函数或类根据传入参数、条件动态创建不同类型对象,大大提高了代码的可维护性和扩展性。
1. 简单工厂模式
简单工厂模式通过一个工厂函数根据传入的参数来创建不同类型的对象。以下是一个简单工厂模式的示例:
// 定义产品类
class Circle {
constructor() {
this.type = 'Circle';
}
draw() {
console.log('绘制圆形');
}
}
class Square {
constructor() {
this.type = 'Square';
}
draw() {
console.log('绘制正方形');
}
}
// 简单工厂函数
function ShapeFactory() {
this.createShape = function (type) {
switch (type) {
case 'circle':
return new Circle();
case 'square':
return new Square();
default:
return null;
}
};
}
// 使用简单工厂
const factory = new ShapeFactory();
const circle = factory.createShape('circle');
const square = factory.createShape('square');
circle.draw(); // 输出: 绘制圆形
square.draw(); // 输出: 绘制正方形
在上述代码中,ShapeFactory 是一个简单工厂,它有一个 createShape 方法,根据传入的 type 参数来创建不同类型的形状对象。
2. 工厂方法模式
工厂方法模式将对象的创建延迟到子类中实现。以下是一个工厂方法模式的示例:
// 抽象工厂类
class ShapeFactory {
createShape() {
throw new Error('此方法必须在子类中实现');
}
}
// 具体工厂类:圆形工厂
class CircleFactory extends ShapeFactory {
createShape() {
return {
type: 'Circle',
draw: function () {
console.log('绘制圆形');
}
};
}
}
// 具体工厂类:正方形工厂
class SquareFactory extends ShapeFactory {
createShape() {
return {
type: 'Square',
draw: function () {
console.log('绘制正方形');
}
};
}
}
// 使用工厂方法模式
const circleFactory = new CircleFactory();
const squareFactory = new SquareFactory();
const circle = circleFactory.createShape();
const square = squareFactory.createShape();
circle.draw(); // 输出: 绘制圆形
square.draw(); // 输出: 绘制正方形
在这个示例中,ShapeFactory 是一个抽象工厂类,它定义了一个抽象方法 createShape。CircleFactory 和 SquareFactory 是具体的工厂子类,它们实现了 createShape 方法来创建具体的形状对象。
Vue 知识考查
vue2 vue3 的区别
- vue2 使用的是选项式API vue3 是组合式api 更好的组织代码,提高代码可维护性
- vue2响应式原理用的是Object.defineProperty vue3使用的是proxy 比vue2有着更好的性能,
- 和更好的数据变化追踪能力
- setup() 是 Vue 3 组合式 API 的入口,替代了 Vue 2 中的 beforeCreate 和 created 钩子。
- vue3 对 TS 的支持更好,
- vue3 的 tree-shaking 优化 ,可以 减少打包体积(Tree Shaking 的作用就是:在打包时,分析出哪些代码是真正被用到的,然后去掉那些没用的代码(比如 subtract 和 multiply),从而让最终的代码更小、更高效。)
- 在 Vue3 中,v-if的优先级高于v-for。与 Vue2 的情况正好相反,当它们同时出现在同一个元素上时,v-if会首先根据条件进行判断,然后再进行v-for的循环渲染。
v-model 的实现
在模板里通过绑定value属性和监听input事件来实现双向数据绑定。例如,通过ref定义响应式数据name,在<input>标签中绑定value属性并监听input事件,在事件处理函数updateName里更新name的值。
<template>
<div>
<input type="text" :value="name" @input = " updateName($event.target.value)">
</div>
</template>
<script setup>
const name = ref('张三'); // 初始值
// 定义更新方法
const updateName = (value) => {
name.value = value; // 更新响应式数据
};
</script>
<style lang="scss" scoped>
</style>
- v-for 为什么需要 key:
key能帮 Vue 高效更新虚拟 DOM,数据变化时,通过key能快速定位更新节点,避免不必要的重新渲染。
JWT
后面可能是想起来我的项目里作了jwt鉴权,然后来问了我jwt,这里我只是简单介绍了,他的流程需要大家去扩展一下
- JWT:由 Header、Payload 和 Signature 三部分组成。Header 定义签名算法,Payload 存用户信息,Signature 用于验证数据完整性。
react和vue的区别
- react和vue的区别:
不同点
- 模板语法 react采用jsx语法 vue使用基于html的模板语法
- 状态管理 react采用redux vue采用vuex
- 数据绑定 vue采用双向数据绑定 react需要手动控制组件的状态和属性
- 响应式原理 react通过单向数据流实现响应式 vue采用双向数据绑定
- 声明周期 vue有8个生命周期 react有10个生命周期
- 组件通信,vue使用props和事件的方式进行父子组件通信,react则通过props和回调函数的方式进行通信。
相同
- 都是采用响应式更新 当数据发生变化时,会自动更新视图
- 都是组件化开发
- 都有虚拟DOM
- 集成度都很高
CSS问了一点
- display:none 和 visibility:hidden 的区别:
display:none隐藏元素不占空间且不响应,会触发回流和重绘;visibility:hidden隐藏元素占空间不响应,只触发重绘。
VUE的组件通信
- 组件通信(这个问的很多,大家可以去扩展了解)
(1) 父组件向子组件传值:通过 props 父组件可以通过 props 将数据传递给子组件。 子组件需要在 props 中声明接收的属性。
(2) 子组件向父组件传值:通过 emit 触发自定义事件,并将数据传递给父组件。 父组件通过监听子组件的事件接收数据。
(3) 兄弟组件通信:通过事件总线 可以使用一个中央事件总线来实现兄弟组件之间的通信。 父组件作为事件总线,子组件通过 $emit 触发事件,父组件监听事件并处理数据。
(4) 跨级组件通信:通过 provide/inject 可以使用 provide/inject 来实现跨级组件之间的通信。 父组件通过 provide 提供数据,子组件通过 inject 注入数据。
(5) Pinia 状态管理 Pinia 是 Vue 官方推荐的状态管理库,它提供了一种集中式的状态管理方式。
JavaScript 基础
-
基本数据类型:包括
Number、String、Boolean、Null、Undefined、Symbol、BigInt。 -
localStorage 和 sessionStorage 的区别:
localStorage数据永久存储,除非手动清除;sessionStorage数据仅在当前会话有效,关闭页面后清除。 -
面向对象特性:即封装、继承和多态。
-
public、protected、private 访问权限:
public表示公共访问权限;protected表示受保护访问权限,仅类内部及子类可访问;private表示私有访问权限,仅类内部可访问。 -
Promise 的状态:有
pending(进行中)、fulfilled(已成功)、rejected(已失败)三个状态。 -
作用域相关:
- 作用域分类:分为词法作用域和动态作用域。词法作用域在代码编写时确定,由代码嵌套结构决定,变量查找从当前作用域向上查找,直到全局作用域;动态作用域变量查找在函数调用时根据调用栈决定,而不是代码结构,比如
this。 - 作用域范围类型:有全局作用域、函数作用域和块级作用域(ES6 引入)。全局作用域在代码最外层定义变量,在浏览器环境下通常绑定到
window对象,在 node 环境下通常绑定到global对象,使用let和const声明的变量不会挂载到window对象;函数作用域在函数内部定义变量,只在该函数及嵌套函数内部可见;块级作用域使用let和const声明变量,只在定义它们的代码块(如if语句或for循环)内部可见。 - 作用域链:当 JavaScript 引擎查找变量值时遵循的查找路径,由当前执行环境的作用域与其所有外部作用域组成链式结构。每个函数创建时生成新的执行上下文,包含指向外部作用域的引用,形成链式结构。查找变量时,先在当前作用域查找,找不到则向上一级作用域查找,直到全局作用域,若全局作用域也未找到,则认为变量未定义,体现了 JavaScript 的词法作用域特性。
- 作用域分类:分为词法作用域和动态作用域。词法作用域在代码编写时确定,由代码嵌套结构决定,变量查找从当前作用域向上查找,直到全局作用域;动态作用域变量查找在函数调用时根据调用栈决定,而不是代码结构,比如
-
引用类型和基本类型区别:基本类型直接存储值,存储在栈中;引用类型存储的是指向对象的内存地址,栈内存存储引用地址,堆内存存储对象。
-
深拷贝实现方式:
- 递归:通过递归遍历对象的属性,实现深拷贝。
- JSON.parse(JSON.stringify()) :可以实现深拷贝,但无法处理函数、
undefined和循环引用等问题。 - structuredClone() :原生的深拷贝方法,用于复制复杂对象,如数组、对象、
Map、Set等。 - 手写深拷贝函数:根据具体需求编写函数实现深拷贝。
性能优化与其他
-
性能优化方式:
- 骨架屏:在页面加载过程中展示一个大致的页面结构,提升用户体验。
- 路由懒加载:在需要时才加载路由对应的组件,减少初始加载时间。
- 图片懒加载:延迟图片加载,提高页面加载速度。
- 防抖和节流:防止频繁触发事件,提高性能。
- 使用 http2:提高网络传输效率。
- 使用 SSR:服务器端渲染,改善首屏加载速度。
- 静态资源用 cdn:从内容分发网络加载静态资源,加快加载速度。
- 将 CSS 放在文件头部,JavaScript 文件放在底部:优化页面渲染顺序。
- 使用 iconfont 代替图片图标:减少图片请求,提高性能。
- 善用缓存,不重复加载相同的资源:节省带宽,提高加载速度。
- 使用事件委托:减少事件绑定数量,提高性能。
-
图片懒加载实现原理:将图片的真实地址存储在
data-src属性中,而不是直接放在src中。监听用户的滚动事件,检测图片是否进入可视区域。如果图片进入可视区域,则将data-src的值赋给src,触发图片加载。 -
调用大模型接口的方法:需要参考对应 API 文档,我调用的是coze的 可以参考coze api 文档。
我上面的面经还需要大家去自己去扩展相应的代码过一遍,这样记忆点才深刻!
这次的面试感受与总结
这次面试让受益很多,也给我打了个警,需要好好沉淀一下自己,金鳞岂是池中物,一遇风云便化龙。 希望大家都做自己人生的金鳞!
无论这次面试结果如何,它都是一次宝贵的成长机会。我希望通过分享这次面经,能为大家提供一些参考和启发。祝各位开发者在春招中披荆斩棘,斩获理想的 offer,在技术的道路上越走越远!