Promise 的详细使用方法总结

Promise 是 JavaScript 用于处理 异步操作 的重要机制。它表示一个未来可能会完成(fulfilled)、拒绝(rejected)或仍在进行中(pending)的操作。


1. Promise 的基本语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const promise = new Promise((resolve, reject) => {
// 执行异步操作
let success = true; // 假设这是异步操作的结果

if (success) {
resolve("操作成功!"); // 任务成功,调用 resolve
} else {
reject("操作失败!"); // 任务失败,调用 reject
}
});

// 使用 then 处理成功,catch 处理失败
promise
.then((result) => {
console.log("成功:", result);
})
.catch((error) => {
console.log("失败:", error);
});

执行过程

  1. new Promise() 创建一个 Promise 对象。
  2. executor 函数(第一个参数)接收两个参数 resolvereject,用于异步任务的成功或失败处理。
  3. promise.then() 处理成功的 resolve 结果。
  4. promise.catch() 处理失败的 reject 结果。

2. Promise 的三种状态

状态 描述
pending(进行中) 初始状态,异步任务尚未完成
fulfilled(已成功) 任务成功完成,调用 resolve()
rejected(已失败) 任务失败,调用 reject()

一旦 Promise 变为 fulfilledrejected,它的状态就不会再改变。


3. Promise 链式调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
new Promise((resolve) => {
setTimeout(() => resolve(10), 1000);
})
.then((num) => {
console.log(num); // 10
return num * 2;
})
.then((num) => {
console.log(num); // 20
return num * 3;
})
.then((num) => {
console.log(num); // 60
})
.catch((error) => {
console.error("出错了:", error);
});

说明

  • then() 返回一个新的 Promise,可以继续调用 then() 处理下一个异步任务。
  • 如果 then() 里面抛出错误,会跳到 catch() 进行错误处理。

4. Promise 的 catch() 处理错误

如果 Promise 发生错误,就会进入 catch()

1
2
3
4
5
new Promise((resolve, reject) => {
throw new Error("故意出错!");
})
.then((res) => console.log(res))
.catch((error) => console.error("错误:", error));

注意:

  • catch() 只捕获前面链式调用中的错误。
  • catch() 相当于 .then(null, errorHandler),但更简洁。

5. Promise 的 finally()

无论 Promise 成功或失败,finally() 都会执行

1
2
3
4
5
6
new Promise((resolve, reject) => {
setTimeout(() => resolve("完成任务"), 1000);
})
.then((res) => console.log("成功:", res))
.catch((err) => console.error("失败:", err))
.finally(() => console.log("任务结束"));

常见用途:

  • 关闭加载动画
  • 清理资源

6. 并行执行多个 Promise

6.1 Promise.all()

一次性执行多个 Promise等所有任务完成后再返回:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Promise.all([
fetch("https://jsonplaceholder.typicode.com/todos/1").then((res) =>
res.json()
),
fetch("https://jsonplaceholder.typicode.com/todos/2").then((res) =>
res.json()
),
])
.then(([result1, result2]) => {
console.log("结果1:", result1);
console.log("结果2:", result2);
})
.catch((error) => {
console.error("失败:", error);
});

特点

  • 全部成功 ✅ → 返回所有 Promise 结果的数组 [res1, res2, ...]
  • 任何一个失败 ❌ → 立即返回错误,不执行后续任务

6.2 Promise.allSettled()

等待所有 Promise 结束(无论成功或失败):

1
2
3
Promise.allSettled([Promise.resolve("成功"), Promise.reject("失败")]).then(
(results) => console.log(results)
);

特点

  • 不会中断,返回所有 Promise 状态 { status, value/reason }
  • 适合需要收集所有任务结果的场景

6.3 Promise.race()

返回最快完成Promise

1
2
3
4
Promise.race([
new Promise((resolve) => setTimeout(() => resolve("任务1完成"), 1000)),
new Promise((resolve) => setTimeout(() => resolve("任务2完成"), 500)),
]).then(console.log); // "任务2完成"

适用场景

  • 超时控制(例如 HTTP 请求超时)
  • 获取最快响应的请求

6.4 Promise.any()

返回第一个**成功的 Promise**:

1
2
3
4
5
6
7
Promise.any([
Promise.reject("失败1"),
Promise.reject("失败2"),
Promise.resolve("成功"),
])
.then(console.log) // "成功"
.catch(console.error); // 不会触发

特点

  • 只要有 1 个成功就返回,忽略失败
  • 所有都失败时才会 catch()

7. Promise 结合 async/await

async/await 让异步代码看起来像同步代码:

1
2
3
4
5
6
7
8
9
10
11
async function fetchData() {
try {
let response = await fetch("https://jsonplaceholder.typicode.com/todos/1");
let data = await response.json();
console.log("获取数据:", data);
} catch (error) {
console.error("请求失败:", error);
}
}

fetchData();

优点

  • 代码更清晰
  • 自动返回 Promise
  • 错误处理简单(使用 try...catch

8. 自己封装 Promise

8.1 封装 setTimeout

1
2
3
4
5
function wait(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

wait(2000).then(() => console.log("2 秒后执行"));

可用于:防抖、防止短时间多次请求


8.2 封装 AJAX 请求

1
2
3
4
5
6
7
8
9
10
11
12
function fetchData(url) {
return new Promise((resolve, reject) => {
fetch(url)
.then((response) => response.json())
.then((data) => resolve(data))
.catch((error) => reject(error));
});
}

fetchData("https://jsonplaceholder.typicode.com/todos/1")
.then(console.log)
.catch(console.error);

可以替代 axios 进行 API 请求


9. 总结

基本用法

  • new Promise((resolve, reject) => {...})
  • .then() 处理成功,.catch() 处理失败
  • .finally() 统一执行清理任务

高级用法

  • Promise.all() ✅:所有成功才返回,任意失败则抛错
  • Promise.allSettled() ✅:所有都返回结果,不会中断
  • Promise.race() ✅:返回第一个完成的 Promise
  • Promise.any() ✅:返回第一个成功Promise

推荐使用 async/await

  • 让代码更清晰
  • try...catch 处理错误

💡 Promise 是现代 JavaScript 异步编程的核心,掌握它可以提升你的开发能力!🚀

2.async & await

asyncawait不是严格一一对应 的。虽然 await 只能在 async 函数内使用,但一个 async 函数中可以有 **多个 await**,也可以 **不使用 await**。


1. 一个 async 函数可以有多个 await

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
async function fetchData() {
let result1 = await new Promise((resolve) =>
setTimeout(() => resolve("数据1"), 1000)
);
console.log(result1);

let result2 = await new Promise((resolve) =>
setTimeout(() => resolve("数据2"), 1000)
);
console.log(result2);

return "全部完成";
}

fetchData().then(console.log);

解释:

  • await 等待 Promise 解决(resolve),然后继续执行下一行代码。
  • 这里 async 对应多个 await

2. async 函数可以不使用 await

1
2
3
4
5
async function noAwait() {
return "直接返回";
}

noAwait().then(console.log); // 直接返回

解释:

  • async 函数默认会返回一个 Promise,即使内部没有 await

3. await 也可以嵌套在非 async 函数中

虽然 await 必须在 async 函数中使用,但可以通过 async 包裹:

1
2
3
4
5
6
7
8
9
10
function normalFunction() {
return (async () => {
let result = await new Promise((resolve) =>
setTimeout(() => resolve("异步数据"), 1000)
);
console.log(result);
})();
}

normalFunction();

解释:

  • 这里 normalFunction 不是 async,但内部用 立即执行的 async 函数 来使用 await

4. await 只会等待当前 Promise,不会阻塞整个程序

1
2
3
4
5
6
7
8
9
10
11
12
13
async function fetchData() {
console.log("开始");

let data = await new Promise((resolve) =>
setTimeout(() => resolve("数据加载完成"), 2000)
);
console.log(data);

console.log("结束");
}

fetchData();
console.log("不会被阻塞");

输出顺序:

1
2
3
4
5
开始
不会被阻塞
(2秒后)
数据加载完成
结束

解释:

  • await 只会阻塞当前 async 函数,不会影响外部同步代码。

5. await 不一定需要 async,在顶层可用(ES2022)

ES2022(ES13) 及更新版本中,await 也能在顶层作用域直接使用:

1
2
3
4
await new Promise((resolve) =>
setTimeout(() => resolve(console.log("顶层 await")), 1000)
);
console.log("执行完毕");

(但在旧环境中,必须用 async 包裹)


总结

用法 是否需要 async
awaitasync 函数中使用 ✅ 必须
async 函数可以不含 await ✅ 允许
一个 async 函数可有多个 await ✅ 允许
await 在非 async 函数中 ❌(但可用 async 立即执行函数)
await 在顶层(ES2022+) ✅ 允许

所以,**asyncawait 并不是一一对应** 的,而是 await 依赖 async,但 async **不一定需要 await**。

3.Debounce & Throttle

防抖(Debounce)和节流(Throttle)是两种常见的优化函数执行频率的技术,适用于监听 resizescrollinputclick 等事件,避免频繁触发回调函数导致性能问题。


1. 防抖(Debounce)

特点:

  • 只有在 停止触发事件后一段时间,回调函数才会执行。
  • 如果在等待时间内又触发了事件,则重新计时。

适用场景:

  • 搜索框输入(用户停止输入后再触发请求)。
  • 窗口调整大小(用户调整完毕后再触发计算)。
  • 按钮防止重复点击(短时间内多次点击,只触发最后一次)。

封装防抖函数

1
2
3
4
5
6
7
function debounce(fn, delay = 300) {
let timer = null;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}

使用示例

1
2
3
4
5
6
7
const input = document.querySelector("#search");
input.addEventListener(
"input",
debounce((event) => {
console.log("搜索内容:", event.target.value);
}, 500)
);

2. 节流(Throttle)

特点:

  • 一定时间间隔内 只允许执行一次回调,即使事件连续触发。
  • 适用于 控制高频触发的事件,保证在一定时间内执行一次。

适用场景:

  • 滚动事件scroll 监听,防止频繁触发)。
  • 鼠标移动事件mousemove 监听,减少计算频率)。
  • 按钮防止重复提交(限制一定时间内只能点击一次)。

封装节流函数

1
2
3
4
5
6
7
8
9
10
function throttle(fn, delay = 300) {
let lastTime = 0;
return function (...args) {
let now = Date.now();
if (now - lastTime >= delay) {
fn.apply(this, args);
lastTime = now;
}
};
}

使用示例

1
2
3
4
5
6
window.addEventListener(
"scroll",
throttle(() => {
console.log("触发滚动事件", new Date().toLocaleTimeString());
}, 1000)
);

3. 结合 leadingtrailing 优化防抖和节流

优化防抖:支持 immediate 选项

1
2
3
4
5
6
7
8
9
10
11
12
function debounce(fn, delay = 300, immediate = false) {
let timer = null;
return function (...args) {
if (immediate && !timer) {
fn.apply(this, args);
}
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}

用法:

1
2
const handleInput = debounce(() => console.log("立即执行"), 500, true);
document.querySelector("#search").addEventListener("input", handleInput);

优化节流:支持 leadingtrailing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function throttle(fn, delay = 300, { leading = true, trailing = true } = {}) {
let lastTime = 0,
timer = null;

return function (...args) {
let now = Date.now();

if (!lastTime && !leading) lastTime = now;

let remaining = delay - (now - lastTime);

if (remaining <= 0) {
clearTimeout(timer);
timer = null;
lastTime = now;
fn.apply(this, args);
} else if (!timer && trailing) {
timer = setTimeout(() => {
lastTime = leading ? Date.now() : 0;
timer = null;
fn.apply(this, args);
}, remaining);
}
};
}

用法:

1
2
3
4
5
const handleScroll = throttle(() => console.log("节流优化"), 1000, {
leading: true,
trailing: false,
});
window.addEventListener("scroll", handleScroll);

总结

防抖(Debounce) 节流(Throttle)
触发方式 停止触发后 等待一段时间执行 固定间隔时间 执行
核心机制 多次触发只执行最后一次 间隔时间内最多执行一次
适用场景 输入框、搜索、窗口调整大小 滚动、鼠标移动、按钮防连点
优化方式 支持 immediate 立即执行 支持 leadingtrailing

这两种技术可以根据不同需求组合使用,例如:

  • 搜索框:防抖,避免频繁请求。
  • 按钮点击:节流,防止短时间多次提交。
  • 滚动监听:节流,减少 scroll 事件执行次数。

你可以根据实际需求选择适合的方法 🚀!

4.递归组件

递归组件是 Vue.js 中非常强大的功能,特别适合处理嵌套数据结构的场景,比如树形结构、嵌套菜单、骨架屏等。递归组件的核心思想是组件在其模板中调用自身,从而实现嵌套渲染。

以下是递归组件的详细说明和使用方法:


1. 递归组件的基本概念

递归组件是指一个组件在其模板中调用自身。为了实现递归,组件需要满足以下条件:

  1. 命名:组件必须有一个 name 属性,以便在模板中调用自身。
  2. 终止条件:递归必须有一个终止条件,否则会导致无限递归。

2. 递归组件的实现步骤

步骤 1:定义组件

在 Vue 中,定义一个递归组件非常简单。只需在组件的模板中调用自身即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Vue.component("recursive-component", {
name: "recursive-component", // 必须定义 name
template: `
<div>
<p>{{ data.name }}</p>
<recursive-component
v-if="data.children"
v-for="child in data.children"
:key="child.id"
:data="child"
/>
</div>
`,
props: {
data: {
type: Object,
required: true,
},
},
});

步骤 2:使用组件

在父组件中使用递归组件,并传入嵌套数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
new Vue({
el: "#app",
data() {
return {
treeData: {
id: 1,
name: "Root",
children: [
{
id: 2,
name: "Child 1",
children: [
{
id: 3,
name: "Grandchild 1",
},
{
id: 4,
name: "Grandchild 2",
},
],
},
{
id: 5,
name: "Child 2",
},
],
},
};
},
template: `
<div>
<recursive-component :data="treeData" />
</div>
`,
});

步骤 3:渲染结果

上述代码会渲染出以下结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div>
<p>Root</p>
<div>
<p>Child 1</p>
<div>
<p>Grandchild 1</p>
</div>
<div>
<p>Grandchild 2</p>
</div>
</div>
<div>
<p>Child 2</p>
</div>
</div>

3. 递归组件的关键点

1. name 属性

递归组件必须定义 name 属性,因为 Vue 需要通过 name 来识别组件自身。

1
2
3
4
Vue.component("recursive-component", {
name: "recursive-component", // 必须定义 name
// ...
});

2. 终止条件

递归必须有一个终止条件,否则会导致无限递归。通常通过 v-ifv-for 来控制递归的终止。

1
2
3
4
5
6
<recursive-component
v-if="data.children" // 终止条件:如果没有 children,则停止递归
v-for="child in data.children"
:key="child.id"
:data="child"
/>

3. props 传递数据

递归组件通过 props 接收数据,并将数据传递给下一层递归。

1
2
3
4
5
6
props: {
data: {
type: Object,
required: true,
},
},

4. 递归组件的应用场景

递归组件非常适合处理以下场景:

  1. 树形结构:如文件目录、组织架构等。
  2. 嵌套菜单:如多级导航菜单。
  3. 骨架屏:如动态生成嵌套的骨架屏结构。
  4. 评论系统:如嵌套的评论回复。

5. 完整示例:树形结构渲染

以下是一个完整的树形结构渲染示例:

数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const treeData = {
id: 1,
name: "Root",
children: [
{
id: 2,
name: "Child 1",
children: [
{
id: 3,
name: "Grandchild 1",
},
{
id: 4,
name: "Grandchild 2",
},
],
},
{
id: 5,
name: "Child 2",
},
],
};

递归组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Vue.component("tree-node", {
name: "tree-node",
template: `
<div>
<p>{{ data.name }}</p>
<tree-node
v-if="data.children"
v-for="child in data.children"
:key="child.id"
:data="child"
/>
</div>
`,
props: {
data: {
type: Object,
required: true,
},
},
});

使用组件

1
2
3
4
5
6
7
8
9
10
11
12
13
new Vue({
el: "#app",
data() {
return {
treeData,
};
},
template: `
<div>
<tree-node :data="treeData" />
</div>
`,
});

渲染结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div>
<p>Root</p>
<div>
<p>Child 1</p>
<div>
<p>Grandchild 1</p>
</div>
<div>
<p>Grandchild 2</p>
</div>
</div>
<div>
<p>Child 2</p>
</div>
</div>

6. 注意事项

  1. 性能问题

    • 递归组件的嵌套层级不宜过深,否则可能导致性能问题。
    • 可以通过 v-ifv-for 的优化来减少不必要的渲染。
  2. 终止条件

    • 必须确保递归有终止条件,否则会导致无限递归和栈溢出。
  3. key 的使用

    • v-for 中必须为每个子元素设置唯一的 key,以便 Vue 高效地更新 DOM。

7. 总结

递归组件是 Vue.js 中处理嵌套数据的强大工具。通过定义 name 属性、设置终止条件和传递 props,可以轻松实现树形结构、嵌套菜单等复杂场景的渲染。在实际开发中,递归组件的使用需要结合具体场景,注意性能和终止条件。

如果有其他问题,欢迎随时提问!

Props

在 Vue 3 的 defineProps() 中,可以对 props 进行类型校验默认值必填验证


📌 1. props 属性详细解析

1
2
3
4
5
6
7
8
9
10
props: {
user: {
type: Object, // 指定类型(如 String, Number, Boolean, Object, Array, Function)
required: true, // 是否必须传递(true = 必填)
default: () => ({ name: "默认用户", age: 18 }), // 默认值(对象要用箭头函数)
validator: (value) => { // 自定义校验规则
return value.name && typeof value.age === "number";
}
}
}

📌 2. Vue 3 `` 版本

在 `` 里,defineProps()不能直接写对象校验,而是用 TypeScript 或defineProps() 传递对象。

1
2
3
4
5
6
7
8
9
10
11
12
<script setup>
defineProps({
user: {
type: Object,
required: true,
default: () => ({ name: "默认用户", age: 18 }),
validator: (value) => {
return value.name && typeof value.age === "number";
},
},
});
</script>

📌 3. 各参数作用

参数 作用
type 指定 props 的类型,如 StringNumberBooleanObject
required 是否必须传递true = 必填
default 默认值(对象/数组要用箭头函数)
validator 自定义校验规则,返回 true 代表合法,false 代表报错

📌 4. 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script setup>
const props = defineProps({
user: {
type: Object,
required: true,
default: () => ({ name: "默认用户", age: 18 }),
validator: (value) => {
return (
value && typeof value.name === "string" && typeof value.age === "number"
);
},
},
});
</script>

<template>
<p>姓名: {{ props.user.name }}</p>
<p>年龄: {{ props.user.age }}</p>
</template>

📌 5. type 可选值

type 说明
String 字符串
Number 数字
Boolean 布尔值
Object 对象
Array 数组
Function 函数

📌 6. validator 自定义校验

1
2
3
4
5
6
7
8
<script setup>
defineProps({
age: {
type: Number,
validator: (value) => value > 0 && value < 150, // 只能是 0~150 的数字
},
});
</script>
  • 如果 age 小于 0 或大于 150,Vue 不会报错,但会在 控制台警告⚠️。

🚀 最佳实践

  • **尽量指定 type**,避免意外类型
  • 如果是对象或数组,default 需要用函数返回
  • 使用 validator 进行额外校验
  • required: true 适用于必须传的 props

这样可以让 props 更加健壮,避免意外数据导致错误!🎯

组件通信

在 Vue 3 中,组件通信方式主要包括以下几种:


1. Props / Emit(父子组件通信)

  • 父组件 → 子组件:使用 props 传递数据。
  • 子组件 → 父组件:使用 emit 触发自定义事件,向父组件发送数据。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!-- Parent.vue -->
<template>
<Child :message="parentMsg" @updateMessage="handleUpdate" />
</template>

<script setup>
import { ref } from "vue";
import Child from "./Child.vue";

const parentMsg = ref("Hello from Parent");

const handleUpdate = (newMsg) => {
parentMsg.value = newMsg;
};
</script>
<!-- Child.vue -->
<template>
<div>
<p>{{ message }}</p>
<button @click="updateParent">Update Parent</button>
</div>
</template>

<script setup>
import { defineProps, defineEmits } from "vue";

const props = defineProps(["message"]);
const emit = defineEmits(["updateMessage"]);

const updateParent = () => {
emit("updateMessage", "Hello from Child!");
};
</script>

2. Provide / Inject(跨层级组件通信)

适用于深层嵌套的组件,可以让祖先组件提供数据,后代组件注入数据。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!-- App.vue -->
<template>
<Parent />
</template>
<!-- Parent.vue -->
<template>
<Child />
</template>

<script setup>
import { provide, ref } from "vue";
import Child from "./Child.vue";

const message = ref("Hello from Provide!");
provide("sharedMessage", message);
</script>
<!-- Child.vue -->
<template>
<p>{{ sharedMessage }}</p>
</template>

<script setup>
import { inject } from "vue";

const sharedMessage = inject("sharedMessage");
</script>

3. Event Bus(兄弟组件通信)

Vue 3 没有内置的事件总线,但可以使用 mitt(推荐):

1
npm install mitt

创建事件总线 (eventBus.js):

1
2
3
import mitt from "mitt";
const eventBus = mitt();
export default eventBus;

组件通信:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!-- Sender.vue -->
<script setup>
import eventBus from "../eventBus";
const sendMessage = () => {
eventBus.emit("customEvent", "Hello from Sender!");
};
</script>

<template>
<button @click="sendMessage">Send Message</button>
</template>
<!-- Receiver.vue -->
<script setup>
import { onMounted, ref } from "vue";
import eventBus from "../eventBus";

const receivedMessage = ref("");

onMounted(() => {
eventBus.on("customEvent", (msg) => {
receivedMessage.value = msg;
});
});
</script>

<template>
<p>Received: {{ receivedMessage }}</p>
</template>

4. Pinia(全局状态管理)

Vue 3 推荐使用 Pinia 作为状态管理工具(比 Vuex 更轻量)。

1
npm install pinia

创建 store

1
2
3
4
5
6
7
8
9
10
// stores/counter.js
import { defineStore } from "pinia";
import { ref } from "vue";

export const useCounterStore = defineStore("counter", () => {
const count = ref(0);
const increment = () => count.value++;

return { count, increment };
});

在组件中使用:

1
2
3
4
5
6
7
8
9
<!-- Counter.vue -->
<template>
<button @click="store.increment">Count: {{ store.count }}</button>
</template>

<script setup>
import { useCounterStore } from "../stores/counter";
const store = useCounterStore();
</script>

5. $attrs 和 v-bind(高阶组件通信)

适用于透传 props 到子组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- Parent.vue -->
<template>
<Child msg="Hello" />
</template>
<!-- Child.vue -->
<template>
<GrandChild v-bind="$attrs" />
</template>

<script setup>
defineProps(["msg"]);
</script>
<!-- GrandChild.vue -->
<template>
<p>{{ msg }}</p>
</template>

<script setup>
defineProps(["msg"]);
</script>

6. Ref / Reactive 共享状态

多个组件共享响应式数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// store.js
import { reactive } from "vue";
export const globalState = reactive({ count: 0 });
<!-- A.vue -->
<template>
<button @click="globalState.count++">Increase</button>
</template>

<script setup>
import { globalState } from "../store";
</script>
<!-- B.vue -->
<template>
<p>Count: {{ globalState.count }}</p>
</template>

<script setup>
import { globalState } from "../store";
</script>

结论

  • 父子通信props / emit
  • 兄弟通信mitt / Pinia
  • 跨层级provide / inject
  • 全局状态Pinia / reactive
  • 透传属性$attrs

根据实际场景选择最合适的方式!🚀

Date

你提供的是ISO 8601 格式的时间字符串"2024-03-21T13:00:00"),在 JavaScript 中,可以使用 Date 对象来解析、格式化或转换它。


1. 解析 ISO 时间字符串

JavaScript Date 构造函数可以直接解析这种格式:

1
2
3
4
const dateStr = "2024-03-21T13:00:00";
const date = new Date(dateStr);
console.log(date);
// 输出: Thu Mar 21 2024 13:00:00 GMT+0000 (UTC)

📌 new Date(dateStr) 会自动解析 ISO 格式,并转换为本地时间


2. 提取日期、时间信息

你可以使用 getFullYear()getMonth()getDate() 等方法:

1
2
3
4
5
6
console.log(date.getFullYear()); // 2024
console.log(date.getMonth() + 1); // 3 (注意:`getMonth()` 从 0 开始)
console.log(date.getDate()); // 21
console.log(date.getHours()); // 13
console.log(date.getMinutes()); // 0
console.log(date.getSeconds()); // 0

3. 格式化为 YYYY-MM-DD HH:mm:ss

1
2
3
4
5
6
7
8
9
10
11
function formatDate(date) {
const Y = date.getFullYear();
const M = String(date.getMonth() + 1).padStart(2, "0");
const D = String(date.getDate()).padStart(2, "0");
const H = String(date.getHours()).padStart(2, "0");
const m = String(date.getMinutes()).padStart(2, "0");
const s = String(date.getSeconds()).padStart(2, "0");
return `${Y}-${M}-${D} ${H}:${m}:${s}`;
}

console.log(formatDate(date)); // "2024-03-21 13:00:00"

📌 padStart(2, "0") 用于补零,确保格式一致。


4. 转换为 UTC 时间

如果你想以 UTC 时间显示,而不是本地时间:

1
2
console.log(date.toISOString());
// 结果: "2024-03-21T13:00:00.000Z"

📌 toISOString() 返回标准 UTC 格式(末尾带 Z 表示 UTC 时区)。

如果要手动获取 UTC 时间:

1
2
3
4
5
6
console.log(date.getUTCFullYear()); // 2024
console.log(date.getUTCMonth() + 1); // 3
console.log(date.getUTCDate()); // 21
console.log(date.getUTCHours()); // 13
console.log(date.getUTCMinutes()); // 0
console.log(date.getUTCSeconds()); // 0

5. 转换为时间戳(毫秒数)

如果需要将日期转换为时间戳(毫秒)

1
2
console.log(date.getTime());
// 结果: 1711026000000

📌 getTime() 返回自 1970-01-01 00:00:00 UTC 以来的毫秒数。

如果需要秒级时间戳

1
2
console.log(Math.floor(date.getTime() / 1000));
// 结果: 1711026000

6. 使用 day.jsmoment.js 进行处理

如果需要更强大的时间处理库,可以使用 day.js(轻量)或 moment.js(较重)。

📌 使用 day.js

1
2
3
4
5
import dayjs from "dayjs";

const dateStr = "2024-03-21T13:00:00";
const formatted = dayjs(dateStr).format("YYYY-MM-DD HH:mm:ss");
console.log(formatted); // "2024-03-21 13:00:00"

📌 使用 moment.js

1
2
3
4
5
import moment from "moment";

const dateStr = "2024-03-21T13:00:00";
const formatted = moment(dateStr).format("YYYY-MM-DD HH:mm:ss");
console.log(formatted); // "2024-03-21 13:00:00"

📌 总结

操作 方法
解析 ISO 8601 日期 new Date(dateStr)
获取年份 date.getFullYear()
获取月份 date.getMonth() + 1(注意 +1
获取日期 date.getDate()
获取时间(小时、分钟、秒) date.getHours(), date.getMinutes(), date.getSeconds()
格式化 YYYY-MM-DD HH:mm:ss 自定义 padStart() 方法 / day.js / moment.js
转换 UTC 时间 date.toISOString()
获取时间戳(毫秒) date.getTime()
获取时间戳(秒) Math.floor(date.getTime() / 1000)

如果只是简单处理,**Date 内置方法就够用**,如果需要更强大的时间操作(时区转换、相对时间等),推荐 day.js

sort

是的,**sort() 方法会修改原数组,它是原地排序in-place sorting**),不会创建新数组。


1. sort() 修改原数组

1
2
3
4
const arr = [3, 1, 4, 1, 5, 9];
arr.sort((a, b) => a - b); // 升序排序
console.log(arr);
// 结果: [1, 1, 3, 4, 5, 9] (原数组被修改)

⚠️ 注意:sort() 不会返回新数组,而是修改 arr 本身!


2. 如何避免 sort() 修改原数组?

如果想要排序但不修改原数组,可以先创建副本

1
2
3
4
5
const arr = [3, 1, 4, 1, 5, 9];
const sortedArr = [...arr].sort((a, b) => a - b); // 创建副本再排序

console.log(arr); // [3, 1, 4, 1, 5, 9] (原数组不变)
console.log(sortedArr); // [1, 1, 3, 4, 5, 9] (新数组已排序)

📌 方法解析

  • [...] 扩展运算符 复制数组,避免 sort() 修改原数组。
  • sort() 只作用于副本 sortedArr,不会影响 arr

3. 使用 toSorted() (ES2023+)

ES2023+ 中,JavaScript 提供了 toSorted() 方法,它不会修改原数组,而是返回排序后的新数组

1
2
3
4
5
const arr = [3, 1, 4, 1, 5, 9];
const sortedArr = arr.toSorted((a, b) => a - b); // 创建新数组

console.log(arr); // [3, 1, 4, 1, 5, 9] (原数组不变)
console.log(sortedArr); // [1, 1, 3, 4, 5, 9] (新数组已排序)

🚀 toSorted() 的优点

  • 不会修改原数组
  • 代码更简洁(不需要 [...] 复制数组)

toSorted() 目前只在最新的浏览器支持,如果你的项目需要兼容旧版浏览器,仍然需要用 sort() + [...]


📌 总结

方法 是否修改原数组 适用场景
arr.sort() ✅ 修改原数组 直接对原数组排序
[...arr].sort() ❌ 不修改原数组 需要排序但保留原数组
arr.toSorted() (ES2023+) ❌ 不修改原数组 更简洁的方法,但仅支持现代浏览器

如果你需要排序但不影响原数组,建议用 [...arr].sort()toSorted()(如果环境支持)。

regex

正则表达式(Regular Expression)在 JavaScript 中是一个强大的工具,用于字符串的匹配、搜索、替换和验证。以下是 JavaScript 中正则表达式的详细用法,包括捕获组(Capturing Groups)的应用。


一、正则表达式的基本语法

在 JavaScript 中,正则表达式可以通过两种方式创建:

  1. 字面量形式/pattern/modifiers
  2. 构造函数形式new RegExp("pattern", "modifiers")

示例:

1
2
3
4
5
// 字面量形式
const regex1 = /abc/gi;

// 构造函数形式
const regex2 = new RegExp("abc", "gi");

二、正则表达式的常用方法

1. test():测试字符串是否匹配

返回布尔值,表示是否找到匹配。

1
2
3
const str = "Hello World";
const regex = /world/i;
console.log(regex.test(str)); // true(i 表示不区分大小写)

2. exec():执行搜索,返回匹配结果

返回一个数组(包含匹配信息)或 null

1
2
3
4
5
6
7
8
9
10
11
12
const str = "2023-10-25";
const regex = /(\d{4})-(\d{2})-(\d{2})/;
const result = regex.exec(str);
console.log(result);
// 输出:
// [
// "2023-10-25",
// "2023", "10", "25",
// index: 0,
// input: "2023-10-25",
// groups: undefined
// ]

3. match():字符串方法,返回匹配结果

类似于 exec(),但属于字符串的方法。

1
2
3
4
const str = "2023-10-25";
const regex = /(\d{4})-(\d{2})-(\d{2})/;
const result = str.match(regex);
console.log(result); // 输出同上

4. replace():替换匹配内容

替换字符串中匹配的部分。

1
2
3
const str = "2023-10-25";
const regex = /(\d{4})-(\d{2})-(\d{2})/;
const newStr = str.replace(regex, "$2/$3/$1"); // "10/25/2023"

5. search():返回匹配的起始位置

返回匹配项的索引,未找到返回 -1

1
2
3
const str = "Hello World";
const regex = /world/i;
console.log(str.search(regex)); // 6

三、捕获组(Capturing Groups)

捕获组是正则表达式中用括号 () 包裹的部分,用于从匹配的字符串中提取特定部分。

1. 基本捕获组

通过索引访问捕获组,索引从 1 开始。

1
2
3
4
5
6
7
8
const str = "John Doe, 30";
const regex = /(\w+)\s(\w+),\s(\d+)/;
const result = regex.exec(str);

console.log(result[0]); // "John Doe, 30"(完整匹配)
console.log(result[1]); // "John"(第一个捕获组)
console.log(result[2]); // "Doe"(第二个捕获组)
console.log(result[3]); // "30"(第三个捕获组)

2. 命名捕获组(ES2018+)

通过 ?<name> 语法为捕获组命名,通过 groups 属性访问。

1
2
3
4
5
6
7
const str = "2023-10-25";
const regex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const result = regex.exec(str);

console.log(result.groups.year); // "2023"
console.log(result.groups.month); // "10"
console.log(result.groups.day); // "25"

3. 非捕获组

使用 ?: 表示非捕获组,匹配但不捕获。

1
2
3
4
5
6
const str = "foo123bar";
const regex = /foo(?:\d+)(bar)/;
const result = regex.exec(str);

console.log(result[0]); // "foo123bar"
console.log(result[1]); // "bar"(仅捕获最后一个组)

四、捕获组的实际应用

1. 提取日期中的年月日

1
2
3
4
const str = "2023-10-25";
const regex = /(\d{4})-(\d{2})-(\d{2})/;
const [, year, month, day] = regex.exec(str);
console.log(year, month, day); // "2023" "10" "25"

2. 替换日期格式

1
2
3
const str = "2023-10-25";
const newStr = str.replace(/(\d{4})-(\d{2})-(\d{2})/, "$2/$3/$1");
console.log(newStr); // "10/25/2023"

3. 解析 URL 参数

1
2
3
4
5
6
7
8
9
10
const url = "https://example.com?name=John&age=30";
const regex = /(\w+)=(\w+)/g;
const params = {};

let match;
while ((match = regex.exec(url)) !== null) {
params[match[1]] = match[2];
}

console.log(params); // { name: "John", age: "30" }

4. 动态替换模板字符串

1
2
3
4
5
const template = "Hello, {name}! You are {age} years old.";
const data = { name: "John", age: 30 };

const result = template.replace(/\{(\w+)\}/g, (match, key) => data[key]);
console.log(result); // "Hello, John! You are 30 years old."

五、常见问题与技巧

1. 贪婪匹配 vs 非贪婪匹配

  • 贪婪匹配:默认模式,尽可能匹配更长的字符串。

    1
    2
    3
    const str = "foo123bar";
    const regex = /foo(\d+)bar/;
    console.log(regex.exec(str)[1]); // "123"
  • 非贪婪匹配:使用 ?,尽可能匹配更短的字符串。

    1
    2
    3
    const str = "foo123bar";
    const regex = /foo(\d+?)bar/;
    console.log(regex.exec(str)[1]); // "1"

2. 捕获组的性能

避免在复杂的正则表达式中使用过多捕获组,可能会影响性能。


六、总结

  • 基础用法test()exec()match()replace()
  • 捕获组:通过 () 提取匹配内容,支持命名捕获组 (?<name>)
  • 应用场景:日期解析、URL 参数提取、模板替换等。
  • 注意事项:贪婪匹配与非贪婪匹配的区别,性能优化。

通过灵活使用正则表达式和捕获组,可以高效处理复杂的字符串操作。

Ajax

使用原生的 Ajax 可以通过 JavaScript 来实现,以下是一个简单的示例,展示了如何使用原生 JavaScript 发起一个 Ajax 请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 创建一个 XMLHttpRequest 对象
var xhr = new XMLHttpRequest();

// 配置请求,包括请求方法、URL 以及是否异步
xhr.open("GET", "https://api.example.com/data", true);

// 监听请求状态变化
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
// 请求成功,处理返回的数据
console.log(xhr.responseText);
} else {
// 请求失败,处理错误
console.error("请求失败:" + xhr.status);
}
}
};

// 发送请求
xhr.send();

在这个例子中:

  1. 创建了一个 XMLHttpRequest 对象,它是处理 Ajax 请求的核心。
  2. 使用 xhr.open() 方法配置了请求的方法(GET、POST 等)、URL 和是否使用异步(true 表示异步)。
  3. 通过监听 xhr.onreadystatechange 事件,可以处理请求状态的变化。当 readyState 变为 XMLHttpRequest.DONE 时,表示请求完成。
  4. 在请求完成时,可以检查 xhr.status 来确定请求是否成功(状态码 200 表示成功),然后处理返回的数据(xhr.responseText)或者处理错误情况。

这是一个基本的 Ajax 请求示例,可以根据具体需求和情境进行进一步的定制和扩展。

fs

Node.js 的 fs 模块是处理文件系统操作的核心模块,它提供了许多方法来进行文件的读取、写入、删除、重命名、监控等操作。以下是 fs 模块的详细总结,涵盖了常用方法和适用场景。


1. 文件读取

异步读取文件:fs.readFile(path, encoding, callback)

异步地读取文件内容,callback 回调函数会在文件读取完毕后执行。

  • 语法

    1
    fs.readFile(path, encoding, callback);
  • 参数

    • path: 要读取的文件路径。

    • encoding: 编码格式(如 'utf8')。如果不指定,返回 Buffer 类型的数据。

    • callback
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20

      : 回调函数,接收两个参数:

      - `err`: 如果有错误,包含错误信息。
      - `data`: 读取到的文件内容。

      - 使用示例



      ```js
      const fs = require("fs");

      fs.readFile("example.txt", "utf8", (err, data) => {
      if (err) {
      console.log("Error:", err);
      } else {
      console.log("File content:", data);
      }
      });

同步读取文件:fs.readFileSync(path, encoding)

同步地读取文件内容,直到文件读取完成,程序才会继续执行。

  • 语法

    1
    const data = fs.readFileSync(path, encoding);
  • 参数

    • path: 要读取的文件路径。
    • encoding: 编码格式(如 'utf8'),如果不指定,返回 Buffer 类型的数据。
  • 使用示例

    1
    2
    3
    4
    5
    6
    7
    8
    const fs = require("fs");

    try {
    const data = fs.readFileSync("example.txt", "utf8");
    console.log("File content:", data);
    } catch (err) {
    console.log("Error:", err);
    }

返回类型:

  • 如果指定编码(如 'utf8'),返回的将是一个字符串。
  • 如果没有指定编码,则返回的是 Buffer 对象。

2. 文件写入

异步写入文件:fs.writeFile(path, data, encoding, callback)

异步地将数据写入文件。如果文件已存在,则会覆盖原文件。

  • 语法

    1
    fs.writeFile(path, data, encoding, callback);
  • 参数

    • path: 要写入的文件路径。
    • data: 要写入的数据,可以是字符串或 Buffer
    • encoding: 编码格式(可选,默认为 'utf8')。
    • callback: 回调函数,接收一个参数 err,表示错误信息。
  • 使用示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const fs = require("fs");

    fs.writeFile("example.txt", "Hello, Node.js!", "utf8", (err) => {
    if (err) {
    console.log("Error writing file:", err);
    } else {
    console.log("File written successfully");
    }
    });

同步写入文件:fs.writeFileSync(path, data, encoding)

同步地将数据写入文件,直到文件写入完成,程序才会继续执行。

  • 语法

    1
    fs.writeFileSync(path, data, encoding);
  • 参数

    • path: 要写入的文件路径。
    • data: 要写入的数据。
    • encoding: 编码格式(可选,默认为 'utf8')。
  • 使用示例

    1
    2
    3
    4
    5
    6
    7
    8
    const fs = require("fs");

    try {
    fs.writeFileSync("example.txt", "Hello, Node.js!", "utf8");
    console.log("File written successfully");
    } catch (err) {
    console.log("Error writing file:", err);
    }

3. 追加文件内容

异步追加:fs.appendFile(path, data, encoding, callback)

将数据追加到文件末尾。如果文件不存在,则会创建新文件。

  • 语法

    1
    fs.appendFile(path, data, encoding, callback);
  • 参数

    • path: 要写入的文件路径。
    • data: 要追加的数据。
    • encoding: 编码格式(可选,默认为 'utf8')。
    • callback: 回调函数,接收一个参数 err,表示错误信息。
  • 使用示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const fs = require("fs");

    fs.appendFile("example.txt", "\nHello again!", "utf8", (err) => {
    if (err) {
    console.log("Error appending to file:", err);
    } else {
    console.log("Content appended successfully");
    }
    });

同步追加:fs.appendFileSync(path, data, encoding)

同步地将数据追加到文件末尾,直到文件写入完成,程序才会继续执行。

  • 语法

    1
    fs.appendFileSync(path, data, encoding);
  • 参数

    • path: 要写入的文件路径。
    • data: 要追加的数据。
    • encoding: 编码格式(可选,默认为 'utf8')。
  • 使用示例

    1
    2
    3
    4
    5
    6
    7
    8
    const fs = require("fs");

    try {
    fs.appendFileSync("example.txt", "\nHello again!", "utf8");
    console.log("Content appended successfully");
    } catch (err) {
    console.log("Error appending to file:", err);
    }

4. 文件和目录检查

检查文件是否存在:fs.exists(path, callback)

检查指定路径的文件或目录是否存在,返回一个布尔值。

  • 语法

    1
    fs.exists(path, callback);
  • 参数

    • path: 要检查的路径。
    • callback: 回调函数,接收一个参数 exists,布尔值,表示文件或目录是否存在。
  • 使用示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const fs = require("fs");

    fs.exists("example.txt", (exists) => {
    if (exists) {
    console.log("File exists");
    } else {
    console.log("File does not exist");
    }
    });

检查文件状态:fs.stat(path, callback)

获取文件或目录的状态信息,包括文件大小、修改时间等。

  • 语法

    1
    fs.stat(path, callback);
  • 参数

    • path: 要检查的文件路径。

    • callback
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20

      : 回调函数,接收两个参数:

      - `err`: 如果有错误,包含错误信息。
      - `stats`: 包含文件信息的 `fs.Stats` 对象。

      - **使用示例**:

      ```js
      const fs = require("fs");

      fs.stat("example.txt", (err, stats) => {
      if (err) {
      console.log("Error checking file:", err);
      } else {
      console.log("File stats:", stats);
      console.log("Is directory?", stats.isDirectory());
      console.log("Is file?", stats.isFile());
      }
      });

5. 删除文件

删除指定路径的文件。

  • 语法

    1
    fs.unlink(path, callback);
  • 参数

    • path: 要删除的文件路径。
    • callback: 回调函数,接收一个参数 err,表示错误信息。
  • 使用示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const fs = require("fs");

    fs.unlink("example.txt", (err) => {
    if (err) {
    console.log("Error deleting file:", err);
    } else {
    console.log("File deleted successfully");
    }
    });

6. 创建和删除目录

创建目录:fs.mkdir(path, callback)

创建一个新目录。

  • 语法

    1
    fs.mkdir(path, callback);
  • 参数

    • path: 要创建的目录路径。
    • callback: 回调函数,接收一个参数 err,表示错误信息。
  • 使用示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const fs = require("fs");

    fs.mkdir("newdir", (err) => {
    if (err) {
    console.log("Error creating directory:", err);
    } else {
    console.log("Directory created successfully");
    }
    });

删除目录:fs.rmdir(path, callback)

删除一个空目录。

  • 语法

    1
    fs.rmdir(path, callback);
  • 参数

    • path: 要删除的目录路径。
    • callback: 回调函数,接收一个参数 err,表示错误信息。
  • 使用示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const fs = require("fs");

    fs.rmdir("newdir", (err) => {
    if (err) {
    console.log("Error removing directory:", err);
    } else {
    console.log("Directory removed successfully");
    }
    });

总结

  • 异步与同步方法fs 模块提供了异步和同步的方法,通常推荐使用异步方法(避免阻塞事件循环),同步方法用于简单场景或者脚本执行。
  • 文件和目录操作:文件的读取、写入、删除、检查、目录的创建与删除等是最常用的操作,基于这些基本功能可以构建文件管理系统、日志记录等功能。
  • 错误处理:所有的异步方法都提供了回调函数来处理错误,确保在文件操作中捕获异常并做出适当反应。

fs

Node.js 的 fs 模块是处理文件系统操作的核心模块,它提供了许多方法来进行文件的读取、写入、删除、重命名、监控等操作。以下是 fs 模块的详细总结,涵盖了常用方法和适用场景。


1. 文件读取

异步读取文件:fs.readFile(path, encoding, callback)

异步地读取文件内容,callback 回调函数会在文件读取完毕后执行。

  • 语法

    1
    fs.readFile(path, encoding, callback);
  • 参数

    • path: 要读取的文件路径。

    • encoding: 编码格式(如 'utf8')。如果不指定,返回 Buffer 类型的数据。

    • callback
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20

      : 回调函数,接收两个参数:

      - `err`: 如果有错误,包含错误信息。
      - `data`: 读取到的文件内容。

      - 使用示例



      ```js
      const fs = require("fs");

      fs.readFile("example.txt", "utf8", (err, data) => {
      if (err) {
      console.log("Error:", err);
      } else {
      console.log("File content:", data);
      }
      });

同步读取文件:fs.readFileSync(path, encoding)

同步地读取文件内容,直到文件读取完成,程序才会继续执行。

  • 语法

    1
    const data = fs.readFileSync(path, encoding);
  • 参数

    • path: 要读取的文件路径。
    • encoding: 编码格式(如 'utf8'),如果不指定,返回 Buffer 类型的数据。
  • 使用示例

    1
    2
    3
    4
    5
    6
    7
    8
    const fs = require("fs");

    try {
    const data = fs.readFileSync("example.txt", "utf8");
    console.log("File content:", data);
    } catch (err) {
    console.log("Error:", err);
    }

返回类型:

  • 如果指定编码(如 'utf8'),返回的将是一个字符串。
  • 如果没有指定编码,则返回的是 Buffer 对象。

2. 文件写入

异步写入文件:fs.writeFile(path, data, encoding, callback)

异步地将数据写入文件。如果文件已存在,则会覆盖原文件。

  • 语法

    1
    fs.writeFile(path, data, encoding, callback);
  • 参数

    • path: 要写入的文件路径。
    • data: 要写入的数据,可以是字符串或 Buffer
    • encoding: 编码格式(可选,默认为 'utf8')。
    • callback: 回调函数,接收一个参数 err,表示错误信息。
  • 使用示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const fs = require("fs");

    fs.writeFile("example.txt", "Hello, Node.js!", "utf8", (err) => {
    if (err) {
    console.log("Error writing file:", err);
    } else {
    console.log("File written successfully");
    }
    });

同步写入文件:fs.writeFileSync(path, data, encoding)

同步地将数据写入文件,直到文件写入完成,程序才会继续执行。

  • 语法

    1
    fs.writeFileSync(path, data, encoding);
  • 参数

    • path: 要写入的文件路径。
    • data: 要写入的数据。
    • encoding: 编码格式(可选,默认为 'utf8')。
  • 使用示例

    1
    2
    3
    4
    5
    6
    7
    8
    const fs = require("fs");

    try {
    fs.writeFileSync("example.txt", "Hello, Node.js!", "utf8");
    console.log("File written successfully");
    } catch (err) {
    console.log("Error writing file:", err);
    }

3. 追加文件内容

异步追加:fs.appendFile(path, data, encoding, callback)

将数据追加到文件末尾。如果文件不存在,则会创建新文件。

  • 语法

    1
    fs.appendFile(path, data, encoding, callback);
  • 参数

    • path: 要写入的文件路径。
    • data: 要追加的数据。
    • encoding: 编码格式(可选,默认为 'utf8')。
    • callback: 回调函数,接收一个参数 err,表示错误信息。
  • 使用示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const fs = require("fs");

    fs.appendFile("example.txt", "\nHello again!", "utf8", (err) => {
    if (err) {
    console.log("Error appending to file:", err);
    } else {
    console.log("Content appended successfully");
    }
    });

同步追加:fs.appendFileSync(path, data, encoding)

同步地将数据追加到文件末尾,直到文件写入完成,程序才会继续执行。

  • 语法

    1
    fs.appendFileSync(path, data, encoding);
  • 参数

    • path: 要写入的文件路径。
    • data: 要追加的数据。
    • encoding: 编码格式(可选,默认为 'utf8')。
  • 使用示例

    1
    2
    3
    4
    5
    6
    7
    8
    const fs = require("fs");

    try {
    fs.appendFileSync("example.txt", "\nHello again!", "utf8");
    console.log("Content appended successfully");
    } catch (err) {
    console.log("Error appending to file:", err);
    }

4. 文件和目录检查

检查文件是否存在:fs.exists(path, callback)

检查指定路径的文件或目录是否存在,返回一个布尔值。

  • 语法

    1
    fs.exists(path, callback);
  • 参数

    • path: 要检查的路径。
    • callback: 回调函数,接收一个参数 exists,布尔值,表示文件或目录是否存在。
  • 使用示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const fs = require("fs");

    fs.exists("example.txt", (exists) => {
    if (exists) {
    console.log("File exists");
    } else {
    console.log("File does not exist");
    }
    });

检查文件状态:fs.stat(path, callback)

获取文件或目录的状态信息,包括文件大小、修改时间等。

  • 语法

    1
    fs.stat(path, callback);
  • 参数

    • path: 要检查的文件路径。

    • callback
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20

      : 回调函数,接收两个参数:

      - `err`: 如果有错误,包含错误信息。
      - `stats`: 包含文件信息的 `fs.Stats` 对象。

      - **使用示例**:

      ```js
      const fs = require("fs");

      fs.stat("example.txt", (err, stats) => {
      if (err) {
      console.log("Error checking file:", err);
      } else {
      console.log("File stats:", stats);
      console.log("Is directory?", stats.isDirectory());
      console.log("Is file?", stats.isFile());
      }
      });

5. 删除文件

删除指定路径的文件。

  • 语法

    1
    fs.unlink(path, callback);
  • 参数

    • path: 要删除的文件路径。
    • callback: 回调函数,接收一个参数 err,表示错误信息。
  • 使用示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const fs = require("fs");

    fs.unlink("example.txt", (err) => {
    if (err) {
    console.log("Error deleting file:", err);
    } else {
    console.log("File deleted successfully");
    }
    });

6. 创建和删除目录

创建目录:fs.mkdir(path, callback)

创建一个新目录。

  • 语法

    1
    fs.mkdir(path, callback);
  • 参数

    • path: 要创建的目录路径。
    • callback: 回调函数,接收一个参数 err,表示错误信息。
  • 使用示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const fs = require("fs");

    fs.mkdir("newdir", (err) => {
    if (err) {
    console.log("Error creating directory:", err);
    } else {
    console.log("Directory created successfully");
    }
    });

删除目录:fs.rmdir(path, callback)

删除一个空目录。

  • 语法

    1
    fs.rmdir(path, callback);
  • 参数

    • path: 要删除的目录路径。
    • callback: 回调函数,接收一个参数 err,表示错误信息。
  • 使用示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const fs = require("fs");

    fs.rmdir("newdir", (err) => {
    if (err) {
    console.log("Error removing directory:", err);
    } else {
    console.log("Directory removed successfully");
    }
    });

总结

  • 异步与同步方法fs 模块提供了异步和同步的方法,通常推荐使用异步方法(避免阻塞事件循环),同步方法用于简单场景或者脚本执行。
  • 文件和目录操作:文件的读取、写入、删除、检查、目录的创建与删除等是最常用的操作,基于这些基本功能可以构建文件管理系统、日志记录等功能。
  • 错误处理:所有的异步方法都提供了回调函数来处理错误,确保在文件操作中捕获异常并做出适当反应。

Path

path 模块是 Node.js 的核心模块之一,主要用于处理和操作文件路径。它提供了多种方法来处理路径字符串,以保证在不同操作系统(如 Windows、Linux、macOS)上的兼容性。


1. 引入 path 模块

path 是 Node.js 内置模块,无需安装,可以直接 require 引入:

1
const path = require("path");

2. path 常用方法

(1) path.join(...paths)

将多个路径片段拼接成一个完整的路径,并自动处理路径分隔符(/\)。

1
2
3
4
const filePath = path.join("user", "documents", "file.txt");
console.log(filePath);
// 在 Windows 上: 'user\documents\file.txt'
// 在 Linux/macOS 上: 'user/documents/file.txt'

适用于:

  • 构建文件路径,适用于不同操作系统,自动处理路径分隔符。

(2) path.resolve(...paths)

返回绝对路径,类似 cd 命令解析路径的方式:

1
2
3
4
5
6
console.log(path.resolve("user", "docs", "file.txt"));
// 假设当前目录为 `/home/user`,则输出:
// '/home/user/user/docs/file.txt'

console.log(path.resolve("/user", "docs", "file.txt"));
// 由于 `/user` 是绝对路径,所以结果为 `/user/docs/file.txt`

适用于:

  • 生成绝对路径,避免路径不明确导致的错误。

(3) path.basename(path, ext?)

获取路径中的 文件名,可选地去掉扩展名。

1
2
3
4
const file = "/user/documents/file.txt";

console.log(path.basename(file)); // 'file.txt'
console.log(path.basename(file, ".txt")); // 'file'

适用于:

  • 获取文件名,比如 file.txt 而不是完整路径。

(4) path.dirname(path)

获取路径中的 目录名(不包含文件名)。

1
2
3
const file = "/user/documents/file.txt";

console.log(path.dirname(file)); // '/user/documents'

适用于:

  • 获取文件所在的目录路径

(5) path.extname(path)

获取文件的 扩展名,包括 . 号。

1
2
3
console.log(path.extname("file.txt")); // '.txt'
console.log(path.extname("index.html")); // '.html'
console.log(path.extname("README")); // ''

适用于:

  • 判断文件类型,如检查文件是否是 .jpg.json 等。

(6) path.parse(path)

解析路径,返回一个对象,包含路径的各个部分:

1
2
3
4
5
6
7
8
9
10
11
const parsed = path.parse("/user/docs/file.txt");
console.log(parsed);
/*
{
root: '/', // 根路径
dir: '/user/docs', // 目录路径
base: 'file.txt', // 文件名(包含扩展名)
ext: '.txt', // 文件扩展名
name: 'file' // 文件名(不包含扩展名)
}
*/

适用于:

  • 提取路径信息(目录、文件名、扩展名等)。

(7) path.format(pathObject)

path.parse() 相反,它将一个路径对象转换回路径字符串:

1
2
3
4
5
6
const formatted = path.format({
root: "/",
dir: "/user/docs",
base: "file.txt",
});
console.log(formatted); // '/user/docs/file.txt'

适用于:

  • 动态拼接路径

(8) path.isAbsolute(path)

判断路径是否为 绝对路径

1
2
3
console.log(path.isAbsolute("/user/docs/file.txt")); // true
console.log(path.isAbsolute("user/docs/file.txt")); // false
console.log(path.isAbsolute("C:\\user\\docs")); // true (Windows)

适用于:

  • 判断路径是否需要转换成绝对路径

(9) path.relative(from, to)

计算 fromto 之间的相对路径:

1
2
3
4
5
console.log(path.relative("/user/docs", "/user/docs/file.txt"));
// 'file.txt'

console.log(path.relative("/user/docs", "/user/images/photo.jpg"));
// '../images/photo.jpg'

适用于:

  • 构建相对路径(如从 docs 目录到 images 目录)。

(10) path.normalize(path)

规范化路径,处理 ...

1
2
console.log(path.normalize("/user/docs/../images/photo.jpg"));
// '/user/images/photo.jpg'

适用于:

  • 确保路径正确,避免 ... 影响路径解析。

(11) path.sep

返回当前系统的路径分隔符:

1
2
3
console.log(path.sep);
// Windows: '\'
// Linux/macOS: '/'

适用于:

  • 跨平台路径处理

(12) path.delimiter

返回环境变量分隔符:

1
console.log(process.env.PATH.split(path.delimiter));

适用于:

  • 解析 PATH 变量(Windows 使用 ;,Linux/macOS 使用 :)。

3. path 典型使用场景

✅ 读取当前脚本所在目录

1
2
const scriptDir = __dirname;
console.log("当前脚本所在目录:", scriptDir);

✅ 获取绝对路径

1
2
const absolutePath = path.resolve("data", "file.txt");
console.log(absolutePath);

✅ 读取文件扩展名

1
2
const ext = path.extname("data/file.txt");
console.log("文件扩展名:", ext);

✅ 动态拼接路径

1
2
const fullPath = path.join(__dirname, "data", "file.txt");
console.log("拼接路径:", fullPath);

✅ 解析路径

1
2
const fileInfo = path.parse("/user/docs/file.txt");
console.log(fileInfo.base); // 'file.txt'

✅ 计算两个路径之间的相对路径

1
2
const relativePath = path.relative("/user/docs", "/user/images/photo.jpg");
console.log("相对路径:", relativePath);

4. path vs. fs

功能 path 模块 fs 模块
处理路径字符串
读取/写入文件
获取文件信息
解析路径
操作文件

5. 总结

  • path 主要用于路径解析、拼接、转换,不会对文件系统进行读写操作。
  • fs 主要用于文件的读写、创建、删除,与 path 结合使用时非常强大。
  • path.join()path.resolve() 是最常用的方法,建议使用 path.join() 处理路径拼接,确保跨平台兼容性。

如果你在 Node.js 项目中需要处理文件路径,path 模块是必不可少的工具! 🚀

set

Set 是 JavaScript 中的一种 集合(Collection)数据结构,它允许你存储 唯一值,即不允许重复元素。它的主要作用是去重、集合运算(交集、并集、差集)等。


1. 创建 Set

(1) 空 Set

1
2
const mySet = new Set();
console.log(mySet); // Set(0) {}

创建一个空的 Set,可以用 .add() 方法添加元素。

(2) 从数组创建 Set(自动去重)

1
2
const mySet = new Set([1, 2, 3, 3, 4, 5, 5]);
console.log(mySet); // Set(5) {1, 2, 3, 4, 5}

Set 会自动去掉重复的 35


2. Set 的基本方法

(1) .add(value) - 添加元素

1
2
3
4
5
const mySet = new Set();
mySet.add(1);
mySet.add(2);
mySet.add(2); // 不会添加重复元素
console.log(mySet); // Set(2) {1, 2}

特点Set 只能存储 唯一值,重复的值会被自动忽略。


(2) .delete(value) - 删除元素

1
2
mySet.delete(2);
console.log(mySet); // Set(1) {1}

返回值:如果删除成功,返回 true,否则返回 false


(3) .has(value) - 检查是否存在

1
2
console.log(mySet.has(1)); // true
console.log(mySet.has(3)); // false

适用于: 快速查找某个值是否存在,比数组 .includes() 更高效。


(4) .clear() - 清空 Set

1
2
mySet.clear();
console.log(mySet); // Set(0) {}

作用:清空所有元素。


(5) .size - 获取元素个数

1
console.log(mySet.size); // 2

**区别于数组 length**,Set 使用 .size 而不是 .length


3. 遍历 Set

Set 是可迭代对象,可以使用 forEach()for...of、解构等方式遍历。

(1) forEach() 遍历

1
2
const mySet = new Set(["apple", "banana", "cherry"]);
mySet.forEach((value) => console.log(value));

(2) for...of 遍历

1
2
3
for (let item of mySet) {
console.log(item);
}

(3) 使用 Array.from() 转数组

1
2
const myArray = Array.from(mySet);
console.log(myArray); // ['apple', 'banana', 'cherry']

4. Set 与数组的转换

(1) Set → 数组

1
2
3
const mySet = new Set([1, 2, 3]);
const myArray = [...mySet];
console.log(myArray); // [1, 2, 3]

(2) 数组去重

1
2
3
const numbers = [1, 2, 2, 3, 4, 4, 5];
const uniqueNumbers = [...new Set(numbers)];
console.log(uniqueNumbers); // [1, 2, 3, 4, 5]

(3) 数组 → Set

1
2
const uniqueSet = new Set(numbers);
console.log(uniqueSet); // Set(5) {1, 2, 3, 4, 5}

5. Set 进行集合运算

(1) 并集(Set 合并)

1
2
3
4
5
const setA = new Set([1, 2, 3]);
const setB = new Set([3, 4, 5]);

const unionSet = new Set([...setA, ...setB]);
console.log(unionSet); // Set(5) {1, 2, 3, 4, 5}

(2) 交集(获取共有的值)

1
2
const intersectionSet = new Set([...setA].filter((x) => setB.has(x)));
console.log(intersectionSet); // Set(1) {3}

(3) 差集(获取 A 有但 B 没有的值)

1
2
const differenceSet = new Set([...setA].filter((x) => !setB.has(x)));
console.log(differenceSet); // Set(2) {1, 2}

6. Set 适用场景

需求 推荐用 Set 还是数组? 理由
数组去重 Set Set 自动去重,代码简洁高效
快速查找值 Set .has(value) 速度比 .includes()
存储唯一值集合 Set 例如存储用户 ID、唯一标识符等
需要索引查找 数组 Set 没有索引,只能用迭代遍历

7. 总结

Set 的主要特点:

  1. 不会存储重复值(自动去重)。
  2. 可以快速判断某个值是否存在(比 Array.includes() 更快)。
  3. 支持遍历forEachfor...of)。
  4. 适用于数组去重、集合运算(并集、交集、差集)。
  5. 不能通过索引访问元素(不像数组 arr[0] 这样操作)。

常见用法

1
2
3
4
const uniqueValues = [...new Set([1, 2, 2, 3, 4, 4, 5])]; // 数组去重
const hasValue = mySet.has(3); // 快速查找值
const union = new Set([...setA, ...setB]); // 并集
const intersection = new Set([...setA].filter((x) => setB.has(x))); // 交集

💡 适用于数据集合管理,尤其是去重、查找和集合运算! 🚀

Map

Map 在 JavaScript 中的使用方法详解

Map 是 JavaScript 的 键值对(key-value)存储数据结构,类似于对象 {},但提供了更多的特性,如 键可以是任意类型元素有序获取性能更好 等。


1. 创建 Map

(1) 创建空 Map

1
2
const myMap = new Map();
console.log(myMap); // Map(0) {}

创建一个空的 Map,可以使用 .set() 方法添加键值对。

(2) 直接初始化 Map

1
2
3
4
5
6
7
8
const myMap = new Map([
["name", "Tom"],
["age", 25],
["job", "developer"],
]);

console.log(myMap);
// Map(3) { 'name' => 'Tom', 'age' => 25, 'job' => 'developer' }

适用于:快速创建 Map 并初始化键值对。


2. Map 的基本方法

(1) .set(key, value) - 添加或更新键值对

1
2
3
4
5
const myMap = new Map();
myMap.set("name", "Alice");
myMap.set("age", 30);
console.log(myMap);
// Map(2) { 'name' => 'Alice', 'age' => 30 }

适用于:存储数据并保持唯一性。


(2) .get(key) - 获取键对应的值

1
2
3
console.log(myMap.get("name")); // 'Alice'
console.log(myMap.get("age")); // 30
console.log(myMap.get("gender")); // undefined

✅ **比对象 {}obj[key] 更安全,不存在的键不会报错,只会返回 undefined


(3) .has(key) - 检查 Map 是否包含某个键

1
2
console.log(myMap.has("name")); // true
console.log(myMap.has("gender")); // false

✅ **比对象 obj.hasOwnProperty(key) 更直观。


(4) .delete(key) - 删除键值对

1
2
3
myMap.delete("age");
console.log(myMap);
// Map(1) { 'name' => 'Alice' }

删除特定的键值对,true 表示删除成功,false 表示不存在该键。


(5) .clear() - 清空 Map

1
2
myMap.clear();
console.log(myMap); // Map(0) {}

适用于: 彻底清空 Map


(6) .size - 获取 Map 的大小

1
console.log(myMap.size); // 2

适用于: 获取 Map 的键值对数量(比 Object.keys(obj).length 更高效)。


3. 遍历 Map

(1) forEach() 遍历

1
2
3
myMap.forEach((value, key) => {
console.log(`${key}: ${value}`);
});

适用于: 方便对 Map 进行迭代操作。


(2) for...of 遍历

1
2
3
for (let [key, value] of myMap) {
console.log(`${key}: ${value}`);
}

✅ **比 forEach() 语法更简洁,可用于解构赋值。


(3) keys() / values() / entries()

1
2
3
console.log([...myMap.keys()]); // ['name', 'age']
console.log([...myMap.values()]); // ['Alice', 30]
console.log([...myMap.entries()]); // [['name', 'Alice'], ['age', 30]]

适用于: 快速获取 Map 的键、值或键值对。


4. Map vs Object

特性 Map Object
键类型 任意类型(对象、数组、函数等) 只能是字符串或 Symbol
键的顺序 按插入顺序存储 无序(ES6 之后 Object 也是有序的)
获取大小 size Object.keys(obj).length
迭代方式 forEach()for...of for...in(需 hasOwnProperty 过滤)
性能 适用于大量数据存取 适用于小型数据结构

5. Map 实际应用场景

(1) 统计字符串中字符出现次数

1
2
3
4
5
6
7
8
9
10
11
12
function countChars(str) {
const charMap = new Map();

for (const char of str) {
charMap.set(char, (charMap.get(char) || 0) + 1);
}

return charMap;
}

console.log(countChars("hello world"));
// Map(8) { 'h' => 1, 'e' => 1, 'l' => 3, 'o' => 2, ' ' => 1, 'w' => 1, 'r' => 1, 'd' => 1 }

比对象 {} 更直观,且不受原型链干扰。


(2) 记住对象的状态

1
2
3
4
5
6
7
8
const user1 = { name: "Alice" };
const user2 = { name: "Bob" };

const userStatus = new Map();
userStatus.set(user1, "online");
userStatus.set(user2, "offline");

console.log(userStatus.get(user1)); // 'online'

比对象 {} 更适合存储对象作为键的数据。


(3) Map 实现 LRU 缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class LRUCache {
constructor(limit) {
this.cache = new Map();
this.limit = limit;
}

get(key) {
if (!this.cache.has(key)) return -1;

const value = this.cache.get(key);
this.cache.delete(key);
this.cache.set(key, value); // 使其变为最新
return value;
}

put(key, value) {
if (this.cache.has(key)) this.cache.delete(key);
else if (this.cache.size >= this.limit)
this.cache.delete(this.cache.keys().next().value); // 删除最早的键

this.cache.set(key, value);
}
}

const cache = new LRUCache(3);
cache.put(1, "one");
cache.put(2, "two");
cache.put(3, "three");
cache.get(2); // 访问 2,使其变为最新
cache.put(4, "four"); // 插入 4,最早的 1 被删除

console.log([...cache.cache.keys()]); // [3, 2, 4]

比数组或对象更适用于 LRU(最近最少使用)缓存。


6. MapSet 对比

特性 Map Set
存储结构 键值对(key-value) 值的集合(无重复)
访问方式 .get(key) .has(value)
迭代方式 .keys() .values() .entries() .values()
适用场景 关联数据(缓存、映射、计数) 唯一值集合(去重、集合运算)

7. 总结

Map 适用于存储

  • 唯一键值对(不重复)
  • 键可以是任何类型(对象、数组、函数)
  • 需要有序存储数据
  • 需要高效读取和删除数据

常见用法

1
2
3
4
5
6
const map = new Map();
map.set("name", "Alice");
console.log(map.get("name")); // 'Alice'
console.log(map.has("name")); // true
map.delete("name");
console.log(map.size); // 0

💡 Map 是对象 {} 的增强版,适用于需要高效存储键值对的场景!🚀

媒体查询

CSS 媒体查询详细使用方法总结

CSS 媒体查询(Media Query)用于根据设备的特性(如屏幕宽度、高度、分辨率、方向等)应用不同的样式,实现响应式设计,使网页适配不同的设备,如手机、平板、桌面端等。


1. 媒体查询基本语法

1
2
3
@media [media-type] and (media-feature) {
/* CSS 样式 */
}
  • media-type(媒体类型,可选)
    • all(默认):适用于所有设备
    • screen:屏幕设备(电脑、手机、平板等)
    • print:打印设备
    • speech:语音阅读器
  • media-feature(媒体特性)
    • width / height:指定视口的宽度/高度
    • min-width / max-width:最小/最大宽度(用于响应式设计)
    • orientation:设备方向(portrait 竖屏 / landscape 横屏)
    • aspect-ratio:屏幕宽高比
    • resolution:屏幕分辨率(min-resolution: 2dppx 适用于 Retina 屏)
    • hover:是否支持悬停(hover: hover 适用于鼠标设备)
    • pointer:指针精度(pointer: coarse 适用于触摸屏)

2. 常见媒体查询使用示例

(1)适配不同屏幕宽度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 小屏幕(手机端) */
@media screen and (max-width: 600px) {
body {
background-color: lightgray;
}
}

/* 平板端 */
@media screen and (min-width: 601px) and (max-width: 1024px) {
body {
background-color: lightblue;
}
}

/* 电脑端 */
@media screen and (min-width: 1025px) {
body {
background-color: white;
}
}

(2)适配设备方向

1
2
3
4
5
6
7
8
9
10
11
12
13
/* 竖屏(portrait)模式 */
@media screen and (orientation: portrait) {
.container {
width: 90%;
}
}

/* 横屏(landscape)模式 */
@media screen and (orientation: landscape) {
.container {
width: 80%;
}
}

(3)高分辨率(Retina 屏幕)适配

1
2
3
4
5
6
/* 适用于高分辨率屏幕,如 Retina 设备 */
@media screen and (min-resolution: 2dppx) {
.logo {
background-image: url("logo@2x.png");
}
}

(4)判断是否支持悬停(鼠标)

1
2
3
4
5
6
7
8
9
10
11
12
13
/* 仅当设备支持鼠标悬停时生效 */
@media (hover: hover) {
.button:hover {
background-color: red;
}
}

/* 适用于触摸屏(不支持悬停) */
@media (hover: none) {
.button {
background-color: blue;
}
}

(5)组合多个条件

1
2
3
4
5
6
/* 屏幕宽度在 600px 到 1200px 之间,且为横屏 */
@media screen and (min-width: 600px) and (max-width: 1200px) and (orientation: landscape) {
.sidebar {
display: none;
}
}

3. 响应式设计策略

  1. 移动优先(Mobile First)

    • 先为小屏幕(手机)编写 CSS 样式,再使用 min-width 媒体查询为更大屏幕(平板、电脑)添加适配规则。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    body {
    font-size: 14px; /* 默认小屏幕 */
    }

    @media screen and (min-width: 768px) {
    body {
    font-size: 16px;
    }
    }

    @media screen and (min-width: 1024px) {
    body {
    font-size: 18px;
    }
    }
  2. 桌面优先(Desktop First)

    • 先为大屏幕(桌面端)编写 CSS 样式,再使用 max-width 媒体查询调整小屏幕(平板、手机)。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    body {
    font-size: 18px; /* 默认大屏幕 */
    }

    @media screen and (max-width: 1024px) {
    body {
    font-size: 16px;
    }
    }

    @media screen and (max-width: 768px) {
    body {
    font-size: 14px;
    }
    }

4. 媒体查询的优化建议

避免过多的断点:只针对关键的屏幕尺寸设置断点,例如:

  • 移动端(≤ 600px)
  • 平板端(601px - 1024px)
  • 桌面端(≥ 1025px)

优先使用 emrem 作为单位:让页面更具伸缩性,不依赖固定像素值。

使用 flexboxgrid 结合媒体查询:提高布局适配能力。

✅ **避免 !important**:会影响后续样式的覆盖。

测试不同设备和浏览器:使用 Chrome DevTools(F12 → 设备模式)或在线工具(如 Responsive Design Checker)检查适配情况。


5. 结论

  • CSS 媒体查询是一种强大的技术,可以根据不同设备特性动态调整样式,实现响应式设计。
  • 基本语法@media media-type and (media-feature) { /* 样式 */ }
  • 常用特性widthmin-widthmax-widthorientationresolutionhover
  • 推荐策略:采用 移动优先(Mobile First)进行开发,更有利于适配不同设备。
  • 优化建议:合理选择断点、使用 em/rem、结合 flexbox/grid,提升适配能力。

这样,你就可以轻松掌握 CSS 媒体查询并灵活运用于项目中啦!🚀 如果你有更具体的需求,比如如何与 Vue.jsTailwind CSS 结合使用,也可以告诉我哦~ 😃

axios

axios 的配置对象可以用于 axios.getaxios.postaxios.create,下面是详细的写法:


1. 基本的 axios 配置对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const axios = require("axios");

const config = {
method: "get", // 请求方法 (get, post, put, delete)
url: "https://api.example.com/data", // 请求地址
baseURL: "https://api.example.com", // 基础URL,会与 `url` 进行拼接
params: { userId: 123 }, // GET 请求参数(会拼接到 URL)
data: { name: "John", age: 30 }, // POST/PUT 请求体
headers: {
"Content-Type": "application/json",
Authorization: "Bearer your_token_here",
},
timeout: 5000, // 请求超时时间 (ms)
responseType: "json", // 响应数据格式 (json, blob, text, document, arraybuffer, stream)
withCredentials: false, // 是否允许携带跨域 Cookie
validateStatus: function (status) {
return status >= 200 && status < 300; // 自定义成功的 HTTP 状态码
},
};

axios(config)
.then((response) => console.log(response.data))
.catch((error) => console.error(error));

2. axios.create 配置全局实例

如果你要创建一个 默认配置的 axios 实例,可以使用 axios.create

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const apiClient = axios.create({
baseURL: "https://api.example.com",
timeout: 5000,
headers: {
"Content-Type": "application/json",
Authorization: "Bearer your_token_here",
},
});

// 使用自定义实例
apiClient.get("/user/123").then((response) => console.log(response.data));
apiClient
.post("/user", { name: "John" })
.then((response) => console.log(response.data));

3. 拦截器(请求和响应)

你可以用 interceptors 在请求或响应前后进行处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const api = axios.create({
baseURL: "https://api.example.com",
timeout: 5000,
});

// 请求拦截器
api.interceptors.request.use(
(config) => {
console.log("请求拦截器:", config);
config.headers.Authorization = "Bearer your_token_here"; // 自动添加 token
return config;
},
(error) => Promise.reject(error)
);

// 响应拦截器
api.interceptors.response.use(
(response) => {
console.log("响应拦截器:", response);
return response.data; // 直接返回数据,减少调用时的 `.data`
},
(error) => {
console.error("响应错误:", error);
return Promise.reject(error);
}
);

// 调用 API
api.get("/user/123").then(console.log).catch(console.error);

4. 发送 FormData

如果要发送 multipart/form-data(比如文件上传),可以这样:

1
2
3
4
5
6
const formData = new FormData();
formData.append("file", fileInput.files[0]); // 假设有一个 <input type="file" />

axios.post("https://api.example.com/upload", formData, {
headers: { "Content-Type": "multipart/form-data" },
});

5. 取消请求

可以用 AbortControllerCancelToken 取消请求:

1
2
3
4
5
6
7
8
const controller = new AbortController();

axios
.get("https://api.example.com/data", { signal: controller.signal })
.catch((err) => console.log("请求取消", err));

// 取消请求
controller.abort();

总结

  • axios(config) 适用于单个请求。
  • axios.create(config) 适用于全局默认配置。
  • interceptors 适用于 拦截请求或响应
  • FormData 适用于 上传文件
  • AbortController 适用于 取消请求

这样你可以根据不同需求灵活使用 axios。 🚀

fetch

fetch 是现代 JavaScript 用于进行 HTTP 请求的 API,相比 XMLHttpRequest 更加简洁、灵活,并且基于 Promise 进行异步操作。下面详细介绍 fetch 的用法,包括基础用法、请求参数、错误处理以及 async/await 方式等。


1. 基本用法

1
2
3
4
fetch("https://jsonplaceholder.typicode.com/posts/1")
.then((response) => response.json()) // 解析 JSON
.then((data) => console.log(data)) // 处理数据
.catch((error) => console.error("请求失败:", error)); // 处理错误

解析

  1. fetch(url) 返回一个 Promise,解析后返回 Response 对象。
  2. response.json() 解析 JSON 数据(也是 Promise)。
  3. .then(data => ...) 处理解析后的数据。
  4. .catch(error => ...) 捕获请求失败(如网络错误)。

2. 使用 async/await

1
2
3
4
5
6
7
8
9
10
11
12
13
14
async function getData() {
try {
let response = await fetch("https://jsonplaceholder.typicode.com/posts/1");
if (!response.ok) {
throw new Error(`请求失败,状态码:${response.status}`);
}
let data = await response.json();
console.log(data);
} catch (error) {
console.error("请求错误:", error.message);
}
}

getData();

解析

  • await fetch(url) 获取 Response 对象。
  • 检查 response.ok(状态码是否 200-299)。
  • await response.json() 解析 JSON 数据。
  • try-catch 捕获异常(如网络错误、JSON 解析错误)。

3. GET 请求(带参数)

1
2
3
4
5
let params = new URLSearchParams({ userId: 1 });

fetch(`https://jsonplaceholder.typicode.com/posts?${params}`)
.then((response) => response.json())
.then((data) => console.log(data));

解析

  • URLSearchParams 用于构造查询字符串,如 ?userId=1
  • fetch(url + '?' + params) 发送带参数的 GET 请求。

4. POST 请求(提交 JSON 数据)

1
2
3
4
5
6
7
8
9
10
11
12
13
fetch("https://jsonplaceholder.typicode.com/posts", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
title: "测试文章",
body: "这是内容",
userId: 1,
}),
})
.then((response) => response.json())
.then((data) => console.log("创建成功:", data));

解析

  • method: "POST" 指定请求方法。
  • headers 设定 Content-Typeapplication/json
  • body: JSON.stringify({...}) 发送 JSON 数据。

5. PUT 请求(更新数据)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fetch("https://jsonplaceholder.typicode.com/posts/1", {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
id: 1,
title: "更新后的标题",
body: "更新的内容",
userId: 1,
}),
})
.then((response) => response.json())
.then((data) => console.log("更新成功:", data));

解析

  • PUT 用于更新资源(通常需要提供 id)。
  • 需要完整的对象数据,否则可能会丢失字段。

6. PATCH 请求(部分更新数据)

1
2
3
4
5
6
7
8
9
10
11
fetch("https://jsonplaceholder.typicode.com/posts/1", {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
title: "仅更新标题",
}),
})
.then((response) => response.json())
.then((data) => console.log("部分更新成功:", data));

解析

  • PATCH 只更新提供的字段,而不是整个对象。

7. DELETE 请求

1
2
3
4
5
6
7
8
9
10
11
fetch("https://jsonplaceholder.typicode.com/posts/1", {
method: "DELETE",
})
.then((response) => {
if (response.ok) {
console.log("删除成功");
} else {
throw new Error("删除失败");
}
})
.catch((error) => console.error("错误:", error));

解析

  • DELETE 方法通常不需要 body
  • 服务器返回 200204 代表删除成功。

8. 超时处理

fetch 本身不支持超时,但可以使用 AbortController 来实现:

1
2
3
4
5
6
7
8
9
const controller = new AbortController();
const signal = controller.signal;

setTimeout(() => controller.abort(), 5000); // 5 秒超时

fetch("https://jsonplaceholder.typicode.com/posts", { signal })
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.error("请求超时或失败:", error));

解析

  • AbortController 允许中止 fetch 请求。
  • 5 秒后 controller.abort() 触发请求中止。

9. 处理二进制数据(如图片/文件)

1
2
3
4
5
6
fetch("https://via.placeholder.com/150")
.then((response) => response.blob()) // 获取 Blob(二进制对象)
.then((blob) => {
let imgURL = URL.createObjectURL(blob);
document.body.innerHTML += `<img src="${imgURL}">`;
});

解析

  • response.blob() 处理图片/文件下载。

10. 并行多个请求

1
2
3
4
5
6
7
8
9
10
Promise.all([
fetch("https://jsonplaceholder.typicode.com/posts/1").then((res) =>
res.json()
),
fetch("https://jsonplaceholder.typicode.com/posts/2").then((res) =>
res.json()
),
]).then((results) => {
console.log("两个请求的数据:", results);
});

解析

  • Promise.all([...]) 并行执行多个 fetch 请求,等所有请求完成后返回结果。

11. 使用 fetch 进行跨域请求

JSONP(如果服务器支持)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function jsonpRequest(url, callbackName) {
return new Promise((resolve, reject) => {
const script = document.createElement("script");
const callback = `jsonpCallback_${Date.now()}`;

window[callback] = function (data) {
resolve(data);
document.body.removeChild(script);
delete window[callback];
};

script.src = `${url}?callback=${callback}`;
script.onerror = reject;
document.body.appendChild(script);
});
}

jsonpRequest("https://example.com/data", "callback")
.then((data) => console.log("JSONP 数据:", data))
.catch((error) => console.error("JSONP 失败:", error));

CORS 方案

如果是 跨域 请求,后端需要支持 Access-Control-Allow-Origin


总结

方法 作用 说明
GET 获取数据 可带查询参数
POST 提交新数据 body 发送 JSON
PUT 更新整个资源 需提供完整数据
PATCH 局部更新资源 只修改部分字段
DELETE 删除资源 一般不带 body
.json() 解析 JSON 响应 Promise 方式解析
.text() 解析文本响应 适用于 HTML 或纯文本
.blob() 解析二进制数据 适用于图片/文件

现在你可以灵活使用 fetch 进行各种 HTTP 请求了!🚀

bind&call&apply

bindcallapply 都是用于改变 this 指向的方法,它们的主要区别如下:


1. call

语法:

1
func.call(thisArg, arg1, arg2, ...)

特点:

  • 立即调用函数
  • 参数直接传递
  • thisArg 指定 this 指向

示例:

1
2
3
4
5
6
function sayHello(greeting) {
console.log(greeting + ", " + this.name);
}

const person = { name: "Alice" };
sayHello.call(person, "Hello"); // 输出: Hello, Alice

2. apply

语法:

1
func.apply(thisArg, [arg1, arg2, ...])

特点:

  • 立即调用函数
  • 参数必须以数组形式传递
  • thisArg 指定 this 指向

示例:

1
2
3
4
5
6
function sum(a, b) {
console.log(this.value + a + b);
}

const obj = { value: 10 };
sum.apply(obj, [2, 3]); // 输出: 15

apply 适用于参数数量不确定的场景,例如:

1
2
const numbers = [1, 2, 3, 4, 5];
console.log(Math.max.apply(null, numbers)); // 输出: 5

3. bind

语法:

1
const newFunc = func.bind(thisArg, arg1, arg2, ...)

特点:

  • 不会立即调用,而是返回一个新的函数
  • 参数可以预先传入
  • thisArg 指定 this 指向

示例:

1
2
3
4
5
6
7
function greet(greeting) {
console.log(greeting + ", " + this.name);
}

const user = { name: "Bob" };
const sayHi = greet.bind(user, "Hi");
sayHi(); // 输出: Hi, Bob

对比总结

方法 是否立即执行 参数传递方式 是否返回新函数
call 逗号分隔传递参数
apply 数组传递参数
bind 逗号分隔传递参数

什么时候用?

  • call立即调用,适用于改变 this 并传递参数的情况。
  • apply立即调用,适用于参数以数组形式传递的情况(如 Math.max.apply(null, arr))。
  • bind返回新函数,适用于需要稍后执行,或部分参数预设的情况。