一天三场面试,口干舌燥要晕倒(二)

紧接着上篇文章。在结束了下午三点半的面试之后,六点迎来了我今天的第三场面试。此时我已经身体乏力、疲惫不堪了,没想到面试是这么消耗精力的一件事,需要集中精神全神贯注,说话方式也得和平时不一样,需要口齿伶俐,说白了就是得夹着嗓子说话(苦笑)。但好在这场面试答得比较好,第二天就给了offer。

好了废话不多说,一起来看看这份面试题吧。

1. vue3 的响应式原理

vue3的响应式原理主要是基于ES6打造的proxy来实现的。比如最常用的两个api,reactive 和 ref:

reactive 是借助proxy的代理作用,代理该引用类型的属性,当该属性被读取值时,返回该属性的值并且为该属性添加副作用函数;当该属性被修改值时,触发掉该属性的副作用函数

ref既可以代理原始类型也可以代理引用类型,当代理的是原始类型时,返回的是一个RefImpl的实例,通过类身上的get和set属性去读取值和修改值;当代理的是引用类型时,其实走的还是reactive的调用机制。

2. 聊聊虚拟dom

虚拟DOM是一个轻量级的JavaScript对象,是真实DOM的抽象表示,主要用于提升页面渲染性能,减少操作真实DOM结构的次数。因为操作真实DOM代价是非常昂贵的,会频繁引发回流与重绘。而采用虚拟DOM结构,当数据变化时,框架会生成一个新的虚拟DOM树,使用Diff算法将新旧两棵虚拟DOM树进行对比,找出需要更新的最小差异部分,将差异部分批量应用到真实DOM,减少渲染次数,并且让开发者只需要关注数据状态,无需手动操作DOM,大大地提升了开发效率,并且虚拟DOM还支持跨平台开发。

3. vue的两种路由方式

vue的路由方式有两种:hash路由和history路由,我已经写过文章了,详情请见这篇文章:

\]([(笔记)前端路由1. 什么是前端路由 前端路由是通过url路径来匹配对应的代码块的这么一套机制 修改 url 后页面要更 - 掘金](https://juejin.cn/post/7473017224056471590 "https://juejin.cn/post/7473017224056471590")) ## 4. 浏览器的同源策略及跨域 浏览器的同源策略及跨域也是常考的问题之一了,JYM一定要在心里自己多复述几遍,确保能够清晰流利地讲给面试官听。面试是一个展示自己的过程,自己会的一定要大声流利地说出来,慢一点也没有关系,要让面试官清晰地知道我们是会的,让面试官给我们加分。 关于跨域的文章我也写过,在这里就不赘述了。跨域就请见这篇文章: \[\> \]([(笔记)什么是跨域?1. 什么是跨域? 想象一下,你住在一个小区里,小区有门禁系统,只允许本小区的住户自由进出。如果外来 - 掘金](https://juejin.cn/post/7473162581315813403 "https://juejin.cn/post/7473162581315813403")) ## 5. 浏览器的存储 浏览器的存储有四种,分别是:localStorage,sessionStorage、cookies和indexDB。它们的特点分别是: localStorage是永久存储,大小在5MB-10MB左右,以字符串类型的键值对进行存储,不可跨域。 sessionStorage是本会话页面有效,大小也在5MB-10MB左右,以字符串类型的键值对进行存储,不可跨域。 cookies可以存放复杂的数据类型,大小4kb-5kb左右,只能由后端来设置,它在每次发送请求时都会自动携带在响应头中传给后端。 indexDB是一种客户端数据库,也能存放复杂数据类型,大小理论上无限大,根据你电脑的硬盘决定。 ## 6. 浏览器的垃圾回收机制 这道题没有答出来,在那里胡编乱造(笑。 浏览器的垃圾回收机制(Garbage Collection, GC)是自动管理内存的核心机制,用于释放不再使用的对象所占用的内存,防止内存泄漏。以下是其核心原理和实现方式的详细说明: #### **1. 垃圾回收的基本目标** * **自动释放内存** :开发者无需手动管理内存(如 `malloc/free`),由引擎自动追踪和回收"不可达"(Unreachable)的对象。 * **避免内存泄漏**:防止因程序逻辑错误导致无用对象长期占用内存。 #### **2. 关键算法** ##### **2.1 标记-清除(Mark and Sweep)** * **现代浏览器的主流算法**(如 V8、SpiderMonkey)。 * **步骤**: 1. **标记阶段**:从根对象(Roots,如全局变量、当前函数作用域链、活动线程等)出发,递归遍历所有可达对象,标记为"存活"。 2. **清除阶段**:遍历堆内存,回收未被标记的对象,将其内存返回空闲列表。 * **优点**:解决循环引用问题(若两个对象互相引用,但无法从根访问,仍会被回收)。 ##### **2.2 引用计数(Reference Counting)** * **早期算法**(如旧版 IE),因无法处理循环引用已基本被淘汰。 * **原理**:记录每个对象的被引用次数,当引用数为 0 时立即回收。 * **缺陷** :循环引用会导致对象永远无法回收(如 `A → B → A`)。 #### **3. 优化策略** ##### **3.1 分代回收(Generational Collection)** * **内存分代**:基于对象存活时间,将堆分为: * **新生代(Young Generation)** :存放短生命周期对象(如临时变量)。使用 **Scavenge 算法**(复制存活对象到新空间,清空旧空间)。 * **老生代(Old Generation)** :存放长生命周期对象(如全局变量)。使用 **标记-清除** 或 **标记-整理(Mark-Compact)** (清除后整理内存碎片)。 * **晋升机制**:新生代对象经历多次 GC 后仍存活,会被移到老生代。 ##### **3.2 增量标记(Incremental Marking)** * **问题**:一次性标记所有对象会导致主线程卡顿(Stop-The-World)。 * **解决**:将标记过程拆分为多个小步骤,穿插在 JavaScript 执行间隙。 ##### **3.3 惰性清理(Lazy Sweeping)** * 清理阶段延迟执行或分片执行,减少对主线程的影响。 ##### **3.4 并行/并发回收** * **并行**:GC 任务在多个辅助线程并行执行(如 V8 的并行标记)。 * **并发**:GC 线程与主线程同时运行(需处理读写竞争)。 #### **4. 触发 GC 的时机** * **主动触发** :开发者可通过 `window.gc()`(需启动 Chrome 时加 `--js-flags="--expose-gc"`)。 * **被动触发**: * 分配内存时,空闲内存不足。 * 脚本执行暂停期间(如事件循环空闲)。 * 根据时间或内存分配阈值动态调整。 #### **5. 内存泄漏的常见原因** 即使有 GC,仍需开发者注意: 1. **意外的全局变量** (未声明的变量会挂载到 `window`)。 2. **未清除的定时器或事件监听** (如 `setInterval`、`addEventListener`)。 3. **闭包引用外部变量**(如函数内部持有外部大对象)。 4. **脱离 DOM 的引用** (如 `const elements = document.querySelectorAll('div')`,即使 DOM 移除,`elements` 仍引用节点)。 ## 7. 组件传值 #### 一、基础传值方式 ##### 1. **Props / 自定义事件(父子通信)** * **Props(父 → 子)** :父组件通过属性向子组件传递数据。 ```js ``` **自定义事件(子 → 父)** :子组件通过 `emit` 触发事件,父组件监听。 ```js ``` ##### 2. **v-model 双向绑定** ##### 1. 默认的单个 `v-model` ```js ``` 子组件需要定义 `modelValue` prop 并触发 `update:modelValue` 事件: ```js ``` ##### 2. 多个 `v-model` 绑定 Vue3 允许同时绑定多个属性,例如同时绑定 `name` 和 `age`: ```js ``` 子组件需定义对应的 props 和事件: ```js ``` #### 二、跨层级通信 ##### 3. **Provide / Inject** * **父组件提供数据** :使用 `provide` 共享数据。 ```js // 父组件(Composition API) import { provide, ref } from 'vue'; const count = ref(0); provide('countKey', count); // 提供响应式数据 ``` **子孙组件注入数据** :使用 `inject` 获取数据。 ```js // 子组件 import { inject } from 'vue'; const injectedCount = inject('countKey', defaultValue); ``` ##### 4. **状态管理(Pinia / Vuex)** * **Pinia(推荐)** :Vue3 官方推荐的状态管理库。 ```js // store/user.js export const useUserStore = defineStore('user', { state: () => ({ name: 'Alice' }), actions: { updateName(newName) { this.name = newName; } } }); // 组件中使用 const userStore = useUserStore(); userStore.updateName('Bob'); ``` ## 8. get 和 post 请求的区别 #### **1. 设计目的** * **GET** 用于**获取资源**(幂等操作),不会修改服务器数据,仅请求指定信息。 * **POST** 用于**提交数据到服务器**(非幂等操作),通常会导致服务器状态变化(如创建或更新资源)。 #### **2. 参数位置** * **GET** 参数通过\*\*URL查询字符串(Query String)\*\*传递,格式为 `?key1=value1&key2=value2`,直接可见。 *示例:* `https://api.example.com/users?id=123&name=John` * **POST** 参数通过\*\*请求体(Request Body)\*\*传递,对用户不可见(但抓包仍可查看)。 *示例:* ```js POST /users HTTP/1.1 Content-Type: application/json {"name": "John", "age": 30} ``` #### **3. 数据长度限制** * **GET** 受**URL长度限制** (不同浏览器限制不同,通常为 2KB\~8KB)。 *原因:* 参数附加在URL中,浏览器或服务器可能截断超长URL。 * **POST** 理论上**无长度限制**(数据在请求体中),但实际受服务器配置限制(如Nginx默认限制为1MB)。 #### **4. 缓存与历史记录** * **GET** 会被浏览器主动**缓存** (如静态资源),URL可能保留在浏览器历史记录或书签中。 *场景:* 重复访问同一URL时可直接使用缓存。 * **POST** 默认**不缓存**,浏览器不会保存请求体数据,也不会保留在历史记录中。 #### **5. 安全性** * **GET** 参数明文暴露在URL中,可能被浏览器历史、服务器日志记录或他人直接看到,**不适合传输敏感信息**(如密码)。 * **POST** 参数在请求体中,相对更隐蔽,但若不使用HTTPS,数据仍可能被截获(如抓包工具)。 *注意:* **安全性取决于是否加密(HTTPS),而非请求方法本身。** ## 9. v-if 和 v-show 的区别 v-if直接让dom结构消失,不加载,而v-show是让display属性为none。 如果需要频繁的切换组件最好使用v-show。 ## 10. rem 和 js实现移动端的适配是怎么做的? 使用js获取屏幕的宽度然后去修改根元素的字体daxiao,这样就能实现rem单位基于屏幕宽度的变化,1rem就是一倍根元素的字体大小。 ```js (function () { const setRootFontSize = () => { const designWidth = 750; // 设计稿宽度(如 750px 对应 iPhone 6/7/8) const baseFontSize = 100; // 1rem = 100px(方便计算,如设计稿中 200px → 2rem) const scale = document.documentElement.clientWidth / designWidth;// 获取屏幕宽度相对于设计稿屏幕宽度的倍数 document.documentElement.style.fontSize = baseFontSize * Math.min(scale, 2) + 'px'; // 限制最大缩放比例 }; setRootFontSize(); window.addEventListener('resize', setRootFontSize); })(); ``` ## 11. css 的盒子模型 浏览器渲染一个容器时,会把容器渲染成 4 个区域,分别是 content、padding、border、margin。由这4 个区域组成了一个盒子模型。 **标准盒模型** :`width/height` = `content`,不含 `padding` 和 `border`。 **IE盒模型** :`width/height` = `content` + `padding` + `border`。 **切换** :`box-sizing: content-box`(标准) / `border-box`(IE模型)。 ## 12. vue的slot #### 1. **默认插槽(Default Slot)** * **作用** :父组件传递的内容会被渲染到子组件中 `` 标签的位置。 * **子组件**: ```js ``` **父组件**: ```js

这是父组件传递的内容

``` #### 2. **具名插槽(Named Slots)** * **作用**:通过名称标识多个插槽,实现内容精准分发。 * **子组件**: ```js ``` **父组件**: ```js

默认插槽的内容

``` #### 3. **作用域插槽(Scoped Slots)** * **作用**:子组件向父组件的插槽传递数据,实现数据驱动的内容渲染。 * **子组件**: ```js ``` **父组件**: ```js ``` ## 13. 手写节流或深拷贝 最后一道题是手写题,从节流和深拷贝中选一个,我选了节流。 不知道为什么在面试的时候敲代码手都是抖的,思路也比较混乱,但还好是写出来的。 ### 节流函数 节流函数的原理是:利用闭包存放上一次点击按钮时的时间,比较当前点击按钮的时间和上一次点击按钮的时间,如果时间小于设定的时间的话,不执行函数;当大于设定的时间时,才执行函数,并更新上次点击按钮的时间的值。 ```js function throttle(fn, wait) { let preTime = null return function (...args) { let nowTime = Date.now() if (nowTime - preTime > wait) { fn.call(this, ...args) preTime = nowTime } } } ``` ### 深拷贝 深拷贝就是浅拷贝加上递归,当需要对象的属性是原始类型时,直接放到新对象中;当属性是引用类型时,递归自己: ```js function deepClone(obj) { let clone = obj instanceof Array ? [] : {} for (let key in obj) { if (obj.hasOwnProperty(key)) { if (obj[key] instanceof Object) { clone[key] = deepClone(obj[key]) } else { clone[key] = obj[key] } } } return clone } ``` ## 总结 感觉知识永远学不完,看文章时总是有不会的,唉,革命尚未完成,同志还须努力。还得沉淀沉淀再沉淀!

相关推荐
七灵微2 小时前
ES6入门---第三单元 模块三:async、await
前端·javascript·es6
七灵微4 小时前
ES6入门---第二单元 模块五:模块化
前端·ecmascript·es6
m0_616188495 小时前
vue3 - keepAlive缓存组件
前端·vue.js·缓存
lh_12546 小时前
Uni-app 组件使用
前端·javascript·uni-app
Kx…………6 小时前
Day3:设置页面全局渐变线性渐变背景色uniapp壁纸实战
前端·学习·uni-app·实战·项目
试着6 小时前
【AI面试准备】元宇宙测试:AI+低代码构建虚拟场景压力测试
人工智能·低代码·面试
Q_Boom6 小时前
前端跨域问题怎么在后端解决
java·前端·后端·spring
搬砖工程师Cola6 小时前
<Revit二次开发> 通过一组模型线构成墙面,并生成墙。Create(Document, IList.Curve., Boolean)
java·前端·javascript
林十一npc6 小时前
Fiddler抓取APP端,HTTPS报错全解析及解决方案(一篇解决常见问题)
android·前端·网络协议·https·fiddler·接口测试
小妖6667 小时前
4个纯CSS自定义的简单而优雅的滚动条样式
前端·javascript·css