金三银四也开始了,学了这么久的前端,也要开始上上战场接受拷打了,于是昨天开始投简历,运气比较好,约到了3场面试,但不巧的是,三场面试都在今天。
昨天晚上安抚着紧张的心情,在背着自我介绍中入睡了,于是迎来了今天的第一场面试,上午10点,废话不多说,最新面经请过目。
面经一
上午10点左右面的这家是一家小公司,规模只有10个人左右。这场面试与其说是面试,不如说是聊天,什么八股文都没问。因为我在自我介绍中提到了对AIGC非常感兴趣,所以面试官一开始就和我在聊AIGC,AI可能有哪些应用啦、聊聊deepseek啦和AI对于前端开发的影响啦之类的,聊了有20多分钟以上,之后就是聊了一下项目是怎么做的。
差不多有40分钟吧,我问他之后还有面试吗,他说本来还有线下面试的,但因为我在外地,无法线下面试,于是给我发了一个链接,一个小作业,让我完成。
总之第一场面试就是这样,面试官挺和蔼的,基本上就是在聊天。
面经二
第二场面试就主打八股文了,面了25分钟左右,下午三点半。首先就是自我介绍,请参考这篇文章:
\]([面试:百度一面,吓尿了前言 在百度的学长,帮我内推了下简历。高兴还没有两秒,就接到通知面试的电话,瞬间压力山大。小公司也 - 掘金](https://juejin.cn/post/7335337310547017768 "https://juejin.cn/post/7335337310547017768")) ### 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 的理由可以归纳为以下几点: 1. **性能更好**:渲染速度更快,包体积更小。 2. **开发体验更佳**:组合式 API、TypeScript 支持、Vite 集成。 3. **灵活性更高**:适合各种规模的项目,逻辑复用更方便。 4. **生态成熟**:Vue Router、Pinia、Nuxt 3 等工具支持完善。 5. **未来趋势**: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,这是因为: 1. **`async function fn()`** : * `async` 关键字用于定义一个异步函数。 * 异步函数会自动返回一个 **Promise** 对象。 * 在函数体内,`return` 的值会被包装成一个 **resolved 状态的 Promise**。 2. **`return 1`**: * 这里的 `return 1` 相当于 `return Promise.resolve(1)`。 * 即使返回的是一个普通值(如 `1`),`async` 函数也会将其包装成一个 Promise。 3. **`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. 1. **`new Promise((resolve) => { resolve(1); })`** : * 创建一个新的 Promise 对象。 * 在 Promise 构造函数中,立即调用 `resolve(1)`,将 Promise 的状态设置为 **resolved** ,并将值 `1` 作为 resolved 值。 2. **`.then(() => 2)`** : * `then` 方法用于处理 Promise 的 resolved 状态。 * 这里的回调函数 `() => 2` 会接收上一个 Promise 的 resolved 值(即 `1`),但并没有使用它。 * 回调函数返回 `2`,这会成为新的 resolved 值。 3. **`fn().then(console.log)`** : * 调用 `fn()`,返回一个 Promise。 * 这个 Promise 的最终 resolved 值是 `2`(由 `.then(() => 2)` 决定)。 * `then(console.log)` 会将 `2` 打印到控制台。