面试题
一、自我介绍
不管对方公司体量如何如何,还是得让你介绍一下自己,自我介绍要说些什么呢?自我介绍的时间一般不能很长。要言简意赅的表达。主要表达清楚三个东西。我是谁?我会什么?我有什么经历或优势。
二、面试问题
1.css问题
1.1 css盒子模型有哪些?
CSS盒子模型是CSS中的基本概念,它定义了如何将HTML元素渲染成页面上的盒状结构。盒子模型由四个部分组成:内容(Content)、内边距(Padding)、边框(Border)和外边距(Margin)。
盒子模型的组成
- 内容(Content) :这是盒子模型的核心,包含了文本和图像等元素的实际内容。
- 内边距(Padding) :内容区域周围的空间,可以通过padding属性来设置。
- 边框(Border) :内边距和内容外的边框,可以通过border属性来设置。
- 外边距(Margin) :边框外的区域,用于在不同元素之间创建空白区域,可以通过margin属性来设置。
CSS中有两种盒子模型:标准盒子模型和IE盒子模型(怪异盒子模型)。
- 标准盒子模型:在这个模型中,元素的width和height属性只包括内容区域的大小,内边距和边框不包含在内。
- IE盒子模型:在这个模型中,元素的width和height属性包括内容区域、内边距和边框的总和。
我感觉问这个问题主要是你能答出标准盒子模型和怪异盒子模型就行。
1.2 弹性盒子flex布局,它的主轴和交叉轴该如何切换?
弹性布局(Flex布局)是一种现代的CSS布局方式,通过使用display: flex属性来创建一个弹性容器,并在其中使用灵活的盒子模型来进行元素的排列和定位。
主轴 与 交叉轴:弹性容器具有主轴(main axis)和交叉轴(cross axis)。默认情况下,主轴是水平方向,交叉轴是垂直方向。
flex-direction: 控制弹性子元素的排列方向,可以是 row, row-reverse, column, 或column-reverse。
- row(默认值):主轴为水平方向,起点在左端。
- row-reverse:主轴为水平方向,起点在右端。
- column:主轴为垂直方向,起点在上沿。
- column-reverse:主轴为垂直方向,起点在下沿
2. JavaScript问题
2.1 你知道JavaScript中的事件循环嘛?你利用过事件循环解决过什么问题?
-
JS 是单线程的,但通过事件循环机制实现了异步。
-
任务分为宏任务和微任务。
-
执行顺序是:一个宏任务 -> 所有微任务 -> (渲染) -> 下一个宏任务。
-
Promise.then和await后面的代码属于微任务,会优先于setTimeout等宏任务执行。
在我的项目中的话,我主要是利用事件循环实现了页面刷新功能。
<script setup lang='ts'>
import { nextTick, ref, watch } from 'vue';
import uselayOutSettingStore from '@/store/modules/setting'
let layoutSettingStore = uselayOutSettingStore()
//控制当前组件
let flag =ref(true)
// 监听仓库内部数据是否发生变化,如果发生变化,说明用户点击过刷新按钮
watch(() => layoutSettingStore.refsh,() => {
flag.value = false
nextTick(() => {
flag.value = true
})
})
</script>
- 强制重新挂载页面组件:点击“刷新”时,先把
flag设为false让router-view内当前组件卸载,再在nextTick中把flag设回true,确保在本轮 DOM 更新完成(卸载已真正生效)后再重新挂载。 - 保证动画与生命周期正确触发:避免同步立即切回
true导致组件未真正销毁就又被复用,出现过渡动画不生效、内部状态不重置、生命周期不重跑等问题。 - 事件循环视角:
nextTick基于微任务队列安排回调到“下一轮 DOM 更新后”执行,借助事件循环使“卸载”和“重挂载”分两步、顺序可靠地发生。
2.2 讲一下promise的方法。await和async的关系
Promise 和 Async/Await 是 JavaScript 中处理异步操作的核心工具。它们的出现解决了传统回调函数的嵌套问题,提高了代码的可读性和维护性。
Promise 支持链式调用,通过 promise.then() 方法可以依次处理多个异步操作:
const promise = new Promise(resolve => {
setTimeout(() => resolve(10), 1000);
});
promise
.then(value => {
console.log('原始值:', value);
// 输出: 原始值: 10
return value * 2; // 返回新值
})
.then(doubled => {
console.log('翻倍后:', doubled);
// 输出: 翻倍后: 20
return doubled + 5; // 继续处理
})
.then(final => {
console.log('最终结果:', final);
// 输出: 最终结果: 25
});
使用 Promise.all() 可以并行执行多个异步操作,所有操作完成后返回结果数组:
function fetchData(id) {
return new Promise(resolve => {
setTimeout(() => resolve(`数据${id}`), Math.random() * 1000);
});
}
Promise.all([
fetchData(1),
fetchData(2),
fetchData(3)
])
.then(results => {
console.log('所有数据:', results);
// 输出: 所有数据: ['数据1', '数据2', '数据3']
return results.join(' | ');
})
.then(combined => {
console.log('合并结果:', combined);
// 输出: 合并结果: 数据1 | 数据2 | 数据3
});
使用Promise.catch() 处理失败状态:
基础用法
const promise = new Promise((resolve, reject) => {
setTimeout(() => reject('错误信息'), 1000);
});
promise.catch(error => {
console.log('捕获到错误:', error);
// 输出: 捕获到错误: 错误信息
});
使用Promise.finally()无论成功失败都执行:
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
Math.random() > 0.5 ? resolve('成功') : reject('失败');
}, 1000);
});
promise
.then(result => console.log('成功:', result))
// 可能输出: 成功: 成功
.catch(error => console.log('失败:', error))
// 可能输出: 失败: 失败
.finally(() => {
console.log('清理工作完成');
// 输出: 清理工作完成
});
Async/Await 是基于 Promise 的语法糖,使异步代码看起来像同步代码。async 用于声明一个函数为异步函数,await 用于等待一个 Promise 的完成。
基础用法
async function basicAwait() {
console.log('开始等待...');
// 输出: 开始等待...
const result = await new Promise(resolve => {
setTimeout(() => resolve('异步结果'), 1000);
});
console.log('等待完成:', result);
// 输出: 等待完成: 异步结果
}
basicAwait();
await只能用在async函数中
// ❌ 错误 - await不能在普通函数中使用
function wrongUsage() {
const result = await someAsyncOperation(); // SyntaxError
}
// ✅ 正确 - await必须在async函数中使用
async function correctUsage() {
const result = await someAsyncOperation();
return result;
}
2.3 Vue响应式原理
什么是响应式?
响应式就是:当数据发生变化时,页面会自动更新。
比如:
// 数据
data() {
return {
message: 'Hello'
}
// 当 message 改变时,页面上的 {{ message }} 会自动更新
核心原理
1. 数据劫持
Vue会"监听"你的数据,当数据改变时,Vue会知道。首先有个问题,在Javascript中,如何侦测一个对象的变化? 其实有两种办法可以侦测到变化:使用Object.defineProperty和ES6的Proxy,这就是进行数据劫持或数据代理。
常见坑(使用Object.defineProperty方法的Vue 2 中尤为重要)
-
新增属性不是响应式:
- 直接
obj.newKey = 1不会被监听,因为没有通过defineProperty包装。 - 解决:在 Vue 中使用
this.$set(obj, 'newKey', 1);自己实现时可以写一个set方法,内部调用defineReactive。
- 直接
-
删除属性不是响应式:
- 直接
delete obj.key不会触发更新。 - 解决:在 Vue 中使用
this.$delete(obj, 'key');自己实现时需要封装删除并手动通知。
- 直接
-
数组变更的特殊性:
Object.defineProperty不能直接拦截通过索引赋值或length变化。- Vue 2 通过“重写数组变更方法”(如
push/pop/shift/unshift/splice/sort/reverse)来触发更新。 - 实践建议:用这些变更方法,不要直接通过索引赋值;或使用
Vue.set(arr, index, value)。
vue3则选择使用 Proxy方法(对比 Vue 2,为什么 Proxy 更爽?)
- 能监听“新增/删除属性”,不需要额外的
$set/$delete。 - 天然支持数组的索引、
length变化和push/splice等,大部分情况下无需手动重写方法。 - 可拦截更多操作(如
in、ownKeys),覆盖面更广。
- 依赖收集
Vue会记录哪些地方用到了这个数据。
// 模板中使用了 {{ name }}
// Vue会记录:name 被用在了这个组件里
- 派发更新
当数据改变时,Vue会通知所有用到这个数据的地方。
// 当 name 改变时
data.name = '李四'
// Vue会自动更新所有显示 name 的地方
简单例子
<template>
<div>
<p>{{ message }}</p>
<button @click="changeMessage">改变消息</button>
</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello Vue!'
}
},
methods: {
changeMessage() {
// 当你点击按钮时
this.message = 'Hello World!'
// Vue会自动更新页面上的 {{ message }}
}
}
}
</script>
3. 项目问题
3.1 select组件封装,如何传参?
比方说:参数有a,b,c,d四个,其中a,b两个显式参数,那剩下的c,d隐式参数又该怎么传过来呢? 这个问题一问,确实有点短路了,显式参数传参我们可以使用props方法将父组件的参数传递给子组件,但是没有用到的隐式参数c、d又该如何传递出来呢?现在我学到了。
简短回答
- 最常用:把 c 当“透传属性”传入,子组件不声明它为 props,用
$attrs/useAttrs()原样转给内部的 select 或你想要的元素/子组件。
示例(Vue 3)
父组件:
<MySelect a="A" :b="1" c="隐式参数" />
子组件 MySelect.vue:
<script setup>
import { useAttrs } from 'vue'
// 显式声明的只有 a、b
const props = defineProps({
a: { type: String, required: true },
b: { type: Number, required: true },
})
const attrs = useAttrs() // 这里会拿到 c
</script>
<template>
<!-- 精准控制透传到哪个元素,常配合 inheritAttrs:false -->
<select v-bind="attrs">
<slot />
</select>
</template>
还有一种方法也应用的比较广泛,可以建立一个组件仓库,将父组件的值传入进仓库中,子组件就可以根据需要获取父组件的参数。
3.2 Vuex状态管理
Vuex的核心概念包括以下几个部分:
- State:是驱动应用的数据源,可以在组件间共享。
- Getters:类似于计算属性,可以从state派生出一些状态,例如对列表进行过滤并计数。
- Mutations:是唯一可以直接更改state的方法,它们必须是同步函数。
- Actions:用于提交mutation,可以包含任意异步操作。
- Modules:当应用变得非常复杂时,可以将store分割成模块,每个模块拥有自己的state、mutations、actions、getters等。
3.3 uni-App中的http请求封装
问你uniapp中htto请求封装你是怎么实现的,首先我是先配置一个baseUrl然后在进行request方法配置
request(option = { showLoading: false }){
// 统一错误处理
if(!option.url) return false
// Loading 状态管理
if(option.showLoading) this.showLoading()
// Token 认证
uni.request({
url: this.baseUrl + option.url,
data: option.data || {},
header: option.header || {},
method: option.method || 'GET',
success: (response) => {
uni.hideLoading()
// 统一响应处理
if(response.data.code != 10000){
option.fail && option.fail(response)
}else{
option.success && option.success(response.data)
}
}
})
}
总结
这一面,我清楚的认识到自己的不足。对于八股题目有着不够清晰的认知,纯粹的在背题,缺少延申,例如面试官问的promise部分,缺少细致的去了解。这一次的面试让人记忆深刻,学习路上没有捷径,一定要一直坚持对于问题的归纳和总结这样才能对自身情况有更深入的了解。