面试题
这是被拷打的一次面试😭😭
从基础的 JavaScript 语言特性,css3布局与适配,到vue框架的使用,再到浏览器的工作原理。
字符串操作(以正则为背景)
先考考正则 当需要从一个复杂的字符串中去除特定字符、提取数字或者对数字进行批量操作时,正则表达式能够以简洁且高效的方式完成任务。
javascript
const str = 'abc345efgabcab';
// 去掉字符串中的 a、b、c 字符
const result1 = str.replace(/[abc]/g, '');
console.log(result1); // 输出: '345efg'
// 将字符串中的数字用中括号括起来
const result2 = str.replace(/\d+/g, '[$&]');
console.log(result2); // 输出: 'abc[345]efgabcab'
// 将字符串中的每个数字的值分别乘以 2
const result3 = str.replace(/\d/g, match => String(Number(match) * 2));
console.log(result3); // 输出: 'abc6810efgabcab'
[abc]
匹配单个字符 a、b 或 c,\d+
匹配一个或多个数字,而 \d
匹配单个数字。
需要熟悉正则的读者,可以移步一篇文章说清:位运算、正则表达式及数据类型
组件内容分发,你对插槽slot了解多少
在构建可复用组件时,slot 插槽机制为组件内容分发提供灵活性。在react 或 vue 中,类似插槽功能都允许父组件向子组件传递自定义内容
在 Vue 中,插槽分为默认插槽、具名插槽和作用域插槽。默认插槽 用于简单的内容传递,具名插槽 则允许定义多个具有不同名称的插槽,以便在子组件中精确地放置内容。作用域插槽更进一步,能够将子组件的数据传递给父组件,使得内容的渲染更加动态和灵活。
js
<!-- 子组件 ChildComponent.vue -->
<template>
<div>
<slot name="header"></slot>
<slot></slot>
<slot name="footer" :footerData="footerData"></slot>
</div>
</template>
<script>
export default {
data() {
return {
footerData: 'Footer Information'
};
}
};
</script>
useEffect 一知半解
- useEffect 是什么? 适用场景是什么样?
- useEffect 执行 return 返回什么?什么时候执行return?
useEffect
是 React 中用于处理副作用的钩子,通过回调函数和依赖数组实现灵活的副作用管理。它常用于数据获取、网络异步请求、订阅等场景。当依赖数组为空时,useEffect
的回调函数仅在组件挂载时执行一次;若依赖数组中存在依赖项,则在依赖项变化时重新执行回调函数,确保副作用与组件状态保持同步。
此外,useEffect
的返回值可用于定义组件卸载时的清理逻辑。例如,清除定时器、移除事件监听或取消订阅等操作,都可以通过返回一个清理函数来实现。这有助于避免内存泄漏,确保组件在卸载时能够正确释放资源。
javascript
// React 示例
import React, { useEffect, useState } from 'react';
function ExampleComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount(prevCount => prevCount + 1);
}, 1000);
return () => clearInterval(timer);
}, []);
return <div>Count: {count}</div>;
}
computed
与 watch
你会分别会在什么时候使用?
- 衍生:watch 与watchEffect 是一样作用吗?
【我当时只想应该computed是计算时候,watch是监听时候。但是当面试官追问,哪个性能更好时,我知道这题不简单】 在 Vue 中,computed
是一个用于定义计算属性的工具,它基于依赖项进行缓存。当依赖项发生变化时,computed
会重新计算并更新缓存值。如果依赖项未发生变化,computed
会直接返回缓存的结果,避免重复计算,从而提高性能。computed
可以传入一个函数或一个包含 get
和 set
的对象,用于处理复杂的计算逻辑。
watch
是 Vue 2 中就存在的监听机制,用于侦听数据的变化并执行回调函数。watch
不会缓存结果,即使依赖项未发生变化,也会在每次数据更新时重新执行回调。不过,watch
提供了 immediate
和 deep
选项,分别用于控制是否在初始时立即执行回调以及是否进行深度监听。这使得组件能够更灵活地响应数据变化。
与 watch
不同,computed
会自动缓存结果,只有在依赖项发生变化时才会重新计算。这种缓存机制使得 computed
在处理复杂计算时更加高效。
Vue 3 引入了 watchEffect
,这是一个新的响应式副作用钩子。watchEffect
会自动侦听其内部访问的所有响应式数据的变化,并在数据变化时重新执行。与 watch
不同,watchEffect
不需要手动指定依赖项,它会自动追踪依赖,并且默认会立即执行一次。这使得 watchEffect
更加适合用于自动响应数据变化的场景,而无需显式配置依赖项。
总结来说,computed
和 watch
都可以监听父组件传递的新值并进行更新,但 computed
具有缓存机制,而 watch
则提供了更灵活的监听选项。watchEffect
作为 Vue 3 的新特性,进一步简化了响应式副作用的处理,使得代码更加简洁和高效。
跨标签页通信:Broadcast Channel API 的应用
【这是不懵逼也得懵逼呀 后面想了想,应该也算考了 '线程'之间怎么通信】
例如实现多标签页的数据同步、用户状态共享等。Broadcast Channel API
提供了一种简单而有效的解决方案,允许不同标签页之间通过消息传递进行通信。使用 postMessage
传递 和 onmessage
接收信息
标签页 1
html
<body>
<h1>发送消息</h1>
<input type="text" id="messageInput" placeholder="输入消息">
<button id="sendButton">发送消息</button>
<script>
// 创建广播通道
const channel = new BroadcastChannel('my-channel');
// 获取按钮和输入框
const messageInput = document.getElementById('messageInput');
const sendButton = document.getElementById('sendButton');
// 点击按钮时发送消息
sendButton.addEventListener('click', () => {
const message = messageInput.value; // 获取输入框的内容
if (message) {
channel.postMessage(message); // 发送消息
console.log('消息已发送:', message);
}
});
</script>
</body>
标签页2
html
<body>
<h1>接收消息</h1>
<div id="messageOutput"></div>
<script>
// 创建广播通道
const channel = new BroadcastChannel('my-channel');
// 获取显示消息的容器
const messageOutput = document.getElementById('messageOutput');
// 监听消息
channel.onmessage = (event) => {
const message = event.data; // 获取消息内容
const newMessage = document.createElement('p');
newMessage.textContent = `收到消息: ${message}`;
messageOutput.appendChild(newMessage); // 显示消息
console.log('收到消息:', message);
};
</script>
</body>
-
发送消息:
- 在第一个标签页中,我们创建了一个
BroadcastChannel
实例,通道名称为my-channel
。 - 用户在输入框中输入消息后,点击发送按钮,通过
channel.postMessage(message)
将消息发送到通道中。
- 在第一个标签页中,我们创建了一个
-
接收消息:
- 在第二个标签页中,我们同样创建了一个
BroadcastChannel
实例,通道名称与发送端一致(my-channel
)。 - 通过监听
channel.onmessage
事件,我们可以接收到来自通道的消息,并将其显示在页面上。
- 在第二个标签页中,我们同样创建了一个
通过创建一个 BroadcastChannel
实例,标签页可以发送和接收消息,实现数据的实时同步。但是BroadcastChannel
只能在同一个浏览器的相同源(协议、域名、端口)之间通信,不能用于跨浏览器或跨域通信。 此外,还可以利用 LocalStorage
事件监听存储变化(直接监听storage 事件就好了,依赖localStorge存储,并且实时性差),或者通过 Cookie
的共享域domain和路径path设置 来实现跨标签页的数据共享,但这些方法在实时性和灵活性上不如 Broadcast Channel API
。
css生效与不生效搭配
- positon:static情况下,z-index 会生效吗
position
属性决定了元素的定位方式,其中 relative
、absolute
和 fixed
支持 z-index
属性,用于控制元素的堆叠顺序。而 static
定位的元素则不受 z-index
影响。
css
/* CSS 示例 */
.container {
position: relative;
}
.element {
position: absolute;
z-index: 10;
}
- 对于
span
等行内元素,设置margin
和padding
不会生效,但通过将其display
属性设置为inline-block
,可以使其同时具备行内元素的特性和块级元素的布局能力。
css
/* span 样式调整 */
span {
display: inline-block;
margin: 5px;
padding: 5px;
}
还有flexbox
和 grid
也会考:
- flexbox 和grid 是css3 新特性
- flexbox 相比flex 浮动更加灵活,flex 只能设置left和right,随着前端页面元素越来越复杂,浮动布局也要求更高。flexbox 相关属性更多,也就更灵活处理页面布局
css
flex-direction: row; /* 子元素水平排列 */
flex-wrap: wrap; /* 超出宽度时自动换行 */
gap: 10px; /* 子元素之间的间距 */
- gird 在二维布局相当有优势,尤其在多行多列表格中
css
/* grid 布局 */
.grid-container {
display: grid;
grid-template-columns: repeat(3, 1fr); /* */
}
grid-template-colums: repeat(3,1fr)
将网格容器分成 3 列 ,每列的宽度相等,且每列占据总可用空间的 1/3
em 和 rem 是相对单位还是绝对单位?
- 延伸:px,vh和fr 使用场景了解吗?
- 适配屏幕,你采用什么方法?如果tailwind里面,你怎么做?
相对还是绝对,好像em和rem 得一个站一边(哈哈第一眼我是这么想的) 那就正中面试官下怀了
em 和rem 都是相对单位,两种都是使用font-size
做屏幕适配。em 是相对父元素进行适配,rem 是相对根元素适配。
css
/* em 和 rem 的使用 */
.parent {
font-size: 16px;
}
.child {
font-size: 1.2em; /* 相对于父元素的 16px,字体大小为 19.2px */
}
.another-child {
font-size: 1.2rem; /* 相对于根元素的 16px,字体大小为 19.2px */
}
1, px,vh和fr 使用场景了解吗?
- px 固定单位,表示屏幕像素点。不随屏幕大小变化,适合固定尺寸元素 如图标、边框等
- vh 是视口(viewport) 高度百分比,随着屏幕高度变化,适合全屏布局和响应式设计
- fr 分数单位,用于css grid 和 flexbox,表示可用空间分数;动态分配空间,适合响应式布局
2,适应屏幕方法
- 百分比
%
基于父容器 或高度进行动态调整 - 视口单位(vw,vh)基于视口动态调整
- fr 在grid 和flexbox 动态分配空间
- 媒体查询 media queries ,根据屏幕高度或宽度切换不同样式
css
.box {
width: 100px; /* 默认宽度 */
}
@media (min-width: 768px) and (max-width: 1024px) {
.box {
width: 150px; /* 当屏幕宽度在 768px 到 1024px 之间时,宽度为 150px */
}
}
3,tailwind css 实现响应式布局(笔试内容,读者可以自行跳过)
Tailwind CSS 提供了一套强大的工具来实现响应式设计,主要通过 响应式前缀 和 配置文件 来实现。
a. 使用响应式前缀
Tailwind 提供了 sm:
、md:
、lg:
、xl:
等响应式前缀,用于在不同屏幕尺寸下应用不同的样式。
html
<div class="w-full sm:w-1/2 md:w-1/3 lg:w-1/4">
<!-- 这个盒子的宽度在不同屏幕尺寸下会动态调整 -->
</div>
预览
- 在 小屏幕 (< 640px)上,宽度为
100%
。 - 在 中等屏幕 (≥640px)上,宽度为
50%
。 - 在 大屏幕 (≥768px)上,宽度为
33.33%
。 - 在 超大屏幕 (≥1024px)上,宽度为
25%
。
b. 使用 Fractional Units
Tailwind 提供了 fr
单位的支持,可以在 Grid 布局中使用。
html
<div class="grid grid-cols-3 gap-4">
<div class="bg-blue-200">盒子 1</div>
<div class="bg-blue-200">盒子 2</div>
<div class="bg-blue-200">盒子 3</div>
</div>
预览
grid-cols-3
:表示将容器分成 3 列,每列宽度相等。
c. 自定义配置
如果需要更复杂的响应式设计,可以在 Tailwind 的配置文件中自定义断点和单位。
js
// tailwind.config.js
module.exports = {
theme: {
screens: {
'sm': '480px',
'md': '768px',
'lg': '1024px',
'xl': '1280px',
},
extend: {
width: {
'1/5': '20%',
'2/5': '40%',
'3/5': '60%',
'4/5': '80%',
},
},
},
};
antd里面table组件怎么实现分页
当时问到antd 我是这个表情🥲
table 组件内置分页属性,使用pagination
启用分页功能
js
<Table
columns={columns}
dataSource={dataSource} // 数据源
pagination={{ pageSize: 10 }} // 每页显示 10 条
/>
也可以设置为false,自定义pagination
组件,完成更详细操作,如
js
pagination={{
current: 1, // 当前页码
pageSize: 10, // 每页显示条数
total: 50, // 数据总数
showSizeChanger: true, // 是否显示每页条数切换
pageSizeOptions: ['10', '20', '50'], // 每页条数选项
showQuickJumper: true, // 是否显示快速跳转
}}
在实际应用中,数据通常从后端动态获取。你需要监听分页事件,并在用户切换页码或更改每页条数时重新请求数据.下面直接采用自定义pagination 进行演示
js
import { Table, Pagination } from 'antd';
const App = () => {
const [pageNo, setPageNo] = useState(1);
const [pageSize, setPageSize] = useState(10);
const [total, setTotal] = useState(0);
// 异步api请求后端
const fetchData = async () => {
const response = await fetch(`/api/data?page=${pageNo}&pageSize=${pageSize}`);
const result = await response.json();
setData(result.data);
setTotal(result.total);
};
useEffect(() => {
fetchData();
}, [pageNo, pageSize]);
const handlePageChange = (newPage) => {
setPageNo(newPage);
};
const handlePageSizeChange = (newPageSize) => {
setPageSize(newPageSize);
setPageNo(1); // 重置页码
};
return (
<>
<Table
columns={columns}
dataSource={data}
rowKey={(record) => record.key}
pagination={false} // 禁用自带分页
/>
<Pagination
current={pageNo}
pageSize={pageSize}
total={total}
onChange={handlePageChange}
onShowSizeChange={handlePageSizeChange}
/>
</>
);
};
Vue 你知道哪些 组件生命周期与状态管理?
在 Vue 框架中,组件的生命周期钩子函数是理解组件行为的关键。从组件的创建到挂载,再到更新和销毁,每个阶段都有对应的钩子函数可以执行特定的逻辑。
javascript
// Vue 组件生命周期示例
export default {
beforeCreate() {
// 组件实例初始化之后,数据观测和事件配置之前
},
created() {
// 组件实例创建完成,数据观测和事件配置完成,但 DOM 还未生成
},
beforeMount() {
// 模板编译渲染之前
},
mounted() {
// 模板编译渲染完成,DOM 已经生成
},
beforeUpdate() {
// 数据更新时,在 DOM 更新之前
},
updated() {
// DOM 更新完成之后
},
beforeDestroy() {
// 组件销毁之前,可以进行一些清理工作
},
destroyed() {
// 组件销毁完成
}
};
在状态管理方面,Vue 提供了 Vuex
作为状态管理库,用于集中管理应用的状态。通过 Vuex
的 store,可以实现跨组件的状态共享和统一管理,避免了通过 props
逐层传递状态的繁琐。
javascript
// Vuex store 示例
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++;
}
},
actions: {
increment(context) {
context.commit('increment');
}
},
getters: {
doubleCount(state) {
return state.count * 2;
}
}
});
pinia你知道吗?当用户刷新后,数据丢失,那怎么实现数据缓存?
使用pinia时,数据默认存储在内存,因此刷新页面数据丢失 如果想了解Pinia 可以先尝尝大菠萝真香
-
使用 pinia-plugin-persistedstate 插件,用于存储到浏览器的localStorage 或 sessionStorage,实现数据持久化。在main.js 主入口文件引入并且配置插件,在定义store时,通过
persist
选项启用持久化,persist 还可以指定存储的key,存储方式(localstorage或sessionStorage)以及需要持久化的路径 -
自定义手动实现持久化,灵活:在store 设定函数方法,使用localStorge.setItem 指定持久化的state 数据(记住对象要JSON.stringify先转成json格式)。如果初始化从存储中加载数据,再 使用localStorge.getItem 读取键
store
,再使用store.$patch()
批量更新状态,实现合并属性到当前Store 状态
HTTP 协议基础:三次握手与四次挥手的原理
在网络通信中,HTTP 协议的三次握手和四次挥手是建立和终止 TCP 连接的核心过程。三次握手确保了客户端和服务器在建立连接前双方都准备好进行数据传输。
- 第一次握手:客户端发送一个带有 SYN 标志的 TCP 包,请求建立连接。
- 第二次握手:服务器收到请求后,回复一个带有 SYN 和 ACK 标志的 TCP 包,确认收到客户端的请求并同意建立连接。
- 第三次握手:客户端收到服务器的响应后,发送一个带有 ACK 标志的 TCP 包,确认连接建立。
四次挥手则是用于断开 TCP 连接的过程,确保双方在断开连接前所有数据都已正确传输。
- 第一次挥手:客户端发送一个 FIN 包,表示要关闭连接。
- 第二次挥手:服务器收到 FIN 包后,回复一个 ACK 包,确认收到关闭请求。
- 第三次挥手:服务器准备好关闭连接时,发送一个 FIN 包。
- 第四次挥手:客户端收到服务器的 FIN 包后,回复一个 ACK 包,确认关闭连接。
如果问到:进程与线程?问到输入URL到渲染页面过程怎么实现? 可以看看下面笔者拙作:整理1:面题[1]
"嘿,兄弟/姐妹,大家代码都写顺手了,面试肯定没问题,稳稳滴!"