金三银四也开始了,学了这么久的前端,也要开始上上战场接受拷打了,于是昨天开始投简历,运气比较好,约到了3场面试,但不巧的是,三场面试都在今天。
昨天晚上安抚着紧张的心情,在背着自我介绍中入睡了,于是迎来了今天的第一场面试,上午10点,废话不多说,最新面经请过目。
面经一
上午10点左右面的这家是一家小公司,规模只有10个人左右。这场面试与其说是面试,不如说是聊天,什么八股文都没问。因为我在自我介绍中提到了对AIGC非常感兴趣,所以面试官一开始就和我在聊AIGC,AI可能有哪些应用啦、聊聊deepseek啦和AI对于前端开发的影响啦之类的,聊了有20多分钟以上,之后就是聊了一下项目是怎么做的。
差不多有40分钟吧,我问他之后还有面试吗,他说本来还有线下面试的,但因为我在外地,无法线下面试,于是给我发了一个链接,一个小作业,让我完成。
总之第一场面试就是这样,面试官挺和蔼的,基本上就是在聊天。
面经二
第二场面试就主打八股文了,面了25分钟左右,下午三点半。首先就是自我介绍,请参考这篇文章:
[](面试:百度一面,吓尿了前言 在百度的学长,帮我内推了下简历。高兴还没有两秒,就接到通知面试的电话,瞬间压力山大。小公司也 - 掘金)
1. 项目介绍
自我介绍完了就开始问你的项目,是不是自己写的,实现了什么功能之类的。
2. js的数据类型
第二个问题也是老生常谈的问题了,js有哪些数据类型。一开始因为很紧张回答顺序都是乱的,导致undefined和 null都没有说出来。
js的数据类型分为基本类型和引用类型。基本类型分为:
js
string
number
boolean
undefined
null
symbol
bigint
引用类型有:
js
object
array
function
Date
Map
Set
2. map与普通对象的区别
因为提到了Map这种数据类型,于是就问了一下Map和普通对象的区别。
这里我只回答到了Map的键可以是任意类型而Object的键只能是字符串,并且Map可以迭代Object不行。
所以请看一下Map和Object的详细差别吧:
1. 键的类型
- Map: 键可以是任意类型,包括对象、函数、基本类型等。
- Object: 键只能是字符串或 Symbol,其他类型会被自动转换为字符串。
2. 大小获取
- Map : 可以通过
size
属性直接获取键值对的数量。 - Object : 需要手动计算属性数量,例如使用
Object.keys(obj).length
。
3. 性能
- Map: 在频繁增删键值对的场景下性能更好,因为它是专门为这种操作优化的。
- Object: 在频繁增删键值对的场景下性能较差,因为它不是为这种操作优化的。
4. 默认键
- Map: 没有默认键,只包含显式插入的键值对。
- Object : 有原型链,可能包含一些默认的属性和方法(如
toString
、hasOwnProperty
等),可能会与自定义键冲突。
5. 序列化
- Map : 不能直接使用
JSON.stringify
序列化,需要手动转换为数组或其他格式。 - Object : 可以直接使用
JSON.stringify
序列化。
6. 迭代
- Map : 可以直接使用
for...of
循环迭代,或者使用forEach
方法。 - Object : 需要使用
for...in
循环(注意会遍历原型链上的属性),或者使用Object.keys
、Object.values
、Object.entries
等方法。
7. 原型链
- Map: 没有原型链,不会继承额外的属性或方法。
- Object: 有原型链,可能会继承一些不期望的属性或方法。
3. vue中能实现数据响应的api
vue中能实现数据响应的api,有watch和computed,ref和reactive。
4. ref 与 reactive 的区别
ref与reactive的区别,首先第一点,ref可以包装基本类型和引用类型,reactive只能包装引用类型。第二点,ref取值要用.value,而reactive可以直接访问。
第三点,ref解构后仍然保持响应性,因为解构的是 ref
对象。
js
const count = ref(0);
const { value } = count; // 仍然具有响应性
而reactive解构后会丢失响应性,因为解构的是普通对象。如果需要解构并保持响应性,可以使用 toRefs
。
js
const state = reactive({ count: 0 });
const { count } = state; // 失去响应性
const { count } = toRefs(state); // 保持响应性
5. object.defineProperty 和 proxy 的区别
1. 功能范围
-
Object.defineProperty
:- 只能拦截对象属性的读取(
get
)和设置(set
)。 - 只能对已知属性进行拦截,无法拦截新增属性或删除属性。
- 适用于对单个属性的精细化控制。
- 只能拦截对象属性的读取(
-
Proxy
:- 可以拦截对象的多种操作,包括属性读取(
get
)、设置(set
)、删除(deleteProperty
)、枚举(enumerate
)、函数调用(apply
)等。 - 可以拦截未知属性的操作,支持动态属性的拦截。
- 功能更强大,适用于对整个对象的全面控制。
- 可以拦截对象的多种操作,包括属性读取(
2. 使用方式
-
Object.defineProperty
:- 需要针对每个属性单独设置拦截器。
- 修改对象的现有行为,而不是创建一个新对象。
js
const obj = {};
Object.defineProperty(obj, 'name', {
get() {
console.log('读取 name 属性');
return this._name;
},
set(value) {
console.log('设置 name 属性');
this._name = value;
},
});
obj.name = 'Alice'; // 设置 name 属性
console.log(obj.name); // 读取 name 属性
Proxy
:
- 创建一个代理对象,拦截对目标对象的所有操作。
- 不修改原对象,而是返回一个新的代理对象。
js
const target = {};
const proxy = new Proxy(target, {
get(obj, prop) {
console.log(`读取 ${prop} 属性`);
return obj[prop];
},
set(obj, prop, value) {
console.log(`设置 ${prop} 属性`);
obj[prop] = value;
return true;
},
});
proxy.name = 'Alice'; // 设置 name 属性
console.log(proxy.name); // 读取 name 属性
3. 拦截操作
-
Object.defineProperty
:-
仅支持
get
和set
拦截。 -
无法拦截以下操作:
- 属性的删除(
delete
)。 - 属性的枚举(
for...in
或Object.keys
)。 - 新增属性。
- 属性的删除(
-
-
Proxy
:-
支持多种操作的拦截,包括:
get
:读取属性。set
:设置属性。deleteProperty
:删除属性。has
:in
操作符。ownKeys
:Object.keys
、for...in
等。apply
:函数调用(当代理目标是函数时)。- 等等。
-
4. 性能
-
Object.defineProperty
:- 性能较好,适合对少量属性进行拦截。
- 但如果需要对大量属性进行拦截,代码会变得冗长且难以维护。
-
Proxy
:- 性能略低于
Object.defineProperty
,因为需要处理更多的拦截操作。 - 但在需要拦截多种操作或动态属性的场景下,
Proxy
的性能优势更明显。
- 性能略低于
7. ref 与 reactive 的实现原理
reactive 是借助proxy的代理作用,代理该引用类型的属性,当该属性被读取值时,返回该属性的值并且为该属性添加副作用函数;当该属性被修改值时,触发掉该属性的副作用函数
ref既可以代理原始类型也可以代理引用类型,当代理的是引用类型时,返回的是一个RefImpl的实例,通过类身上的get和set属性去读取值和修改值;当代理的是引用类型时,其实走的还是reactive的调用机制。
8. 聊一聊webpack 和 vite
1. 工作原理
-
Webpack:
- 采用打包机制,将所有模块打包成一个或多个 bundle 文件。
- 开发模式下使用
webpack-dev-server
提供服务,通过热更新(HMR)实现快速开发。 - 构建时需要遍历所有依赖,生成依赖图,然后进行打包。
-
Vite:
- 采用基于浏览器 ES 模块的开发模式,直接利用浏览器加载模块。
- 开发模式下使用原生 ES 模块,按需加载文件,无需打包。
- 生产模式下使用 Rollup 进行打包,生成优化的静态文件。
2. 开发体验
-
Webpack:
- 开发模式下,随着项目规模增大,启动时间和热更新速度可能变慢。
- 配置复杂,尤其是需要处理多种资源类型时。
-
Vite:
- 开发模式下启动速度极快,因为不需要打包,直接按需加载文件。
- 热更新速度更快,因为只更新修改的模块。
- 配置简单,开箱即用。
9. 前端领域的一些新框架
-
Svelte:
- 一个编译型前端框架,将组件编译为高效的原生 JavaScript 代码。
- 无虚拟 DOM,运行时开销极小,适合构建高性能应用。
- SvelteKit 是 Svelte 的全栈框架,支持 SSR、SSG 和客户端渲染。
-
Solid.js:
- 一个高性能的响应式 UI 框架,语法类似 React,但无虚拟 DOM。
- 通过细粒度的响应式更新实现高性能。
- 适合需要极致性能的应用。
-
Qwik:
- 一个专注于即时加载性能的框架,通过延迟加载 JavaScript 实现极快的首屏加载。
- 适合内容密集型网站,如电商或新闻站点。
10. 为什么选择vue3
选择 Vue 3 的理由可以归纳为以下几点:
- 性能更好:渲染速度更快,包体积更小。
- 开发体验更佳:组合式 API、TypeScript 支持、Vite 集成。
- 灵活性更高:适合各种规模的项目,逻辑复用更方便。
- 生态成熟:Vue Router、Pinia、Nuxt 3 等工具支持完善。
- 未来趋势:Vue 3 是 Vue 生态的未来,学习和使用 Vue 3 是前端开发的必然选择。
11.代码题
js
const obj = reactive({
a: 1,
b: 2,
})
const { a, b } = obj
obj.a = 3
console.log(a);
输出值是多少?
还是1,因为解构reactive包装的对象会丢失响应性,那怎么可以让它输出3呢,用toRefs就可以了。
js
const obj = reactive({
a: 1,
b: 2,
})
const { a, b } = toRefs(obj)
obj.a = 3
console.log(a);
12. 代码题
js
const obj = {
1: 1,
'1': 2
}
console.log(obj[1]);
输出结果是什么?
结果会输出2,因为对象的键值只能为字符串或symbol类型,数字1会自动转换为字符串1,于是后面的覆盖前面的。
13. 代码题
js
async function fn() {
return 1
}
fn().then(console.log)
结果会输出1,这是因为:
-
async function fn()
:async
关键字用于定义一个异步函数。- 异步函数会自动返回一个 Promise 对象。
- 在函数体内,
return
的值会被包装成一个 resolved 状态的 Promise。
-
return 1
:- 这里的
return 1
相当于return Promise.resolve(1)
。 - 即使返回的是一个普通值(如
1
),async
函数也会将其包装成一个 Promise。
- 这里的
-
fn().then(console.log)
:fn()
调用异步函数,返回一个 Promise。- 使用
.then()
方法处理 Promise 的 resolved 值。 - 当 Promise 成功解决(resolved)时,
then
中的回调函数console.log
会被调用,并接收 resolved 的值(这里是1
)。
14. 代码题
js
function fn() {
return new Promise((resolve) => {
resolve(1)
}).then(() => 2)
}
fn().then(console.log);
输出结果是2.
-
new Promise((resolve) => { resolve(1); })
:- 创建一个新的 Promise 对象。
- 在 Promise 构造函数中,立即调用
resolve(1)
,将 Promise 的状态设置为 resolved ,并将值1
作为 resolved 值。
-
.then(() => 2)
:then
方法用于处理 Promise 的 resolved 状态。- 这里的回调函数
() => 2
会接收上一个 Promise 的 resolved 值(即1
),但并没有使用它。 - 回调函数返回
2
,这会成为新的 resolved 值。
-
fn().then(console.log)
:- 调用
fn()
,返回一个 Promise。 - 这个 Promise 的最终 resolved 值是
2
(由.then(() => 2)
决定)。 then(console.log)
会将2
打印到控制台。
- 调用