aitty 的技术后花园

从零到一,记录每一次踩坑与成长。

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:

pmU2yJU.png

轮询:

轮询是指客户端每隔一段固定的时间,主动向服务器发送一次请求,查询是否有新的状态或数据。它不依赖服务器主动推送,而是靠客户端不断询问来实现状态同步。

以二维码登录为例,网页首先向后端请求一个二维码,服务器返回一个唯一标识和二维码图片。网页把这个二维码展示给用户,同时开启一个定时器,比如每两秒向后端发送一次查询请求,参数就是那个唯一标识。服务器收到请求后,检查这个二维码当前的状态,然后返回给网页。返回的状态一般有四种:如果用户还没扫,状态就是“等待扫码”,网页继续轮询;如果用户已经用手机扫码但还没有确认,状态就是“已扫码”,网页可以提示用户在手机上确认;如果用户点击了确认,状态就是“已确认”,服务器返回登录凭证,网页拿到后停止轮询并完成登录;如果二维码长时间未被使用而失效,状态就是“已过期”,网页提示用户刷新二维码。这种场景之所以用轮询,是因为用户扫码的过程本身就有时延,两秒的轮询间隔完全在可接受范围内,而且实现起来非常简单,不需要维护长连接,对服务器资源消耗也很小。

组件通信

vue3

  1. Props + Emits ✅
  2. Ref + defineExpose ✅
  3. Pinia / Vuex ✅
  4. Mitt(Event Bus)✅
  5. Provide / Inject ✅
  6. localStorage / sessionStorage ✅
  7. 忘了的那个 → 大概率是 $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

pmU2WLR.png

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

pmU25o6.png

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布局

Flexible Box - aitty - 博客园

grid布局

Grid布局 - aitty - 博客园

盒子居中

水平居中--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;

闭包

JavaScript闭包 - aitty - 博客园

posted @ 2026-06-30 20:57  aitty  阅读(2)  评论(0)    收藏  举报

© 2025 aitty | 写于博客园 · 冷静地热爱技术

博客首页 | 回到顶部