紧接着上篇文章。在结束了下午三点半的面试之后,六点迎来了我今天的第三场面试。此时我已经身体乏力、疲惫不堪了,没想到面试是这么消耗精力的一件事,需要集中精神全神贯注,说话方式也得和平时不一样,需要口齿伶俐,说白了就是得夹着嗓子说话(苦笑)。但好在这场面试答得比较好,第二天就给了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
message = newValue"
/>
```
子组件需要定义 `modelValue` prop 并触发 `update:modelValue` 事件:
```js
```
##### 2. 多个 `v-model` 绑定
Vue3 允许同时绑定多个属性,例如同时绑定 `name` 和 `age`:
```js
userName = newName"
:age="userAge"
@update:age="newAge => userAge = newAge"
/>
```
子组件需定义对应的 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
{{ item.text }}
```
## 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
}
```
## 总结
感觉知识永远学不完,看文章时总是有不会的,唉,革命尚未完成,同志还须努力。还得沉淀沉淀再沉淀!