[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$f5CJCRgyac7GJ3vLkV6gVnKGUxV2FI5mUYulbninDFyE":3,"donations-sidebar":9,"posts-{\"page\":1,\"pageSize\":10,\"categoryId\":\"d10456a5-e649-4741-a38f-f07f266ce5f2\"}":10,"sidebar-data":164},{"id":4,"name":5,"slug":5,"description":5,"sortOrder":6,"createdAt":7,"updatedAt":8},"d10456a5-e649-4741-a38f-f07f266ce5f2","开发环境",3,"2022-05-24T01:52:41.000Z","2023-02-08T02:49:14.000Z",[],{"list":11,"total":161,"page":162,"pageSize":163},[12,32,48,61,76,90,106,119,132,148],{"id":13,"title":14,"slug":15,"content":16,"excerpt":14,"coverImage":17,"status":18,"isPinned":19,"pinnedAt":20,"viewCount":21,"publishedAt":22,"createdAt":22,"updatedAt":23,"category":24,"author":25,"tags":28},"a4e1d5e8-65a8-4e34-bafc-415f013cc83e","Java 21 零基础入门教程","java-21-零基础入门教程","# Java 21 零基础入门教程\n\n> 本文档提供完整的 Java 21 入门教程，从环境搭建到面向对象编程，包含最新的 Java 21 特性。\n\n## 目录\n\n- [第一部分：Java 简介与环境搭建](#第一部分java-简介与环境搭建)\n- [第二部分：第一个 Java 程序](#第二部分第一个-java-程序)\n- [第三部分：Java 基础语法](#第三部分java-基础语法)\n- [第四部分：流程控制](#第四部分流程控制)\n- [第五部分：数组](#第五部分数组)\n- [第六部分：方法（函数）](#第六部分方法函数)\n- [第七部分：面向对象编程](#第七部分面向对象编程)\n- [第八部分：Java 21 新特性](#第八部分java-21-新特性)\n- [第九部分：异常处理](#第九部分异常处理)\n- [第十部分：集合框架](#第十部分集合框架)\n- [学习建议](#学习建议)\n- [下一步学习方向](#下一步学习方向)\n\n## 第一部分：Java 简介与环境搭建\n\n### 1.1 什么是 Java？\n\nJava 是一种广泛使用的编程语言，具有以下特点：\n\n- **跨平台**：一次编写，到处运行\n- **面向对象**：支持封装、继承、多态\n- **简单易学**：语法类似 C++，但更简洁\n- **开源免费**：拥有强大的社区支持\n\n### 1.2 安装 Java 开发环境\n\n#### 下载 JDK 21\n\n1. 访问 [Oracle 官网](https:\u002F\u002Fwww.oracle.com\u002Fjava\u002Ftechnologies\u002Fjavase-jdk21-downloads.html)\n2. 选择对应操作系统的 JDK 21\n3. 下载并安装\n\n#### 配置环境变量\n\n**Windows**：\n\n```bash\n# 添加系统变量\nJAVA_HOME = C:\\Program Files\\Java\\jdk-21\nPATH = %JAVA_HOME%\\bin;...\n```\n\n**Mac\u002FLinux**：\n\n```bash\n# 编辑 ~\u002F.bashrc 或 ~\u002F.zshrc\nexport JAVA_HOME=\u002FLibrary\u002FJava\u002FJavaVirtualMachines\u002Fjdk-21.jdk\u002FContents\u002FHome\nexport PATH=$JAVA_HOME\u002Fbin:$PATH\n```\n\n#### 验证安装\n\n打开终端\u002F命令提示符，输入：\n\n```bash\njava -version\njavac -version\n```\n\n## 第二部分：第一个 Java 程序\n\n### 2.1 Hello World 程序\n\n创建文件 `HelloWorld.java`：\n\n```java\npublic class HelloWorld {\n    public static void main(String[] args) {\n        System.out.println(\\\"Hello, World!\\\");\n    }\n}\n```\n\n编译和运行：\n\n```bash\njavac HelloWorld.java    # 编译\njava HelloWorld          # 运行\n```\n\n### 2.2 程序结构解析\n\n- `public class HelloWorld`：定义类，类名必须与文件名相同\n- `public static void main(String[] args)`：程序入口点\n- `System.out.println()`：输出语句\n\n## 第三部分：Java 基础语法\n\n### 3.1 变量和数据类型\n\n```java\npublic class Variables {\n    public static void main(String[] args) {\n        \u002F\u002F 基本数据类型\n        int age = 25;                    \u002F\u002F 整数\n        double price = 19.99;            \u002F\u002F 浮点数\n        char grade = 'A';                \u002F\u002F 字符\n        boolean isJavaFun = true;        \u002F\u002F 布尔值\n        String name = \\\"张三\\\";            \u002F\u002F 字符串\n        \n        \u002F\u002F 输出变量\n        System.out.println(\\\"姓名: \\\" + name);\n        System.out.println(\\\"年龄: \\\" + age);\n        System.out.println(\\\"价格: \\\" + price);\n    }\n}\n```\n\n### 3.2 数据类型详解\n\n| 类型 | 大小 | 范围 | 示例 |\n|------|------|------|------|\n| byte | 8位 | -128 ~ 127 | `byte b = 100;` |\n| short | 16位 | -32768 ~ 32767 | `short s = 1000;` |\n| int | 32位 | -2³¹ ~ 2³¹-1 | `int i = 100000;` |\n| long | 64位 | -2⁶³ ~ 2⁶³-1 | `long l = 100000L;` |\n| float | 32位 | 单精度浮点数 | `float f = 3.14f;` |\n| double | 64位 | 双精度浮点数 | `double d = 3.14;` |\n| char | 16位 | Unicode字符 | `char c = 'A';` |\n| boolean | 1位 | true\u002Ffalse | `boolean flag = true;` |\n\n### 3.3 运算符\n\n```java\npublic class Operators {\n    public static void main(String[] args) {\n        int a = 10, b = 3;\n        \n        \u002F\u002F 算术运算符\n        System.out.println(\\\"a + b = \\\" + (a + b));  \u002F\u002F 13\n        System.out.println(\\\"a - b = \\\" + (a - b));  \u002F\u002F 7\n        System.out.println(\\\"a * b = \\\" + (a * b));  \u002F\u002F 30\n        System.out.println(\\\"a \u002F b = \\\" + (a \u002F b));  \u002F\u002F 3\n        System.out.println(\\\"a % b = \\\" + (a % b));  \u002F\u002F 1\n        \n        \u002F\u002F 比较运算符\n        System.out.println(\\\"a == b: \\\" + (a == b)); \u002F\u002F false\n        System.out.println(\\\"a > b: \\\" + (a > b));   \u002F\u002F true\n        \n        \u002F\u002F 逻辑运算符\n        boolean x = true, y = false;\n        System.out.println(\\\"x && y: \\\" + (x && y)); \u002F\u002F false\n        System.out.println(\\\"x || y: \\\" + (x || y)); \u002F\u002F true\n        System.out.println(\\\"!x: \\\" + (!x));         \u002F\u002F false\n    }\n}\n```\n\n## 第四部分：流程控制\n\n### 4.1 条件语句\n\n```java\npublic class Conditionals {\n    public static void main(String[] args) {\n        int score = 85;\n        \n        \u002F\u002F if-else 语句\n        if (score >= 90) {\n            System.out.println(\\\"优秀\\\");\n        } else if (score >= 80) {\n            System.out.println(\\\"良好\\\");\n        } else if (score >= 60) {\n            System.out.println(\\\"及格\\\");\n        } else {\n            System.out.println(\\\"不及格\\\");\n        }\n        \n        \u002F\u002F switch 语句 (Java 14+ 新语法)\n        String grade = switch (score \u002F 10) {\n            case 10, 9 -> \\\"A\\\";\n            case 8 -> \\\"B\\\";\n            case 7 -> \\\"C\\\";\n            case 6 -> \\\"D\\\";\n            default -> \\\"F\\\";\n        };\n        System.out.println(\\\"等级: \\\" + grade);\n    }\n}\n```\n\n### 4.2 循环语句\n\n```java\npublic class Loops {\n    public static void main(String[] args) {\n        \u002F\u002F for 循环\n        System.out.println(\\\"for 循环:\\\");\n        for (int i = 1; i \u003C= 5; i++) {\n            System.out.println(\\\"数字: \\\" + i);\n        }\n        \n        \u002F\u002F while 循环\n        System.out.println(\\\"\\\nwhile 循环:\\\");\n        int j = 1;\n        while (j \u003C= 3) {\n            System.out.println(\\\"计数: \\\" + j);\n            j++;\n        }\n        \n        \u002F\u002F do-while 循环\n        System.out.println(\\\"\\\ndo-while 循环:\\\");\n        int k = 1;\n        do {\n            System.out.println(\\\"值: \\\" + k);\n            k++;\n        } while (k \u003C= 3);\n        \n        \u002F\u002F 增强 for 循环\n        System.out.println(\\\"\\\n增强 for 循环:\\\");\n        int[] numbers = {1, 2, 3, 4, 5};\n        for (int num : numbers) {\n            System.out.println(\\\"数组元素: \\\" + num);\n        }\n    }\n}\n```\n\n## 第五部分：数组\n\n### 5.1 数组的基本操作\n\n```java\npublic class ArraysDemo {\n    public static void main(String[] args) {\n        \u002F\u002F 数组声明和初始化\n        int[] numbers = new int[5];           \u002F\u002F 长度为5的数组\n        int[] scores = {90, 85, 78, 92, 88}; \u002F\u002F 直接初始化\n        \n        \u002F\u002F 访问和修改数组元素\n        numbers[0] = 10;\n        numbers[1] = 20;\n        \n        System.out.println(\\\"第一个分数: \\\" + scores[0]);\n        System.out.println(\\\"数组长度: \\\" + scores.length);\n        \n        \u002F\u002F 遍历数组\n        System.out.println(\\\"所有分数:\\\");\n        for (int i = 0; i \u003C scores.length; i++) {\n            System.out.println(\\\"分数 \\\" + i + \\\": \\\" + scores[i]);\n        }\n        \n        \u002F\u002F 使用增强 for 循环\n        System.out.println(\\\"使用增强 for 循环:\\\");\n        for (int score : scores) {\n            System.out.println(score);\n        }\n    }\n}\n```\n\n### 5.2 多维数组\n\n```java\npublic class MultiDimArray {\n    public static void main(String[] args) {\n        \u002F\u002F 二维数组\n        int[][] matrix = {\n            {1, 2, 3},\n            {4, 5, 6},\n            {7, 8, 9}\n        };\n        \n        \u002F\u002F 遍历二维数组\n        for (int i = 0; i \u003C matrix.length; i++) {\n            for (int j = 0; j \u003C matrix[i].length; j++) {\n                System.out.print(matrix[i][j] + \\\" \\\");\n            }\n            System.out.println();\n        }\n    }\n}\n```\n\n## 第六部分：方法（函数）\n\n### 6.1 方法的定义和使用\n\n```java\npublic class Methods {\n    \n    \u002F\u002F 无返回值的方法\n    public static void greet(String name) {\n        System.out.println(\\\"你好, \\\" + name + \\\"!\\\");\n    }\n    \n    \u002F\u002F 有返回值的方法\n    public static int add(int a, int b) {\n        return a + b;\n    }\n    \n    \u002F\u002F 方法重载\n    public static double add(double a, double b) {\n        return a + b;\n    }\n    \n    public static void main(String[] args) {\n        \u002F\u002F 调用方法\n        greet(\\\"世界\\\");\n        \n        int sum1 = add(5, 3);\n        double sum2 = add(2.5, 3.7);\n        \n        System.out.println(\\\"整数和: \\\" + sum1);\n        System.out.println(\\\"浮点数和: \\\" + sum2);\n        \n        \u002F\u002F 递归方法示例\n        int factorial = factorial(5);\n        System.out.println(\\\"5的阶乘: \\\" + factorial);\n    }\n    \n    \u002F\u002F 递归方法\n    public static int factorial(int n) {\n        if (n == 0 || n == 1) {\n            return 1;\n        }\n        return n * factorial(n - 1);\n    }\n}\n```\n\n## 第七部分：面向对象编程\n\n### 7.1 类和对象\n\n```java\n\u002F\u002F 定义类\nclass Student {\n    \u002F\u002F 属性（字段）\n    String name;\n    int age;\n    String studentId;\n    \n    \u002F\u002F 构造方法\n    public Student(String name, int age, String studentId) {\n        this.name = name;\n        this.age = age;\n        this.studentId = studentId;\n    }\n    \n    \u002F\u002F 方法\n    public void study() {\n        System.out.println(name + \\\"正在学习...\\\");\n    }\n    \n    public void displayInfo() {\n        System.out.println(\\\"学生信息:\\\");\n        System.out.println(\\\"姓名: \\\" + name);\n        System.out.println(\\\"年龄: \\\" + age);\n        System.out.println(\\\"学号: \\\" + studentId);\n    }\n}\n\npublic class OOPDemo {\n    public static void main(String[] args) {\n        \u002F\u002F 创建对象\n        Student student1 = new Student(\\\"张三\\\", 20, \\\"2023001\\\");\n        Student student2 = new Student(\\\"李四\\\", 19, \\\"2023002\\\");\n        \n        \u002F\u002F 使用对象\n        student1.displayInfo();\n        student1.study();\n        \n        student2.displayInfo();\n        student2.study();\n    }\n}\n```\n\n### 7.2 封装\n\n```java\nclass BankAccount {\n    \u002F\u002F 私有字段（封装）\n    private String accountNumber;\n    private double balance;\n    private String owner;\n    \n    \u002F\u002F 构造方法\n    public BankAccount(String accountNumber, String owner, double initialBalance) {\n        this.accountNumber = accountNumber;\n        this.owner = owner;\n        this.balance = initialBalance;\n    }\n    \n    \u002F\u002F Getter 方法\n    public String getAccountNumber() {\n        return accountNumber;\n    }\n    \n    public double getBalance() {\n        return balance;\n    }\n    \n    public String getOwner() {\n        return owner;\n    }\n    \n    \u002F\u002F 存款方法\n    public void deposit(double amount) {\n        if (amount > 0) {\n            balance += amount;\n            System.out.println(\\\"存款成功! 当前余额: \\\" + balance);\n        } else {\n            System.out.println(\\\"存款金额必须大于0\\\");\n        }\n    }\n    \n    \u002F\u002F 取款方法\n    public void withdraw(double amount) {\n        if (amount > 0 && amount \u003C= balance) {\n            balance -= amount;\n            System.out.println(\\\"取款成功! 当前余额: \\\" + balance);\n        } else {\n            System.out.println(\\\"取款失败: 余额不足或金额无效\\\");\n        }\n    }\n}\n\npublic class EncapsulationDemo {\n    public static void main(String[] args) {\n        BankAccount account = new BankAccount(\\\"123456\\\", \\\"张三\\\", 1000);\n        \n        account.deposit(500);\n        account.withdraw(200);\n        account.withdraw(2000); \u002F\u002F 应该失败\n        \n        System.out.println(\\\"最终余额: \\\" + account.getBalance());\n    }\n}\n```\n\n### 7.3 继承\n\n```java\n\u002F\u002F 父类\nclass Animal {\n    String name;\n    int age;\n    \n    public Animal(String name, int age) {\n        this.name = name;\n        this.age = age;\n    }\n    \n    public void eat() {\n        System.out.println(name + \\\"正在吃东西\\\");\n    }\n    \n    public void sleep() {\n        System.out.println(name + \\\"正在睡觉\\\");\n    }\n}\n\n\u002F\u002F 子类\nclass Dog extends Animal {\n    String breed;\n    \n    public Dog(String name, int age, String breed) {\n        super(name, age); \u002F\u002F 调用父类构造方法\n        this.breed = breed;\n    }\n    \n    \u002F\u002F 方法重写\n    @Override\n    public void eat() {\n        System.out.println(name + \\\"正在吃狗粮\\\");\n    }\n    \n    \u002F\u002F 子类特有方法\n    public void bark() {\n        System.out.println(name + \\\"在汪汪叫\\\");\n    }\n}\n\npublic class InheritanceDemo {\n    public static void main(String[] args) {\n        Dog dog = new Dog(\\\"旺财\\\", 3, \\\"金毛\\\");\n        dog.eat();      \u002F\u002F 调用重写的方法\n        dog.sleep();    \u002F\u002F 调用继承的方法\n        dog.bark();     \u002F\u002F 调用子类特有方法\n    }\n}\n```\n\n### 7.4 多态\n\n```java\nclass Shape {\n    public void draw() {\n        System.out.println(\\\"绘制形状\\\");\n    }\n    \n    public double calculateArea() {\n        return 0;\n    }\n}\n\nclass Circle extends Shape {\n    double radius;\n    \n    public Circle(double radius) {\n        this.radius = radius;\n    }\n    \n    @Override\n    public void draw() {\n        System.out.println(\\\"绘制圆形，半径: \\\" + radius);\n    }\n    \n    @Override\n    public double calculateArea() {\n        return Math.PI * radius * radius;\n    }\n}\n\nclass Rectangle extends Shape {\n    double width, height;\n    \n    public Rectangle(double width, double height) {\n        this.width = width;\n        this.height = height;\n    }\n    \n    @Override\n    public void draw() {\n        System.out.println(\\\"绘制矩形，宽: \\\" + width + \\\", 高: \\\" + height);\n    }\n    \n    @Override\n    public double calculateArea() {\n        return width * height;\n    }\n}\n\npublic class PolymorphismDemo {\n    public static void main(String[] args) {\n        \u002F\u002F 多态示例\n        Shape[] shapes = new Shape[2];\n        shapes[0] = new Circle(5);\n        shapes[1] = new Rectangle(4, 6);\n        \n        for (Shape shape : shapes) {\n            shape.draw();\n            System.out.println(\\\"面积: \\\" + shape.calculateArea());\n            System.out.println(\\\"---------\\\");\n        }\n    }\n}\n```\n\n## 第八部分：Java 21 新特性\n\n### 8.1 记录类 (Records)\n\n```java\n\u002F\u002F 传统方式\nclass Person {\n    private final String name;\n    private final int age;\n    \n    public Person(String name, int age) {\n        this.name = name;\n        this.age = age;\n    }\n    \n    public String name() { return name; }\n    public int age() { return age; }\n    \n    @Override\n    public boolean equals(Object o) {\n        \u002F\u002F 复杂的equals实现...\n    }\n    \n    @Override\n    public int hashCode() {\n        \u002F\u002F 复杂的hashCode实现...\n    }\n    \n    @Override\n    public String toString() {\n        \u002F\u002F toString实现...\n    }\n}\n\n\u002F\u002F Java 16+ 记录类 (简化版)\nrecord PersonRecord(String name, int age) {\n    \u002F\u002F 自动生成构造方法、getter、equals、hashCode、toString\n}\n\npublic class RecordsDemo {\n    public static void main(String[] args) {\n        PersonRecord person = new PersonRecord(\\\"张三\\\", 25);\n        System.out.println(person.name());    \u002F\u002F 自动生成的getter\n        System.out.println(person.age());     \u002F\u002F 自动生成的getter\n        System.out.println(person);           \u002F\u002F 自动生成的toString\n    }\n}\n```\n\n### 8.2 文本块 (Text Blocks)\n\n```java\npublic class TextBlocks {\n    public static void main(String[] args) {\n        \u002F\u002F 传统字符串\n        String oldStyle = \\\"{\\\n\\\" +\n                         \\\"  \\\\\"name\\\\\": \\\\\"张三\\\\\",\\\n\\\" +\n                         \\\"  \\\\\"age\\\\\": 25\\\n\\\" +\n                         \\\"}\\\";\n        \n        \u002F\u002F Java 15+ 文本块\n        String json = \\\"\\\"\\\"\n                      {\n                        \\\"name\\\": \\\"张三\\\",\n                        \\\"age\\\": 25\n                      }\n                      \\\"\\\"\\\";\n        \n        String html = \\\"\\\"\\\"\n                      \u003Chtml>\n                          \u003Cbody>\n                              \u003Ch1>欢迎\u003C\u002Fh1>\n                          \u003C\u002Fbody>\n                      \u003C\u002Fhtml>\n                      \\\"\\\"\\\";\n        \n        System.out.println(json);\n        System.out.println(html);\n    }\n}\n```\n\n### 8.3 模式匹配\n\n```java\npublic class PatternMatching {\n    public static void main(String[] args) {\n        Object obj = \\\"Hello, Java 21!\\\";\n        \n        \u002F\u002F 传统的 instanceof\n        if (obj instanceof String) {\n            String str = (String) obj;\n            System.out.println(str.toUpperCase());\n        }\n        \n        \u002F\u002F Java 16+ 模式匹配 instanceof\n        if (obj instanceof String str) {\n            System.out.println(str.toUpperCase());\n        }\n        \n        \u002F\u002F switch 表达式 + 模式匹配\n        Object value = 42;\n        String result = switch (value) {\n            case Integer i -> \\\"整数: \\\" + i;\n            case String s -> \\\"字符串: \\\" + s;\n            case Double d -> \\\"浮点数: \\\" + d;\n            default -> \\\"未知类型\\\";\n        };\n        System.out.println(result);\n    }\n}\n```\n\n## 第九部分：异常处理\n\n```java\npublic class ExceptionHandling {\n    \n    public static int divide(int a, int b) {\n        return a \u002F b;\n    }\n    \n    public static void main(String[] args) {\n        \u002F\u002F 基本的异常处理\n        try {\n            int result = divide(10, 0);\n            System.out.println(\\\"结果: \\\" + result);\n        } catch (ArithmeticException e) {\n            System.out.println(\\\"发生算术异常: \\\" + e.getMessage());\n        } finally {\n            System.out.println(\\\"这是finally块，总是会执行\\\");\n        }\n        \n        \u002F\u002F 多个catch块\n        try {\n            int[] numbers = {1, 2, 3};\n            System.out.println(numbers[5]); \u002F\u002F 数组越界\n        } catch (ArrayIndexOutOfBoundsException e) {\n            System.out.println(\\\"数组索引越界: \\\" + e.getMessage());\n        } catch (Exception e) {\n            System.out.println(\\\"发生其他异常: \\\" + e.getMessage());\n        }\n        \n        \u002F\u002F 抛出异常\n        try {\n            checkAge(15);\n        } catch (IllegalArgumentException e) {\n            System.out.println(\\\"捕获异常: \\\" + e.getMessage());\n        }\n    }\n    \n    public static void checkAge(int age) {\n        if (age \u003C 18) {\n            throw new IllegalArgumentException(\\\"年龄必须大于等于18岁\\\");\n        }\n        System.out.println(\\\"年龄验证通过\\\");\n    }\n}\n```\n\n## 第十部分：集合框架\n\n```java\nimport java.util.*;\n\npublic class CollectionsDemo {\n    public static void main(String[] args) {\n        \u002F\u002F List - 有序集合\n        List\u003CString> list = new ArrayList\u003C>();\n        list.add(\\\"苹果\\\");\n        list.add(\\\"香蕉\\\");\n        list.add(\\\"橙子\\\");\n        System.out.println(\\\"List: \\\" + list);\n        \n        \u002F\u002F Set - 无序不重复集合\n        Set\u003CInteger> set = new HashSet\u003C>();\n        set.add(1);\n        set.add(2);\n        set.add(1); \u002F\u002F 重复元素，不会被添加\n        System.out.println(\\\"Set: \\\" + set);\n        \n        \u002F\u002F Map - 键值对\n        Map\u003CString, Integer> map = new HashMap\u003C>();\n        map.put(\\\"张三\\\", 25);\n        map.put(\\\"李四\\\", 30);\n        map.put(\\\"王五\\\", 28);\n        System.out.println(\\\"Map: \\\" + map);\n        System.out.println(\\\"张三的年龄: \\\" + map.get(\\\"张三\\\"));\n        \n        \u002F\u002F 遍历集合\n        System.out.println(\\\"\\\n遍历List:\\\");\n        for (String fruit : list) {\n            System.out.println(fruit);\n        }\n        \n        System.out.println(\\\"\\\n遍历Map:\\\");\n        for (Map.Entry\u003CString, Integer> entry : map.entrySet()) {\n            System.out.println(entry.getKey() + \\\": \\\" + entry.getValue());\n        }\n        \n        \u002F\u002F 使用 Stream API (Java 8+)\n        System.out.println(\\\"\\\n使用Stream:\\\");\n        list.stream()\n            .filter(fruit -> fruit.length() > 2)\n            .forEach(System.out::println);\n    }\n}\n```\n\n## 学习建议\n\n1. **多动手实践**：每个示例代码都要亲自编写和运行\n2. **理解概念**：不要死记硬背，要理解面向对象的思想\n3. **阅读文档**：学会查看 [Java 官方文档](https:\u002F\u002Fdocs.oracle.com\u002Fjavase\u002F)\n4. **做项目**：尝试编写小项目来巩固知识\n5. **参与社区**：加入编程社区，向他人学习和提问\n\n## 下一步学习方向\n\n- Java 高级特性（泛型、反射、注解）\n- Java 网络编程\n- 数据库连接（JDBC）\n- 多线程编程\n- Spring 框架\n- 微服务开发\n\n---\n\n**下载说明**：您可以将此文档保存为 `Java21-入门教程.md` 文件，方便随时查阅和学习。\n\n这个教程涵盖了 Java 21 的基础知识，适合零基础学习者。建议按照顺序学习每个部分，并完成相应的练习。祝你学习顺利！","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002Fab7fca936a4c2c439faaa731c55a8da4_1762759275356.png","PUBLISHED",false,null,897,"2025-11-10T07:21:19.000Z","2026-06-27T11:04:01.926Z",{"id":4,"name":5,"slug":5},{"id":26,"name":27,"avatar":20},"f9d0f2de-c700-4f90-b535-afd3dbe78128","Admin",[29],{"id":30,"name":31,"slug":31},"692d5d68-b188-4e5c-aca8-65d0229399a1","渐变",{"id":33,"title":34,"slug":35,"content":36,"excerpt":34,"coverImage":37,"status":18,"isPinned":19,"pinnedAt":20,"viewCount":38,"publishedAt":39,"createdAt":39,"updatedAt":40,"category":41,"author":42,"tags":43},"9bd0f6b6-1b70-4b33-976b-26633d4842af","pm2 入门教程","pm2-入门教程","## 切换成root\n\n```language\nsudo -i\n```\n\n\n## 安装node命令(CentOS)\n\n```language\nsudo yum install nodejs npm\n```\n\n## 安装node命令(Ubuntu)\n\n```language\ncurl -fsSL https:\u002F\u002Fdeb.nodesource.com\u002Fsetup_22.x | sudo -E bash -\nsudo apt install -y nodejs\n```\n\n\n\n\n## 安装pm2(CentOS)\n\n```language\nsudo npm install -g pm2\n```\n\n## 查看node安装目录\n\n```language\nwhich node\n```\n\n\n\n## 配置ecosystem.config.cjs文件\n\n```shell\nmodule.exports = {\n  apps: [\n    \u002F\u002F 测试环境\n    {\n      name: 'web-dev',\n      instances: 1,\n      exec_mode: 'fork', \u002F\u002F 改为 fork 模式而不是 cluster\n      cwd: '\u002Fhome\u002Fec2-user\u002Fserver\u002Fquicking-h5',\n      script: '.\u002Fserver\u002Findex.mjs',\n      interpreter: '\u002Fusr\u002Fbin\u002Fnode', \u002F\u002F 或者实际的 node 路径\n      log_date_format: 'YYYY-MM-DD HH:mm Z',\n      error_file: '.\u002Flogs\u002Ferr.log',\n      out_file: '.\u002Flogs\u002Fout.log',\n      log_file: '.\u002Flogs\u002Fcombined.outerr.log',\n      time: true,\n      autorestart: true,\n      max_restarts: 10,\n      min_uptime: '10s',\n      max_memory_restart: '2G',\n      watch: false,\n      ignore_watch: ['node_modules', 'logs', '.nuxt', '.output'],\n      env: {\n        PORT: 6999\n      }\n    },\n  ]\n}\n\n```\n# 服务器部署命令\n\n```language\nrm -rf \u002Fhome\u002Fec2-user\u002Fserver\u002Fquicking-h5\u002F;\nmkdir \u002Fhome\u002Fec2-user\u002Fserver\u002Fquicking-h5\u002F;\nmkdir \u002Fhome\u002Fec2-user\u002Fserver\u002Fquicking-h5\u002Flogs\u002F;\ntar -zxvf \u002Fhome\u002Fec2-user\u002Fserver\u002Fquicking-h5-bf\u002Fdist.tgz  -C \u002Fhome\u002Fec2-user\u002Fserver\u002Fquicking-h5;\ncd \u002Fhome\u002Fec2-user\u002Fserver\u002Fquicking-h5\npm2 delete quicking-web\n#PORT=6999 pm2 start server\u002Findex.mjs --name quicking-web\npm2 start \u002Fhome\u002Fec2-user\u002Fserver\u002Fquicking-h5\u002Fecosystem.config.cjs --only quicking-web\n```\n\n\n\n## pm2常用命令\n\n\n```language\n# 启动应用\npm2 start ecosystem.config.cjs\n\n# 启动应用并指定名称\npm2 start aecosystem.config.cjs --name my-app\n\n# 启动应用并监听文件变化（开发模式）\npm2 start ecosystem.config.cjs --watch\n\n# 启动应用并指定实例数（集群模式）\npm2 start ecosystem.config.cjs -i max\n\n# 从 package.json 启动\npm2 start npm --name \\\"my-app\\\" -- start\n\n# 列出所有应用\npm2 list\npm2 ls\npm2 status\n\n# 查看特定应用信息\npm2 show app-name\n\n# 监控所有应用\npm2 monit\n\n# 停止应用\npm2 stop app-name\npm2 stop all\n\n# 重启应用\npm2 restart app-name\npm2 restart all\n\n# 重载应用（零停机重启）\npm2 reload app-name\npm2 reload all\n\n# 删除应用\npm2 delete app-name\npm2 delete all\n\n\n# 查看所有应用日志\npm2 logs\n\n# 查看特定应用日志\npm2 logs app-name\n\n# 查看最后 N 行日志\npm2 logs --lines 200\n\n# 清空日志\npm2 flush\n\n# 以 JSON 格式输出日志\npm2 logs --json\n\n\n# 查看系统信息\npm2 show pm2\n\n# 生成启动脚本\npm2 startup\n\n# 保存当前进程列表\npm2 save\n\n# 恢复保存的进程列表\npm2 resurrect\n\n# 更新 PM2\npm2 update\n\n# 查看详细的应用信息\npm2 show app-name\n\n# 查看 PM2 自身日志\npm2 logs pm2\n\n# 检查 PM2 版本和状态\npm2 --version\npm2 info\n\n# 重置 PM2（清除所有应用）\npm2 kill\n```\n\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F20c0f16b5ae0af7383a13df4f51b8ba6_1761125154013.jpg",417,"2025-10-22T09:34:41.000Z","2026-06-27T11:34:21.296Z",{"id":4,"name":5,"slug":5},{"id":26,"name":27,"avatar":20},[44],{"id":45,"name":46,"slug":47},"b73007a8-bb5c-42a8-9fd9-163033a5b45d","Linux","linux",{"id":49,"title":50,"slug":50,"content":51,"excerpt":50,"coverImage":52,"status":18,"isPinned":19,"pinnedAt":20,"viewCount":53,"publishedAt":54,"createdAt":54,"updatedAt":55,"category":56,"author":57,"tags":58},"24e1dc20-0811-4feb-ac0d-76ddabf3706b","windows修改hosts并马上生效","### 如何修改hosts\n>使用快捷键windows + r 打开命令输入窗口，然后输入以下内容C:\\Windows\\System32\\drivers\\etc后回车，在打开的文件夹中可以看到hosts文件，用编辑器打开直接修改就可以了\n\n>数据的格式大致是：IP+空格+域名，给个栗子：\n\n```shell\n 127.0.0.1 baidu.com\n```\n\n\n### 通过刷新dns来让hosts文件的修改立刻生效\n\n\n> 使用快捷键windows + r 打开命令输入窗口，然后输入cmd后回车。在弹出的命令行窗口中输入ipconfig \u002Fflushdns回车就好了。就是下面这个样子：\n\n```shell\nPS C:\\Users\\xiaobailong> ipconfig \u002Fflushdns\n\nWindows IP 配置\n\n已成功刷新 DNS 解析缓存。\n```\n\n### 常见问题\n> 权限不足\n配置无权限保存\n在配置hosts时，有时会遇到无权限保存情况，这时需要给当前用户分配权限。\n\n- 在hosts文件上右键菜单点击属性\n\n- Windows系统如何修改Hosts文件\n\n- 弹出窗口点击页签安全，点击选中当前用户，点击编辑按钮\n\n- Windows系统如何修改Hosts文件\n\n- 点击当前用户，添加权限，全部勾选所需要的权限，然后点击确定即可\n\n- 这样就给用户分配了读写等权限\n\n\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F4ffdd84720c20ba1a101006a403f0414.png",716,"2022-10-12T03:21:16.000Z","2026-06-27T11:20:01.196Z",{"id":4,"name":5,"slug":5},{"id":26,"name":27,"avatar":20},[59],{"id":60,"name":5,"slug":5},"3c46ed3f-6d6b-4f91-bcb3-af5112860bf5",{"id":62,"title":63,"slug":64,"content":65,"excerpt":63,"coverImage":66,"status":18,"isPinned":19,"pinnedAt":20,"viewCount":67,"publishedAt":68,"createdAt":68,"updatedAt":69,"category":70,"author":71,"tags":72},"b014a109-4f81-4e96-992f-65ea67e371b5","Yarn 安装与使用教程","yarn-安装与使用教程","## Yarn介绍\n\n> Yarn 是 Facebook, Google, Exponent 和 Tilde 开发的一款新的 JavaScript 包管理工具。 你可以通过它使用全世界开发者的代码，或者分享自己的代码。代码通过包（package）（或者称为模块（module））的方式来共享。 一个包里包含所有需要共享的代码，以及描述包信息的文件，称为package.json。它的优点是更快、更安全、更可靠。它的主要特性有离线模式、确定性、网络性能、多注册、网络恢复能力、扁平模式以及 Emoji。\n\n\n\n## Yarn 的优点\n\n- 快速：Yarn 缓存了每个下载过的包，所以再次使用时无需重复下载。 同时利用并行下载以最大化资源利用率，因此安装速度更快。\n\n- 可靠：使用详细、简洁的锁文件格式和明确的安装算法，Yarn 能够保证在不同系统上无差异的工作。\n\n- 安全：在执行代码之前，Yarn 会通过算法校验每个安装包的完整性。\n\n\n\n\n## Yarn安装\n\n```shell\nnpm install -g yarn\n```\n\n\n\n## yarn 的安装路径和缓存路径\n\n> 查看 yarn 全局bin位置\n\n```shell\nyarn global bin\n```\n\n\n\n> 查看 yarn 全局安装位置\n\n```shell\nyarn global dir\n```\n\n\n\n> 查看 yarn 全局cache位置\n\n```shell\nyarn cache dir\n```\n\n\n\n> 改变 yarn 全局bin位置\n\n```\nyarn config set prefix \\\"D:\\NodeNvm\\yarn\\Data\\\"\n```\n\n\n\n> 改变 yarn 全局安装位置\n\n```\nyarn config  set global-folder \\\"D:\\NodeNvm\\yarn\\Data\\global\\\"\n```\n\n\n\n> 改变 yarn 全局cache位置\n\n```\nyarn config set cache-folder \\\"D:\\NodeNvm\\yarn\\Cache\\\"\n```\n\n\n\n> 改变 yarn 全局 link 位置\n\n```\nyarn config set link-folder \\\"D:\\NodeNvm\\yarn\\Data\\link\\\"\n```\n\n\n\n## 配置环境变量\n\n> 将全局路径配置在环境变量中此电脑 -> 属性 -> 高级系统设置 -> 环境变量 -> 系统变量 -> path ->编辑 - > 新增路径 -D:\\software\\\nodeJs\\info\\\node_global （路径可以根据npm prefix -g查看）\n>\n> \n\n## Yarn命令\n\n### 查看版本\n\n```shell\nyarn --version\n```\n\n\n\n### 【1】初始化新项目\n\n```shell\nyarn init\n```\n\n\n\n### 【2】添加依赖包\n\n\n\n#### 会自动安装最新版本，会覆盖指定版本号\n\n```shell\nyarn add [package] \n```\n\n\n\n#### 一次性添加多个包\n\n```shell\nyarn add [package] [package] [package]\n```\n\n\n\n#### 添加指定版本的包\n\n```shell\nyarn add [package]@[version]\n```\n\n\n\n#### 安装某个tag（比如beta,next或者latest）\n\n```shell\nyarn add [package]@[tag] \n```\n\n\n\n### 【3】将依赖项添加到不同依赖项类别\n\n> 不指定依赖类型默认安装到dependencies里，你也可以指定依赖类型分别添加到 devDependencies、peerDependencies 和 optionalDependencies\n\n```shell\nyarn add [package] --dev 或 yarn add [package] -D \u002F\u002F 加到 devDependencies\nyarn add [package] --peer 或 yarn add [package] -P \u002F\u002F 加到 peerDependencies\nyarn add [package] --optional 或 yarn add [package] -O \u002F\u002F 加到 optionalDependencies\n```\n\n\n\n### 【4】升级依赖包\n\n```shell\nyarn upgrade [package] \u002F\u002F 升级到最新版本\nyarn upgrade [package]@[version] \u002F\u002F 升级到指定版本\nyarn upgrade [package]@[tag] \u002F\u002F 升级到指定tag\n```\n\n\n\n### 【5】移除依赖包\n\n```shell\nyarn remove [package] \u002F\u002F 移除包\n```\n\n\n\n### 【6】安装package.json里的包依赖，并将包及它的所有依赖项保存进yarn.lock\n\n\n\n#### 安装所有依赖\n\n```shell\nyarn 或 yarn install \n```\n\n\n\n#### 安装一个包的单一版本\n\n```shell\nyarn install --flat\n```\n\n\n\n#### 强制重新下载所有包\n\n```shell\nyarn install --force\n```\n\n\n\n#### 只安装生产环境依赖\n\n```\nyarn install --production\n```\n\n\n\n### 【7】发布包\n\n```shell\nyarn publish\n```\n\n\n\n### 【8】运行脚本\n\n> 用来执行在 package.json 中 scripts 属性下定义的脚本\n\n```shell\nyarn run \n```\n\n\n\n### 【9】显示某个包的信息\n\n>  可以用来查看某个模块的最新版本信息\n\n```shell\nyarn info [package] \n```\n\n\n\n### 【10】缓存\n\n```shell\nyarn cache\nyarn cache list \u002F\u002F 列出已缓存的每个包\nyarn cache dir \u002F\u002F 返回全局缓存位置\nyarn cache clean \u002F\u002F 清除缓存\n```\n\n\n\n\n\n## yarn 和 npm 命令对比\n\n| NPM                          | Yarn                      | 说明                            |\n| ---------------------------- | ------------------------- | ------------------------------- |\n| npm init                     | yarn init                 | 初始化某个项目                  |\n| npm install\u002Flink             | yarn install\u002Flink         | 默认安装依赖                    |\n| npm install taco --save      | yarn add taco             | 安装某个依赖并默认保存到package |\n| npm uninstall taco --save    | yarn remove taco          | 移除某个依赖                    |\n| npm install taco --save -dev | yarn add taco -dev        | 安装某个开发时的依赖            |\n| npm update taco --save       | yarn upgrade taco         | 更新某个依赖项目                |\n| npm install taco --global    | yarn global add taco      | 安装某个全局依赖项目            |\n| npm publish\u002Flogin\u002Flogout     | yarn publish\u002Flogin\u002Flogout | 发布\u002F登录\u002F退出                  |\n| npm run\u002Ftest                 | yarn run\u002Ftest             | 运行某个命令                    |\n\n\n\n##  临时修改（只生效一次）\n\n```cpp\nyarn save 包的名字 --registry https:\u002F\u002Fregistry.npmmirror.com\n```\n\n\n\n##  设置yarn的配置项（全局配置）\n\n- 查看yarn源的当前地址\n\n```csharp\nyarn config get registry\n```\n\n- 设置淘宝镜像\n\n```cpp\nyarn config set registry https:\u002F\u002Fregistry.npmmirror.com\n```\n\n\n\n## yarn 设置代理\n\n查看代理\n\n```shell\nyarn config list\n```\n\n设置\n\n```shell\nyarn config set proxy  http:\u002F\u002F127.0.0.1:7890\nyarn confit set https-proxy http:\u002F\u002F127.0.0.1:7890\n```\n\n取消\n\n```shell\nyarn config delete proxy\nyarn config delete https-proxy\n```","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F9da5a17be907b7c26c48c04331eaa8de.png",977,"2022-09-24T03:56:20.000Z","2026-06-27T11:35:01.378Z",{"id":4,"name":5,"slug":5},{"id":26,"name":27,"avatar":20},[73],{"id":74,"name":75,"slug":75},"0f1cc678-40e4-44b1-b2cf-a6fd8a1c867a","npm",{"id":77,"title":78,"slug":78,"content":79,"excerpt":78,"coverImage":80,"status":18,"isPinned":19,"pinnedAt":20,"viewCount":81,"publishedAt":82,"createdAt":82,"updatedAt":83,"category":84,"author":85,"tags":86},"918562bf-e701-45ec-86ab-7f89f4ae5b26","项目性能优化","## DNS 预解析\n> 在解析域名之前尝试在请求资源\n```js\n\u003Clink rel=\\\"dns-prefetch\\\" href=\\\"https:\u002F\u002Fcdn.xiaolong0418.com\u002F\\\">\n```\n\n\n## 静态资源放OSS+CDN分流\n\n## 静态资源设置浏览器缓存\n> 一般设置index.html强制不缓存，其他资源设置为强缓存或者协商缓存，建议设置协商缓存\n### 强缓存\n> 实现强缓存可以通过两种响应头实现：Expires 和 Cache-Control 。强缓存表示在缓存期间不需要请求，state code 为 200\n```js\nExpires: Wed, 22 Oct 2018 08:41:00 GMT\n```\n>Expires 是 HTTP \u002F 1.0 的产物，表示资源会在 Wed, 22 Oct 2018 08:41:00 GMT 后过期，需要再次请求。并且 Expires 受限于本地时间，如果修改了本地时间，可能会造成缓存失效。\n```js\nCache-control: max-age=30\n```\n>Cache-Control 出现于 HTTP \u002F 1.1，优先级高于 Expires 。该属性表示资源会在 30 秒后过期，需要再次请求。\n\n### 协商缓存\n> 如果缓存过期了，我们就可以使用协商缓存来解决问题。协商缓存需要请求，如果缓存有效会返回 304。\n协商缓存需要客户端和服务端共同实现，和强缓存一样，也有两种实现方式。\n\n#### Last-Modified 和 If-Modified-Since\n> Last-Modified 表示本地文件最后修改日期，If-Modified-Since 会将 Last-Modified 的值发送给服务器，询问服务器在该日期后资源是否有更新，有更新的话就会将新的资源发送回来。\n但是如果在本地打开缓存文件，就会造成 Last-Modified 被修改，所以在 HTTP \u002F 1.1 出现了 ETag 。\n\n#### ETag 和 If-None-Match\n> ETag 类似于文件指纹，If-None-Match 会将当前 ETag 发送给服务器，询问该资源 ETag 是否变动，有变动的话就将新的资源发送回来。并且 ETag 优先级比 Last-Modified 高。\n\n### 选择合适的缓存策略\n> 对于大部分的场景都可以使用强缓存配合协商缓存解决，但是在一些特殊的地方可能需要选择特殊的缓存策略\n\n- 对于某些不需要缓存的资源，可以使用 Cache-control: no-store ，表示该资源不需要缓存\n- 对于频繁变动的资源，可以使用 Cache-Control: no-cache 并配合 ETag 使用，表示该资源已被缓存，但是每次都会发送请求询问资源是否更新。\n- 对于代码文件来说，通常使用 Cache-Control: max-age=31536000 并配合策略缓存使用，然后对文件进行指纹处理，一旦文件名变动就会立刻下载新的文件。\n\n\n\n\n\n\n## 图片\n> 常用webp图片格式，设置图片懒加载，区分大小图加载\n\n## javascript、html、css\n> 打包去除无用代码，例如console、sourcemap等，再压缩（webpack和vite都支持）\n\n## 第三库\n> 按需引用\n\n>全局导入或者多项目的时候，固定项目之间的版本，可以采用CDN+缓存，提高缓存命中率\n\n> 分析一下有没有实现相同的功能，库还比较小的,例如Moment.js库改成day.js\n\n\n## 使用 HTTP \u002F 2.0\n> 因为浏览器会有并发请求限制，在 HTTP \u002F 1.1 时代，每个请求都需要建立和断开，消耗了好几个 RTT 时间，并且由于 TCP 慢启动的原因，加载体积大的文件会需要更多的时间。\n在 HTTP \u002F 2.0 中引入了多路复用，能够让多个请求使用同一个 TCP 链接，极大的加快了网页的加载速度。并且还支持 Header 压缩，进一步的减少了请求的数据大小。\n\n\n\n\n## 路由采用keep-alive缓存\n\n## 路由懒加载\n\n## 组件懒加载\n\n## Nginx配置\n> gzip 压缩\n\n## 防抖节流\n\n## 页面销毁清除监听事件\n\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002Fa8e2b319285dce292278bb971dfb1ff2.png",717,"2022-09-01T06:41:20.000Z","2026-06-27T11:07:17.403Z",{"id":4,"name":5,"slug":5},{"id":26,"name":27,"avatar":20},[87],{"id":88,"name":89,"slug":89},"dbfc086a-73a6-4560-814d-593acb61cf98","性能优化",{"id":91,"title":92,"slug":93,"content":94,"excerpt":92,"coverImage":95,"status":18,"isPinned":19,"pinnedAt":20,"viewCount":96,"publishedAt":97,"createdAt":97,"updatedAt":98,"category":99,"author":100,"tags":101},"bf04a0d7-2755-412b-9029-18dd4cb9c21a","Git常用命令","git常用命令","\n\n\n```language\n几个专用名词的译名如下: Workspace：\n\n工作区 Index \u002F Stage：\n\n暂存区 Repository：\n\n仓库区（或本地仓库） Remote：远程仓库\n```\n\n\n### 一、新建代码库\n\n# 在当前目录新建一个Git代码库\n```language\ngit init\n```\n\n# 新建一个目录，将其初始化为Git代码库\n```language\ngit init [project-name]\n```\n\n# 下载一个项目和它的整个代码历史\n```language\ngit clone [url]\n```\n\n### 二、配置\n> Git的设置文件为.gitconfig，它可以在用户主目录下(全局配置)，也可以在项目目录下(项目配置)\n\n# 显示当前的Git配置\n```language\ngit config --list\n```\n\n# 编辑Git配置文件\n```language\ngit config -e [--global]\n```\n\n# 设置提交代码时的用户信息\n```language\ngit config [--global] user.name \\\"[name]\\\"\ngit config [--global] user.email \\\"[email address]\\\"\n```\n\n# 颜色设置\n```language\ngit config --global color.ui true                         # git status等命令自动着色\ngit config --global color.status auto\ngit config --global color.diff auto\ngit config --global color.branch auto\ngit config --global color.interactive auto\ngit config --global --unset http.proxy                    # remove  proxy configuration on git\n```\n\n三、增加\u002F删除文件\n# 添加指定文件到暂存区\n```language\ngit add [file1] [file2] ...\n```\n\n# 添加指定目录到暂存区，包括子目录\n```language\ngit add [dir]\n```\n\n# 添加当前目录的所有文件到暂存区\n```language\ngit add .\n```\n\n# 添加每个变化前，都会要求确认\n# 对于同一个文件的多处变化，可以实现分次提交\n```language\ngit add -p\n```\n\n# 删除工作区文件，并且将这次删除放入暂存区\n```language\ngit rm [file1] [file2] ...\n```\n\n# 停止追踪指定文件，但该文件会保留在工作区\n```language\ngit rm --cached [file]\n```\n\n# 改名文件，并且将这个改名放入暂存区\n```language\ngit mv [file-original] [file-renamed]\n```\n\n四、代码提交\n# 提交暂存区到仓库区\n```language\ngit commit -m [message]\n```\n\n# 提交暂存区的指定文件到仓库区\n```language\ngit commit [file1] [file2] ... -m [message]\n```\n\n# 提交工作区自上次commit之后的变化，直接到仓库区\n```language\ngit commit -a\n```\n\n# 提交时显示所有diff信息\n```language\ngit commit -v\n```\n\n# 将add和commit合为一步\n```language\ngit commit -am 'message'\n```\n\n# 使用一次新的commit，替代上一次提交\n# 如果代码没有任何新变化，则用来改写上一次commit的提交信息\n```language\ngit commit --amend -m [message]\n```\n\n# 重做上一次commit，并包括指定文件的新变化\n```language\ngit commit --amend [file1] [file2] ...\n```\n\n# （创建反向commit）\n```language\ngit revert  [commit]\n```\n\n五、分支\n# 刷新分支\n```language\ngit remote update origin --prune\n```\n\n\n# 列出所有本地分支\n```language\ngit branch\n```\n\n# 列出所有远程分支\n```language\ngit branch -r\n\n```\n# 列出所有本地分支和远程分支\n```language\ngit branch -a\n```\n\n# 新建一个分支，但依然停留在当前分支\n```language\ngit branch [branch-name]\n```\n\n# 新建一个分支，并切换到该分支\n```language\ngit checkout -b [branch]\n```\n\n# 新建一个分支，指向指定commit\n```language\ngit branch [branch] [commit]\n```\n\n# 新建一个分支，与指定的远程分支建立追踪关系\n```language\ngit branch --track [branch] [remote-branch]\n```\n\n# 切换到指定分支，并更新工作区\n```language\ngit checkout [branch-name]\n```\n\n# 切换到上一个分支\n```language\ngit checkout -\n```\n\n# 建立追踪关系，在现有分支与指定的远程分支之间\n```language\ngit branch --set-upstream [branch] [remote-branch]\n```\n\n# 合并指定分支到当前分支\n```language\ngit merge [branch]\n```\n\n# 选择一个commit，合并进当前分支\n```language\ngit cherry-pick [commit]\n```\n\n# 删除分支\n```language\ngit branch -D [branch-name]\n```\n\n# 删除远程分支\n```language\ngit push origin --delete [branch-name]\ngit branch -dr [remote\u002Fbranch]\n```\n# 删除了远程仓库不存在的分支\n```language\ngit remote prune origin\n```\n\n\n\n# 检出版本v2.0\n```language\ngit checkout v2.0\n```\n\n# 从远程分支develop创建新本地分支devel并检出\n```language\ngit checkout -b devel origin\u002Fdevelop\n```\n\n# 检出head版本的README文件（可用于修改错误回退）\n```language\ngit checkout -- README \n```\n\n### 六、标签\n# 列出所有tag\n```language\ngit tag\n```\n\n# 新建一个tag在当前commit\n```language\ngit tag [tag]\n```\n\n# 新建一个tag在指定commit\n```language\ngit tag [tag] [commit]\n```\n\n# 删除本地tag\n```language\ngit tag -d [tag]\n```\n\n# 删除远程tag\n```language\ngit push origin :refs\u002Ftags\u002F[tagName]\n```\n\n# 查看tag信息\n```language\ngit show [tag]\n```\n\n# 提交指定tag\n```language\ngit push [remote] [tag]\n```\n\n# 提交所有tag\n```language\ngit push [remote] --tags\n```\n\n# 新建一个分支，指向某个tag\n```language\ngit checkout -b [branch] [tag]\n```\n\n七、查看信息\n# 显示有变更的文件\n```language\ngit status\n```\n\n# 显示当前分支的版本历史\n```language\ngit log\n```\n\n# 显示commit历史，以及每次commit发生变更的文件\n```language\ngit log --stat\n```\n\n# 搜索提交历史，根据关键词\n```language\ngit log -S [keyword]\n```\n\n# 显示某个commit之后的所有变动，每个commit占据一行\n```language\ngit log [tag] HEAD --pretty=format:%s\n```\n\n# 显示某个commit之后的所有变动，其\\\"提交说明\\\"必须符合搜索条件\n```language\ngit log [tag] HEAD --grep feature\n```\n\n# 显示某个文件的版本历史，包括文件改名\n```language\ngit log --follow [file]\n```\n\n```language\ngit whatchanged [file]\n```\n\n# 显示指定文件相关的每一次diff\n```language\ngit log -p [file]\n```\n\n# 显示过去5次提交\n```language\ngit log -5 --pretty --oneline\n```\n\n# 显示所有提交过的用户，按提交次数排序\n```language\ngit shortlog -sn\n```\n\n# 显示指定文件是什么人在什么时间修改过\n```language\ngit blame [file]\n```\n\n# 显示暂存区和工作区的差异\n```language\ngit diff\n```\n\n# 显示暂存区和上一个commit的差异\n```language\ngit diff --cached [file]\n```\n\n# 显示工作区与当前分支最新commit之间的差异\n```language\ngit diff HEAD\n```\n\n# 显示两次提交之间的差异\n```language\ngit diff [first-branch]...[second-branch]\n```\n\n# 显示今天你写了多少行代码\n```language\ngit diff --shortstat \\\"@{0 day ago}\\\"\n```\n\n# 显示某次提交的元数据和内容变化\n```language\ngit show [commit]\n```\n\n# 显示某次提交发生变化的文件\n```language\ngit show --name-only [commit]\n```\n\n# 显示某次提交时，某个文件的内容\n```language\ngit show [commit]:[filename]\n```\n\n# 显示当前分支的最近几次提交\n```language\ngit reflog\n```\n\n八、远程同步\n# 下载远程仓库的所有变动\n```language\ngit fetch [remote]\n```\n\n# 显示所有远程仓库\n```language\ngit remote -v\n```\n\n# 显示某个远程仓库的信息\n```language\ngit remote show [remote]\n```\n\n# 增加一个新的远程仓库，并命名\n```language\ngit remote add [shortname] [url]\n```\n\n# 取回远程仓库的变化，并与本地分支合并\n```language\ngit pull [remote] [branch]\n```\n\n# 上传本地指定分支到远程仓库\n```language\ngit push [remote] [branch]\n```\n\n# 强行推送当前分支到远程仓库，即使有冲突\n```language\ngit push [remote] --force\n```\n\n# 推送所有分支到远程仓库\n```language\ngit push [remote] --all\n```\n\n九、撤销\n# 恢复暂存区的指定文件到工作区\n```language\ngit checkout [file]\n```\n\n# 恢复某个commit的指定文件到暂存区和工作区\n```language\ngit checkout [commit] [file]\n```\n\n# 恢复暂存区的所有文件到工作区\n```language\ngit checkout .\n```\n\n# 重置暂存区的指定文件，与上一次commit保持一致，但工作区不变\n```language\ngit reset [file]\n```\n\n# 重置暂存区与工作区，与上一次commit保持一致\n```language\ngit reset --hard\n```\n\n# 重置当前分支的指针为指定commit，同时重置暂存区，但工作区不变\n```language\ngit reset [commit]\n```\n\n# 重置当前分支的HEAD为指定commit，同时重置暂存区和工作区，与指定commit一致\n```language\ngit reset --hard [commit]\n```\n\n# 重置当前HEAD为指定commit，但保持暂存区和工作区不变\n```language\ngit reset --keep [commit]\n```\n\n# 新建一个commit，用来撤销指定commit\n# 后者的所有变化都将被前者抵消，并且应用到当前分支\n```language\ngit revert [commit]\n```\n\n# 暂时将未提交的变化移除，稍后再移入\n```language\ngit stash\ngit stash pop\n```\n\n## 代码回滚\n```language\ngit log  查看历史提交记录\n\ngit reset HEAD                回退到当前版本（取消暂存区的内容）\n\ngit reset --hard HEAD^    回退到上个版本\n\ngit reset --hard HEAD^^   回退到上上个版本\n\ngit reset --hard id（commit hash值）   回退到指定版本\n\ngit push origin HEAD --force   强推到远程：\n\ngit reset --hard origin\u002Fmaster  强推到远程并更新远程代码库\n```\n\n\n十、其他\n```language\ngit init                                                  # 初始化本地git仓库（创建新仓库）\ngit config --global user.name \\\"xxx\\\"                       # 配置用户名\ngit config --global user.email \\\"xxx@xxx.com\\\"              # 配置邮件\ngit config --global color.ui true                         # git status等命令自动着色\ngit config --global color.status auto\ngit config --global color.diff auto\ngit config --global color.branch auto\ngit config --global color.interactive auto\ngit config --global --unset http.proxy                    # remove  proxy configuration on git\ngit clone git+ssh:\u002F\u002Fgit@192.168.53.168\u002FVT.git             # clone远程仓库\ngit status                                                # 查看当前版本状态（是否修改）\ngit add xyz                                               # 添加xyz文件至index\ngit add .                                                 # 增加当前子目录下所有更改过的文件至index\ngit commit -m 'xxx'                                       # 提交\ngit commit --amend -m 'xxx'                               # 合并上一次提交（用于反复修改）\ngit commit -am 'xxx'                                      # 将add和commit合为一步\ngit rm xxx                                                # 删除index中的文件\ngit rm -r *                                               # 递归删除\ngit log                                                   # 显示提交日志\ngit log -1                                                # 显示1行日志 -n为n行\ngit log -5\ngit log --stat                                            # 显示提交日志及相关变动文件\ngit log -p -m\ngit show dfb02e6e4f2f7b573337763e5c0013802e392818         # 显示某个提交的详细内容\ngit show dfb02                                            # 可只用commitid的前几位\n```\n\n\n### 已经推送过的文件，想在以后的提交时忽略此文件，即使本地已经修改过，而且修改git远程库中相应文件\n\n> git update-index --assume-unchanged config\u002Findex.js\n\n### 恢复跟踪\n> git update-index --no-assume-unchanged config\u002Findex.js\n\n## 常见问题\n### 在签出前，请清理仓库工作树\n![](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F8003dffe6164cc1593ed65e8e1e95b4a.png)\n\n>解决方案：打开GIT日志，查看冲突的文件，把文件放入暂存区，重新同步一下代码\n\n```language\ngit stash\ngit pull\ngit stash pop\n```\n\n\n### 代码合并没有成功，重新合并\n>error: You have not concluded your merge (MERGE_HEAD exists).\nhint: Please, commit your changes before merging.\nfatal: Exiting because of unfinished merge.\n\n> 解决方案\n\n一: 保留本地的更改,中止合并->重新合并->重新拉取\n\n\n```shell\ngit merge --abort\ngit reset --merge\ngit pull\n```\n\n\n二: 舍弃本地代码,远端版本覆盖本地版本,这步代码不可逆\n\n```shell\ngit fetch --all\ngit reset --hard origin\u002Fmaster\ngit fetch\n```\n\n\n\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F1658224785941.png",1299,"2022-07-19T10:04:01.000Z","2026-06-27T11:34:24.458Z",{"id":4,"name":5,"slug":5},{"id":26,"name":27,"avatar":20},[102],{"id":103,"name":104,"slug":105},"2de16806-ef3f-4e54-a259-d1e1e182468c","Git","git",{"id":107,"title":108,"slug":109,"content":110,"excerpt":108,"coverImage":111,"status":18,"isPinned":19,"pinnedAt":20,"viewCount":112,"publishedAt":113,"createdAt":113,"updatedAt":114,"category":115,"author":116,"tags":117},"55b1f9bf-a19f-4e46-94ab-61de26633dfd","Linux常用命令","linux常用命令","\n\n\n|         功能         |  命令   |                   示例                   |                             说明                             |\n| :------------------: | :-----: | :--------------------------------------: | :----------------------------------------------------------: |\n|     获取root权限     | sudo su |                    -                     |                              -                               |\n|       新建目录       |  mkdir  |                mkdir css                 |                         新建css目录                          |\n|     显示文件路径     |   pwd   |                    -                     |                              -                               |\n|       切换目录       |   cd    |             cd css或者cd ..\u002F             |                进入css目录或者返回上一级目录                 |\n|       文件上传       | rz -bye |                    -                     |                              -                               |\n|  用于解压缩zip文件   |  unzip  |             unzip  dist.zip              |                         解压dist.zip                         |\n|    移除文件或目录    | rm -rf  | rm -rf dist.zip img\u002F index.html css\u002F js\u002F |                删除dist.zip、img\u002F文件夹等文件                |\n|       下载文件       |   sz    |               sz dist.zip                |                         下载dist.zip                         |\n|       复制文件       |   cp    |    cp .\u002Fdist.zip .\u002Fback\u002Fdist_back.zip    | 将此目录下的dist.zip 复制到 \u002Fback 下，并命名为 dist_back.zip |\n| 查看文件与目录的命令 |   ls    |                    -                     |                              -                               |\n| 移动文件、目录或更名 |   mv    |                mv css js                 |                      把css目录更名为 js                      |\n|    改变文件的权限    |  chmod  |            chmod -R 777 \u002Fcss             |          更改css文件夹内所有的文件为可写可读可执行           |\n|       立即重启       | reboot  |                    -                     |                              -                               |\n\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F1657982212483.png",558,"2022-07-16T14:37:13.000Z","2026-06-27T11:37:31.875Z",{"id":4,"name":5,"slug":5},{"id":26,"name":27,"avatar":20},[118],{"id":45,"name":46,"slug":47},{"id":120,"title":121,"slug":122,"content":123,"excerpt":121,"coverImage":124,"status":18,"isPinned":19,"pinnedAt":20,"viewCount":125,"publishedAt":126,"createdAt":126,"updatedAt":127,"category":128,"author":129,"tags":130},"c286ffd9-8bec-4aee-b202-14cd210a4099","gitignore 配置规则","gitignore-配置规则","\n\n## gitignore 忽略规则简单说明\n\n| 代码          | 说明                                                                         |\n| ------------- | ---------------------------------------------------------------------------- |\n| #             | 表示此为注释,将被 Git 忽略                                                   |\n| \\*.a          | 忽略所有 .a 结尾的文件                                                       |\n| !lib.a        | 但 lib.a 除外                                                                |\n| \u002FTODO         | 仅仅忽略项目根目录下的 TODO 文件，不包括 subdir\u002FTODO                         |\n| build\u002F        | 忽略 build\u002F目录下的所有文件，过滤整个 build 文件夹                           |\n| doc\u002F\\*.txt    | 忽略 doc\u002Fnotes.txt 但不包括 doc\u002Fserver\u002Farch.txt                              |\n| bin\u002F:         | 忽略当前路径下的 bin 文件夹，该文件夹下的所有内容都会被忽略，不忽略 bin 文件 |\n| \u002Fbin:         | 忽略根目录下的 bin 文件                                                      |\n| \u002F\\*.c:        | 忽略 cat.c，不忽略 build\u002Fcat.c                                               |\n| debug\u002F\\*.obj: | 忽略 debug\u002Fio.obj，不忽略 debug\u002Fcommon\u002Fio.obj 和 tools\u002Fdebug\u002Fio.obj          |\n| \\*\\*\u002Ffoo:     | 忽略\u002Ffoo,a\u002Ffoo,a\u002Fb\u002Ffoo 等                                                    |\n| a\u002F\\*\\*\u002Fb:     | 忽略 a\u002Fb, a\u002Fx\u002Fb,a\u002Fx\u002Fy\u002Fb 等                                                   |\n| !\u002Fbin\u002Frun.sh  | 不忽略 bin 目录下的 run.sh 文件                                              |\n| \\*.log:       | 忽略所有 .log 文件                                                           |\n| config.php:   | 忽略当前路径的 config.php 文件                                               |\n| \u002Fmtk\u002F         | 过滤整个文件夹                                                               |\n| \u002Fmtk\u002Fdo.c     | 过滤某个具体文件                                                             |\n| \\*.zip        | 过滤所有.zip 文件                                                            |\n| !\\*.zip       | 指定要将哪些文件添加到版本管理                                               |\n| fd1\u002F\\*        | 忽略目录 fd1 下的全部内容                                                    |\n\n## 前端常用规则\n\n```\n> # 众多无视的扩展\n*.bak\n*.patch\n*.diff\n*.err\n\n# git冲突合并的临时文件\n*.orig\n*.log\n*.rej\n*.swo\n*.swp\n*.zip\n*.vi\n*~\n*.sass-cache\n*.tmp.html\n*.dump\n\n#操作系统或编辑器文件夹\n.DS_Store\n._*\n.cache\n.project\n.settings\n.tmproj\n*.esproj\n*.sublime-project\n*.sublime-workspace\nnbproject\nthumbs.db\n*.iml\n\n# F忽略的文件夹\n.hg\n.svn\n.CVS\n.idea\nnode_modules\u002F\njscoverage_lib\u002F\nbower_components\u002F\ndist\u002F\n\n```\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F1657981475363.png",547,"2022-07-16T14:24:58.000Z","2026-06-27T11:35:04.952Z",{"id":4,"name":5,"slug":5},{"id":26,"name":27,"avatar":20},[131],{"id":103,"name":104,"slug":105},{"id":133,"title":134,"slug":135,"content":136,"excerpt":134,"coverImage":137,"status":18,"isPinned":19,"pinnedAt":20,"viewCount":138,"publishedAt":139,"createdAt":139,"updatedAt":140,"category":141,"author":142,"tags":143},"0b638ebb-4979-46a5-9282-d51b27d29eee","通过Nginx实现链接重定向","通过nginx实现链接重定向","- 通过nginx实现链接重定向\n- 如果只是单纯地借助nginx实现重定向，可以通过rewrite来实现。\n\n- 需求：\n> 请求 \u002Ffoo\u002Fxxx时，重定向到 另一个站点的\u002Fbar\u002Fxxx\n\n```language\nserver {\n        listen  80;\n        # ...\n\t    rewrite ^\u002Ffoo\u002F(.*)$ http:\u002F\u002Fanother-site.com\u002Fbar\u002F$1 break;\n}\n```\n\n\n> rewrite的第一个参数是一个正则表达式，()之内是一个capture，捕获到的内容会放到变量\\$1中。第二个参数使用了\\$1。\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F1657255221284.png",483,"2022-07-08T04:41:34.000Z","2026-06-27T11:35:29.174Z",{"id":4,"name":5,"slug":5},{"id":26,"name":27,"avatar":20},[144],{"id":145,"name":146,"slug":147},"52c41978-da06-4962-9636-45bbaeedda80","Nginx","nginx",{"id":149,"title":150,"slug":151,"content":152,"excerpt":150,"coverImage":153,"status":18,"isPinned":19,"pinnedAt":20,"viewCount":154,"publishedAt":155,"createdAt":155,"updatedAt":156,"category":157,"author":158,"tags":159},"1f52f4a2-f0cb-4a99-8caf-500f128b9f54","Nginx的跨域配置","nginx的跨域配置","\n\n## Nginx配置（这个配置，可以解决 95% 以上的跨域问题）\n\n```shell\n  # HTTP反向代理相关配置开始 >>>\n    location ~ \u002Fpurge(\u002F.*) {\n        proxy_cache_purge cache_one $host$request_uri$is_args$args;\n    }   \n     \n\n    location \u002F {  \n    add_header Access-Control-Allow-Origin * always;\n       add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS, PUT, PATCH, DELETE, HEAD';\n       add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,authorization';\n\n       if ($request_method = 'OPTIONS') {        \n       \treturn 200;\n      }\n        proxy_pass http:\u002F\u002F127.0.0.1:3008;\n        proxy_set_header Host $host:$server_port;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header REMOTE-HOST $remote_addr;\n        add_header X-Cache $upstream_cache_status;\n        proxy_set_header X-Host $host:$server_port;\n        proxy_set_header X-Scheme $scheme;\n        proxy_connect_timeout 30s;\n        proxy_read_timeout 86400s;\n        proxy_send_timeout 30s;\n        proxy_http_version 1.1;\n        proxy_set_header Upgrade $http_upgrade;\n        proxy_set_header Connection \\\"upgrade\\\";\n    }\n    # HTTP反向代理相关配置结束 \u003C\u003C\u003C\n```\n\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002Fe2e9f323e8c6d14c0b01479c0208afb8.png",700,"2022-06-01T14:02:10.000Z","2026-06-27T11:35:03.782Z",{"id":4,"name":5,"slug":5},{"id":26,"name":27,"avatar":20},[160],{"id":145,"name":146,"slug":147},13,1,10,{"categories":165,"tags":246,"postCount":414,"tagCount":415,"hotPosts":416},[166,173,179,186,187,194,201,205,212,216,220,225,231,236,240],{"id":167,"name":168,"slug":169,"description":170,"sortOrder":171,"createdAt":172,"updatedAt":172,"postCount":162},"e8d0bd45-d10c-46d3-8afb-0c072df7f8a7","技术","tech","技术文章",0,"2026-06-27T04:18:37.371Z",{"id":174,"name":175,"slug":176,"description":175,"sortOrder":162,"createdAt":177,"updatedAt":8,"postCount":178},"15ac46ad-edf7-4435-9a64-ff78117d58c5","Vue3 生态","vue3-生态","2022-05-21T08:05:39.000Z",6,{"id":180,"name":181,"slug":182,"description":181,"sortOrder":183,"createdAt":184,"updatedAt":8,"postCount":185},"11d4d397-685c-4180-a7b3-9b0e3a1e411e","Css","css",2,"2022-05-23T07:19:37.000Z",9,{"id":4,"name":5,"slug":5,"description":5,"sortOrder":6,"createdAt":7,"updatedAt":8,"postCount":161},{"id":188,"name":189,"slug":190,"description":189,"sortOrder":191,"createdAt":192,"updatedAt":8,"postCount":193},"5ed5cc62-43ea-49a2-b0b2-38bc7aae52a0","Vue3","vue3",4,"2022-05-24T01:55:05.000Z",8,{"id":195,"name":196,"slug":197,"description":196,"sortOrder":198,"createdAt":199,"updatedAt":8,"postCount":200},"da130ba9-d4f4-49f3-aa0f-149078097ef0","JavaScript","javascript",5,"2022-05-24T02:22:57.000Z",18,{"id":202,"name":203,"slug":203,"description":203,"sortOrder":178,"createdAt":204,"updatedAt":8,"postCount":162},"d8cbe380-54b3-4a61-a12d-5438c2918574","限时优惠","2022-05-25T07:18:03.000Z",{"id":206,"name":207,"slug":208,"description":207,"sortOrder":209,"createdAt":210,"updatedAt":8,"postCount":211},"e0f3b8d8-cfe7-41fb-802b-a79699d95968","JavaScript插件","javascript插件",7,"2022-06-01T14:08:31.000Z",16,{"id":213,"name":214,"slug":214,"description":214,"sortOrder":193,"createdAt":215,"updatedAt":8,"postCount":178},"4ea3d8af-9cc3-49bb-a9cd-34dbcdc3bd85","构建工具","2022-06-02T07:28:13.000Z",{"id":217,"name":218,"slug":218,"description":218,"sortOrder":185,"createdAt":219,"updatedAt":8,"postCount":193},"9ed9827c-9cbb-42da-80e4-d04c7fdba886","开发工具","2022-06-21T03:35:05.000Z",{"id":221,"name":222,"slug":223,"description":222,"sortOrder":163,"createdAt":224,"updatedAt":8,"postCount":198},"6b9179c3-17b2-43ff-a431-a03d6eb32d89","Vue2 生态","vue2-生态","2022-07-16T13:14:29.000Z",{"id":226,"name":227,"slug":228,"description":227,"sortOrder":229,"createdAt":230,"updatedAt":8,"postCount":198},"73a5f62c-3c47-45b9-9ae2-f29953ae8dc0","Node","node",11,"2022-07-16T13:15:39.000Z",{"id":232,"name":233,"slug":233,"description":233,"sortOrder":234,"createdAt":235,"updatedAt":8,"postCount":183},"2b696c16-48ef-403b-a88b-6e57cfc79596","开发问题",12,"2022-07-16T14:06:54.000Z",{"id":237,"name":238,"slug":238,"description":238,"sortOrder":161,"createdAt":239,"updatedAt":8,"postCount":162},"c0f0561e-a47a-4ecd-8caa-cc1df2315d57","算法","2022-07-16T14:22:34.000Z",{"id":241,"name":242,"slug":243,"description":242,"sortOrder":244,"createdAt":245,"updatedAt":8,"postCount":6},"a629c1f7-29f1-439e-be3c-29670b17ba20","Vue2","vue2",15,"2022-07-16T14:41:51.000Z",[247,253,258,261,266,268,272,276,278,283,288,290,291,296,300,304,309,314,319,324,328,332,336,338,342,347,352,355,358,361,364,367,371,374,376,377,378,382,385,389,393,396,399,401,402,404,408,411],{"id":248,"name":249,"slug":250,"createdAt":251,"updatedAt":252},"076bd8b9-293e-45cb-9dc3-e162007ca474","Axios","axios","2022-06-05T07:41:56.000Z","2025-12-30T07:26:21.000Z",{"id":254,"name":255,"slug":256,"createdAt":257,"updatedAt":8},"2aa7f6d0-1fac-4ed1-b9bb-f3afc813f42c","Axure","axure","2022-06-21T03:35:15.000Z",{"id":259,"name":181,"slug":182,"createdAt":260,"updatedAt":8},"b084ddd8-09be-4e57-98f0-cf4e376aecd7","2022-05-21T09:59:55.000Z",{"id":262,"name":263,"slug":264,"createdAt":265,"updatedAt":8},"78a62bff-ff77-4878-8c25-3e6aae18c668","Docker","docker","2022-07-16T14:34:37.000Z",{"id":103,"name":104,"slug":105,"createdAt":267,"updatedAt":8},"2022-07-16T14:25:15.000Z",{"id":269,"name":270,"slug":271,"createdAt":260,"updatedAt":8},"994cc226-578b-4a72-a57e-a47a63d2793e","JavaScript生态","javascript生态",{"id":273,"name":274,"slug":275,"createdAt":260,"updatedAt":8},"5086e93c-23b9-43d3-9643-cc87f0e9ee94","JenKins","jenkins",{"id":45,"name":46,"slug":47,"createdAt":277,"updatedAt":8},"2022-07-16T14:40:17.000Z",{"id":279,"name":280,"slug":281,"createdAt":282,"updatedAt":8},"0b658b92-dd6b-4db3-a398-9f6d69950a02","Markdown","markdown","2022-07-16T14:39:25.000Z",{"id":284,"name":285,"slug":286,"createdAt":287,"updatedAt":8},"ab034d3a-6e5b-4db5-a2dc-faf4ccbb63f5","Nest","nest","2022-07-16T13:15:49.000Z",{"id":145,"name":146,"slug":147,"createdAt":289,"updatedAt":8},"2022-05-21T09:59:56.000Z",{"id":74,"name":75,"slug":75,"createdAt":289,"updatedAt":8},{"id":292,"name":293,"slug":294,"createdAt":295,"updatedAt":8},"a4370d78-70e1-4073-a8f6-3dc5d81fd8fd","Nuxt","nuxt","2022-06-01T13:07:07.000Z",{"id":297,"name":298,"slug":299,"createdAt":260,"updatedAt":8},"d232e01f-048e-4151-8a0a-fff9561f946f","Pinia","pinia",{"id":301,"name":302,"slug":303,"createdAt":289,"updatedAt":8},"14e9ab02-b0bb-4c85-8604-fe6f1f0f33cd","Pnpm","pnpm",{"id":305,"name":306,"slug":307,"createdAt":308,"updatedAt":308},"399d1d38-cc0d-43ce-8baf-c769447a2ebd","React生态","react生态","2023-02-21T02:03:09.000Z",{"id":310,"name":311,"slug":312,"createdAt":313,"updatedAt":8},"c95bbe84-bdd0-410a-86a9-e87958c55f4f","Redis","redis","2022-10-05T05:14:14.000Z",{"id":315,"name":316,"slug":317,"createdAt":318,"updatedAt":8},"6d05f9df-e116-450f-af57-85ed710c4870","Swiper","swiper","2022-06-01T14:08:46.000Z",{"id":320,"name":321,"slug":322,"createdAt":323,"updatedAt":8},"66f3aeb0-84ef-45f6-a43a-944eefc9895a","Vite","vite","2022-06-02T07:28:24.000Z",{"id":325,"name":326,"slug":327,"createdAt":260,"updatedAt":8},"bf5b94d3-090b-4098-a03c-4bc69781fb2d","Vue","vue",{"id":329,"name":330,"slug":331,"createdAt":289,"updatedAt":8},"2f7fb1be-b9c5-4606-b54f-e9f66f2653b2","Vue-Router","vue-router",{"id":333,"name":334,"slug":335,"createdAt":260,"updatedAt":8},"2fef3b91-1c1c-4ae8-b2c1-0e04b4f9b3a2","Vue2生态","vue2生态",{"id":337,"name":189,"slug":190,"createdAt":260,"updatedAt":8},"62b94c93-724f-488d-8fc5-0449971d9204",{"id":339,"name":340,"slug":341,"createdAt":260,"updatedAt":8},"20bff9cd-7848-4c16-8775-42cf12b44b30","Vue3生态","vue3生态",{"id":343,"name":344,"slug":345,"createdAt":346,"updatedAt":8},"c807b2c6-cb12-4409-a1f1-6bea9f330a6b","Vuex","vuex","2022-07-16T13:14:59.000Z",{"id":348,"name":349,"slug":350,"createdAt":351,"updatedAt":8},"5782dff5-2ea2-4427-9696-d4363a7fd5bc","Webpack","webpack","2022-07-16T14:33:41.000Z",{"id":353,"name":354,"slug":354,"createdAt":260,"updatedAt":8},"d0aa41f4-68f8-48d4-a4ed-3a503ea90451","下载",{"id":356,"name":357,"slug":357,"createdAt":260,"updatedAt":8},"a046060c-39ef-474a-8c85-2546aca0e2e5","代码片段",{"id":359,"name":360,"slug":360,"createdAt":260,"updatedAt":8},"fee73435-b2be-4b55-85b1-d133ea96aea4","伪元素",{"id":362,"name":363,"slug":363,"createdAt":260,"updatedAt":8},"436bd369-8c57-4869-8827-e88e50e5e0ab","伪类",{"id":365,"name":366,"slug":366,"createdAt":260,"updatedAt":8},"4c6be544-8a00-4445-92a3-e3dcbaf6142e","动画",{"id":368,"name":369,"slug":369,"createdAt":370,"updatedAt":8},"9321a12e-ea72-49a9-a32d-5566149f812f","图片压缩","2022-08-02T00:37:47.000Z",{"id":372,"name":373,"slug":373,"createdAt":289,"updatedAt":8},"512b16fb-576a-4397-a7c5-dd20e6a8f9ca","布局",{"id":375,"name":218,"slug":218,"createdAt":260,"updatedAt":8},"f32faa96-f2ec-45c6-9a17-2c76062edcb0",{"id":60,"name":5,"slug":5,"createdAt":260,"updatedAt":8},{"id":88,"name":89,"slug":89,"createdAt":260,"updatedAt":8},{"id":379,"name":380,"slug":380,"createdAt":381,"updatedAt":8},"1831cd06-0d6b-48f7-94fa-324782fe23cb","拖拽","2022-07-28T12:39:13.000Z",{"id":383,"name":384,"slug":384,"createdAt":289,"updatedAt":8},"9a74300d-06f7-46d0-80d9-8fe67ec0539b","数组",{"id":386,"name":387,"slug":387,"createdAt":388,"updatedAt":8},"19ac8998-7e0a-459b-9702-bb1adca70e8c","文本复制","2022-07-17T01:54:45.000Z",{"id":390,"name":391,"slug":391,"createdAt":392,"updatedAt":8},"5ff33473-71a4-4e02-8c82-f9ea369a768f","时间","2022-07-17T01:51:12.000Z",{"id":394,"name":395,"slug":395,"createdAt":260,"updatedAt":8},"aa47ca4d-d3f6-4cac-b495-2c67c9592c36","最新优惠",{"id":397,"name":398,"slug":398,"createdAt":289,"updatedAt":8},"f6766d54-54fc-405e-932d-b7d550559125","服务器",{"id":400,"name":214,"slug":214,"createdAt":260,"updatedAt":8},"d856559a-03ff-40b4-980d-3f272b998c3c",{"id":30,"name":31,"slug":31,"createdAt":260,"updatedAt":8},{"id":403,"name":238,"slug":238,"createdAt":260,"updatedAt":8},"38e1fd6b-d7c6-4d62-bf70-7bacc175bea9",{"id":405,"name":406,"slug":406,"createdAt":407,"updatedAt":8},"be7b10bc-49eb-4a03-bea7-ceb915d500fe","规范","2022-07-16T14:41:06.000Z",{"id":409,"name":410,"slug":410,"createdAt":289,"updatedAt":8},"b42e2916-ad62-4b8a-a863-cd8c19a829de","面试",{"id":412,"name":413,"slug":413,"createdAt":289,"updatedAt":8},"7069add9-b636-44f1-9cd4-ea3a6d2b85d3","面试题",104,48,[417,429,441,454,467],{"id":418,"title":419,"slug":419,"content":420,"excerpt":419,"coverImage":421,"status":18,"isPinned":19,"pinnedAt":20,"viewCount":422,"publishedAt":423,"createdAt":423,"updatedAt":424,"category":425,"author":426,"tags":427},"a1bd1f49-6f6d-4fea-9789-b5636e19a6b3","uni-app瀑布流","## 实现思路\n> 获取父组件的列表数组，watch监听数组长度变化，截取后面新的数据，创建两个左右数组，比较左右dom的长度，哪个短，就push一条数据进去，源数组删除一条数据。利用img的load（加载成功）和error方法（加载失败），触发数组的push，实现瀑布流\n\n## 代码实现\n\n```language\n\u003Ctemplate>\n    \u003Cview class=\\\"waterfall\\\">\n        \u003Cview class=\\\"waterfall_left\\\">\n            \u003Cview class=\\\"waterfall_list\\\" v-for=\\\"(item, index) in leftList\\\" :key=\\\"index\\\">\n                \u003CSearch\n                    :name=\\\"item.name\\\"\n                    :image=\\\"item.src\\\"\n                    :label=\\\"item.label\\\"\n                    :item=\\\"item\\\"\n                    @considerPush=\\\"considerPush\\\"\n                >\n                \u003C\u002FSearch>\n            \u003C\u002Fview>\n        \u003C\u002Fview>\n        \u003Cview class=\\\"waterfall_right\\\">\n            \u003Cview class=\\\"waterfall_list\\\" v-for=\\\"(item, index) in rightList\\\" :key=\\\"index\\\">\n                \u003CSearch\n                    :name=\\\"item.name\\\"\n                    :image=\\\"item.src\\\"\n                    :label=\\\"item.label\\\"\n                    :item=\\\"item\\\"\n                    @considerPush=\\\"considerPush\\\"\n                >\n                \u003C\u002FSearch>\n            \u003C\u002Fview>\n        \u003C\u002Fview>\n    \u003C\u002Fview>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\nimport Search from '@\u002Fcomponents\u002Fsearch\u002Fsearch.vue';\nexport default {\n    components: { Search },\n    props: {\n        list: {\n            type: Array,\n            default: () => []\n        }\n    },\n    data() {\n        return {\n            \u002F\u002F 左侧列表\n            leftList: [],\n            \u002F\u002F 右侧列表\n            rightList: [],\n            \u002F\u002F 组件数据备份\n            newList: [],\n            \u002F\u002F默认请求数,主要为了正常排序\n            interceptNumber: 10\n        };\n    },\n    created() {\n        this.touchOff(); \u002F\u002F 触发排列\n    },\n    mounted() {},\n    watch: {\n        list(newValue, oldValue) {\n            this.interceptNumber = newValue.length - oldValue.length;\n            this.touchOff();\n        }\n    },\n    computed: {},\n    methods: {\n        \u002F\u002F 触发重新排列\n        touchOff() {\n            this.newList = [...this.list.slice(-this.interceptNumber)];\n            if (this.newList.length !== 0) {\n                this.leftList.push(this.newList.shift()); \u002F\u002F触发排列\n            }\n        },\n        \u002F\u002F 计算排列\n        considerPush() {\n            this.$nextTick(() => {\n                if (this.newList.length == 0) return; \u002F\u002F没有数据了\n                let leftH = 0;\n                let rightH = 0; \u002F\u002F左右高度\n                let query = uni.createSelectorQuery().in(this);\n                query.selectAll('.waterfall_left').boundingClientRect();\n                query.selectAll('.waterfall_right').boundingClientRect();\n                query.exec(res => {\n                    leftH = res[0].length != 0 ? res[0][0].height : 0; \u002F\u002F防止查询不到做个处理\n                    rightH = res[1].length != 0 ? res[1][0].height : 0;\n                    if (leftH == rightH || leftH \u003C rightH) {\n                        \u002F\u002F 相等 || 左边小\n                        this.leftList.push(this.newList.shift());\n                    } else {\n                        \u002F\u002F 右边小\n                        this.rightList.push(this.newList.shift());\n                    }\n\n                    \u002F\u002F console.log('左右高度：', leftH, rightH, leftH == rightH || leftH \u003C rightH);\n                });\n            });\n        }\n    }\n};\n\u003C\u002Fscript>\n\n\u003Cstyle scoped lang=\\\"scss\\\">\n.waterfall {\n    display: flex;\n    align-items: flex-start;\n    justify-content: flex-start;\n    .waterfall_left {\n        flex: 1;\n    }\n\n    .waterfall_right {\n        flex: 1;\n    }\n}\n\u003C\u002Fstyle>\n\n```\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F7d4fa8d1d2775177b882a4656e3a5ed5.png",1818,"2022-11-22T07:54:41.000Z","2026-06-27T11:04:59.676Z",{"id":195,"name":196,"slug":197},{"id":26,"name":27,"avatar":20},[428],{"id":269,"name":270,"slug":271},{"id":430,"title":431,"slug":431,"content":432,"excerpt":431,"coverImage":433,"status":18,"isPinned":19,"pinnedAt":20,"viewCount":434,"publishedAt":435,"createdAt":435,"updatedAt":436,"category":437,"author":438,"tags":439},"b2c46bf6-d971-4cce-b21b-052dbea8e8a2","v-html使用img点击实现放大效果","## 代码实现\n```js\n\u002F**\n * JS获取html代码中所有的图片地址\n * @param htmlstr\n * @returns arr 数组\n *\u002F\n\nexport function getimgsrc(htmlstr) {\n    let reg = \u002F\u003Cimg.+?src=('|\\\")?([^'\\\"]+)('|\\\")?(?:\\s+|>)\u002Fg;\n    let arr = [];\n    let tem = 0;\n    \u002F\u002Feslint-disable-next-line\n    while ((tem = reg.exec(htmlstr))) {\n        arr.push(tem[2]); \u002F\u002F eslint-disable-line\n    }\n\n    return arr;\n}\n\n```\n\n\n\n```vue\n\u003Ctemplate>\n    \u003Cdiv class=\\\"image-expansion\\\" :class=\\\"classArr\\\">\n        \u003Cdiv @click.stop=\\\"hanldeImage($event)\\\" v-html=\\\"formatHtmlData\\\">\u003C\u002Fdiv>\n\n        \u003Cel-image-viewer\n            v-if=\\\"imgPreviewUrl\\\"\n            :initial-index=\\\"subscript\\\"\n            :src=\\\"imgPreviewUrl\\\"\n            :on-close=\\\"closeViewer\\\"\n            :url-list=\\\"imgList\\\"\n        >\u003C\u002Fel-image-viewer>\n    \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\nimport { getimgsrc } from '..\u002F..\u002Futils\u002Fgetimgsrc';\nimport ElImageViewer from 'element-ui\u002Fpackages\u002Fimage\u002Fsrc\u002Fimage-viewer';\nexport default {\n    components: {\n        ElImageViewer\n    },\n    props: {\n        htmlData: {\n            type: String,\n            default: () => {\n                return '';\n            }\n        },\n        classArr: {\n            type: Array,\n            default: () => {\n                return ['min'];\n            }\n        },\n\n        isArticle: {\n            type: Boolean,\n            default: () => {\n                return false;\n            }\n        }\n    },\n    data() {\n        return {\n            imgList: [],\n            formatHtmlData: '',\n            imgPreviewUrl: '',\n            subscript: 0\n        };\n    },\n    computed: {},\n\n    watch: {\n        \u002F\u002F监听数据，防止数据不更新\n        htmlData: {\n            handler(newName, oldName) {\n                \u002F\u002F判断是否为文章\n                if (this.isArticle) {\n                    newName ? (this.formatHtmlData = newName.replace(\u002F(\u003C([^>]+)>)\u002Fgi, '').replace(\u002F[\\\r\\\n]\u002Fg, '')) : '';\n                } else {\n                    \u002F\u002F剔除strong和p标签\n                    newName ? (this.formatHtmlData = newName.replace(\u002F(\u003C\\\u002F?strong.*?>)|(\u003C\\\u002F?p.*?>)\u002Fg, '')) : '';\n\n                    \u002F\u002F获取html全部图片，push成图片数组\n                    this.imgList = Object.values(getimgsrc(this.formatHtmlData));\n                    \u002F\u002F获取图片下标\n                    let subscript = this.imgList.indexOf(this.imgPreviewUrl);\n                    this.subscript = subscript > -1 ? subscript : 0;\n                }\n            },\n\n            immediate: true\n        }\n    },\n\n    mounted() {},\n\n    methods: {\n        \u002F\u002F监听点击事件\n        hanldeImage(event) {\n            if (event.target.nodeName === 'IMG' || event.target.nodeName === 'img') {\n                \u002F\u002F获取点击的图片url,decodeURIComponent转码一下，防禁url转码\n                this.imgPreviewUrl = decodeURIComponent(event.target.currentSrc);\n\n                \u002F\u002F获取图片下标\n                let subscript = this.imgList.indexOf(this.imgPreviewUrl);\n                this.subscript = subscript > -1 ? subscript : 0;\n\n                \u002F\u002F禁止遮罩层后面的内容滚动\n                document.documentElement.style.overflowY = 'hidden';\n            } else {\n                this.$emit('goDetail');\n            }\n        },\n\n        \u002F\u002F关闭弹框\n        closeViewer() {\n            this.imgPreviewUrl = '';\n            \u002F\u002F恢复遮罩层后面的内容滚动\n            document.documentElement.style.overflowY = 'auto';\n        }\n    }\n};\n\u003C\u002Fscript>\n\n\u003Cstyle lang=\\\"scss\\\" scoped>\n.image-expansion {\n}\n\n.min {\n    \u002Fdeep\u002F img {\n        cursor: pointer;\n        height: 28px;\n        padding: 0 10px 3px;\n    }\n}\n\n.max {\n    \u002Fdeep\u002F img {\n        cursor: pointer;\n    }\n}\n\n.class1 {\n    \u002Fdeep\u002F div {\n        font-size: 15px;\n        font-family: Microsoft YaHei;\n        font-weight: 400;\n        color: #888888;\n        line-height: 30px;\n    }\n}\n\u003C\u002Fstyle>\n\n\n```\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002Fc77c3fb113d1ab2f67e7afba1ca33b95.png",1572,"2023-01-10T07:22:29.000Z","2026-06-27T11:07:40.499Z",{"id":221,"name":222,"slug":223},{"id":26,"name":27,"avatar":20},[440],{"id":368,"name":369,"slug":369},{"id":442,"title":443,"slug":444,"content":445,"excerpt":443,"coverImage":446,"status":18,"isPinned":19,"pinnedAt":20,"viewCount":447,"publishedAt":448,"createdAt":448,"updatedAt":449,"category":450,"author":451,"tags":452},"9f33b49b-0785-4404-9a4e-d85937e7fc92","在 vue3 中优雅的使用 jsx\u002Ftsx","在-vue3-中优雅的使用-jsx-tsx","## 安装插件（@vitejs\u002Fplugin-vue-jsx）\n```shell\npnpm add @vitejs\u002Fplugin-vue-jsx -D\n```\n\n## 配置vite.config.ts\n```ts\nimport vueJsx from \\\"@vitejs\u002Fplugin-vue-jsx\\\";\n\nexport default defineConfig({\n  plugins: [\n    vueJsx(),\n  ]\n})\n```\n\n## 插值\n```language\n\u002F\u002F vue3模板语法\n\u003Cspan>{{ a + b }}\u003C\u002Fspan>\n\n\u002F\u002F jsx\u002Ftsx\n\u003Cspan>{ a + b }\u003C\u002Fspan>\n```\n\n\n## class与style 绑定\n\n```ts\n\u002F\u002F 模板字符串\n\u003Cdiv className={`header ${ isBg ? 'headerBg' : '' }`}>header\u003C\u002Fdiv>\n\u002F\u002F数组\n\u003Cdiv class={ [ 'header', isBg && 'headerBg' ] } >header\u003C\u002Fdiv>\n```\n\n```ts\nconst color = 'red'\nconst element = \u003Csapn style={{ color, fontSize: '16px' }}>style\u003C\u002Fsapn>\n```\n\n## 条件渲染\n\n```ts\n   setup() {\n       const isShow = false\n       const element = () => {\n           if (isShow) {\n               return \u003Cspan>我是if\u003C\u002Fspan>\n           } else {\n               return \u003Cspan>我是else\u003C\u002Fspan>\n           }\n       }\n       return () => (\n           \u003Cdiv>\n               \u003Cspan v-show={isShow}>我是v-show\u003C\u002Fspan>\n               {\n                   element()\n               }\n               {\n                   isShow ? \u003Cp>我是三目1\u003C\u002Fp> : \u003Cp>我是三目2\u003C\u002Fp>\n               }\n           \u003Cdiv>\n       )\n   }\n```\n\n## 列表渲染\n```language\nsetup() {\n   const listData = [\n       {name: 'Tom', age: 18},\n       {name: 'Jim', age: 20},\n       {name: 'Lucy', age: 16}\n   ]\n   return () => (\n       \u003Cdiv>\n           \u003Cdiv class={'box'}>\n               \u003Cspan>姓名\u003C\u002Fspan>\n               \u003Cspan>年龄\u003C\u002Fspan>\n           \u003C\u002Fdiv>\n           {\n               prop.listData.map(item => {\n                   return \u003Cdiv class={'box'}>\n                       \u003Cspan>{item.name}\u003C\u002Fspan>\n                       \u003Cspan>{item.age}\u003C\u002Fspan>\n                   \u003C\u002Fdiv>\n               })\n           }\n       \u003C\u002Fdiv>\n   )\n}\n\n```\n\n## 事件处理\n\n```language\nsetup() {\n    const clickBox = val => {\n        console.log(val)\n    }\n    return () => (\n        \u003Cdiv class={'box1'} onClick={() => clickBox('box1')}>\n            \u003Cspan>我是box1\u003C\u002Fspan>\n            \u003Cdiv class={'box2'} onClick={() => clickBox('box2')}>\n                \u003Cspan>我是box2\u003C\u002Fspan>\n                \u003Cdiv class={'box3'} onClick={withModifiers(() => clickBox('box3'), ['stop'])}>我是box3\u003C\u002Fdiv>\n            \u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n    )\n}\n```\n\n## v-model\n\n```ts\n\u002F\u002F 正常写法\n\u003Cinput v-model=\\\"value\\\" \u002F> \u002F\u002F vue\n\u003Cinput v-model={value} \u002F> \u002F\u002F jsx\n\n\u002F\u002F 指定绑定值写法\n\u003Cinput v-model:modelValue=\\\"value\\\" \u002F> \u002F\u002F vue\n\u003Cinput v-model={[value,'modelValue']} \u002F> \u002F\u002F jsx\n\n\u002F\u002F 修饰符写法\n\u003Cinput v-model:modelValue.trim=\\\"value\\\" \u002F> \u002F\u002F vue\n\u003Cinput v-model={[value,'modelValue',['trim']]} \u002F> \u002F\u002F jsx\n```\n\n## slot插槽\n\n### 定义插槽\n```ts\nimport { renderSlot } from \\\"vue\\\"\nexport default defineComponent({\n    \u002F\u002F 从ctx中解构出来 slots\n    setup(props, { slots }) {\n        return () => (\n            \u003Cdiv>\n                { renderSlot(slots, 'default') }\n                { slots.title?.() }\n            \u003C\u002Fdiv>\n        )\n    }\n})\n\n```\n\n### 使用插槽\n\n```ts\nimport Vslot from '.\u002FslotTem'\nexport default defineComponent({\n    setup() {\n        return () => (\n            \u003Cdiv class={'box'}>\n                \u003CVslot v-slots={{\n                    title: () => {\n                        return \u003Cp>我是title插槽\u003C\u002Fp>\n                    },\n                    default: () => {\n                        return \u003Cp>我是default插槽\u003C\u002Fp>\n                    }\n                }} \u002F>\n            \u003C\u002Fdiv>\n        )\n    }\n})\n\n```\n\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F3e4cb0d3df611ee3f57b8ed503e1015e.png",1392,"2023-04-19T09:23:27.000Z","2026-06-27T11:08:34.303Z",{"id":188,"name":189,"slug":190},{"id":26,"name":27,"avatar":20},[453],{"id":337,"name":189,"slug":190},{"id":455,"title":456,"slug":457,"content":458,"excerpt":456,"coverImage":459,"status":18,"isPinned":19,"pinnedAt":20,"viewCount":460,"publishedAt":461,"createdAt":461,"updatedAt":462,"category":463,"author":464,"tags":465},"1dcdd755-0e46-45a3-8998-9a213fd3fcd5","Vue3 导入导出Excel","vue3-导入导出excel","## 安装\n```shell\npnpm add -S XLSX\n```\n\n\n## 方法封装\n```js\nimport * as XLSX from 'xlsx';\n\n\u002F\u002F参数说明\n\u002F\u002Fconfiguration: {\n\u002F\u002F  data: [], \u002F\u002F 导出的数据\n\u002F\u002F  head: {}, \u002F\u002F 导出的数据对应的表头\n\u002F\u002F  name: '', \u002F\u002F 导出的文件名\n\u002F\u002F  label: '', \u002F\u002F 导出的表单名\n\u002F\u002F  widthArr: [], \u002F\u002F 导出的表单列宽\n\u002F\u002F}\n\n\u002F\u002F 导出excel\nexport const ExportXlsx = (configuration) => {\n  const { data, head, name, label, widthArr } = configuration;\n\n  const list = data.map((item) => {\n    const obj = {};\n    for (const k in item) {\n      if (head[k]) {\n        obj[head[k]] = item[k];\n      }\n    }\n    return obj;\n  });\n\n  \u002F\u002F 创建工作表\n  const xLSXData = XLSX.utils.json_to_sheet(list);\n  \u002F\u002F 创建工作簿\n  const wb = XLSX.utils.book_new();\n  \u002F\u002F 将工作表放入工作簿中\n  XLSX.utils.book_append_sheet(wb, xLSXData, label);\n  xLSXData['!cols'] = [];\n  \u002F\u002F 设置列宽\n  widthArr.forEach((item) => {\n    xLSXData['!cols'].push({ wpx: item });\n  });\n\n  \u002F\u002F 生成文件并下载\n  XLSX.writeFile(wb, `${name}.xlsx`);\n};\n\n\u002F\u002F 导入excel\nexport const ImportXlsx = (e) => {\n  const file = e.target.files[0];\n  const reader = new FileReader();\n  reader.readAsArrayBuffer(file);\n  reader.onload = (e) => {\n    const data = e.target.result;\n    const workbook = XLSX.read(data, { type: 'binary', cellDates: true });\n    const wsname = workbook.SheetNames[0];\n    const outdata = XLSX.utils.sheet_to_json(workbook.Sheets[wsname]);\n    console.log(outdata);\n  };\n};\n\n```\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F80231a326d3846b895a7d46c99e738ce.png",1335,"2023-03-14T06:18:59.000Z","2026-06-27T11:06:09.162Z",{"id":195,"name":196,"slug":197},{"id":26,"name":27,"avatar":20},[466],{"id":269,"name":270,"slug":271},{"id":468,"title":469,"slug":470,"content":471,"excerpt":469,"coverImage":472,"status":18,"isPinned":19,"pinnedAt":20,"viewCount":473,"publishedAt":474,"createdAt":474,"updatedAt":475,"category":476,"author":480,"tags":481},"80920598-a452-4357-bf53-842b200560e8","React18入门到精通教程","react18入门到精通教程","\n## JSX实现列表渲染\n> 使用`map()`方法遍历数组，必须添加`key`属性提高性能\n```jsx\nconst songs = [\n  { id: 1, name: \\\"helo1\\\" },\n  { id: 2, name: \\\"helo2\\\" },\n  { id: 3, name: \\\"helo3\\\" },\n];\n\nfunction App() {\n  return (\n    \u003Cdiv>\n      \u003Cul>\n        {songs.map((song) => (\n          \u002F\u002F 关键：添加唯一key标识符（避免使用索引）\n          \u003Cli key={song.id}>  \n            {song.id}-{song.name}\n          \u003C\u002Fli>\n        ))}\n      \u003C\u002Ful>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n**最佳实践：**\n1. 使用`\u003Cul>`包裹列表项\n2. `key`应使用稳定唯一标识（如ID），避免数组索引\n3. 空列表处理：`{songs.length > 0 && ...}` 或 `{songs.map(...) || \u003CEmptyView\u002F>}`\n\n## JSX实现条件渲染\n### 简单逻辑\n```jsx\n\u002F\u002F 三元表达式\n{isLoggedIn ? \u003CDashboard \u002F> : \u003CLoginForm \u002F>}\n\n\u002F\u002F 逻辑短路\n{hasNotification && \u003CNotificationBadge count={5} \u002F>}\n\n\u002F\u002F 空值处理\n{userProfile?.avatar || \u003CDefaultAvatar \u002F>}\n```\n\n### 复杂逻辑\n```jsx\nconst renderContent = (type) => {\n  switch(type) {\n    case 'success': \n      return \u003CSuccessAlert \u002F>;\n    case 'error':\n      return \u003CErrorAlert \u002F>;\n    default:\n      return \u003CInfoAlert \u002F>;\n  }\n}\n\nfunction App() {\n  return (\n    \u003Cdiv className=\\\"container\\\">\n      {renderContent(status)}\n      \n      {\u002F* 另一种模式：立即执行函数 *\u002F}\n      {(() => {\n        if (isLoading) return \u003CSpinner \u002F>;\n        if (isEmpty) return \u003CEmptyState \u002F>;\n        return \u003CDataTable \u002F>;\n      })()}\n    \u003C\u002Fdiv>\n  )\n}\n```\n\n## JSX样式处理\n### 行内样式\n```jsx\n\u002F\u002F 直接对象\n\u003Cdiv style={{ \n  color: 'white', \n  backgroundColor: 'teal',\n  padding: '1rem'\n}}>\n\n\u002F\u002F 样式对象复用\nconst alertStyle = {\n  padding: '15px',\n  borderRadius: '4px',\n  margin: '10px 0'\n};\n\nfunction Alert({ type }) {\n  return (\n    \u003Cdiv style={{\n      ...alertStyle,  \u002F\u002F 扩展运算符合并样式\n      background: type === 'error' ? '#f8d7da' : '#d4edda'\n    }}>\n      {message}\n    \u003C\u002Fdiv>\n  )\n}\n```\n\n### 类名控制（推荐）\n```css\n\u002F* styles.module.css *\u002F\n.card {\n  border: 1px solid #ddd;\n  border-radius: 8px;\n  padding: 20px;\n  box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n}\n\n.highlight {\n  background-color: #ffffe0;\n}\n```\n\n```jsx\nimport styles from '.\u002Fstyles.module.css';\n\nfunction ProductCard({ featured }) {\n  return (\n    \u003Cdiv className={`${styles.card} ${featured ? styles.highlight : ''}`}>\n      {\u002F* 内容 *\u002F}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n## React 18新特性\n### 并发模式（Concurrent Mode）\n```jsx\nimport { startTransition } from 'react';\n\n\u002F\u002F 非紧急状态更新\nfunction handleSearch(query) {\n  startTransition(() => {\n    setSearchQuery(query); \u002F\u002F 可中断的渲染\n  });\n}\n```\n\n### 自动批处理（Automatic Batching）\n```jsx\n\u002F\u002F React 17及之前：两次渲染\n\u002F\u002F React 18：自动批处理，一次渲染\nfunction handleClick() {\n  setCount(c => c + 1);\n  setFlag(f => !f);\n}\n```\n\n## Redux状态管理（现代写法）\n### 安装依赖\n```bash\nnpm install @reduxjs\u002Ftoolkit react-redux\n```\n\n### 创建Store\n```js\n\u002F\u002F store.js\nimport { configureStore, createSlice } from '@reduxjs\u002Ftoolkit';\n\nconst counterSlice = createSlice({\n  name: 'counter',\n  initialState: { value: 0 },\n  reducers: {\n    increment: state => { state.value += 1 },\n    decrement: state => { state.value -= 1 },\n    incrementByAmount: (state, action) => {\n      state.value += action.payload\n    }\n  }\n});\n\n\u002F\u002F 异步操作示例\nexport const fetchUserData = () => async (dispatch) => {\n  const response = await fetch('\u002Fapi\u002Fuser');\n  dispatch(setUser(await response.json()));\n};\n\nexport const store = configureStore({\n  reducer: {\n    counter: counterSlice.reducer,\n    \u002F\u002F 其他reducer...\n  }\n});\n\nexport const { increment, decrement } = counterSlice.actions;\n```\n\n### 组件集成\n```jsx\n\u002F\u002F index.js\nimport { Provider } from 'react-redux';\nimport { store } from '.\u002Fstore';\n\nroot.render(\n  \u003CProvider store={store}>\n    \u003CApp \u002F>\n  \u003C\u002FProvider>\n);\n\n\u002F\u002F Counter.js\nimport { useSelector, useDispatch } from 'react-redux';\nimport { increment, decrement } from '.\u002Fstore';\n\nfunction Counter() {\n  const count = useSelector(state => state.counter.value);\n  const dispatch = useDispatch();\n\n  return (\n    \u003Cdiv>\n      \u003Cbutton onClick={() => dispatch(decrement())}>-\u003C\u002Fbutton>\n      \u003Cspan>{count}\u003C\u002Fspan>\n      \u003Cbutton onClick={() => dispatch(increment())}>+\u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n## 最佳实践总结\n1. **组件设计**：遵循单一职责原则，拆分智能组件（容器组件）和展示组件\n2. **状态管理**：\n   - 局部状态用`useState`\u002F`useReducer`\n   - 全局共享状态用Redux\n   - 避免过度使用状态提升\n3. **性能优化**：\n   - 使用`React.memo`记忆组件\n   - 使用`useCallback`\u002F`useMemo`避免不必要的重渲染\n   - 虚拟化长列表（react-window）\n4. **Hooks规范**：\n   - 避免在循环\u002F条件中使用Hook\n   - 自定义Hook以`use`前缀命名\n5. **TypeScript集成**：\n   ```tsx\n   interface UserCardProps {\n     name: string;\n     age: number;\n     onSelect: (id: string) => void;\n   }\n   \n   const UserCard: React.FC\u003CUserCardProps> = ({ name, age }) => (\n     \u003Cdiv>{name} ({age})\u003C\u002Fdiv>\n   )\n   ```\n\n下面为您完善教程，增加React路由和生命周期相关内容：\n\n## React路由管理（React Router v6）\n\n### 安装与基础配置\n```bash\nnpm install react-router-dom@6\n```\n\n### 路由基础结构\n```jsx\n\u002F\u002F index.js\nimport { BrowserRouter } from 'react-router-dom';\n\nconst root = ReactDOM.createRoot(document.getElementById('root'));\nroot.render(\n  \u003CBrowserRouter>\n    \u003CApp \u002F>\n  \u003C\u002FBrowserRouter>\n);\n\n\u002F\u002F App.js\nimport { Routes, Route, Link } from 'react-router-dom';\n\nfunction App() {\n  return (\n    \u003Cdiv>\n      \u003Cnav>\n        \u003CLink to=\\\"\u002F\\\">首页\u003C\u002FLink>\n        \u003CLink to=\\\"\u002Fabout\\\">关于\u003C\u002FLink>\n        \u003CLink to=\\\"\u002Fusers\\\">用户列表\u003C\u002FLink>\n      \u003C\u002Fnav>\n      \n      \u003CRoutes>\n        \u003CRoute path=\\\"\u002F\\\" element={\u003CHome \u002F>} \u002F>\n        \u003CRoute path=\\\"\u002Fabout\\\" element={\u003CAbout \u002F>} \u002F>\n        \u003CRoute path=\\\"\u002Fusers\\\" element={\u003CUserList \u002F>} \u002F>\n        \u003CRoute path=\\\"\u002Fusers\u002F:id\\\" element={\u003CUserDetail \u002F>} \u002F>\n        \u003CRoute path=\\\"*\\\" element={\u003CNotFound \u002F>} \u002F>\n      \u003C\u002FRoutes>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### 嵌套路由\n```jsx\n\u002F\u002F Dashboard.js\nimport { Outlet } from 'react-router-dom';\n\nfunction Dashboard() {\n  return (\n    \u003Cdiv>\n      \u003Ch2>仪表盘\u003C\u002Fh2>\n      \u003COutlet \u002F> {\u002F* 子路由渲染位置 *\u002F}\n    \u003C\u002Fdiv>\n  );\n}\n\n\u002F\u002F 路由配置\n\u003CRoutes>\n  \u003CRoute path=\\\"\u002Fdashboard\\\" element={\u003CDashboard \u002F>}>\n    \u003CRoute index element={\u003CDashboardHome \u002F>} \u002F>\n    \u003CRoute path=\\\"settings\\\" element={\u003CDashboardSettings \u002F>} \u002F>\n    \u003CRoute path=\\\"analytics\\\" element={\u003CDashboardAnalytics \u002F>} \u002F>\n  \u003C\u002FRoute>\n\u003C\u002FRoutes>\n```\n\n### 编程式导航\n```jsx\nimport { useNavigate, useParams, useLocation } from 'react-router-dom';\n\nfunction UserCard({ user }) {\n  const navigate = useNavigate();\n  \n  return (\n    \u003Cdiv onClick={() => navigate(`\u002Fusers\u002F${user.id}`)}>\n      {user.name}\n    \u003C\u002Fdiv>\n  );\n}\n\nfunction UserDetail() {\n  const { id } = useParams(); \u002F\u002F 获取URL参数\n  const location = useLocation(); \u002F\u002F 获取位置对象\n  \n  return (\n    \u003Cdiv>\n      \u003Ch2>用户ID: {id}\u003C\u002Fh2>\n      \u003Cp>当前路径: {location.pathname}\u003C\u002Fp>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### 路由守卫（认证保护）\n```jsx\nimport { Navigate } from 'react-router-dom';\n\nfunction ProtectedRoute({ children }) {\n  const { isAuthenticated } = useAuth();\n  \n  if (!isAuthenticated) {\n    return \u003CNavigate to=\\\"\u002Flogin\\\" replace \u002F>;\n  }\n  \n  return children;\n}\n\n\u002F\u002F 使用\n\u003CRoute \n  path=\\\"\u002Fdashboard\\\" \n  element={\n    \u003CProtectedRoute>\n      \u003CDashboard \u002F>\n    \u003C\u002FProtectedRoute>\n  } \n\u002F>\n```\n\n## React生命周期\n\n### 类组件生命周期方法\n\n```jsx\nclass LifecycleDemo extends React.Component {\n  \u002F\u002F 1. 初始化阶段\n  constructor(props) {\n    super(props);\n    this.state = { count: 0 };\n    console.log('Constructor');\n  }\n\n  \u002F\u002F 2. 挂载阶段\n  componentDidMount() {\n    console.log('Component did mount');\n    \u002F\u002F 适合进行API调用、事件订阅\n    this.timer = setInterval(() => {\n      this.setState(prev => ({ count: prev.count + 1 }));\n    }, 1000);\n  }\n\n  \u002F\u002F 3. 更新阶段\n  shouldComponentUpdate(nextProps, nextState) {\n    console.log('Should component update?');\n    return nextState.count !== this.state.count;\n  }\n\n  componentDidUpdate(prevProps, prevState) {\n    console.log('Component did update');\n  }\n\n  \u002F\u002F 4. 卸载阶段\n  componentWillUnmount() {\n    console.log('Component will unmount');\n    \u002F\u002F 清理操作\n    clearInterval(this.timer);\n  }\n\n  render() {\n    console.log('Render');\n    return \u003Cdiv>Count: {this.state.count}\u003C\u002Fdiv>;\n  }\n}\n```\n\n### 函数组件生命周期（Hooks实现）\n\n```jsx\nimport { useState, useEffect } from 'react';\n\nfunction FunctionLifecycle() {\n  const [count, setCount] = useState(0);\n  const [data, setData] = useState(null);\n\n  \u002F\u002F 相当于componentDidMount + componentDidUpdate\n  useEffect(() => {\n    console.log('每次渲染后执行');\n  });\n\n  \u002F\u002F 相当于componentDidMount\n  useEffect(() => {\n    console.log('组件挂载后执行');\n    \n    \u002F\u002F 数据获取\n    fetch('\u002Fapi\u002Fdata')\n      .then(res => res.json())\n      .then(setData);\n    \n    \u002F\u002F 相当于componentWillUnmount\n    return () => {\n      console.log('组件卸载前执行');\n    };\n  }, []); \u002F\u002F 空依赖数组\n\n  \u002F\u002F 依赖变化时执行\n  useEffect(() => {\n    console.log('count变化时执行:', count);\n    \n    document.title = `Count: ${count}`;\n    \n    return () => {\n      console.log('清理count效果');\n    };\n  }, [count]); \u002F\u002F count依赖\n\n  return (\n    \u003Cdiv>\n      \u003Cp>Count: {count}\u003C\u002Fp>\n      \u003Cbutton onClick={() => setCount(c => c + 1)}>增加\u003C\u002Fbutton>\n      {data && \u003Cpre>{JSON.stringify(data, null, 2)}\u003C\u002Fpre>}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### 生命周期阶段对比\n\n| 阶段 | 类组件方法 | 函数组件Hook |\n|------|------------|--------------|\n| **挂载** | constructor | useState初始化 |\n|       | render      | 函数体执行    |\n|       | componentDidMount | useEffect(() => {}, []) |\n| **更新** | shouldComponentUpdate | React.memo, useMemo |\n|       | render      | 函数体执行    |\n|       | componentDidUpdate | useEffect(() => {}) |\n| **卸载** | componentWillUnmount | useEffect返回函数 |\n| **错误处理** | componentDidCatch | 暂无直接等效，需错误边界组件 |\n\n### 现代React开发建议\n\n1. **优先使用函数组件+Hooks**：\n   - 90%的场景可替代类组件\n   - 更简洁的代码结构\n   - 更好的逻辑复用\n\n2. **关键生命周期替代**：\n   - `componentDidMount` → `useEffect(() => {}, [])`\n   - `componentDidUpdate` → `useEffect(() => {})` 或带依赖的 `useEffect`\n   - `componentWillUnmount` → `useEffect(() => { return cleanup }, [])`\n   - `shouldComponentUpdate` → `React.memo` 或 `useMemo`\n\n3. **数据获取最佳实践**：\n```jsx\nuseEffect(() => {\n  let isMounted = true;\n  \n  const fetchData = async () => {\n    try {\n      const result = await api.getData();\n      if (isMounted) setData(result);\n    } catch (error) {\n      if (isMounted) setError(error);\n    }\n  };\n  \n  fetchData();\n  \n  return () => {\n    isMounted = false; \u002F\u002F 避免组件卸载后设置状态\n  };\n}, []);\n```\n\n## 路由与生命周期整合示例\n\n```jsx\nfunction UserProfile() {\n  const { id } = useParams();\n  const [user, setUser] = useState(null);\n  const [loading, setLoading] = useState(true);\n\n  useEffect(() => {\n    let isActive = true;\n    \n    const fetchUser = async () => {\n      try {\n        setLoading(true);\n        const data = await fetchUserById(id);\n        if (isActive) {\n          setUser(data);\n          setLoading(false);\n        }\n      } catch (error) {\n        if (isActive) {\n          setError(error.message);\n          setLoading(false);\n        }\n      }\n    };\n    \n    fetchUser();\n    \n    return () => {\n      isActive = false; \u002F\u002F 清理效果\n    };\n  }, [id]); \u002F\u002F id变化时重新获取\n\n  if (loading) return \u003CSpinner \u002F>;\n  \n  return (\n    \u003Cdiv>\n      \u003Ch2>{user.name}\u003C\u002Fh2>\n      \u003Cp>Email: {user.email}\u003C\u002Fp>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n## 完整项目结构建议\n\n```\nsrc\u002F\n├── components\u002F      # 通用UI组件\n├── pages\u002F           # 页面组件\n├── layouts\u002F         # 布局组件\n├── hooks\u002F           # 自定义Hooks\n├── store\u002F           # Redux状态\n│   ├── slices\u002F\n│   └── store.js\n├── services\u002F        # API服务\n├── routers\u002F         # 路由配置\n├── utils\u002F           # 工具函数\n├── assets\u002F          # 静态资源\n└── App.js           # 主应用组件\n```\n\n这些新增内容涵盖了React路由的现代用法（v6版本）以及React生命周期的详细解释，包括类组件和函数组件的实现方式对比。同时还提供了路由与生命周期整合的实际示例，帮助开发者理解如何在真实项目中应用这些概念。","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F46e205aa1bd33d1bb7201019fc2fdf43.png",1148,"2023-02-21T02:01:52.000Z","2026-06-27T11:00:41.557Z",{"id":477,"name":478,"slug":479},"f1701085-b8c1-413a-8750-58e7a0a33832","React","react",{"id":26,"name":27,"avatar":20},[482],{"id":305,"name":306,"slug":307}]