📌 HTML 5 语义化标签
语义化标签是指使用诸如 <header>
、<article>
、<nav>
、<aside>
这类标签名字就包含其含义的标签。这些标签可以帮助开发者理解页面结构,从而更好地理解代码维护项目。但这不是主要的目的,况且还因这些语义化标签通常带有一些初始样式影响开发者,所以没被所有开发者接受,还是很多开发者选择 <div>
一把梭的。
现在更多是是博客类、分享类的网站开发者使用HTML语义化,来使得SEO表现更好,因为搜索引擎会识别这些标签去进行爬取,优化搜索结果。另外就是可以对浏览器提供的无障碍功能或如“沉浸式阅读器”这类功能提供额外支持。
📌 CSS 怎么实现垂直居中
flex 布局
flex
布局可谓是最简单直接的布局方法了,父级容器 display: flex;
+ align-items: center;
即可。如果设置了 flex-direction: column;
,则是使用 justify-content: center;
来垂直居中(因为 justify-content
用于设置主轴,此时主轴被设置为纵轴)。
grid 布局
grid
布局也很常用,flex
是一种一维布局,grid
是二维的。它只需设置父级容器 display: grid;
+ place-items: center;
即可。但这么设置会使得水平、垂直同时居中。
absolute 定位 + margin 设置外边距
上面两种方法可能会影响到容器内的其它元素,如果想单纯调整单一元素,则可以使用绝对定位的方法。将居中基准的父级容器设置为 position: relative;
使得该元素以父级容器为标准设置绝对定位。然后为居中元素添加 position: absolute;
+ top: 50%;
+ margin-top: -50px;
(这里是假设居中元素的高度为 100px
),实际上就是 margin-top
要设置为元素高度一半的负值。这个方法很坑... 就是必须要知道元素本身的高度,如果此时元素高度不确定,这个方法就行不通了。
absolute 定位 + transform 移动
先将居中基准的父级容器设置为 position: relative;
,然后为居中元素添加 position: absolute;
+ top: 50%;
+ transform: translate(0, -50%);
即可。因为使用 top
设置移动时是将元素的重心进行移动,如果不设置 transform
就会出现元素偏下的情况,需要将元素中心偏移回自身高度的一半才是真正的垂直居中。水平居中也是同理。
absolute 定位 + margin: auto
这个方法也是需要父级容器 position: relative;
,居中元素 position: absolute;
且 top
、right
、bottom
、left
都设置为 0
,然后使用 margin: auto;
即可(水平+垂直居中),如果仅垂直居中使用 margin-top: auto
。
inline 元素设置 line-height
为元素高度
这个方法主要是用于简单的文本居中。水平居中就使用 text-align: center;
解决。
📌 父子元素都 position: absolute;
,子元素会有作用吗?
当父子元素都使用 position: absolute;
时,子元素的定位确实会有作用,但它的定位是相对于 最近的已定位祖先元素(即最近的 position
设置为 relative
, absolute
, fixed
, 或 sticky
的元素)进行的,而不是相对于父元素本身。
📌 CSS 中 flex: 1;
是什么意思?
flex: 1;
是以下三个属性的简写,亦等同于 flex: 1 1 0;
:
flex-grow: 1;
:元素可根据剩余空间进行拓展,数值越大分配到的空间越多。flex-shrink: 1;
:元素在空间不足时可以缩小,数值越大收缩比例就越大。flex-basis: 0;
:元素的基础尺寸为0
,分配空间会以这个值为基础进行伸缩。
如果在一个 flex
容器中,所有子元素都含 flex: 1;
,则这些子元素会平均分配该容器的全部空间。如其中一个元素为 flex: 2;
,则该元素会分配到相较于其它元素的两倍空间。如果是 flex: 0 1 auto;
,则该元素的基础空间会根据元素自身内容而定,而且该元素不可拓展,会在必要时按比例缩小。
追问: 如使用
flex
横向布局,左侧内容特别长且设置了flex: 1
, 右侧放置一个内含文字的小按钮,怎样让右侧内容不受到挤压 ?
回答: 右侧按钮可以设置flex-shrink: 0
,这可以防止按钮被缩小(即不受左侧内容的挤压)。同时,可以为右侧按钮设置一个固定宽度,确保它的大小保持一致。
.container { display: flex; align-items: center; /* 垂直居中 */ } .left { flex: 1; /* 使左侧内容占据剩余空间 */ white-space: nowrap; /* 防止文本换行 */ } .right { flex-shrink: 0; /* 防止右侧按钮缩小 */ padding: 10px 20px; /* 设置按钮大小 */ margin-left: 10px; /* 给按钮一些间距 */ min-width: 100px; /* 设置最小宽度 */ }
📌 如何用 CSS 实现超出文本显示为省略号
对于单行文本而言,仅需使用好以下三个属性就可以实现:
white-space: nowrap
:阻止文本换行,使文本在一行内显示。overflow: hidden
:确保超出容器的文本被隐藏,防止它们溢出显示。text-overflow: ellipsis
:为超出容器的文本添加省略号。
/* 单行文本超出添加省略号 */
.container {
width: 200px; /* 容器宽度 */
white-space: nowrap; /* 禁止换行 */
overflow: hidden; /* 隐藏超出文本 */
text-overflow: ellipsis; /* 显示省略号 */
border: 1px solid #ccc; /* 边框,仅为显示效果 */
}
如果是多行文本,则需要以下四个属性:
display: -webkit-box;
:使容器变成弹性盒子模型。-webkit-box-orient: vertical;
: 确定弹性盒子在垂直方向上排列子元素。-webkit-line-clamp: 3;
: 控制显示的行数,超出部分会显示为省略号。这个属性只能与-webkit-box
结合使用。text-overflow: ellipsis;
: 为超出部分的文本添加省略号。
.container-multiline {
width: 200px; /* 容器宽度 */
height: 60px; /* 设置容器高度来限制文本显示的行数 */
overflow: hidden; /* 隐藏超出部分 */
display: -webkit-box; /* 使容器成为弹性盒模型 */
-webkit-line-clamp: 3; /* 限制显示的行数 */
-webkit-box-orient: vertical; /* 垂直排列子元素 */
text-overflow: ellipsis; /* 超出部分显示省略号 */
line-height: 20px; /* 设置行高以保证文本分行 */
}
📌 ES6 的新特性
let
和const
关键字:具有块级作用域,且用“暂时性死区(TDZ)”解决了提升问题。let
可用来声明变量,const
用来声明常量防止被再次赋值。- 箭头函数:新的函数声明方式,语法简洁。
- 模版字符串:字符串插值功能,可定义多行字符串。
- 解构赋值:允许从数组或对象中提取属性或值,并将这些值赋给其他变量。
- 默认参数:函数参数可设置默认值。
- 扩展运算符:可以将数组展开为逗号分隔的参数序列,或者合并多个对象或数组。
- 类与模块:可通过
class
关键字定义类,使用import
和export
来导入和导出模块 ——ES Module
语法。 - Promise:用于处理异步操作。
- Symbol和迭代器:提供了一种新的基本数据类型和自定义迭代行为的方式。
- 新的数据结构
Map
、Set
📌 let
、const
与 var
的区别
作用域
var
:具有 函数作用域(function-scoped)。它只在函数内部有效,在函数外部无法访问。若在函数外部声明,var
声明的变量是全局变量。let
和const
:都具有 块级作用域(block-scoped),即它们只在代码块(如if
、for
、while
)内部有效。不同于var
,它们在块外不可访问。
function testVar() {
if (true) {
var x = 10; // var 是函数作用域
}
console.log(x); // 输出: 10
}
function testLetConst() {
if (true) {
let y = 20; // let 是块级作用域
const z = 30; // const 也是块级作用域
}
console.log(y); // 错误: y is not defined
console.log(z); // 错误: z is not defined
}
提升机制
var
:变量声明会被提升到作用域的顶部,但是它的赋值操作不会。未赋值的var
变量会被初始化为undefined
。let
和const
:也会被提升到作用域顶部,但它们在被声明之前处于“暂时性死区(TDZ)”。在 TDZ 中,访问这些变量会导致ReferenceError
错误。
console.log(a); // 输出: undefined
var a = 5;
console.log(b); // 错误: Cannot access 'b' before initialization
let b = 10;
console.log(c); // 错误: Cannot access 'c' before initialization
const c = 20;
可变性
-
var
、let
:都允许重新赋值和修改变量的值。 -
const
:声明的是常量,不允许重新赋值。但是,const
只是保证变量的绑定(引用)不可改变,引用类型(如对象、数组)仍然可以修改对象或数组的内容。
最佳实践总结
- 使用
const
声明那些不会改变的变量是个好习惯,可以强调代码的不可变性。 - 只有在明确需要重新赋值的情况下使用
let
。 - 在现代 JavaScript 编程中,几乎不会再用到
var
了,因为let
和const
能更好地处理作用域和提升问题。
📌 JS 引用数据类型和基础数据类型的区别
- 基本数据类型包括:
string
、number
、boolean
、null
、undefined
等。 - 引用数据类型包括:
object
、function
、array
等。
存储位置区别
- 基本数据类型存储在 栈(stack) 中,值直接保存在变量访问的位置,由于其大小固定且频繁使用,存储在栈中具有更高的性能。
- 引用数据类型存储在 堆(heap) 中,占用空间较大且大小不固定,变量保存的是对实际对象的引用(即指针),这些引用存储在栈中。
赋值区别
- 基本数据类型:复制的是值本身。例如,将一个number类型的变量赋值给另一个变量,两个变量互不影响。
- 引用数据类型:复制的是指针。多个变量引用同一个对象时,一个变量的修改会影响其他变量。
📌 用过 JS 数组的哪些方法
数组与字符串之间的转换
-
toString()
用途:将数组转换为字符串。
用法:arr.toString()
将数组的每个元素转换为字符串并用逗号连接起来。const arr = [1, 2, 3]; console.log(arr.toString()); // "1,2,3"
-
toLocaleString()
用途:将数组转换为字符串,考虑本地化设置(例如数字格式)。
用法:arr.toLocaleString()
返回根据区域设置格式化的数组元素字符串。const arr = [1234567.89]; console.log(arr.toLocaleString()); // 输出根据浏览器语言环境不同,格式可能不同
-
join()
用途:将数组的所有元素连接成一个字符串,可以指定连接符。
用法:arr.join(separator)
用指定的分隔符连接数组元素,默认分隔符是逗号。const arr = ['a', 'b', 'c']; console.log(arr.join('-')); // "a-b-c"
数组首部、尾部的添加/删除操作
-
pop()
用途:从数组的尾部删除一个元素,并返回该元素。
用法:arr.pop()
删除数组的最后一个元素,并返回该元素。const arr = [1, 2, 3]; const last = arr.pop(); // last = 3, arr = [1, 2]
-
push()
用途:将一个或多个元素添加到数组的尾部,并返回新数组的长度。
用法:arr.push(element)
在数组末尾添加元素。const arr = [1, 2]; arr.push(3); // arr = [1, 2, 3]
-
unshift()
用途:将一个或多个元素添加到数组的头部,并返回新数组的长度。
用法:arr.unshift(element)
将元素添加到数组开头。const arr = [2, 3]; arr.unshift(1); // arr = [1, 2, 3]
-
shift()
用途:从数组的头部删除一个元素,并返回该元素。
用法:arr.shift()
删除数组的第一个元素,并返回该元素。const arr = [1, 2, 3]; const first = arr.shift(); // first = 1, arr = [2, 3]
数组的拼接、合并子数组、排序等
-
concat()
用途:合并两个或多个数组,返回一个新的数组。
用法:arr.concat(otherArray)
将其他数组拼接到当前数组的末尾。const arr1 = [1, 2]; const arr2 = [3, 4]; const newArr = arr1.concat(arr2); // newArr = [1, 2, 3, 4]
-
flat()
用途:将多维数组“拉平”成一维数组。
用法:arr.flat(depth)
将嵌套数组展平,depth
参数指定展平的层级,默认值为 1。const arr = [1, [2, 3], [4, [5, 6]]]; console.log(arr.flat(2)); // [1, 2, 3, 4, 5, 6]
-
sort()
用途:对数组中的元素进行排序。
用法:arr.sort(compareFunction)
对数组按升序或自定义比较函数进行排序。const arr = [3, 1, 4, 2]; arr.sort(); // arr = [1, 2, 3, 4]
-
reverse()
用途:反转数组的顺序。
用法:arr.reverse()
将数组中的元素顺序倒置。const arr = [1, 2, 3]; arr.reverse(); // arr = [3, 2, 1]
数组的查找、遍历与调整
-
find()
用途:返回数组中第一个满足条件的元素。
用法:arr.find(callback)
根据给定的测试函数返回数组中符合条件的第一个元素。const arr = [5, 12, 8, 130, 44]; const found = arr.find(element => element > 10); // found = 12
-
findIndex()
用途:返回数组中第一个满足条件的元素的索引。
用法:arr.findIndex(callback)
返回数组中符合条件的元素的索引。const arr = [5, 12, 8, 130, 44]; const index = arr.findIndex(element => element > 10); // index = 1
-
forEach()
用途:对数组的每个元素执行一次给定的函数。
用法:arr.forEach(callback)
遍历数组并对每个元素执行指定的回调函数。const arr = [1, 2, 3]; arr.forEach((element) => console.log(element)); // 输出: 1 2 3
-
map()
用途:创建一个新数组,数组中的元素是原数组元素调用回调函数的结果。
用法:arr.map(callback)
返回一个新数组,包含回调函数对每个元素的处理结果。const arr = [1, 2, 3]; const newArr = arr.map(x => x * 2); // newArr = [2, 4, 6]
-
splice()
用途:用于添加、删除或替换数组的元素。
用法:arr.splice(start, deleteCount, item1, item2, ...)
在指定位置删除、替换或添加元素。const arr = [1, 2, 3, 4]; arr.splice(2, 1, 5, 6); // arr = [1, 2, 5, 6, 4]
-
slice()
用途:返回数组的一个浅拷贝(不修改原数组)。
用法:arr.slice(start, end)
返回数组中指定区间的元素的浅拷贝,包含start
索引但不包括end
索引。const arr = [1, 2, 3, 4, 5]; const newArr = arr.slice(1, 3); // newArr = [2, 3]
-
filter()
用途:返回一个新数组,其中包含所有通过测试的元素。
用法:arr.filter(callback)
返回一个新数组,其中包含所有回调函数返回true
的元素。const arr = [1, 2, 3, 4]; const newArr = arr.filter(x => x > 2); // newArr = [3, 4]
📌 TS 中 interface
和 type
的区别
声明合并
同一个 interface
可以多次声明,TS 会将多次声明合并成一次:
interface Person {
name: string;
age: number;
}
interface Person {
address: string;
}
const person: Person = {
name: 'Alice',
age: 30,
address: '123 Main St'
};
而 type
不支持这一特性,一旦第二次声明会立马报错:
type Person = {
name: string;
age: number;
};
type Person = {
address: string;
};
// 错误: Duplicate identifier 'Person'.
联合类型、交叉类型、元组类型
type
更加灵活,可以表示更多类型。除了对象类型外,type
还支持联合类型、交叉类型、元组、字面量类型等。
type Status = 'pending' | 'completed' | 'failed'; // 联合类型
type ID = number | string; // 联合类型
type User = { name: string } & { age: number }; // 交叉类型
type PersonTuple = [string, number]; // 元组
继承与交叉类型的区别
-
interface
使用extends
来继承其他接口:interface Person { name: string; age: number; } interface Employee extends Person { jobTitle: string; } const emp: Employee = { name: 'Alice', age: 30, jobTitle: 'Engineer' };
-
type
使用&
来进行交叉类型:type Person = { name: string; age: number; }; type Employee = Person & { jobTitle: string; }; const emp: Employee = { name: 'Alice', age: 30, jobTitle: 'Engineer' };
这两者都能达到相同的效果,但语法有所不同。
实例化与实现
-
interface
更常用于类的结构定义,可以通过implements
来让类实现某个接口。interface Person { name: string; age: number; } class Employee implements Person { name: string; age: number; jobTitle: string; constructor(name: string, age: number, jobTitle: string) { this.name = name; this.age = age; this.jobTitle = jobTitle; } }
-
type
不能直接用于类的implements
,但是可以定义类的类型或构造函数的类型。
总结
- 类型别名(
type
):可以用来定义基本类型、联合类型、交叉类型等。它提供了更多灵活性,能够定义复杂的类型结构。类型别名不能被扩展或合并。 - 接口(
interface
):主要用于定义对象的结构,包括属性和方法。接口可以被扩展和合并,这使得它在定义和继承对象类型时非常有用。
⚠ 虽然
type
和interface
在定义对象类型时有很多相似之处,并且在大多数情况下可以互换使用,但它们在扩展方式、类型组合、复杂类型定义和声明合并等方面存在差异。还是更加建议使用接口的方式。
追问: 在 TS 中,如何在一个接口(
interface
)继承另一个接口时剔除一些属性?
假设有一个接口 A,它有多个属性:
interface A { id: number; name: string; age: number; address: string; }
你需要创建一个接口 B,继承接口 A,但剔除
address
和age
属性。并为其添加gender
属性。回答: 使用
Omit
工具类型:
interface B extends Omit<A, 'address' | 'age'> { // B 可以继承 A 中剩余的属性(即 id 和 name) gender: string; }
Omit<A, 'address' | 'age'>
:这段代码的意思是从接口 A 中剔除address
和age
属性,生成一个新的类型。- 然后,接口 B 扩展了这个剔除属性后的类型,并可以添加自己的属性(如
gender
)。不仅如此,
type
的交叉类型也可以使用这一工具达成同样效果:
type A = { id: number; name: string; age: number; address: string; }; // 使用 Omit 剔除 `address` 和 `age` 属性 type B = Omit<A, 'address' | 'age'> & { gender: string; }; // 测试:创建一个符合 B 类型的对象 const personB: B = { id: 1, name: 'Alice', gender: 'female' };
📌 一般你都对 Axios 做了哪些封装?
-
封装 Axios 实例
- 创建一个
axios
实例,配置基本的请求 URL、请求头、超时时间等。 - 将常用的拦截器(请求拦截 和 响应拦截)封装成模块,便于管理。
import axios from 'axios'; const axiosInstance = axios.create({ baseURL: 'https://api.example.com', // 基础URL timeout: 5000, // 请求超时时间 headers: { 'Content-Type': 'application/json' } }); // 请求拦截器 axiosInstance.interceptors.request.use(config => { // 可以添加 token 或进行其他操作 return config; }, error => { return Promise.reject(error); }); // 响应拦截器 axiosInstance.interceptors.response.use(response => { return response.data; // 返回数据部分 }, error => { return Promise.reject(error); }); export default axiosInstance;
- 创建一个
-
统一处理请求
- 错误处理:统一处理请求失败的情况,避免每个请求单独处理。
- 加载状态:可以封装加载状态的显示与隐藏,避免重复代码。
// 封装请求 const fetchData = async (url, config = {}) => { try { const response = await axiosInstance(url, config); return response; } catch (error) { console.error('Request Error:', error); throw error; // 可以自定义错误提示 } }; // 请求调用 fetchData('/data') .then(data => console.log(data)) .catch(error => console.error(error));
-
取消请求
- 使用
CancelToken
或者AbortController
来取消重复的请求,避免因网络请求过多影响性能。
const source = axios.CancelToken.source(); axiosInstance.get('/data', { cancelToken: source.token }).catch(thrown => { if (axios.isCancel(thrown)) { console.log('Request canceled', thrown.message); } }); // 取消请求 source.cancel('Operation canceled by the user.');
- 使用
追问: 现有一个发送请求的按钮。如何实现在请求没有完成之前,点击按钮不会重复发送请求?
-
禁用按钮
在请求开始时禁用按钮,请求完成后再恢复按钮的可用状态。这样可以确保在请求未完成时,用户无法重复点击按钮。
<template> <button @click="sendRequest" :disabled="isRequesting">发送请求</button> </template> <script> export default { data() { return { isRequesting: false, // 标志变量,表示请求是否正在进行 }; }, methods: { async sendRequest() { if (this.isRequesting) return; // 如果请求正在进行中,直接返回 this.isRequesting = true; // 设置请求中标志 try { // 发送请求 const response = await axios.get('/api/data'); console.log(response.data); } catch (error) { console.error(error); } finally { this.isRequesting = false; // 请求完成,恢复按钮状态 } } } }; </script>
-
使用标志变量
另一种方法是使用标志变量来控制请求的发送。该方法不依赖于禁用按钮,而是通过在点击事件中判断是否正在请求,来决定是否继续发送请求。
<template> <button @click="sendRequest">发送请求</button> </template> <script> export default { data() { return { isRequesting: false, // 请求标志 }; }, methods: { sendRequest() { if (this.isRequesting) { console.log("请求正在进行中,请稍后再试。"); return; // 如果请求正在进行,阻止再次发送 } this.isRequesting = true; // 设置请求状态为进行中 axios.get('/api/data') .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }) .finally(() => { this.isRequesting = false; // 请求完成,恢复状态 }); } } }; </script>
-
防抖 (Debounce)
如果按钮连续快速点击,除了禁用按钮外,还可以使用防抖技术,在一定时间内只允许发起一次请求。
<template> <button @click="debouncedRequest">发送请求</button> </template> <script> import { debounce } from 'lodash'; // 引入防抖函数 export default { methods: { sendRequest() { axios.get('/api/data') .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); }, debouncedRequest: debounce(function() { this.sendRequest(); }, 1000) // 设置防抖时间为 1000 毫秒 } }; </script>
-
通过请求取消机制(CancelToken)
如果希望在请求未完成时避免发起新的请求,可以使用 Axios 的
CancelToken
来取消当前正在进行的请求,从而避免重复请求。<template> <button @click="sendRequest" :disabled="isRequesting">发送请求</button> </template> <script> export default { data() { return { isRequesting: false, cancelTokenSource: null, // 用于取消当前请求 }; }, methods: { async sendRequest() { if (this.isRequesting) return; // 如果请求正在进行,直接返回 this.isRequesting = true; // 创建取消令牌 this.cancelTokenSource = axios.CancelToken.source(); try { const response = await axios.get('/api/data', { cancelToken: this.cancelTokenSource.token }); console.log(response.data); } catch (error) { if (axios.isCancel(error)) { console.log('请求被取消:', error.message); } else { console.error(error); } } finally { this.isRequesting = false; } }, cancelRequest() { if (this.cancelTokenSource) { this.cancelTokenSource.cancel('请求被取消'); this.isRequesting = false; } } } }; </script>
📌 手写题:请写一个函数以解析URL,返回一个对象
const getUrlParams = () => {
let url = window.location.href;
return url.split('?')[1].split('&').reduce((pre, cur) => {
const [key, value] = cur.split('=')
pre[`${key}`] = decodeURIComponent(value)
return pre;
}, {})
}