大家好,我是前端架构师,关注微信公众号【程序员大卫】:
- 回复 [面试] :免费领取“前端面试大全2025(Vue,React等)”
- 回复 [架构师] :免费领取“前端精品架构师资料”
- 回复 [书] :免费领取“前端精品电子书”
- 回复 [软件] :免费领取“Window和Mac精品安装软件”
Vue2 中对大数据的性能优化
在 Vue2 中,如果某些大数据对象不需要响应式处理,可以使用 Object.freeze 将其冻结。Vue 内部会通过 Object.isFrozen 判断对象是否已被冻结,从而跳过该对象的响应式转换过程,以提升性能。
需要注意的是:
Object.freeze只能冻结对象的第一层属性。- 对被冻结对象的属性进行直接赋值会抛出错误。
- 对嵌套属性(如第二层)赋值不会报错,但由于整个对象已被 Vue 忽略响应式转换,因此这些更改不会反映在页面上,尽管实际值已被修改。
示例代码:
<template>
<div>
{{ obj.a }}
{{ obj.b.c }}
</div>
</template>
<script>
export default {
data() {
return {
obj: Object.freeze({
a: 1111,
b: {
c: 2222
},
})
}
},
mounted() {
// 报错: TypeError: Cannot assign to read only property 'a' of object '#<Object>'
this.obj.a = 3333;
// 虽然页面上显示的仍是原值,但实际值已发生改变
this.obj.b.c = 4444;
console.log(this.obj.b.c); // 4444
}
}
</script>
Object.freeze 只能冻结第一层:
const obj = Object.freeze({
a: 1111,
b: {
c: 222
}
});
obj.a = 8888;
console.log(obj.a); //已经冻结; 1111
obj.b.c = 9999
console.log(obj.b.c); // 没有冻结; 9999
Vue 2 中 data 和 props 使用相同属性名的行为
如果在组件中 data 和 props 定义了同名的属性,最终会以 props 中的值为准,而 data 中定义的同名属性会被忽略(不会生效)。
<template>
<div>{{ num }}</div>
</template>
<script>
export default {
props: {
num: {
type: String,
required: true
}
},
data() {
return {
num: '这是 data 中的 num(但不会生效)'
}
}
}
</script>
在 Vue 中获取动态资源路径的方法
export const getAssetUrl = (src: string): string =>
new URL(`/src/assets/${src}`, import.meta.url).href;
Vue2 和 Vue3 的区别
1. 性能优化
✅ 更快的虚拟 DOM
Vue 3 采用 基于 Block 的静态提升 和 编译优化,减少了不必要的虚拟 DOM 重新渲染,提高了性能。
✅ 更小的体积
Vue 3 使用 Tree Shaking 机制,未使用的 API 不会被打包进最终构建结果,使得体积更小。
✅ 更好的 TypeScript 支持
Vue 3 在设计时考虑到了 TypeScript,API 具有更好的类型推导,相比 Vue 2 更容易进行类型检查。
2. Vue3 引入 Composition API
可以使用 setup 进行逻辑组织,更加灵活、可复用。
3. 响应式系统升级
Vue 3 采用了 Proxy 取代 Vue 2 的 Object.defineProperty,解决了 Vue 2 的响应式缺陷:
4. 生命周期钩子变更
移除了 Vue2 的 beforeCreate 和 created。
5. Vue 3 新增功能
- Teleport(跨层级传送)
- Fragment(多个根节点)
- Suspense(异步组件加载)
Pinia 与 Vuex 区别
Pinia移除了mutations,使用起来更简洁。Pinia更好的TypeScript支持。
当然可以,下面是对你这段内容的重组和润色,使其结构更清晰、语言更流畅:
Proxy 与 Object.defineProperty 的区别
1. 响应式对象的代理方式不同
Object.defineProperty 需要逐个遍历对象的每一个属性,对每个属性进行劫持。这在处理嵌套对象时效率较低,尤其是深层嵌套结构。
相比之下,Proxy 的响应式是按需递归:只有在读取属性且该属性为对象时,才会对其进行代理。这种“懒代理”方式能显著提升性能,避免了不必要的深层遍历。
示例:
const isObject = (obj) => typeof obj === "object" && obj !== null;
const obj = {
a: 1,
b: {
c: 2,
},
};
const reactive = (obj) => {
return new Proxy(obj, {
get(target, key) {
console.log("读取", key);
if (isObject(target[key])) {
return reactive(target[key]);
}
return target[key];
},
set(target, key, value) {
if (target[key] === value) return;
console.log("设置", key);
target[key] = value;
},
});
};
const proxy = reactive(obj);
proxy.a;
proxy.b.c = 40;
输出结果:
读取 a
读取 b
设置 c
2. 拦截能力差异
Vue 2 基于 Object.defineProperty 实现响应式系统,存在以下限制:
- 无法监听新增属性
如:this.arr[this.arr.length] = 100不会触发视图更新,需使用this.$set(this.arr, this.arr.length, 100)。 - 无法监听属性删除
如:delete this.obj.key也不会触发视图更新,需改用this.$delete(this.obj, 'key')。 - 数组下标赋值无效
例如:this.arr[0] = 100不会触发更新,需使用this.$set(this.arr, 0, 100)。
虽然数据本身是变了,但 Vue 无法感知,除非手动调用 this.$forceUpdate() 强制刷新视图。
此外,Vue 2 不支持对 Map、Set 等 ES6 数据结构的响应式处理。
而 Vue 3 使用 Proxy 实现响应式,具备更强的能力:
- 能监听新增、删除属性
- 完整支持数组下标变动
- 支持对Map、Set 等新数据结构的响应式追踪
v-if 和 v-for 的优先级哪个高?
- Vue 2中
v-for的优先级更高。 - Vue 3中
v-if的优先级更高。
Vite 常用的配置
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";
import { UserConfig, ConfigEnv, loadEnv, defineConfig } from "vite";
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import Icons from "unplugin-icons/vite";
import IconsResolver from "unplugin-icons/resolver";
import { createSvgIconsPlugin } from "vite-plugin-svg-icons";
import mockDevServerPlugin from "vite-plugin-mock-dev-server";
import { resolve } from "path";
import {
name,
version,
engines,
dependencies,
devDependencies,
} from "./package.json";
/* 平台的名称、版本、运行时所需的 `node` 版本、依赖、构建时间的类型提示 */
const __APP_INFO__ = {
pkg: { name, version, engines, dependencies, devDependencies },
buildTimestamp: Date.now(),
};
const pathSrc = resolve(__dirname, "src");
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
const env = loadEnv(mode, process.cwd());
const isPrd = mode === "production";
return {
define: {
__APP_INFO__: JSON.stringify(__APP_INFO__),
},
resolve: {
alias: {
"@": pathSrc,
},
},
css: {
// CSS 预处理器
preprocessorOptions: {
// 定义全局 SCSS 变量
scss: {
api: "modern",
javascriptEnabled: true,
additionalData: `
@use "@/styles/variables.scss" as *;
`,
},
},
},
server: {
// 允许 IP 访问
host: "0.0.0.0",
// 应用端口(默认:3000)
port: Number(env.VITE_APP_PORT),
// 运行时是否自动打开浏览器
open: true,
proxy: {
// 代理前缀为 /api 的请求
[env.VITE_APP_BASE_API]: {
changeOrigin: true,
target: env.VITE_APP_BASE_API,
rewrite: (path) =>
path.replace(new RegExp("^" + env.VITE_APP_BASE_API), ""),
},
},
},
plugins: [
vue(),
// jsx, tsx 语法支持
vueJsx(),
// MOCK 服务
env.VITE_MOCK_DEV_SERVER === "true" ? mockDevServerPlugin() : null,
AutoImport({
// 自动导入官方 vue
imports: ["vue", "@vueuse/core", "pinia", "vue-router"],
eslintrc: {
// 是否自动生成 eslint 规则
enabled: false,
// 指定自动导入函数 eslint 规则的文件
filepath: "./.eslintrc-auto-import.json",
globalsPropValue: true,
},
// 是否在 vue 模版中自动导入
vueTemplate: true,
dts: "./src/types/auto-imports.d.ts",
}),
Components({
resolvers: [
IconsResolver({
// 需配合安装: @iconify-json/ic, @iconify-json/mdi
enabledCollections: ["ic", "mdi"],
}),
],
dts: false,
}),
Icons({
// 自动安装图标库
autoInstall: true,
}),
createSvgIconsPlugin({
// 指定需要缓存的图标文件夹
iconDirs: [resolve(pathSrc, "assets/svg")],
// 指定 symbolId 格式
symbolId: "svg-[dir]-[name]",
}),
],
esbuild: {
drop: isPrd ? ["console", "debugger"] : [],
},
// 构建配置
build: {
chunkSizeWarningLimit: 2000, // 消除打包大小超过500kb警告
rollupOptions: {
output: {
// 用于入口点生成的块的输出格式,[name] 表示文件名, [hash] 表示该文件的内容 hash 值
entryFileNames: "js/[name].[hash].js",
// 用于命名代码拆分时创建的共享块的输出命名
chunkFileNames: "js/[name].[hash].js",
// 用于输出静态资源的命名,[ext] 表示文件的扩展名
assetFileNames: (assetInfo: any) => {
const info = assetInfo.name.split(".");
let extType = info[info.length - 1];
if (
/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/i.test(assetInfo.name)
) {
extType = "media";
} else if (/\.(png|jpe?g|gif|svg)(\?.*)?$/.test(assetInfo.name)) {
extType = "img";
} else if (/\.(woff2?|eot|ttf|otf)(\?.*)?$/i.test(assetInfo.name)) {
extType = "fonts";
}
return `${extType}/[name].[hash].[ext]`;
},
},
},
},
};
});
1. 配置 CSS 预处理器
定义全局 SCSS 变量。
css: {
preprocessorOptions: {
scss:{
api: "modern",
javascriptEnabled: true,
additionalData: `
@use "@/styles/variables.scss" as *;
`,
}
}
}
2. createSvgIconsPlugin 使用
- 在
main.ts中引入以下模块
import "vertual:svg-icons-register";
-
将 SVG 文件放入指定目录,例如:
assets/svg。假设你添加了一个名为clothes.svg的图标文件。 -
在 Vue 组件中,可以这样使用该图标:
<template>
<svg
aria-hidden="true"
class="xxxx"
stroke="#f00"
:strokeWidth="10"
:style="{ width: 20, height: 20 }"
>
<use xlink:href="#svg-clothes" fill="#ff0" />
</svg>
</template>
3.plugins
unplugin-auto-import自动导入常用的 Vue API 和其他库,如Pinia、vue-router等。unplugin-vue-components和unplugin-icons/vite配合使用,自动解析和导入iconify的 SVG 图标,并且使用 pnpm 安装@iconify-json/ic和@iconify-json/mdi
4. .env.development 和 .env.production 配置
.env.development 配置说明:
# 本地开发环境应用端口
VITE_APP_PORT = 3000
# 开发环境下的 API 请求前缀
VITE_APP_BASE_API = "/api"
# 开发环境下实际请求的 API 服务地址
VITE_APP_API_URL = http://172.190.xxx.xx:8083
# 是否启用 mock 服务
VITE_MOCK_DEV_SERVER = true
.env.production 配置说明:
# 生产环境接口前缀
VITE_APP_BASE_API = '/api'
5. build 构建配置
配置 chunks 和 assets 输出目录等。
Vue2 组件中 data 为什么是一个函数?
因为组件是用来复用的,且 JS 里对象是引用关系。
Vue 组件全局注册
Vue2:
import Vue from 'vue'
import MyComponent from './MyComponent.vue'
// 全局注册组件
Vue.component('my-component', MyComponent)
Vue3:
import { createApp } from 'vue'
import App from './App.vue'
import MyComponent from './components/MyComponent.vue'
const app = createApp(App)
// 全局注册组件
app.component('my-component', MyComponent)
app.mount('#app')
MVVM 的理解
MVVM 是 Model-View-ViewModel 的缩写。Model 层代表数据模型,View 代表 UI 组件,通过 ViewModel 把 Model 和 View 连接起来。
Vue 实现双向数据绑定原理是什么?
Vue 的数据双向绑定整合了 Observer,Compile 和 Watcher 三者,通过 Observer 来监听自己的 model 的数据变化,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observer 和 Compile 之间的通信桥梁,达到数据变化->视图更新,视图交互变化(例如 input 操作)->数据 model 变更的双向绑定效果。
keep-alive 的实现原理
keep-alive 在内部维护了一个 key 数组和一个缓存对象。
nextTick 的实现原理
Vue 的 DOM 更新是异步进行的。当数据发生变化时,DOM 并不会立刻更新。Vue 会在 DOM 更新完成后,再统一执行相关回调。nextTick 的实现原理是通过判断当前环境支持的异步队列机制,优先使用微任务(如 Promise 和 MutationObserver),在不支持的情况下则回退到宏任务(如 setImmediate 或 setTimeout)来实现回调的延迟执行。
watch 与 computed 的区别是什么?
- computed 有缓存,它依赖的值变了才会重新计算,watch 没有;
- watch 支持异步,computed 不支持;
- watch 监听某一个值变化,执行对应操作;computed 是监听属性依赖于其他属性。
Vue 实现双向数据绑定的原理
Vue 的双向数据绑定机制由 Observer、Compile 和 Watcher 三个核心模块共同实现:
- Observer:用于监听数据模型(model)的变化,一旦数据发生变化,就会触发相应的更新操作。
- Compile:用于解析模板指令(如
v-model、v-bind等),并将初始模板渲染到页面上,同时将指令中的数据依赖与对应的 Watcher 建立关联。 - Watcher:作为 Observer 和 Compile 之间的桥梁,负责接收数据变化的通知,并触发视图的更新。
通过这三者的协作,Vue 实现了数据变化 → 视图更新,视图交互(如输入框操作)→ 数据更新的双向绑定效果。
如何监听 pushstate 和 replacestate 的变化呢?
重写 history 的 pushState 和 replaceStat。
var _wr = function (type) {
var orig = history[type];
return function () {
var rv = orig.apply(this, arguments);
var e = new Event(type);
e.arguments = arguments;
window.dispatchEvent(e);
return rv;
};
};
history.pushState = _wr("pushState");
history.replaceState = _wr("replaceState");
v-if 和 v-show 的区别
-
v-show: 无论初始条件是什么,始终会被渲染。相当于设置 css 的 display 属性的显示(display: block)和隐藏(display: none)。
-
v-if: 是惰性的,初始条件为 false,则不渲染。元素切换的时候事件监听器和子组件都会被销毁与重建。
Vue3 组件 v-model
v-model 默认绑定的是 modelValue,当你写:
<CustomInput v-model="searchText" />
Vue 实际上转换成:
<CustomInput
:model-value="searchText"
@update:model-value="val => searchText = val"
/>
子组件做的两件事:
-
绑定
modelValue到<input>的value; -
在
input事件触发时,通过$emit('update:modelValue', newValue)通知父组件更新。
<!-- CustomInput.vue -->
<script>
export default {
props: ['modelValue'],
emits: ['update:modelValue']
}
</script>
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
✅ Vue 3 中支持多个 v-model
你也可以这样写:
<MyComponent v-model:title="title" v-model:content="content" />
在组件内对应:
props: ['title', 'content'],
emits: ['update:title', 'update:content']
Vue3 使用组件的两种方式
组合式(<script setup>)
<script setup>
import SlotAdvance from "./components/SlotAdvance/SlotAdvance.vue";
</script>
<template>
<SlotAdvance></SlotAdvance>
</template>
选项式 API(传统写法)
<script>
import SlotAdvance from "./components/SlotAdvance/SlotAdvance.vue";
export default {
components: {
SlotAdvance
}
};
</script>
<template>
<SlotAdvance></SlotAdvance>
</template>
❌ 错误写法:两种 API 混用会报错
混用会报错: 一个模块不能具有多个默认导出。ts(2528)
<script setup>
import SlotAdvance from "./components/SlotAdvance/SlotAdvance.vue";
export default {
components: {
SlotAdvance
}
};
</script>