文章目录
- 前言
-
- [1. 什么是 AST(抽象语法树)?](#1. 什么是 AST(抽象语法树)?)
- [2.Vue 的 SFC 是什么?如何最大化利用?](#2.Vue 的 SFC 是什么?如何最大化利用?)
- [3. SSR 是什么?如何使用?](#3. SSR 是什么?如何使用?)
- [4. Vue2 为什么无法监听数组变化,该如何做?](#4. Vue2 为什么无法监听数组变化,该如何做?)
- [5.keep-alive 是什么?react 如何实现类似功能?](#5.keep-alive 是什么?react 如何实现类似功能?)
- [6. 什么时候用计算属性,什么时候用监听?](#6. 什么时候用计算属性,什么时候用监听?)
- [7. JS 中生成器和迭代器是什么,实际有什么应用?](#7. JS 中生成器和迭代器是什么,实际有什么应用?)
- [8. 什么是 TS 的 infer?如何使用?](#8. 什么是 TS 的 infer?如何使用?)
- 9.简要描述vite的原理
- [10. 简要说明如何最大支持 Tree Shaking?](#10. 简要说明如何最大支持 Tree Shaking?)
- 11.用math.random来计算抽奖概率会有什么问题?
前言
大家好,今天是冲击中级工程师第四天,让我们来完成今天的面试题打卡。
1. 什么是 AST(抽象语法树)?
答案:编译器(如 Babel、Webpack)无法直接修改你的代码字符串。它们必须先把代码转换成一种树状结构(JSON对象),这棵树精确地描述了代码的每一个部分(变量名、操作符、函数体)。如系列2所示,流程:源代码 -> 词法分析(Token) -> 语法分析 -> AST -> 修改/转换 -> 生成新代码。
下面代码可以很直观看到AST和我们编写代码的区别
javascript
// 源代码
const a = 1;
// 对应的 AST (简化)
{
"type": "VariableDeclaration", // 这是一个变量声明
"kind": "const", // 类型是 const
"declarations": [
{
"type": "VariableDeclarator",
"id": { "type": "Identifier", "name": "a" }, // 变量名 a
"init": { "type": "Literal", "value": 1 } // 初始值 1
}
]
}
2.Vue 的 SFC 是什么?如何最大化利用?
答案:SFC,全称为:Single File Component,直译为单文件组件,是 Vue 的核心概念之一,指把一个组件的结构、逻辑、样式写在同一个 .vue 文件中。
下面是一个典型的SFC文件:
html
<template>
<div>{{ msg }}</div>
</template>
<script setup>
import { ref } from 'vue'
const msg = ref('Hello SFC')
</script>
<style scoped>
div {
color: red;
}
</style>
vue这样做的优点:
1.高内聚:模板 + 逻辑 + 样式放在一起,方便维护
2.可编译优化:Vue 编译器可做静态提升、patch flag 等优化
3.强工程化支持:TS、CSS 预处理器、HMR、lint、tree-shaking
4.组件级作用域样式:避免样式污染
3. SSR 是什么?如何使用?
SSR,全称为Server-Side Rendering,服务端渲染,主要是指页面在服务器上先渲染成完整 HTML,再返回给浏览器展示,而不是等 JS 在浏览器执行后再生成页面。
以往都是客户端渲染,对比客户端渲染
CSR (客户端渲染):浏览器请求页面 -> 拿到空的 HTML -> 下载 JS -> JS 运行并把内容填进页面(首屏慢,爬虫抓不到内容)。
SSR (服务端渲染):浏览器请求页面 -> 服务器运行 Vue -> 生成完整的 HTML 字符串 -> 浏览器直接显示(首屏快,SEO 好)
如何使用?
vue可以使用Nuxt,打开SSR,React可以使用Next
4. Vue2 为什么无法监听数组变化,该如何做?
答案:Vue2 基于 Object.defineProperty,无法监听数组索引和 length 的变化,因此不能通过 arr[index] 或修改 length 触发更新。Vue2 通过重写数组的 7 个变异方法(push, pop, shift, unshift, splice, sort, reverse。只要你调用这 7 个方法,Vue 就能监听到)来实现数组响应式。正确做法是使用 splice 或 Vue.set。Vue3 是基于 ES6 Proxy(代理) 实现响应式,通过拦截对象和数组的所有基本操作(get / set / deleteProperty / has / ownKeys 等),统一做依赖收集和派发更新,因此可以完整监听数组索引、length 变化以及属性新增删除,不再需要像 Vue2 那样重写 Array 原型方法。
javascript
// 错误做法 (Vue2 不会更新视图)
this.list[0] = '新的值';
// 正确做法 1: 使用重写的方法
this.list.splice(0, 1, '新的值');
// 正确做法 2: 使用 $set
this.$set(this.list, 0, '新的值');
5.keep-alive 是什么?react 如何实现类似功能?
答案:keep-alive 是 Vue 提供的内置抽象组件,用于在组件切换时缓存组件实例,而不是销毁,从而保留组件内部状态并减少重复创建带来的性能开销。被 keep-alive 包裹的组件不会触发 unmounted,而是通过 activated 和 deactivated 生命周期进行激活与失活。内部基于 key 维护缓存 Map,并通过 LRU 策略结合 max 属性控制缓存上限。
React 没有官方等价能力,因为 React 的设计是组件不在渲染树中就会被卸载。实际项目中通常通过样式隐藏、状态上提,或使用 react-activation 等第三方库来模拟组件缓存行为。
6. 什么时候用计算属性,什么时候用监听?
答案:computed 用于从已有响应式状态派生出新的状态,适合纯计算场景,具有缓存能力,依赖不变不会重复计算,强调声明式的数据关系。watch 用于在数据变化后执行副作用,比如发起接口请求、修改其他状态、路由跳转或与第三方库交互,属于命令式响应。原则上能用 computed 解决的派生数据不应该用 watch,watch 不用于计算新值,而用于处理变化带来的行为。
javascript
// 场景:购物车总价 -> 用 Computed
const total = computed(() => price.value * count.value);
// 场景:搜索框输入停止 1秒 后自动搜索 -> 用 Watch
watch(searchText, (newVal) => {
setTimeout(() => { api.search(newVal) }, 1000);
});
7. JS 中生成器和迭代器是什么,实际有什么应用?
答案:Iterator 是 ES 提供的统一遍历协议,通过 next 方法返回 value 和 done,使不同数据结构可以被 for...of、解构和扩展运算符统一遍历。Generator 是一种可以暂停和恢复执行的函数,用来更方便地创建迭代器,Generator 本身自动实现了 Iterator 和 Iterable 接口。它们的核心价值在于统一遍历接口、支持惰性计算以及可暂停的控制流。在工程中,Iterator 广泛用于 for...of 等语言特性,Generator 常用于自定义可遍历结构、惰性序列,以及在 redux-saga、Koa v1 等场景中用于复杂异步流程控制和流程编排。
实际使用示例:
javascript
async function fetchUsers(page: number, pageSize: number) {
const res = await fetch(`/api/users?page=${page}&pageSize=${pageSize}`)
const data = await res.json()
return data.list as User[]
}
async function* userIterator(pageSize = 100) {
let page = 1
while (true) {
const list = await fetchUsers(page, pageSize)
if (!list.length) {
return
}
for (const user of list) {
yield user // 每次只 yield 一个用户
}
page++
}
}
8. 什么是 TS 的 infer?如何使用?
答案:infer 是 TypeScript 条件类型中的关键字,用于在类型匹配过程中声明一个待推断的类型变量,从复杂类型结构中反向提取子类型。它常用于从函数类型中推断返回值和参数类型,从 Promise 中拆解返回值,从数组和元组中提取元素类型,以及在字符串模板类型中提取子串。TypeScript 内置的 ReturnType、Parameters、Awaited 等高级工具类型本质上都是基于 infer 实现的。infer 的核心价值是让类型具备模式匹配和结构拆解能力,从而减少重复类型定义并提升类型系统的表达能力。
例子(提取函数参数)
javascript
type FnInfo<T> =
T extends (...args: infer P) => infer R
? { params: P; return: R }
: never
type Info = FnInfo<(id: number, name: string) => boolean>
// { params: [number, string]; return: boolean }
9.简要描述vite的原理
答案:Vite 在开发阶段基于浏览器原生 ES Module,通过 dev server 按需编译源码,不做全量打包,因此冷启动和热更新非常快。同时使用 esbuild 对第三方依赖进行预构建,统一为 ESM 并减少请求数量。在生产环境,Vite 使用 Rollup 进行静态打包,负责 Tree Shaking、代码分割和资源优化,从而兼顾开发体验和生产性能。
10. 简要说明如何最大支持 Tree Shaking?
答案:Tree Shaking 是基于 ES Module 静态分析的打包优化技术,用于在构建阶段移除未被使用的导出代码。它依赖 import/export 语法和无副作用模块设计,配合 sideEffects 标记和代码压缩工具,可以显著减少最终 bundle 体积并提升加载性能。
1.基于 ES Module 的命名导出 API确保项目是基于ES Module来进行的。
2.尽量用 export const func = ... 导出单个函数,而不是 export default { ... } 导出一个大对象(大对象很难被拆分优化)。
11.用math.random来计算抽奖概率会有什么问题?
答案:V8 引擎(Chrome/Node.js)底层使用的是 xorshift128+ 算法。这是一种确定性算法。只要拿到足够的历史数据序列,就能推算出内部状态(Seed),从而预测未来所有的随机数。对于涉及真金白银的抽奖,这是巨大的漏洞。虽然现代浏览器的算法改进了很多,但在某些特定数值范围内,Math.random() 并不是完美的均匀分布
解决方案:使用安全伪随机数生成器 (CSPRNG)
javascript
// 1. 创建一个容器存放随机数
const array = new Uint32Array(1);
// 2. 填充真正的随机数 (基于物理噪声)
window.crypto.getRandomValues(array);
// 3. 归一化处理 (这一步比较复杂,简化理解为取模)
const winnerIndex = array[0] % userCount;
#总结
今天面试题就到这里,让我们每天学习几个题,一起成长。管他难易,获得成长就好。明天见。