xxx科技-前端工程师
1.Vue的路由实现, 如果要配置history模式要怎么处理
答案:需要服务器配置支持
原理对比:
| 模式 | URL 格式 | 原理 | 需要服务器配置 |
|---|---|---|---|
| hash | //#/home | 使用 URL 的 hash 部分 | ❌ 否 |
| history | /home | 使用 History API | ✅ 是 |
前端配置:
// router/index.js
import { createRouter, createWebHistory } from 'vue-router';
const router = createRouter({
history: createWebHistory(), // 使用 history 模式而不是 createWebHashHistory
routes: [
{
path: '/',
component: Home
},
{
path: '/about',
component: About
}
]
});
export default router;服务器配置(必需):
// Node.js/Express
app.use(express.static('dist'));
// 对所有非文件请求,返回 index.html
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'dist', 'index.html'));
});# Nginx
location / {
try_files $uri $uri/ /index.html;
}// Python/Flask
@app.route('/')
@app.route('/<path:path>')
def catch_all(path=None):
return render_template('index.html')2.Vue2 和 Vue3 双向数据绑定有什么区别
| 特性 | Vue 2 | Vue 3 |
|---|---|---|
| 实现方式 | Object.defineProperty() | Proxy |
| 支持情况 | 已有对象属性变化 | 对象新增属性也支持 |
| 性能 | 对象属性逐个劫持 | 一次代理整个对象 |
| Array 监听 | 需要特殊处理 | 原生支持 |
| 兼容性 | 支持 IE9+ | 不支持 IE |
Vue 2 限制示例:
data() {
return {
user: { name: 'John' }
}
},
methods: {
updateUser() {
// ❌ 新增属性不会被追踪
this.user.age = 30; // 视图不会更新
// ✅ 需要使用 $set
this.$set(this.user, 'age', 30);
}
}Vue 3 的优势:
setup() {
const user = reactive({
name: 'John'
});
// ✅ 直接添加新属性即可被追踪
user.age = 30; // 视图会自动更新
}3.Vue3 的新增特性
主要新增特性:
-
Composition API
- 按逻辑组织代码
- 更好的代码复用
-
更快的性能
- 基于 Proxy 的响应式系统
- 更小的包体积
-
更好的 TypeScript 支持
- 原生支持 TS 的泛型
- 更好的类型推导
-
Fragment、Teleport、Suspense
<!-- Fragment:不需要根元素 --> <template> <div>1</div> <div>2</div> </template> <!-- Teleport:传送门 --> <Teleport to="body"> <div>Modal</div> </Teleport> <!-- Suspense:异步组件 --> <Suspense> <AsyncComponent /> </Suspense> -
新的
<script setup>语法糖 -
响应式 API 改进
ref、reactive、computed、watch等
4.Vue 中 v-if 和 v-show 的区别和使用场景
| 特性 | v-if | v-show |
|---|---|---|
| 实现 | 销毁/创建 DOM 元素 | 切换 CSS display 属性 |
| 初始渲染 | 快(条件为 false) | 慢(总是编译) |
| 切换开销 | 高(创建/销毁) | 低(仅改变属性) |
| 支持 v-else | ✅ 支持 | ❌ 不支持 |
使用场景:
<template>
<!-- v-if:不需要频繁切换时 -->
<div v-if="isLoggedIn">
<Profile />
</div>
<!-- v-show:经常切换的场景 -->
<div v-show="isVisible">
可见内容
</div>
<!-- v-if 支持 v-else -->
<div v-if="type === 'A'">A</div>
<div v-else-if="type === 'B'">B</div>
<div v-else>C</div>
</template>
<script setup>
const isLoggedIn = ref(false);
const isVisible = ref(false);
</script>5.路由传参数组, 对象, 或者长字符, 防止被截取, 怎么解决
问题: URL 过长或含有特殊字符时,参数可能被截取
解决方案:
// 方案1:使用 query 参数(推荐小数据)
router.push({
name: 'details',
query: {
id: 123,
name: 'John',
data: JSON.stringify({ x: 1, y: 2 })
}
});
// 访问:/details?id=123&name=John&data=%7B%22x%22:1%7D
// 方案2:使用 params(URL 中不显示某些参数)
router.push({
name: 'details',
params: {
id: 123
}
});
// 方案3:使用 sessionStorage/localStorage(最安全)
// 传递时只传递 key,组件中获取真实数据
const passData = (data) => {
const key = 'temp_' + Date.now();
sessionStorage.setItem(key, JSON.stringify(data));
router.push({
name: 'details',
query: { key }
});
};
// 接收组件
const key = route.query.key;
const data = JSON.parse(sessionStorage.getItem(key));
sessionStorage.removeItem(key); // 清理最佳实践:
// 传递复杂数据时
const complexData = {
array: [1, 2, 3],
object: { name: 'John', age: 30 },
nested: { level: 2, data: 'value' }
};
// 使用 sessionStorage + key 方案
const key = 'data_' + Date.now();
sessionStorage.setItem(key, JSON.stringify(complexData));
router.push({
name: 'target',
query: { key }
});
// 目标页面接收
const key = route.query.key;
const data = JSON.parse(sessionStorage.getItem(key));6.computed 和 watch 的区别
| 特性 | computed | watch |
|---|---|---|
| 作用 | 计算属性,可缓存 | 侦听属性变化 |
| 缓存 | ✅ 有缓存 | ❌ 无缓存 |
| 返回值 | 必须 | 可选 |
| 依赖追踪 | 自动 | 手动指定 |
| 异步 | ❌ 不支持 | ✅ 支持 |
computed 示例:
setup() {
const firstName = ref('John');
const lastName = ref('Doe');
// 只有当 firstName 或 lastName 变化时才重新计算
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`;
});
return { fullName };
}watch 示例:
setup() {
const user = ref({ name: 'John', age: 30 });
// 监听单个属性
watch(() => user.value.name, (newVal, oldVal) => {
console.log(`名字从 ${oldVal} 变为 ${newVal}`);
});
// 监听对象(深度监听)
watch(
() => user.value,
(newVal) => {
console.log('用户信息更新:', newVal);
},
{ deep: true }
);
// 异步操作
watch(user, async (newVal) => {
const response = await fetch(`/api/user/${newVal.id}`);
const data = await response.json();
console.log(data);
});
return { user };
}7.Vue3 的 setup 入口函数有什么作用
setup 函数: Vue 3 Composition API 的核心
export default {
setup(props, context) {
// props:接收父组件的 props
// context.emit:派发事件
// context.slots:插槽
// context.attrs:属性
// context.expose:暴露属性
// 在这里定义响应式数据和方法
const count = ref(0);
const increment = () => {
count.value++;
};
// 必须返回对象,其中的属性和方法才能在模板中使用
return {
count,
increment
};
}
}setup 执行时机:
- 在
beforeCreate和created之间执行 - 此时组件实例还未创建
- 无法访问
this
新语法糖 <script setup>:
<script setup>
import { ref } from 'vue';
// 不需要手动 return,自动暴露
const count = ref(0);
const increment = () => count.value++;
// defineProps、defineEmits 等宏可直接使用
const props = defineProps({
msg: String
});
const emit = defineEmits(['update']);
</script>8.vite 对比 vue-cli, 有什么优势和不足
| 特性 | Vite | Vue-CLI |
|---|---|---|
| 开发服务器启动 | 极快(秒级) | 较慢(分钟) |
| 热更新 | 极快 | 需等待重新编译 |
| 打包工具 | esbuild/Rollup | webpack |
| 配置学习曲线 | 较平缓 | 陡峭 |
| 生态插件 | 快速增长 | 成熟丰富 |
| 兼容性 | 现代浏览器 | 广泛支持 |
| 调试 | SourceMap 支持好 | 需配置 |
优势:
// Vite:快速的开发体验
vite dev // 秒级启动不足:
- 较新,插件生态还在完善
- 某些老版本浏览器不支持
9.vuex 实现几种不同的映射
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';
export default {
// 1. mapState:映射 state
computed: {
...mapState(['count', 'user']),
// 或自定义名称
...mapState({
counter: 'count',
userData: 'user'
})
},
// 2. mapGetters:映射 getters
computed: {
...mapGetters(['doubleCount', 'userName'])
},
// 3. mapMutations:映射 mutations
methods: {
...mapMutations(['increment', 'decrement']),
// 或自定义名称
...mapMutations({
add: 'increment'
})
},
// 4. mapActions:映射 actions
methods: {
...mapActions(['fetchUser', 'updateUser'])
}
}10.v-for 获取回数据未能立刻刷新视图
原因: Vue 不能检测到数组和对象变化的某些方法
问题示例:
// ❌ 不会触发视图更新
this.items[0] = newItem;
this.obj.name = 'newName';
// ✅ 使用 $set 保证更新
this.$set(this.items, 0, newItem);
this.$set(this.obj, 'name', 'newName');
// ✅ Vue 3 的 reactive API 自动处理
const state = reactive({ items: [], obj: {} });
state.items[0] = newItem; // 自动更新11.列出几种父子组件通信的方法
-
props + $emit
<!-- 父 --> <Child :msg="message" @change="handleChange" /> <!-- 子 --> <script> props: ['msg'], emits: ['change'], methods: { update() { this.$emit('change', newValue); } } </script> -
v-model
<Child v-model="count" /> -
children(不推荐)
-
ref
<Child ref="child" /> <!-- 父组件直接访问子组件 --> this.$refs.child.method(); -
Provide/Inject(跨层级)
12.Object.defineProperties() 和 Object.defineProperty() 的区别
| 特性 | defineProperty | defineProperties |
|---|---|---|
| 定义属性个数 | 单个 | 多个 |
| 语法 | (obj, prop, descriptor) | (obj, props) |
示例:
const obj = {};
// defineProperty:单个属性
Object.defineProperty(obj, 'name', {
value: 'John',
writable: true
});
// defineProperties:多个属性
Object.defineProperties(obj, {
age: { value: 30, writable: true },
email: { value: 'john@example.com', enumerable: true }
});13.base64 保证 image 标签正常使用, 应该怎么处理
// 方案1:直接在 src 中使用
const img = document.createElement('img');
img.src = 'data:image/png;base64,iVBORw0KGg...';
// 方案2:Canvas 转 base64
const canvas = document.createElement('canvas');
canvas.width = 200;
canvas.height = 200;
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, 200, 200);
const base64 = canvas.toDataURL('image/png');
img.src = base64;
// 方案3:File/Blob 转 base64
const fileInput = document.querySelector('input[type="file"]');
const file = fileInput.files[0];
const reader = new FileReader();
reader.onload = (e) => {
img.src = e.target.result; // 自动是 base64 格式
};
reader.readAsDataURL(file);
// 方案4:确保 MIME 类型正确
const mimeType = 'image/png'; // 或 jpeg、webp 等
const base64String = '...'; // base64 字符串
img.src = `data:${mimeType};base64,${base64String}`;14.区分 onLoad 和 onShow, onHide 和 onUnload
| 函数 | 触发时机 | 说明 |
|---|---|---|
| onLoad | 页面加载 | 仅一次,接收路由参数 |
| onShow | 页面显示 | 多次,每次切换到此页面 |
| onHide | 页面隐藏 | 页面失焦或被覆盖时 |
| onUnload | 页面卸载 | 页面被销毁时 |
15.some 和 every 的区别以及使用场景
| 方法 | 返回条件 | 使用场景 |
|---|---|---|
| some | 至少一个元素满足条件 | 检查是否存在符合条件的元素 |
| every | 全部元素都满足条件 | 检查是否全部符合条件 |
const nums = [1, 2, 3, 4, 5];
// some:是否存在大于 3 的数
nums.some(n => n > 3); // true
// every:是否全部大于 0
nums.every(n => n > 0); // true
// 实际应用
const users = [
{ name: 'John', verified: true },
{ name: 'Jane', verified: false }
];
// 检查是否有未验证的用户
if (users.some(u => !u.verified)) {
console.log('存在未验证用户');
}
// 检查是否全部验证通过
if (users.every(u => u.verified)) {
console.log('全部用户已验证');
}16.列出几种删除对象指定属性的方法
const obj = { a: 1, b: 2, c: 3 };
// 1. delete 操作符
delete obj.a; // { b: 2, c: 3 }
// 2. 解构赋值(获取需要的属性)
const { b, c } = obj;
const newObj = { b, c }; // { b: 2, c: 3 }
// 3. Object.keys() + reduce
const newObj = Object.keys(obj)
.filter(key => key !== 'a')
.reduce((acc, key) => {
acc[key] = obj[key];
return acc;
}, {}); // { b: 2, c: 3 }
// 4. Object.assign() + delete
const newObj = { ...obj };
delete newObj.a; // { b: 2, c: 3 }
// 5. Lodash omit
const _ = require('lodash');
const newObj = _.omit(obj, ['a']); // { b: 2, c: 3 }17.TypeScript 的 as 语法是什么
as 语法: 类型断言,告诉编译器把值当作特定类型
// 基础用法
const num = '123' as number; // ❌ 运行时不安全
const num = parseInt('123'); // ✅ 正确做法
// 实际应用
const elem = document.getElementById('app') as HTMLDivElement;
// any 类型到具体类型
const data: any = { name: 'John' };
const user = data as { name: string; age?: number };
// 类型缩小
interface A { x: number }
interface B { y: string }
const val: A | B = { x: 1 } as A;18.什么是 TypeScript 中的类型断言
类型断言: 告诉编译器值的具体类型,绕过类型检查
// 方式1:as 语法
const value = '123' as string;
// 方式2:<type> 语法(JSX 中会冲突)
const value = <string>'123';
// 使用场景
function getValue(value: string | number): string {
if (typeof value === 'string') {
return value.toUpperCase(); // 编译器能推断
}
return (value as number).toFixed(2);
}19.解释 TypeScript 中的泛型
泛型: 编写可复用的、灵活的代码,同时保持类型安全
// 基础泛型
function identity<T>(value: T): T {
return value;
}
identity<string>('hello'); // T = string
identity<number>(123); // T = number
// 泛型接口
interface Box<T> {
content: T;
}
const stringBox: Box<string> = { content: 'hello' };
const numberBox: Box<number> = { content: 123 };
// 泛型约束
function printName<T extends { name: string }>(obj: T) {
console.log(obj.name);
}
printName({ name: 'John', age: 30 }); // ✅
// printName({ age: 30 }); // ❌ Error
// 默认泛型
type Response<T = any> = {
status: number;
data: T;
};
const res: Response = { status: 200, data: 'any' };
const typedRes: Response<string> = { status: 200, data: 'hello' };Last updated on