2026/6/25前端实习面试复盘
面试复盘
工程化
Vite跟Webpackage的区别
| Webpack | Vite | |
|---|---|---|
| 启动方式 | 先全量打包整个应用,再启动开发服务器 | 利用浏览器原生 ES Module,按需编译 |
| 热更新 | 改动一个文件,重新打包相关模块 | 只替换改动的模块,速度极快 |
| 底层工具 | Babel + Webpack 自己打包 | esbuild(预构建)+ Rollup(生产打包) |
按需编译
// a.js
export const name = 'Tom';
// b.js
import { name } from './a.js';
//html
<script type="module" src="b.js"></script>
浏览器看到 type="module" 后,会自动帮你去请求并执行 b.js 及其依赖的 a.js。
Vite 怎么利用这个?
开发时,Vite 直接把源码里的 import 原样交给浏览器,浏览器按需去请求每个模块。只有浏览器请求到哪个文件,Vite 才实时编译哪个文件再返回。
底层工具(Babel + Webpack 自己打包)
在介绍这两部分工作之前,先说一下webpack的本质
webpack本质是一个模块打包器(Module Bundler),它的核心工作就是,从入门文件出发,找到所有的依赖关系,把多个模块文件打包成一个或者多个浏览器能处理的js代码
关键点:浏览器原本不认识import/export这些语法,所以需要webpack的帮助
Babel,将ES6新语法(如箭头函数、解构赋值、`const`、`async/await`等转化成ES5老语法)
esbuild(预构建)+ Rollup(生产打包)
vite并不是把一个工具从头干到尾,而且分为开发阶段和生产阶段
时期 工具 原因
开发阶段 esbuild(预构建+即时编译) 追求极致的启动速度和热编译速度
生产打包 Rollup 追求极致的打包体积和代码拆分
esbuild
esbuild是go语言开发的打包的打包器,编译速度比js写的工具快10-100倍,它主要做两件事
一.预构建
1.将Common.js转化成Es Module,很多npm还是module.exports,浏览器不认识,esbuild将这些包转换成export
2.零碎文件整合,比如`lodash`可能存在几十上百个个内部模块,如果浏览器挨个请求,会触发很多次HTTP请求导致页面卡顿,esbuild将它们处理到一个文件中
二.即时编译
开发时你写的代码(vue、ts),浏览器不认识,当你请求某个文件时,esbuild在毫秒级将他们编译好返回给浏览器,因为是编译被请求的文件,所以速度极快
Rollup
生产编译阶段就到了Rolluop大显神威了
因为webpack
1.代码切分(code splitting)能力不成熟
2.Tree Shaking(去除无用代码)能力不如rollup精细
3.对高级语法和插件的生态支持不如rollup
Code Splitting
把代码拆成多个文件,按需加载,而不是一次性把所有 JS 打包到一个巨大的文件里。
Tree Shaking
打包时自动删掉没有被引用的代码,就像摇树一样把枯叶抖掉。
Ts
interface跟type的区别
| interface | type | |
|---|---|---|
| 同名合并 | ✅ 同名 interface 自动合并 | ❌ 同名 type 报错 |
| 联合类型 / 交叉类型 | ❌ 不支持 ` | 和&` 直接声明 |
| 扩展方式 | extends |
& 交叉 |
| 适用场景 | 描述对象结构,适合公开 API | 复杂类型运算、工具类型、映射类型 |
复杂类型运算:通过|跟&实现
工具类型:TypeScript 内置了一些"类型函数",接收一个类型,返回一个新类型。
映射类型:映射类型可以遍历一个类型的属性,逐个改造。
通信
我提到了使用websocket,hr跟我说知不知道“轮询”,我一开始说不知道,后面hr介绍的时候我想到了二维码那部分使用到了
websocket:
轮询:
轮询是指客户端每隔一段固定的时间,主动向服务器发送一次请求,查询是否有新的状态或数据。它不依赖服务器主动推送,而是靠客户端不断询问来实现状态同步。
以二维码登录为例,网页首先向后端请求一个二维码,服务器返回一个唯一标识和二维码图片。网页把这个二维码展示给用户,同时开启一个定时器,比如每两秒向后端发送一次查询请求,参数就是那个唯一标识。服务器收到请求后,检查这个二维码当前的状态,然后返回给网页。返回的状态一般有四种:如果用户还没扫,状态就是“等待扫码”,网页继续轮询;如果用户已经用手机扫码但还没有确认,状态就是“已扫码”,网页可以提示用户在手机上确认;如果用户点击了确认,状态就是“已确认”,服务器返回登录凭证,网页拿到后停止轮询并完成登录;如果二维码长时间未被使用而失效,状态就是“已过期”,网页提示用户刷新二维码。这种场景之所以用轮询,是因为用户扫码的过程本身就有时延,两秒的轮询间隔完全在可接受范围内,而且实现起来非常简单,不需要维护长连接,对服务器资源消耗也很小。
组件通信
vue3
- Props + Emits ✅
- Ref + defineExpose ✅
- Pinia / Vuex ✅
- Mitt(Event Bus)✅
- Provide / Inject ✅
- localStorage / sessionStorage ✅
- 忘了的那个 → 大概率是
$attrs/$listeners或 路由传参
1.props+Emits
父组件
<template>
<Child :message="msg" @update:message="msg = $event" />
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const msg = ref('Hello')
</script>
子组件
<template>
<input :value="message" @input="$emit('update:message', $event.target.value)" />
</template>
<script setup>
defineProps(['message'])
defineEmits(['update:message'])
</script>
2.Ref + defineExpose
父组件
<template>
<Child ref="childRef" />
<button @click="handleClick">调用子组件方法</button>
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const childRef = ref(null)
function handleClick() {
childRef.value.focus()
console.log(childRef.value.count)
}
</script>
子组件
<template>
<input ref="inputRef" />
</template>
<script setup>
import { ref } from 'vue'
const inputRef = ref(null)
const count = ref(0)
function focus() {
inputRef.value.focus()
count.value++
}
defineExpose({ focus, count })
</script>
3.pinia/vuex
vuex
store 定义
// store/index.js
import { createStore } from 'vuex'
// 创建总仓库
export default createStore({
// 状态数据
state: {
count: 0,
user: null
},
// 同步修改状态(通过 commit 触发)
mutations: {
// 参数:(state, payload)
increment(state, payload) {
state.count += payload
},
setUser(state, user) {
state.user = user
}
},
// 异步操作(通过 dispatch 触发)
actions: {
// 参数:(context, payload) 或解构 { commit }
async fetchUser({ commit }) {
const res = await fetch('/api/user')
const data = await res.json()
commit('setUser', data) // action 内部调用 mutation 修改状态
}
},
// 计算属性(类似 computed)
getters: {
doubleCount: state => state.count * 2
}
})
组件中使用(Options API + Composition API 两种写法)
<template>
<!-- 直接使用 state 和 getters -->
<div>计数:{{ $store.state.count }}</div>
<div>两倍:{{ $store.getters.doubleCount }}</div>
<button @click="add">同步 +1</button>
<button @click="getUser">异步获取用户</button>
</template>
<script>
export default {
methods: {
add() {
// 调用 mutation(同步)
this.$store.commit('increment', 1)
},
getUser() {
// 调用 action(异步)
this.$store.dispatch('fetchUser')
}
}
}
</script>
<!-- Composition API 写法 -->
<script setup>
import { useStore } from 'vuex'
const store = useStore()
function add() {
store.commit('increment', 1) // 触发 mutation
}
function getUser() {
store.dispatch('fetchUser') // 触发 action
}
</script>
pinia
store 定义(小仓库,每个文件一个 store)
// stores/counter.js
import { defineStore } from 'pinia'
// defineStore 创建小仓库,不需要总仓库
export const useCounterStore = defineStore('counter', {
// 状态数据
state: () => ({
count: 0
}),
// 计算属性
getters: {
doubleCount: (state) => state.count * 2
},
// 同步 + 异步方法写在一起,不用区分 mutations
actions: {
// 同步修改,直接 this.xxx
increment(payload) {
this.count += payload
},
// 异步操作,同样直接 this.xxx
async fetchAndUpdate() {
const res = await fetch('/api/count')
const data = await res.json()
this.count = data.count // 直接修改,不用 commit
}
}
})
组件
<template>
<div>计数:{{ counter.count }}</div>
<div>两倍:{{ counter.doubleCount }}</div>
<button @click="counter.increment(1)">+1</button>
<button @click="counter.fetchAndUpdate()">异步更新</button>
</template>
<script setup>
import { useCounterStore } from '@/stores/counter'
// 引入即用,不需要 useStore()
const counter = useCounterStore()
</script>
4.Mitt —— Vue 3 中的 Event Bus 替代方案
Vue 3 移除了 $on / $off,跨组件通信可用 Mitt 这个小巧的事件库。
安装
bash
npm install mitt
创建事件总线(utils/emitter.js)
import mitt from 'mitt'
export const emitter = mitt()
组件 A(发送事件)
<script setup>
import { emitter } from '@/utils/emitter'
const sendMsg = () => emitter.emit('chat', 'Hello from A')
</script>
组件 B(接收事件 + 销毁)
<script setup>
import { emitter } from '@/utils/emitter'
import { onMounted, onUnmounted } from 'vue'
onMounted(() => emitter.on('chat', handleChat))
onUnmounted(() => emitter.off('chat', handleChat)) // ⚠️ 记得解绑,防止内存泄漏
function handleChat(msg) { console.log(msg) }
</script>
5.Provide / Inject —— 祖先向后代传递数据(跨层级)
祖先组件(提供数据)
<script setup>
import { provide } from 'vue'
provide('msg', 'Hello from ancestor')
</script>
后代组件(接收数据,任意深度都能拿到)
<script setup>
import { inject } from 'vue'
const msg = inject('msg', '默认值')
</script>
核心:祖先 provide('key', 值),后代 inject('key', 默认值)。不需要经过中间组件层层 props 传递。
6.localStorage / sessionStorage —— 浏览器本地存储,可用于跨组件共享数据
写入
localStorage.setItem('token', 'abc123')
sessionStorage.setItem('temp', 'data')
读取
const token = localStorage.getItem('token')
const temp = sessionStorage.getItem('temp')
删除
localStorage.removeItem('token')
localStorage.clear() // 清空全部
组件中使用
// 存
localStorage.setItem('user', JSON.stringify({ name: 'Tom' }))
// 取
const user = JSON.parse(localStorage.getItem('user'))
核心区别
| localStorage | sessionStorage | |
|---|---|---|
| 生命周期 | 永久保存,手动删除 | 关闭标签页即清除 |
| 作用范围 | 同源所有标签页共享 | 仅当前标签页 |
| 容量 | 约 5MB | 约 5MB |
适合场景:token 持久化、未登录购物车、草稿暂存等不需要响应式的数据共享。注意存对象要用 JSON.stringify,取出来用 JSON.parse。
v-mode
本质是props + emit 的语法糖
<input v-model="text" />
等价于
<input :value="text" @input="text = $event.target.value" />
| 元素类型 | 绑定的 prop | 监听的事件 |
|---|---|---|
<input type="text">跟 <textarea> |
value |
input |
<input type="checkbox"> |
checked |
change |
<input type="radio"> |
checked |
change |
<select> |
value |
change |
组件上
<MyInput v=model="text" />
等价于
<MyInput :modelValue="text" @update:modelValue="text = $event" />
所以子组件里必须:
- 用
defineProps接收modelValue - 用
defineEmits发送update:modelValue事件
<!-- MyInput.vue -->
<template>
<input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" />
</template>
<script setup>
defineProps(['modelValue']);
defineEmits(['update:modelValue']);
</script>
布局
flex跟grid
flex布局
grid布局
盒子居中
水平居中--margin
width:100px;
margin: 0 auto;
position(绝对定位居中)
position:abosulute;
width:100px;
height:100px;
top:0;
right:0;
bottom:0;
left:0;
margin: auto;
如果不想设置宽高
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
flex
display: flex;
justify-content: center; /* 主轴(水平)居中 */
align-items: center; /* 交叉轴(垂直)居中 */
/*多行(flex-wrap:wrap)*/
align-content: center
Grid
/* Grid 居中 —— 在父容器上设置 */
display: grid;
place-items: center; /* 子元素在各自单元格内水平垂直居中 */
place-content: center; /* 整个网格在父容器内水平垂直居中(容器有剩余空间时生效) */
文本居中
text-align:center;




浙公网安备 33010602011771号