Compare commits

...

10 Commits

Author SHA1 Message Date
bunny 6555c46f0f 13-过滤器 2025-06-14 22:25:00 +08:00
bunny 24786f25f4 🎉 Vue2 数组变化监测原理与实战指南 2025-06-14 21:54:37 +08:00
bunny aa00c07eb0 11-列表过滤 2025-06-14 21:31:02 +08:00
bunny 26bd061953 10-列表渲染 2025-06-14 18:54:01 +08:00
bunny 34bd3c0a8b 🎉 9-监视属性 2025-06-14 18:47:36 +08:00
bunny 45ffbb6cdd D:\Project\Study\Java\vue-java-tutorials\vue2-tutorials\import-script\8-计算属性.md 2025-06-14 18:29:40 +08:00
bunny 5ca6962782 🎉 事件总结 2025-06-14 18:16:48 +08:00
bunny afd04a456b 理解数据代理 2025-06-14 17:13:25 +08:00
bunny 0df9decdd5 Object.defineProperty 绑定 2025-06-14 17:10:53 +08:00
bunny e81e7f2528 vue2中数据绑定和 模板语法 2025-06-14 17:02:42 +08:00
39 changed files with 17454 additions and 198 deletions

173
Vim/Vim相关命令.md Normal file
View File

@ -0,0 +1,173 @@
# Vim相关命令
## 基本移动命令
| 命令 | 描述 |
| -------- | -------------------------------- |
| `h` | 向左移动 |
| `j` | 向下移动 |
| `k` | 向上移动 |
| `l` | 向右移动 |
| `w` | 移动到下一个单词开头 |
| `W` | 移动到下一个单词开头(忽略符号) |
| `b` | 移动到上一个单词开头 |
| `B` | 移动到上一个单词开头(忽略符号) |
| `e` | 移动到当前单词末尾 |
| `E` | 移动到当前单词末尾(忽略符号) |
| `0` | 移动到行首 |
| `^` | 移动到行首第一个非空白字符 |
| `$` | 移动到行尾 |
| `gg` | 移动到文件开头 |
| `G` | 移动到文件末尾 |
| `:[n]` | 移动到第 n 行 |
| `Ctrl+f` | 向下翻页 |
| `Ctrl+b` | 向上翻页 |
| `H` | 移动到屏幕顶部 |
| `M` | 移动到屏幕中间 |
| `L` | 移动到屏幕底部 |
---
## 插入模式
| 命令 | 描述 |
| -------- | -------------------------- |
| `i` | 在光标前插入 |
| `a` | 在光标后插入 |
| `I` | 在行首插入 |
| `A` | 在行尾插入 |
| `o` | 在当前行下方新建一行并插入 |
| `O` | 在当前行上方新建一行并插入 |
| `R` | 进入替换模式(覆盖字符) |
| `Ctrl+h` | 删除前一个字符(退格键) |
| `Ctrl+w` | 删除前一个单词 |
| `Ctrl+u` | 删除到行首 |
---
## 编辑命令
| 命令 | 描述 |
| -------- | -------------------- |
| `x` | 删除当前字符 |
| `X` | 删除前一个字符 |
| `dd` | 删除当前行 |
| `dw` | 删除到下一个单词开头 |
| `db` | 删除到上一个单词开头 |
| `d$` | 删除到行尾 |
| `d^` | 删除到行首 |
| `dG` | 删除到文件末尾 |
| `dgg` | 删除到文件开头 |
| `yy` | 复制当前行 |
| `yw` | 复制到下一个单词开头 |
| `y$` | 复制到行尾 |
| `p` | 粘贴到光标后 |
| `P` | 粘贴到光标前 |
| `u` | 撤销 |
| `Ctrl+r` | 重做 |
| `.` | 重复上一次修改 |
| `J` | 合并下一行到当前行 |
| `>>` | 向右缩进 |
| `<<` | 向左缩进 |
| `==` | 自动缩进当前行 |
---
## 搜索与替换
| 命令 | 描述 |
| ---------------- | ------------------------ |
| `/pattern` | 向前搜索 pattern |
| `?pattern` | 向后搜索 pattern |
| `n` | 下一个匹配项 |
| `N` | 上一个匹配项 |
| `:%s/old/new/g` | 全局替换 old 为 new |
| `:%s/old/new/gc` | 全局替换,每次确认 |
| `:s/old/new/g` | 当前行替换 |
| `*` | 向前搜索当前光标下的单词 |
| `#` | 向后搜索当前光标下的单词 |
| `:noh` | 临时取消搜索高亮 |
---
## 可视模式(选择文本)
| 命令 | 描述 |
| ---------------- | ------------------------------ |
| `v` | 进入字符可视模式(按字符选择) |
| `V` | 进入行可视模式(按行选择) |
| `Ctrl+v` | 进入块可视模式(矩形选择) |
| `ggVG``ggvG` | **全选**整个文件 |
| `o` | 切换选择的光标端 |
| `y` | 复制选中内容 |
| `d` | 删除(剪切)选中内容 |
| `c` | 修改(删除并进入插入模式) |
| `~` | 切换选中内容的大小写 |
---
## 窗口管理
| 命令 | 描述 |
| ------------- | -------------- |
| `:sp [file]` | 水平分割窗口 |
| `:vsp [file]` | 垂直分割窗口 |
| `Ctrl+w h` | 移动到左侧窗口 |
| `Ctrl+w j` | 移动到下方窗口 |
| `Ctrl+w k` | 移动到上方窗口 |
| `Ctrl+w l` | 移动到右侧窗口 |
| `Ctrl+w w` | 循环切换窗口 |
| `Ctrl+w c` | 关闭当前窗口 |
| `Ctrl+w o` | 只保留当前窗口 |
| `Ctrl+w +` | 增加窗口高度 |
| `Ctrl+w -` | 减少窗口高度 |
| `Ctrl+w >` | 增加窗口宽度 |
| `Ctrl+w <` | 减少窗口宽度 |
| `Ctrl+w =` | 均衡窗口尺寸 |
---
## 标签页
| 命令 | 描述 |
| ---------------- | --------------------- |
| `:tabnew [file]` | 新建标签页 |
| `gt` | 下一个标签页 |
| `gT` | 上一个标签页 |
| `:tabclose` | 关闭当前标签页 |
| `:tabonly` | 关闭其他标签页 |
| `:tabmove [n]` | 移动标签页到第 n 位置 |
---
## 文件操作
| 命令 | 描述 |
| -------------- | -------------------------- |
| `:e file` | 打开文件 |
| `:w` | 保存文件 |
| `:w file` | 另存为 file |
| `:q` | 退出 |
| `:q!` | 强制退出不保存 |
| `:wq` | 保存并退出 |
| `:x` | 保存并退出(仅当有修改时) |
| `:saveas file` | 另存为 file |
| `:r file` | 插入文件内容到当前位置 |
| `:r !command` | 插入命令输出到当前位置 |
---
## 其他实用命令
| 命令 | 描述 |
| ----------------- | --------------------------------------- |
| `:set nu` | 显示行号 |
| `:set nonu` | 隐藏行号 |
| `:set hlsearch` | 高亮搜索结果 |
| `:set nohlsearch` | 取消高亮搜索结果 |
| `:set paste` | 进入粘贴模式(防止自动缩进) |
| `:set nopaste` | 退出粘贴模式 |
| `:!command` | 执行外部命令 |
| `:help` | 打开帮助文档 |
| `Ctrl+g` | 显示当前文件信息 |
| `:%!command` | 用命令处理整个文件(如 `:%!sort` 排序) |

View File

@ -1,60 +0,0 @@
## **1. 基础移动Navigation**
- **字符移动**
- `h` ← / `j` ↓ / `k` ↑ / `l`
- 方向键也能用,但 Vim 提倡用 `hjkl` 保持手不离键盘。
- **单词移动**
- `w` → 跳到下一个单词开头
- `b` ← 跳到上一个单词开头
- `e` → 跳到当前单词末尾
- **行内跳转**
- `0` 跳到行首,`^` 跳到第一个非空字符
- `$` 跳到行尾
- `f<char>` 向后搜索字符(如 `fa` 跳到下一个 `a`
- **屏幕移动**
- `Ctrl + u` 上翻半屏,`Ctrl + d` 下翻半屏
- `gg` 跳到文件开头,`G` 跳到文件末尾
- `:<行号>` 跳转到指定行(如 `:42` 跳到第 42 行)
---
## **2. 编辑文本Editing**
- **插入模式**
- `i` 在光标前插入
- `a` 在光标后插入
- `I` 在行首插入,`A` 在行尾插入
- `o` 在下一行插入,`O` 在上一行插入
- **删除**
- `x` 删除当前字符
- `dw` 删除一个单词
- `dd` 删除整行
- `D` 删除从光标到行尾
- **复制/粘贴**
- `yy` 复制当前行
- `p` 粘贴到光标后,`P` 粘贴到光标前
- `yiw` 复制当前单词
- **撤销/重做**
- `u` 撤销
- `Ctrl + r` 重做
---
## **3. 选择与可视化模式Visual Mode**
- `v` 进入字符选择模式
- `V` 进入行选择模式
- `Ctrl + v` 进入块选择模式(适合多行编辑)
- 选中后可以用 `d`(删除)、`y`(复制)、`>`(缩进)等操作
---
## **4. 搜索与替换Search & Replace**
- `/` + 关键词 → 搜索(`n` 下一个,`N` 上一个)
- `:%s/old/new/g` → 全局替换(`g` 表示所有匹配)
- `*` 快速搜索当前光标下的单词
---
## **5. 组合命令Power Moves**
- `di"` → 删除引号内的内容
- `ciw` → 修改当前单词
- `dt<char>` → 删除直到某个字符(如 `dt)` 删除到 `)`
- `.` → 重复上一次操作

View File

@ -1,109 +0,0 @@
## 基本移动命令
| 命令 | 描述 |
| ------ | -------------------------- |
| `h` | 向左移动 |
| `j` | 向下移动 |
| `k` | 向上移动 |
| `l` | 向右移动 |
| `w` | 移动到下一个单词开头 |
| `b` | 移动到上一个单词开头 |
| `e` | 移动到当前单词末尾 |
| `0` | 移动到行首 |
| `^` | 移动到行首第一个非空白字符 |
| `$` | 移动到行尾 |
| `gg` | 移动到文件开头 |
| `G` | 移动到文件末尾 |
| `:[n]` | 移动到第 n 行 |
## 插入模式
| 命令 | 描述 |
| ---- | -------------------------- |
| `i` | 在光标前插入 |
| `a` | 在光标后插入 |
| `I` | 在行首插入 |
| `A` | 在行尾插入 |
| `o` | 在当前行下方新建一行并插入 |
| `O` | 在当前行上方新建一行并插入 |
## 编辑命令
| 命令 | 描述 |
| -------- | -------------------- |
| `x` | 删除当前字符 |
| `dd` | 删除当前行 |
| `dw` | 删除到下一个单词开头 |
| `d$` | 删除到行尾 |
| `d^` | 删除到行首 |
| `yy` | 复制当前行 |
| `p` | 粘贴 |
| `u` | 撤销 |
| `Ctrl+r` | 重做 |
| `.` | 重复上一次修改 |
## 搜索与替换
| 命令 | 描述 |
| ---------------- | ------------------- |
| `/pattern` | 向前搜索 pattern |
| `?pattern` | 向后搜索 pattern |
| `n` | 下一个匹配项 |
| `N` | 上一个匹配项 |
| `:%s/old/new/g` | 全局替换 old 为 new |
| `:%s/old/new/gc` | 全局替换,每次确认 |
## 可视模式
| 命令 | 描述 |
| -------- | ---------------- |
| `v` | 进入字符可视模式 |
| `V` | 进入行可视模式 |
| `Ctrl+v` | 进入块可视模式 |
## 窗口管理
| 命令 | 描述 |
| ------------- | -------------- |
| `:sp [file]` | 水平分割窗口 |
| `:vsp [file]` | 垂直分割窗口 |
| `Ctrl+w h` | 移动到左侧窗口 |
| `Ctrl+w j` | 移动到下方窗口 |
| `Ctrl+w k` | 移动到上方窗口 |
| `Ctrl+w l` | 移动到右侧窗口 |
| `Ctrl+w c` | 关闭当前窗口 |
| `Ctrl+w o` | 只保留当前窗口 |
## 标签页
| 命令 | 描述 |
| ---------------- | -------------- |
| `:tabnew [file]` | 新建标签页 |
| `gt` | 下一个标签页 |
| `gT` | 上一个标签页 |
| `:tabclose` | 关闭当前标签页 |
## 文件操作
| 命令 | 描述 |
| --------- | -------------- |
| `:e file` | 打开文件 |
| `:w` | 保存文件 |
| `:w file` | 另存为 file |
| `:q` | 退出 |
| `:q!` | 强制退出不保存 |
| `:wq` | 保存并退出 |
| `:x` | 保存并退出 |
## 其他实用命令
| 命令 | 描述 |
| ----------------- | --------------------------------- |
| `*` | 搜索当前光标下的单词 |
| `#` | 反向搜索当前光标下的单词 |
| `:%!command` | 将当前缓冲区内容通过 command 过滤 |
| `:r!command` | 将 command 的输出插入到当前位置 |
| `:set nu` | 显示行号 |
| `:set nonu` | 隐藏行号 |
| `:set hlsearch` | 高亮搜索结果 |
| `:set nohlsearch` | 取消高亮搜索结果 |

View File

@ -69,6 +69,5 @@
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -49,4 +49,4 @@ spring:
param: user
value: bunny
# filters:
# - RedirectTo=/api/order/?(?<segment>.*), /$\{segment}
# - RedirectTo=/api/order/?(?<segment>.*), /$\{segment}

View File

@ -7,7 +7,6 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
// Feign 客户端
// @FeignClient(value = "service-product", path = "/api/product", fallback = ProductFeignClientFallback.class)
@FeignClient(value = "gateway", path = "/api/product", fallback = ProductFeignClientFallback.class)
public interface ProductFeignClient {

1104
interview/Base.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -13,20 +13,7 @@
<version>0.0.1-SNAPSHOT</version>
<name>mq-demo</name>
<description>mq-demo</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
<spring-modulith.version>1.3.5</spring-modulith.version>

View File

@ -3,26 +3,28 @@ package cn.bunny.mq.mqdemo.mq.listener;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;
import java.io.IOException;
import static cn.bunny.mq.mqdemo.domain.RabbitMQMessageListenerConstants.*;
@Component
@Slf4j
public class MessageListenerOrder {
// /* 测试这个需要注释下main那个 */
// @RabbitListener(bindings = @QueueBinding(
// exchange = @Exchange(value = EXCHANGE_DIRECT),
// value = @Queue(value = QUEUE_NAME, durable = "true"),
// key = ROUTING_KEY_DIRECT,
// arguments = @Argument(name = "alternate-exchange", value = ALTERNATE_EXCHANGE_BACKUP)
// )
// )
// public void processMessage(String dataString, Message message, Channel channel) {
// System.out.println("消费端接受消息:" + dataString);
// }
/* 测试这个需要注释下main那个 */
@RabbitListener(bindings = @QueueBinding(
exchange = @Exchange(value = EXCHANGE_DIRECT),
value = @Queue(value = QUEUE_NAME, durable = "true"),
key = ROUTING_KEY_DIRECT,
arguments = @Argument(name = "alternate-exchange", value = ALTERNATE_EXCHANGE_BACKUP)
)
)
public void processMessage(String dataString, Message message, Channel channel) {
System.out.println("消费端接受消息:" + dataString);
}
// /* 如果测试这个需要注释上面那个 */
// @RabbitListener(queues = {QUEUE_NAME})

View File

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="js/vue@2.7.16.js"></script>
<title>模板语法</title>
</head>
<body>
<div id="app">
<h1>插值语法</h1>
<h3>你好:{{name}}</h3>
<br />
<h1>指令语法</h1>
<a :href="url" target="_blank">v2.cn.vuejs.org</a>
<!-- v-bind 可以省略 -->
<a v-bind:href="url" target="_blank">v2.cn.vuejs.org</a>
</div>
</body>
<script>
new Vue({
el: "#app",
data: {
name: "Bunny",
url: "https://v2.cn.vuejs.org/",
},
});
</script>
</html>

View File

@ -0,0 +1,21 @@
## Vue2 模板语法详解
在 Vue2 中,使用双大括号 `{{}}` 的语法称为插值语法Mustache 语法),它用于在模板中显示数据。
### 基本用法
```html
<h3>你好:{{name}}</h3>
```
### 实现原理
当 Vue 实例创建时,会将 data 对象中的属性转换为 getter/setter使其成为响应式数据。当数据变化时视图会自动更新。
```javascript
new Vue({
el: "#app",
data: {
name: "Bunny", // 响应式数据
url: "https://v2.cn.vuejs.org/"
}
})
```

View File

@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="js/vue@2.7.16.js"></script>
<title>10-列表渲染</title>
</head>
<body>
<div id="app">
<ul>
<!-- 可以遍历数组、对象、字符串 -->
<li v-for="(item,index) in list" :key="index">
{{item}}--{{item.name}}--{{index}}
</li>
</ul>
<br>
<ul>
<li v-for="(item,index) of person" :key="index ">
{{item}}--{{index}}
</li>
</ul>
</div>
</body>
<script>
const vm = new Vue({
el: "#app",
data: {
list: [
{ id: 1, name: "1" },
{ id: 2, name: "2" },
{ id: 3, name: "3" },
{ id: 4, name: "4" },
],
person: {
name: "Bunny",
age: 16
}
}
})
</script>
</html>

View File

@ -0,0 +1,158 @@
# Vue2 列表渲染v-for详解文档
## 一、基本概念
Vue2 中的 `v-for` 指令用于基于源数据多次渲染元素或模板块。它可以遍历数组、对象和字符串,是构建动态列表的核心指令。
## 二、代码示例解析
```html
<div id="app">
<!-- 遍历数组 -->
<ul>
<li v-for="(item, index) in list" :key="item.id">
{{item}}--{{item.name}}--{{index}}
</li>
</ul>
<!-- 遍历对象 -->
<ul>
<li v-for="(value, key, index) of person" :key="key">
{{value}}--{{key}}--{{index}}
</li>
</ul>
</div>
<script>
const vm = new Vue({
el: "#app",
data: {
list: [
{ id: 1, name: "1" },
{ id: 2, name: "2" },
{ id: 3, name: "3" },
{ id: 4, name: "4" }
],
person: {
name: "Bunny",
age: 16
}
}
});
</script>
```
## 三、核心语法
### 1. 遍历数组
```html
<element v-for="(item, index) in array" :key="uniqueId">
{{ index }}: {{ item }}
</element>
```
### 2. 遍历对象
```html
<element v-for="(value, key, index) in object" :key="key">
{{ index }}. {{ key }}: {{ value }}
</element>
```
### 3. 遍历字符串
```html
<element v-for="(char, index) in string" :key="index">
{{ index }}: {{ char }}
</element>
```
## 四、关键特性
| 特性 | 说明 | 示例 |
| --------- | ---------------- | ----------------------- |
| `in`/`of` | 两种语法等效 | `v-for="item in items"` |
| 索引参数 | 可选的第二个参数 | `(item, index)` |
| `:key` | 必须的唯一标识 | `:key="item.id"` |
| 嵌套循环 | 支持多层嵌套 | 嵌套使用 `v-for` |
## 五、最佳实践
### 1. 正确的 key 使用
- **必须**为每个节点提供唯一的 `key` 属性
- **避免**使用索引作为 key当列表会变化时
- **理想** key 应该是数据中的唯一 ID
```html
<!-- 推荐 -->
<li v-for="item in items" :key="item.id">
<!-- 不推荐 -->
<li v-for="(item, index) in items" :key="index">
```
### 2. 性能优化
- 对大列表使用虚拟滚动(如 vue-virtual-scroller
- 避免在 `v-for` 中使用复杂表达式
- 必要时使用 `v-if``v-for` 分离Vue2 中 `v-for` 优先级更高)
### 3. 数组更新检测
Vue 能检测以下数组方法的变化:
- `push()`
- `pop()`
- `shift()`
- `unshift()`
- `splice()`
- `sort()`
- `reverse()`
## 六、高级用法
### 1. 范围迭代
```html
<span v-for="n in 10">{{ n }} </span>
```
### 2. 组件中使用
```html
<my-component
v-for="(item, index) in items"
:item="item"
:index="index"
:key="item.id">
</my-component>
```
### 3. 过滤/排序列表
```javascript
computed: {
filteredItems() {
return this.items.filter(item => item.isActive);
},
sortedItems() {
return [...this.items].sort((a, b) => a.price - b.price);
}
}
```
## 七、常见问题
1. **为什么列表不更新?**
- 确保使用了变异方法修改数组
- 对于非变异方法,使用新数组替换旧数组
- 对象属性添加需要使用 `Vue.set`
2. **`v-if``v-for` 一起使用?**
- Vue2 中不推荐同一元素使用两者
- 解决方案:
```html
<template v-for="item in items">
<div v-if="item.isVisible" :key="item.id">
{{ item.name }}
</div>
</template>
```
3. **如何获取当前元素?**
- 通过事件传递:
```html
<div v-for="item in items" @click="handleClick(item)">
```

View File

@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="js/vue@2.7.16.js"></script>
<title>列表过滤</title>
</head>
<body>
<div id="app">
<h1>列表过滤</h1>
<input type="text" v-model="keyword" />
<button @click="sortType = 2">年龄升序</button>
<button @click="sortType = 1">年龄降序</button>
<button @click="sortType = 0">重置</button>
<ul>
<li v-for="(item,index) in filterPersion" :key="index">
{{item.name}}---{{item.age}}---{{item.sex}}
</li>
</ul>
</div>
</body>
<script>
const vm = new Vue({
el: "#app",
data: {
sortType: 0,
keyword: "",
list: [
{ id: "001", name: "Bunny-001", age: 19, sex: "男" },
{ id: "002", name: "Bunny-002", age: 16, sex: "男" },
{ id: "003", name: "Bunny-003", age: 20, sex: "女" },
{ id: "004", name: "Bunny-004", age: 18, sex: "男" },
{ id: "005", name: "Bunny-005", age: 19, sex: "女" },
],
},
computed: {
filterPersion() {
let newList = this.list.filter(
(persion) => persion.name.indexOf(this.keyword) !== -1
);
if (this.sortType != 0) {
newList = newList.sort((a, b) =>
this.sortType == 2 ? a.age - b.age : b.age - a.age
);
}
return newList;
},
},
methods: {},
});
</script>
</html>

View File

@ -0,0 +1,168 @@
# Vue2 列表过滤与排序综合文档
## 一、功能概述
本示例展示了 Vue2 中如何实现列表的实时过滤与动态排序功能,结合计算属性实现了高效的数据处理。
## 二、代码实现解析
```html
<div id="app">
<h1>列表过滤</h1>
<!-- 搜索框 -->
<input type="text" v-model="keyword">
<!-- 排序按钮 -->
<button @click="sortType = 2">年龄升序</button>
<button @click="sortType = 1">年龄降序</button>
<button @click="sortType = 0">重置</button>
<!-- 结果列表 -->
<ul>
<li v-for="(item,index) in filterPersion" :key="item.id">
{{item.name}}---{{item.age}}---{{item.sex}}
</li>
</ul>
</div>
<script>
const vm = new Vue({
el: "#app",
data: {
sortType: 0, // 0-默认 1-降序 2-升序
keyword: "", // 搜索关键词
list: [
{ id: "001", name: "Bunny-001", age: 19, sex: "男" },
{ id: "002", name: "Bunny-002", age: 16, sex: "男" },
// ...其他数据
]
},
computed: {
filterPersion() {
// 1. 过滤
let newList = this.list.filter(person =>
person.name.includes(this.keyword)
);
// 2. 排序
if (this.sortType !== 0) {
newList.sort((a, b) =>
this.sortType === 2 ? a.age - b.age : b.age - a.age
);
}
return newList;
}
}
});
</script>
```
## 三、核心机制详解
### 1. 数据流设计
```
原始数据 (list)
[过滤] → 根据 keyword 筛选
[排序] → 根据 sortType 排序 (可选)
渲染列表 (filterPersion)
```
### 2. 计算属性优势
- **自动缓存**:依赖项未变化时不重新计算
- **响应式**keyword 或 sortType 变化自动更新
- **声明式**:模板保持简洁,逻辑集中管理
### 3. 排序算法优化
```javascript
// 三元运算符实现升降序切换
(a, b) => this.sortType === 2 ? a.age - b.age : b.age - a.age
```
## 四、最佳实践建议
### 1. 性能优化
- **避免深拷贝**:直接操作过滤后的数组
- **防抖处理**:搜索框添加防抖(使用 lodash.debounce
- **分页加载**:大数据集应配合分页
### 2. 代码改进
```javascript
// 更安全的字符串包含检查
person.name.toLowerCase().includes(this.keyword.toLowerCase())
// 可配置的排序函数
const sortStrategies = {
1: (a, b) => b.age - a.age,
2: (a, b) => a.age - b.age
};
newList.sort(sortStrategies[this.sortType]);
```
### 3. Key 的使用
```html
<!-- 使用唯一id而非index作为key -->
<li v-for="item in filterPersion" :key="item.id">
```
## 五、扩展功能实现
### 1. 多条件过滤
```javascript
filterPersion() {
return this.list.filter(person => {
const nameMatch = person.name.includes(this.keyword);
const ageMatch = this.filterAge === '' || person.age == this.filterAge;
return nameMatch && ageMatch;
});
}
```
### 2. 多列排序
```javascript
data() {
return {
sortConfig: { field: 'age', order: 1 } // 1升序-1降序
}
},
computed: {
sortedList() {
return [...this.filteredList].sort((a, b) => {
return a[this.sortConfig.field] > b[this.sortConfig.field]
? this.sortConfig.order : -this.sortConfig.order;
});
}
}
```
### 3. 异步数据加载
```javascript
async loadData() {
this.list = await fetch('/api/persons').then(res => res.json());
}
```
## 六、常见问题解决方案
1. **列表不更新问题**
- 确保使用变异方法修改原始数组
- 或使用 `Vue.set` 更新数组元素
2. **排序状态保持**
```javascript
created() {
this.sortType = Number(localStorage.getItem('sortType')) || 0;
},
watch: {
sortType(newVal) {
localStorage.setItem('sortType', newVal);
}
}
```
3. **空搜索结果处理**
```html
<div v-if="filterPersion.length === 0">暂无匹配结果</div>
```

View File

@ -0,0 +1,61 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="js/vue@2.7.16.js"></script>
<title>Vue监测数据的原理_数组</title>
</head>
<body>
<div id="app">
<h1>列表过滤</h1>
<button @click="changeList">修改</button>
<ul>
<li v-for="(item,index) in list" :key="index">
{{item.name}}---{{item.age}}---{{item.sex}}
</li>
</ul>
</div>
</body>
<script>
const vm = new Vue({
el: "#app",
data: {
list: [
{ id: "001", name: "Bunny-001", age: 19, sex: "男" },
{ id: "002", name: "Bunny-002", age: 16, sex: "男" },
{ id: "003", name: "Bunny-003", age: 20, sex: "女" },
{ id: "004", name: "Bunny-004", age: 18, sex: "男" },
{ id: "005", name: "Bunny-005", age: 19, sex: "女" },
],
},
methods: {
changeList() {
// 无法修改数组,显示不出来修改
// this.list[0] = { id: "002", name: "Bunny-002", age: 16, sex: "男" };
// 可以这样修改
// 详细参考 https://v2.cn.vuejs.org/v2/guide/list.html#%E5%8F%98%E6%9B%B4%E6%96%B9%E6%B3%95
// this.list.splice(0, 1, {
// id: "0021",
// name: "Bunny-0021",
// age: 16,
// sex: "男",
// });
// 或者这样修改
// Vue.$set(this.list, 0, {
this.$set(this.list, 0, {
id: "0021",
name: "Bunny-0021",
age: 16,
sex: "男",
});
},
},
});
</script>
</html>

View File

@ -0,0 +1,175 @@
# Vue2 数组变化监测原理与实战指南
## 一、核心原理
Vue2 通过重写数组的变异方法实现对数组变化的监测,这是其响应式系统的关键部分。当直接通过索引修改数组元素时(如 `arr[0] = newValue`Vue 无法自动检测到这种变化。
## 二、代码示例解析
```html
<div id="app">
<h1>列表更新演示</h1>
<button @click="changeList">修改数组</button>
<ul>
<li v-for="(item,index) in list" :key="item.id">
{{item.name}}---{{item.age}}---{{item.sex}}
</li>
</ul>
</div>
<script>
const vm = new Vue({
el: "#app",
data: {
list: [
{ id: "001", name: "Bunny-001", age: 19, sex: "男" },
// ...其他初始数据
],
},
methods: {
changeList() {
// ❌ 无效的修改方式
// this.list[0] = { id: "002", name: "Bunny-002", age: 16, sex: "男" };
// ✅ 方式1使用变异方法
this.list.splice(0, 1, {
id: "0021",
name: "Bunny-0021",
age: 16,
sex: "男",
});
// ✅ 方式2使用Vue.set/$set
// Vue.$set(this.list, 0, {
this.$set(this.list, 0, {
id: "0021",
name: "Bunny-0021",
age: 16,
sex: "男",
});
},
},
});
</script>
```
## 三、响应式数组操作方法
### 1. Vue 包装的变异方法
Vue 重写了以下数组方法,使其能触发视图更新:
- `push()` / `pop()`
- `shift()` / `unshift()`
- `splice()`
- `sort()` / `reverse()`
### 2. 特殊场景处理
| 场景 | 正确方法 | 错误方法 |
| -------- | ------------------------------- | ----------------------- |
| 修改元素 | `Vue.set(arr, index, newValue)` | `arr[index] = newValue` |
| 添加元素 | `arr.splice(index, 0, newItem)` | `arr[index] = newItem` |
| 删除元素 | `arr.splice(index, 1)` | `delete arr[index]` |
### 3. 注意事项
- 使用 `filter()`、`concat()` 等非变异方法时,需要用返回的新数组替换原数组
- 嵌套数组需要深度观测
## 四、原理深入
### 1. 实现机制
Vue 通过以下步骤实现数组响应式:
1. 拦截数组原型方法
2. 在方法执行后通知依赖更新
3. 对新增元素进行响应式处理
### 2. 源码关键部分
```javascript
// 简化版的数组响应式实现
const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);
const methodsToPatch = [
"push",
"pop",
"shift",
"unshift",
"splice",
"sort",
"reverse",
];
methodsToPatch.forEach(function (method) {
const original = arrayProto[method];
def(arrayMethods, method, function mutator(...args) {
const result = original.apply(this, args);
const ob = this.__ob__;
let inserted;
switch (method) {
case "push":
case "unshift":
inserted = args;
break;
case "splice":
inserted = args.slice(2);
break;
}
if (inserted) ob.observeArray(inserted);
ob.dep.notify(); // 通知更新
return result;
});
});
```
## 五、最佳实践
### 1. 性能优化
- 大数据量操作时使用 `Vue.nextTick` 批量更新
- 避免在模板中直接操作复杂数组计算
### 2. 代码规范
- 统一使用 `Vue.set``splice` 修改数组
- 为 `v-for` 设置合适的 `key`
### 3. 扩展方法
```javascript
// 安全的数组修改方法
function safeArrayUpdate(arr, index, newValue) {
if (Array.isArray(arr)) {
return Vue.set(arr, index, newValue);
}
throw new Error("Target is not an array");
}
```
## 六、常见问题解决方案
1. **为什么我的数组修改不生效?**
- 检查是否使用了 Vue 能检测的变异方法
- 确认没有直接修改数组长度(如 `arr.length = 0`
2. **如何强制更新数组?**
```javascript
// 方法1使用空splice触发更新
this.list.splice();
// 方法2使用Vue.set
Vue.set(this.list, 0, this.list[0]);
```
3. **对象数组中的对象属性修改**
- 直接修改对象属性可以触发更新(因为对象是响应式的)
```javascript
// 这是有效的
this.list[0].name = "new name";
```

View File

@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="js/vue@2.7.16.js"></script>
<script src="js/dayjs.min.js"></script>
<title>过滤器</title>
</head>
<body>
<div id="app">
<!-- 管道符只能使用在filters中 -->
<h3>现在是:{{time | timeFormater}}</h3>
<h3>现在是:{{time | timeFormater("YYYY年MM月DD日 HH:mm:ss")}}</h3>
<h3>
现在是:{{time | timeFormater("YYYY年MM月DD日 HH:mm:ss") |
timeFormaterSlice}}
</h3>
<!-- 不能使用在v-model -->
<h3 :peiqi="message | globeTimeFormaterSlice">
使用全局过滤器:{{message | globeTimeFormaterSlice}}
</h3>
</div>
</body>
<script>
// 定义全局过滤器
Vue.filter("globeTimeFormaterSlice", function (val) {
return val.slice(0, 4);
});
const vm = new Vue({
el: "#app",
data: {
time: new Date(),
message: "全局的过滤器使用在标签上...",
},
methods: {},
filters: {
/* 时间格式化 */
timeFormater(val, str = "YYYY-MM-DD HH:mm:ss") {
return dayjs(val).format(str);
},
timeFormaterSlice(val) {
return val.slice(0, 4);
},
},
});
</script>
</html>

View File

@ -0,0 +1,143 @@
# Vue2 过滤器(Filter)使用指南
## 一、基本概念
过滤器(Filters)是Vue2中用于文本格式化处理的特殊函数可以在模板中使用管道符(`|`)对数据进行转换处理。
## 二、代码示例解析
```html
<div id="app">
<!-- 使用局部过滤器 -->
<h3>现在是:{{time | timeFormater}}</h3>
<!-- 传参的过滤器 -->
<h3>现在是:{{time | timeFormater("YYYY年MM月DD日 HH:mm:ss")}}</h3>
<!-- 过滤器链式调用 -->
<h3>现在是:{{time | timeFormater("YYYY年MM月DD日 HH:mm:ss") | timeFormaterSlice}}</h3>
<!-- 使用全局过滤器 -->
<h3 :peiqi="message | globeTimeFormaterSlice">
使用全局过滤器:{{message | globeTimeFormaterSlice}}
</h3>
</div>
<script>
// 全局过滤器定义
Vue.filter("globeTimeFormaterSlice", function(val) {
return val.slice(0, 4);
});
const vm = new Vue({
el: "#app",
data: {
time: new Date(),
message: "全局的过滤器使用在标签上..."
},
filters: {
// 局部过滤器定义
timeFormater(val, str = "YYYY-MM-DD HH:mm:ss") {
return dayjs(val).format(str);
},
timeFormaterSlice(val) {
return val.slice(0, 4);
}
}
});
</script>
```
## 三、核心特性
### 1. 过滤器类型
| 类型 | 定义位置 | 作用域 | 示例 |
| ---------- | ---------------------- | -------- | --------------------------- |
| 局部过滤器 | Vue实例的`filters`选项 | 当前组件 | `{{ data | localFilter }}` |
| 全局过滤器 | `Vue.filter()` | 所有组件 | `{{ data | globalFilter }}` |
### 2. 使用方式
| 方式 | 语法 | 说明 |
| -------- | --------------------------------- | ------------------ |
| 基本使用 | `{{ data | filter }}` | 单个过滤器 |
| 传参使用 | `{{ data | filter(arg1, arg2) }}` | 可传递额外参数 |
| 链式调用 | `{{ data | filter1 | filter2 }}` | 多个过滤器依次处理 |
## 四、最佳实践
### 1. 命名规范
- 使用动词+名词形式(如`formatDate`、`truncateText`
- 全局过滤器加前缀(如`globe_`)避免命名冲突
### 2. 适用场景
- 文本格式化(日期、货币等)
- 数据简单转换(截取、大小写转换等)
- 显示内容的修饰(添加前缀/后缀)
### 3. 注意事项
- **不能**在`v-model`中使用
- **避免**复杂业务逻辑,复杂处理应使用计算属性
- **推荐**纯函数实现,不要修改原始数据
## 五、高级用法
### 1. 动态过滤器名
```javascript
filters: {
dynamicFilter(val, filterName) {
return this[filterName](val);
}
}
```
### 2. 过滤器工厂函数
```javascript
function createPrefixFilter(prefix) {
return function(val) {
return prefix + val;
}
}
Vue.filter('withPrefix', createPrefixFilter('Info: '));
```
### 3. 结合计算属性使用
```javascript
computed: {
formattedTime() {
return this.$options.filters.timeFormater(this.time);
}
}
```
## 六、与Vue3的区别
| 特性 | Vue2 | Vue3 |
| ---------- | ------ | ----------------- |
| 过滤器支持 | ✅ 支持 | ❌ 移除 |
| 替代方案 | - | 使用方法/计算属性 |
## 七、常见问题
1. **为什么过滤器不生效?**
- 检查过滤器名称拼写
- 确认是否在正确的作用域定义
- 确保返回值不是undefined
2. **如何调试过滤器?**
```javascript
filters: {
debugFilter(val) {
console.log('Filter input:', val);
return val;
}
}
```
3. **过滤器能接收多个参数吗?**
```html
<!-- 可以,第一个参数永远是管道符前的值 -->
{{ data | filter(arg1, arg2) }}
```

View File

@ -0,0 +1,64 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="js/vue@2.7.16.js"></script>
<title>数据绑定</title>
</head>
<body>
<div id="app">
单项数据绑定:<input type="text" v-bind:value="name">
双向数据绑定:<input type="text" v-model="name">
</div>
<div id="root">
2秒后绑定到这个上
单项数据绑定:<input type="text" v-bind:value="name">
双向数据绑定:<input type="text" v-model="name">
</div>
</body>
<script>
// 只要vm有的内容都可以用
const v = new Vue({
// el: "#app",
// data: {
// name: "数据绑定"
// },
// 不能用箭头函数
// data: function () {
// return {
// name: "数据绑定"
// }
// }
data() {
return {
name: "数据绑定"
}
}
});
// 下面内容可以简写成 v-if形式
setTimeout(() => {
v.$mount("#app");
console.log("app");
}, 1000);
setTimeout(() => {
// 正确做法:销毁旧实例,创建新实例
v.$destroy();
const v2 = new Vue({
data: {
name: "新数据绑定"
}
});
v2.$mount("#root");
console.log("root");
}, 2000);
</script>
</html>

View File

@ -0,0 +1,95 @@
# Vue2 数据绑定
本示例展示了 Vue2 中的两种数据绑定方式:
- 单向数据绑定 (`v-bind`)
- 双向数据绑定 (`v-model`)
## 核心概念详解
### 1. 数据绑定类型
| 类型 | 指令 | 特点 | 示例 |
| -------- | --------- | ----------------- | ----------------------------- |
| 单向绑定 | `v-bind` | 数据→视图单向流动 | `<input v-bind:value="name">` |
| 双向绑定 | `v-model` | 数据⇄视图双向同步 | `<input v-model="name">` |
### 2. Vue 实例创建方式
```javascript
// 推荐写法 (ES6简写)
data() {
return {
name: "数据绑定"
}
}
// 等价于
data: function() {
return {
name: "数据绑定"
}
}
// 不推荐在组件中使用
data: {
name: "数据绑定"
}
```
### 3. 动态挂载机制
```javascript
// 延迟挂载示例
setTimeout(() => {
v.$mount("#app"); // 手动挂载到#app
}, 1000);
// 实例销毁与重建
setTimeout(() => {
v.$destroy(); // 销毁旧实例
const v2 = new Vue({ // 创建新实例
data: { name: "新数据绑定" }
});
v2.$mount("#root"); // 挂载到新位置
}, 2000);
```
1. **数据绑定选择**
- 表单元素使用 `v-model` 实现双向绑定
- 普通属性使用 `v-bind` 实现单向绑定
2. **实例管理**
- 避免在同一个元素上重复挂载不同实例
- 切换挂载目标时务必先销毁旧实例
3. **数据定义**
- 组件中必须使用函数形式返回data对象
- 根实例可以使用对象形式
4. **性能优化**
- 大量数据绑定时考虑使用计算属性
- 复杂场景可使用自定义指令优化
## 扩展说明
### 1. `v-model` 原理
`v-model` 本质上是语法糖,等价于:
```html
<input
:value="name"
@input="name = $event.target.value">
```
### 2. 挂载方式对比
| 方式 | 示例 | 适用场景 |
| ---------- | ------------------- | -------------------- |
| el选项 | `el: "#app"` | 初始化时确定挂载点 |
| $mount方法 | `vm.$mount("#app")` | 需要延迟或条件挂载时 |
### 3. 实例生命周期
- `new Vue()` 创建实例
- `$mount()` 触发挂载流程
- `$destroy()` 触发销毁流程
通过本文档,您可以全面了解 Vue2 的数据绑定机制和实例管理方法,为实际开发提供参考。

View File

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Object.defineProperty</title>
</head>
<body>
</body>
<script>
let age = 18
const person = {
name: "Bunny",
sex: "男",
// age: 16
}
Object.defineProperty(person, 'age', {
// value: 18,// 赋值
// enumerable: true,// 控制是否可以被枚举
// writable: true,// 控制是否可以被修改
// configurable: true,// 控制是否可以被删除
// 修改属性时触发
get() {
console.log("修改属性时触发");
return age
},
// 修改age时触发
set(value) {
console.log("修改age时触发");
age = value
}
})
console.log(person);
</script>
</html>

View File

@ -0,0 +1,76 @@
# `Object.defineProperty` 方法
## 一、基本概念
`Object.defineProperty()` 是 JavaScript 中用于直接在一个对象上定义一个新属性,或者修改一个对象的现有属性的方法。它允许精确控制属性的行为特性。
## 属性描述符详解
### 1. 数据描述符(已注释部分)
```javascript
{
value: 18, // 属性值
enumerable: true, // 是否可枚举for...in或Object.keys()
writable: true, // 是否可修改
configurable: true // 是否可删除或修改特性
}
```
### 2. 存取描述符(实际使用部分)
```javascript
{
get() {
// 读取属性时调用
return age;
},
set(value) {
// 设置属性时调用
age = value;
}
}
```
## 特性说明
| 特性 | 类型 | 默认值 | 描述 |
| ------------ | ---- | --------- | ------------------------ |
| configurable | 布尔 | false | 是否可删除属性或修改特性 |
| enumerable | 布尔 | false | 是否出现在枚举属性中 |
| value | 任意 | undefined | 属性值 |
| writable | 布尔 | false | 是否可被赋值运算符改变 |
| get | 函数 | undefined | 读取属性时调用的函数 |
| set | 函数 | undefined | 设置属性时调用的函数 |
## 使用场景
1. **实现数据响应式**如Vue2的核心实现
2. **创建私有属性**通过getter/setter控制访问
3. **属性访问拦截**(在读取或设置时执行额外操作)
4. **定义不可枚举属性**(如内置对象的一些方法)
## 注意事项
1. 数据描述符value, writable和存取描述符get, set不能同时使用
2. 默认情况下通过defineProperty添加的属性不可枚举、不可写、不可配置
3. 在严格模式下setter必须设置一个参数否则会抛出错误
4. getter不应有副作用如修改其他属性值
**实现简单响应式**
```javascript
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
console.log(`读取 ${key}: ${val}`);
return val;
},
set(newVal) {
console.log(`设置 ${key}: ${newVal}`);
val = newVal;
}
});
}
const data = {};
defineReactive(data, 'message', 'Hello');
```

View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>理解数据代理</title>
</head>
<body>
</body>
<script>
let obj1 = { x: 100 }
let obj2 = { y: 200 }
Object.defineProperty(obj2, "x", {
get() {
return obj1.x
},
set(value) {
obj1.x = value
}
})
</script>
</html>

View File

@ -0,0 +1,104 @@
# JavaScript 数据代理实现文档
## 一、基本概念
数据代理是指通过一个对象(代理对象)间接访问和操作另一个对象(目标对象)的属性。本示例展示了如何使用 `Object.defineProperty` 实现简单的数据代理。
## 二、代码实现解析
```javascript
let obj1 = { x: 100 }; // 目标对象(被代理对象)
let obj2 = { y: 200 }; // 代理对象
// 在obj2上定义x属性的代理
Object.defineProperty(obj2, "x", {
get() {
return obj1.x; // 读取时返回obj1的x属性
},
set(value) {
obj1.x = value; // 设置时修改obj1的x属性
}
});
```
## 三、核心机制说明
1. **代理原理**
- 通过 `Object.defineProperty` 在代理对象obj2上定义新属性
- 使用 getter/setter 方法实现对目标对象obj1属性的间接访问
2. **访问流程**
- 读取 `obj2.x` → 触发 getter → 返回 `obj1.x` 的值
- 设置 `obj2.x` → 触发 setter → 将值赋给 `obj1.x`
3. **特性**
- 透明的属性访问(使用者无需知道代理存在)
- 可以在访问前后执行额外逻辑(如验证、日志等)
## 四、应用场景
| 场景 | 说明 | 示例 |
| -------- | -------------------- | ------------------------------ |
| 属性转发 | 跨对象访问属性 | 本示例实现 |
| 数据验证 | 设置属性前检查有效性 | setter中添加验证逻辑 |
| 访问控制 | 限制某些属性的访问 | getter中添加权限检查 |
| 日志记录 | 跟踪属性访问 | getter/setter中添加console.log |
## 五、扩展实现
### 1. 多属性代理
```javascript
function proxyProperties(target, source, props) {
props.forEach(prop => {
Object.defineProperty(target, prop, {
get() {
return source[prop];
},
set(value) {
source[prop] = value;
}
});
});
}
// 使用示例
proxyProperties(obj2, obj1, ['x', 'a', 'b']);
```
### 2. Vue2 风格的数据代理
```javascript
function observe(obj) {
const handler = {
get(target, prop) {
console.log(`读取 ${prop}`);
return target[prop];
},
set(target, prop, value) {
console.log(`设置 ${prop} 为 ${value}`);
target[prop] = value;
return true;
}
};
return new Proxy(obj, handler);
}
const observed = observe(obj1);
```
## 六、注意事项
1. **性能考虑**
- 每个代理属性都会增加访问开销
- 避免在性能关键路径上过度使用
2. **引用关系**
- 代理对象和目标对象保持独立
- 修改代理属性会影响原始对象
3. **枚举特性**
- 默认情况下代理属性不可枚举
- 需要显式设置 `enumerable: true`
4. **兼容性**
- `Object.defineProperty` 是 ES5 特性
- 现代开发可考虑使用 ES6 Proxy

View File

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="js/vue@2.7.16.js"></script>
<title>Vue中的数据代理</title>
</head>
<body>
<div id="app">
Vue中的数据代理: <span>{{name}}</span>
</div>
</body>
<script>
const vm = new Vue({
el: "#app",
data: {
name: "代理..."
}
});
// 如果要查看可以使用 vm._data 看到name在_data中做了数据劫持
// 验证data==_data 可以将data定义到外部之后使用 vm._data == data 即可
// 通过 Object.defineProperty 对vm中对象进行映射只要修改了 vm._data 或者 vm.xxx 会触发双向绑定
</script>
</html>

View File

@ -0,0 +1,146 @@
# Vue2 数据代理机制详解
## 一、核心概念
Vue2 中的数据代理是指 Vue 实例vm代理其 `data` 对象中属性的访问和修改操作。通过这种机制,我们可以直接通过 `vm.xxx` 访问 `data` 中的属性,而不需要写 `vm._data.xxx`
## 二、代码示例解析
```html
<div id="app">
Vue中的数据代理: <span>{{name}}</span>
</div>
<script>
const vm = new Vue({
el: "#app",
data: {
name: "代理..."
}
});
</script>
```
## 三、实现原理
### 1. 数据存储结构
- 原始 `data` 对象被存储在 `vm._data` 属性中
- 通过 `Object.defineProperty``data` 的属性代理到 Vue 实例上
### 2. 验证方法
```javascript
// 验证代理关系
const externalData = { name: "代理..." };
const vm = new Vue({
el: "#app",
data: externalData
});
console.log(vm._data === externalData); // true
console.log(vm.name === vm._data.name); // true
```
### 3. 代理实现机制
Vue 内部大致执行了以下操作:
```javascript
// 伪代码展示代理原理
Object.keys(data).forEach(key => {
Object.defineProperty(vm, key, {
get() {
return vm._data[key];
},
set(value) {
vm._data[key] = value;
}
});
});
```
## 四、数据流图示
```
模板访问
vm.name (代理访问)
vm._data.name (实际数据存储)
数据劫持 (响应式系统)
```
## 五、关键特性
| 特性 | 说明 | 示例 |
| ---------- | ---------------------- | ------------------------------ |
| 直接访问 | 省略 `_data` 前缀 | `vm.name` 代替 `vm._data.name` |
| 响应式绑定 | 代理属性也是响应式的 | 修改 `vm.name` 会触发视图更新 |
| 数据隔离 | `_data` 保存原始数据 | 防止直接修改破坏响应式 |
| 统一入口 | 提供一致的数据访问方式 | 简化模板和方法的编写 |
## 六、与数据劫持的关系
1. **数据代理**
- 解决的是访问便捷性问题(`vm.xxx` vs `vm._data.xxx`
- 通过 `Object.defineProperty` 实现属性转发
2. **数据劫持**
- 解决的是响应式问题(数据变化触发视图更新)
- 在 `vm._data` 上实现,通过重写 getter/setter
3. **协作流程**
- 模板访问 `{{name}}` → 触发代理 getter
- 代理 getter 访问 `_data.name` → 触发劫持 getter
- 建立依赖收集关系
## 七、注意事项
1. **新增属性**
- 直接 `vm.newProp = value` 不会成为响应式
- 必须使用 `Vue.set` 或预先声明
2. **性能影响**
- 每个代理属性都会产生一定的内存开销
- 大型项目应考虑分模块管理数据
3. **调试技巧**
- 通过 `vm._data` 查看原始数据
- 使用 Vue Devtools 观察数据变化
4. **与 Vue3 区别**
- Vue3 使用 Proxy 实现更完善的代理
- 可以检测属性添加/删除操作
## 八、扩展应用
### 1. 自定义代理逻辑
```javascript
// 在Vue实例上添加自定义代理
created() {
Object.defineProperty(this, 'fullName', {
get() {
return `${this.firstName} ${this.lastName}`;
},
set(value) {
const parts = value.split(' ');
this.firstName = parts[0];
this.lastName = parts[1] || '';
}
});
}
```
### 2. 代理模式应用
```javascript
// 实现组件间的数据代理
const proxyMixin = {
methods: {
proxy(prop) {
return {
get: () => this[prop],
set: (val) => this[prop] = val
};
}
}
};
```

View File

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="js/vue@2.7.16.js"></script>
<title>事件处理</title>
</head>
<body>
<div id="app">
<h1>Vue2-事件处理</h1>
<button @click="showinfo1">函数不传参</button>
<button @click="showinfo2($event,66)">函数传参</button>
</div>
</body>
<script>
const vm = new Vue({
el: "#app",
data: {
},
methods: {
showinfo1() {
alert("函数不传参");
},
showinfo2(event, val) {
alert("函数传参")
console.log(event, val);
}
}
})
</script>
</html>

View File

@ -0,0 +1,158 @@
# Vue2 事件处理机制文档
## 一、基本概念
Vue2 提供了 `v-on` 指令(简写为 `@`)用于监听 DOM 事件并执行相应的 JavaScript 代码。本示例展示了 Vue2 中事件处理的基本用法,包括不传参和传参两种场景。
## 二、代码示例解析
```html
<div id="app">
<h1>Vue2-事件处理</h1>
<button @click="showinfo1">函数不传参</button>
<button @click="showinfo2($event,66)">函数传参</button>
</div>
<script>
const vm = new Vue({
el: "#app",
methods: {
showinfo1() {
alert("函数不传参");
},
showinfo2(event, val) {
alert("函数传参")
console.log(event, val);
}
}
})
</script>
```
## 三、核心特性说明
### 1. 事件绑定语法
| 语法形式 | 示例 | 说明 |
| -------- | ---------------------- | ----------------- |
| 完整写法 | `v-on:click="handler"` | 使用 `v-on:` 前缀 |
| 简写形式 | `@click="handler"` | 更简洁的写法 |
### 2. 方法定义位置
所有事件处理函数应定义在 Vue 实例的 `methods` 选项中:
```javascript
methods: {
handler1() { /* ... */ },
handler2() { /* ... */ }
}
```
### 3. 参数传递方式
| 场景 | 示例 | 说明 |
| -------------- | ------------------------------- | ------------------------------ |
| 不传参 | `@click="showinfo1"` | 自动传入事件对象 |
| 传参 | `@click="showinfo2($event,66)"` | 使用 `$event` 传递原生事件对象 |
| 仅传自定义参数 | `@click="showinfo2(66)"` | 事件对象将不可用 |
## 四、事件对象详解
### 1. 获取事件对象的方式
- **不传参时**:自动作为第一个参数传入
```javascript
methods: {
showinfo1(event) {
// event 可用
}
}
```
- **传参时需要**:必须显式传递 `$event`
```html
<button @click="showinfo2($event,66)">按钮</button>
```
### 2. 常用事件对象属性
| 属性/方法 | 类型 | 说明 |
| ----------------------- | -------- | -------------- |
| event.target | Element | 触发事件的元素 |
| event.currentTarget | Element | 绑定事件的元素 |
| event.preventDefault() | Function | 阻止默认行为 |
| event.stopPropagation() | Function | 停止事件冒泡 |
## 五、最佳实践建议
1. **方法命名**
- 使用动词开头(如 `handleClick`、`submitForm`
- 保持名称语义化
2. **参数处理**
- 需要事件对象时确保正确传递 `$event`
- 复杂参数建议使用对象形式:
```html
<button @click="handleClick({id: 1, name: 'test'})">按钮</button>
```
3. **方法组织**
- 相关功能的方法放在一起
- 大型组件可考虑按功能分模块
## 六、扩展应用
### 1. 事件修饰符
Vue 提供了特殊的事件修饰符:
```html
<!-- 阻止默认行为 -->
<form @submit.prevent="onSubmit"></form>
<!-- 停止事件冒泡 -->
<div @click.stop="doThis"></div>
<!-- 按键修饰符 -->
<input @keyup.enter="submit">
<!-- 串联修饰符 -->
<button @click.stop.prevent="doThat"></button>
```
![image-20250614180522000](./images/image-20250614180522000.png)
### 2. 自定义事件
组件间通信时使用:
```javascript
// 子组件
this.$emit('my-event', payload);
// 父组件
<child-component @my-event="handleEvent"></child-component>
```
### 3. 原生事件绑定到组件
使用 `.native` 修饰符:
```html
<my-component @click.native="handleClick"></my-component>
```
## 七、注意事项
1. **避免内联计算**
```html
<!-- 不推荐 -->
<button @click="counter++">增加</button>
<!-- 推荐 -->
<button @click="increment">增加</button>
```
2. **this 指向**
- methods 中的方法自动绑定到 Vue 实例
- 避免使用箭头函数定义方法,会导致 this 指向错误
3. **性能考虑**
- 频繁触发的事件(如 scroll建议使用防抖/节流
- 大量事件监听应考虑事件委托

View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="js/vue@2.7.16.js"></script>
<title>键盘事件</title>
</head>
<body>
<div id="app">
键盘事件:<input type="text" @keydown.huiche="showinfo">
</div>
</body>
<script>
Vue.config.keyCodes.huiche = 13
new Vue({
el: "#app",
data: {},
methods: {
showinfo() {
alert("showinfo")
}
}
})
</script>
</html>

View File

@ -0,0 +1,153 @@
# Vue2 键盘事件处理文档
## 一、基本概念
Vue2 提供了对键盘事件的支持,允许开发者监听特定的按键操作。本示例展示了如何自定义键盘事件别名并处理回车键事件。
## 二、代码示例解析
```html
<div id="app">
键盘事件:<input type="text" @keydown.huiche="showinfo">
</div>
<script>
// 自定义按键别名
Vue.config.keyCodes.huiche = 13; // 13是回车键的keyCode
new Vue({
el: "#app",
methods: {
showinfo() {
alert("回车键被按下");
}
}
});
</script>
```
## 三、核心机制说明
### 1. 键盘事件绑定语法
| 语法形式 | 示例 | 说明 |
| ---------- | --------------------------- | ------------------ |
| 基础写法 | `@keydown="handler"` | 监听所有按键 |
| 按键修饰符 | `@keydown.enter="handler"` | 仅监听回车键 |
| 自定义别名 | `@keydown.huiche="handler"` | 使用自定义按键别名 |
### 2. 按键修饰符类型
Vue 提供了这些常用内置按键别名:
- `.enter`
- `.tab`
- `.delete` (捕获"删除"和"退格"键)
- `.esc`
- `.space`
- `.up`
- `.down`
- `.left`
- `.right`
### 3. 自定义按键别名
通过 `Vue.config.keyCodes` 对象添加:
```javascript
// 语法
Vue.config.keyCodes.自定义名称 = 按键keyCode;
// 示例将F1键(112)别名为help
Vue.config.keyCodes.help = 112;
```
## 四、键盘事件对象
在方法中可以访问原生事件对象:
```javascript
methods: {
showinfo(event) {
console.log(event.key); // 按键名称(如'Enter'
console.log(event.keyCode); // 按键代码如13
console.log(event.target); // 触发事件的元素
}
}
```
## 五、最佳实践建议
1. **优先使用内置别名**
- 提高代码可读性
- 避免记忆keyCode数字
2. **复杂按键组合**
```html
<!-- Ctrl + Enter -->
<input @keydown.ctrl.enter="submit">
<!-- Alt + C -->
<input @keydown.alt.67="copy">
```
3. **系统修饰键**
- `.ctrl`
- `.alt`
- `.shift`
- `.meta` (Mac是Command键Windows是Windows键)
## 六、扩展应用
### 1. 自动聚焦示例
```html
<input @keyup.enter="nextInput" ref="currentInput">
methods: {
nextInput() {
this.$refs.nextInput.focus();
}
}
```
### 2. 表单提交优化
```html
<form @submit.prevent>
<input @keyup.enter="submit">
<button @click="submit">提交</button>
</form>
```
### 3. 游戏控制示例
```javascript
// 监听方向键控制游戏角色
created() {
window.addEventListener('keyup', this.handleKeyup);
},
methods: {
handleKeyup(e) {
switch(e.keyCode) {
case 37: this.moveLeft(); break;
case 38: this.moveUp(); break;
// ...
}
}
}
```
## 七、注意事项
1. **keyCode已废弃**
- 现代浏览器建议使用 `event.key` 代替
- Vue3 已移除对keyCode的支持
2. **输入法问题**
- 某些输入法下可能无法正确触发事件
- 中文输入时考虑使用 `@keyup` 代替 `@keydown`
3. **移动端适配**
- 虚拟键盘行为可能不一致
- 建议配合触摸事件使用
4. **事件冒泡**
- 使用 `.stop` 修饰符阻止冒泡
```html
<div @keydown.enter.stop="handleEnter">
```

View File

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="js/vue@2.7.16.js"></script>
<title>计算属性</title>
</head>
<body>
<div id="app">
姓:<input type="text" v-model="firstName" />
名:<input type="text" v-model="lastName" />
全名:<span>{{fullName}}</span>
</div>
</body>
<script>
const vm = new Vue({
el: "#app",
data() {
return {
firstName: "",
lastName: "",
}
},
methods: {},
computed: {
fullName: {
get() {
return `${this.firstName}-${this.lastName}`
},
set(val) { }
}
}
})
</script>
</html>

View File

@ -0,0 +1,165 @@
# Vue2 计算属性详解文档
## 一、基本概念
计算属性Computed Properties是 Vue2 中用于声明式地定义派生数据的特性。它们基于响应式依赖进行缓存,只在相关依赖发生改变时才重新计算。
## 二、代码示例解析
```html
<div id="app">
姓:<input type="text" v-model="firstName">
名:<input type="text" v-model="lastName">
全名:<span>{{fullName}}</span>
</div>
<script>
const vm = new Vue({
el: "#app",
data() {
return {
firstName: "",
lastName: ""
}
},
computed: {
fullName: {
get() {
return `${this.firstName}-${this.lastName}`
},
set(val) {
// 可选的setter
}
}
}
})
</script>
```
## 三、核心特性
### 1. 计算属性 vs 方法
| 特性 | 计算属性 | 方法 |
| -------- | -------------------------- | --------------------------- |
| 缓存 | 有缓存,依赖不变不重新计算 | 每次调用都执行 |
| 调用方式 | 作为属性访问 | 需要调用执行 |
| 适用场景 | 派生数据 | 事件处理/需要重复执行的逻辑 |
### 2. 完整语法结构
```javascript
computed: {
属性名: {
get() {
// 计算逻辑
return 派生值
},
set(value) {
// 可选的反向更新逻辑
}
}
}
```
### 3. 简写形式(只读场景)
```javascript
computed: {
fullName() {
return `${this.firstName}-${this.lastName}`
}
}
```
## 四、最佳实践
1. **命名规范**
- 使用名词形式(如 `fullName`
- 避免动词开头(这是方法的命名习惯)
2. **纯计算逻辑**
- 不要在getter中执行异步操作或副作用
- 复杂计算考虑拆分为多个计算属性
3. **setter使用场景**
- 当需要反向更新依赖数据时
- 示例:
```javascript
set(val) {
const parts = val.split('-');
this.firstName = parts[0] || '';
this.lastName = parts[1] || '';
}
```
## 五、高级用法
### 1. 依赖多个数据源
```javascript
computed: {
summary() {
return `总计:${this.items.length}件,${this.totalPrice}元`
}
}
```
### 2. 结合过滤器使用
```javascript
computed: {
formattedDate() {
return dayjs(this.rawDate).format('YYYY-MM-DD')
}
}
```
### 3. 计算属性嵌套
```javascript
computed: {
baseInfo() {
return { ...this.user, age: this.calcAge }
},
calcAge() {
return new Date().getFullYear() - this.user.birthYear
}
}
```
## 六、性能优化
1. **减少大数组计算**
```javascript
computed: {
visibleItems() {
return this.items.filter(item =>
item.name.includes(this.search) &&
item.status === 'active'
)
}
}
```
2. **避免频繁计算**
- 对于复杂计算考虑使用 `methods` + 防抖
- 或使用 `watch` 手动控制计算时机
3. **缓存利用**
- 相同依赖多次访问不会重复计算
- 模板中可安全多次引用同一计算属性
## 七、常见问题
1. **为什么计算属性不更新?**
- 检查依赖数据是否是响应式的
- 确保没有在getter中意外修改了依赖数据
2. **何时使用计算属性 vs watch**
- 计算属性:基于多个数据的派生数据
- watch需要在数据变化时执行异步或开销较大的操作
3. **计算属性能接收参数吗?**
- 不能直接接收参数,如需参数化应使用方法
- 替代方案:返回函数或使用闭包

View File

@ -0,0 +1,168 @@
# Vue 2 中样式动态绑定和使用文档
在 Vue 2 中,动态绑定样式是非常常见的需求,可以通过多种方式实现。以下是详细的文档说明:
## 一、对象语法
### 1. 基本对象语法
```html
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
```
```javascript
data() {
return {
activeColor: 'red',
fontSize: 30
}
}
```
### 2. 样式对象直接绑定
```html
<div :style="styleObject"></div>
```
```javascript
data() {
return {
styleObject: {
color: 'red',
fontSize: '13px'
}
}
}
```
## 二、数组语法
可以将多个样式对象应用到同一个元素上:
```html
<div :style="[baseStyles, overridingStyles]"></div>
```
```javascript
data() {
return {
baseStyles: {
color: 'blue',
fontSize: '14px'
},
overridingStyles: {
color: 'red'
}
}
}
```
## 三、自动前缀
当使用需要浏览器前缀的 CSS 属性时(如 `transform`Vue 会自动检测并添加适当的前缀。
```html
<div :style="{ transform: 'scale(' + scale + ')' }"></div>
```
## 四、多重值
可以为样式属性提供包含多个值的数组,常用于提供多个带前缀的值:
```html
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
```
## 五、绑定 class
### 1. 对象语法
```html
<div class="static" :class="{ active: isActive, 'text-danger': hasError }"></div>
```
```javascript
data() {
return {
isActive: true,
hasError: false
}
}
```
### 2. 数组语法
```html
<div :class="[activeClass, errorClass]"></div>
```
```javascript
data() {
return {
activeClass: 'active',
errorClass: 'text-danger'
}
}
```
### 3. 在组件上使用
当在自定义组件上使用 `class` 属性时,这些类将被添加到该组件的根元素上,且不会覆盖已有的类。
```html
<my-component class="baz boo" :class="{ active: isActive }"></my-component>
```
## 六、计算属性绑定
对于复杂的样式逻辑,推荐使用计算属性:
```html
<div :class="classObject"></div>
```
```javascript
computed: {
classObject() {
return {
active: this.isActive && !this.error,
'text-danger': this.error && this.error.type === 'fatal'
}
}
}
```
## 七、注意事项
1. **浏览器兼容性**Vue 会自动添加浏览器前缀,但仍需注意某些 CSS 属性在不同浏览器中的支持情况。
2. **性能考虑**:频繁修改样式绑定可能会影响性能,特别是在大量元素上使用时。
3. **优先级**`:style` 和 `:class` 绑定的样式会与普通样式合并,`:style` 的优先级高于内联样式。
4. **CSS 变量**Vue 2.6+ 支持 CSS 变量绑定:
```html
<div :style="{'--color': themeColor}"></div>
```
## 八、最佳实践
1. 对于简单的样式切换,使用对象语法
2. 对于多个条件类,使用计算属性
3. 避免在模板中写复杂的样式逻辑
4. 考虑使用 CSS Modules 或 Scoped CSS 来管理组件样式
## 九、与 CSS 预处理器配合
Vue 支持与 Sass/SCSS、Less、Stylus 等预处理器一起使用:
```html
<style lang="scss">
$primary-color: #42b983;
.text {
color: $primary-color;
}
</style>
```

View File

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="js/vue@2.7.16.js"></script>
<title>监视属性</title>
</head>
<body>
<div id="app">
<h2>天气很什么:{{isHot}}</h2>
<button @click="changeWeather">切换天气</button>
</div>
</body>
<script>
const vm = new Vue({
el: "#app",
data: {
isHot: true
},
methods: {
changeWeather() {
this.isHot = !this.isHot
}
},
watch: {
immediate: true,
isHot: {
handler(newVal, oldVal) {
console.log("值修改", newVal, oldVal);
}
}
}
})
</script>
</html>

View File

@ -0,0 +1,166 @@
# Vue2 监视属性Watch详解文档
## 一、基本概念
监视属性Watch是 Vue2 中用于观察和响应数据变化的机制。它允许你在数据变化时执行异步操作或更复杂的逻辑。
## 二、代码示例解析
```html
<div id="app">
<h2>天气很什么:{{isHot ? '炎热' : '凉爽'}}</h2>
<button @click="changeWeather">切换天气</button>
</div>
<script>
const vm = new Vue({
el: "#app",
data: {
isHot: true
},
methods: {
changeWeather() {
this.isHot = !this.isHot;
}
},
watch: {
isHot: {
immediate: true, // 立即执行handler
handler(newVal, oldVal) {
console.log("天气状态修改", newVal, oldVal);
}
}
}
});
</script>
```
## 三、核心特性
### 1. 基本语法结构
```javascript
watch: {
要监视的属性: {
handler(newValue, oldValue) {
// 响应变化
},
immediate: false, // 是否立即执行
deep: false // 是否深度监视
}
}
```
### 2. 简写形式(不需要配置项时)
```javascript
watch: {
isHot(newVal, oldVal) {
console.log("天气变化", newVal, oldVal);
}
}
```
### 3. 深度监视deep watch
通常监视对象属性。
```javascript
watch: {
someObject: {
handler(newVal) {
console.log("对象内部变化", newVal);
},
deep: true // 深度监视对象内部值变化
}
}
```
## 四、配置选项说明
| 选项 | 类型 | 默认值 | 说明 |
| --------- | -------- | ------ | ---------------------------- |
| handler | Function | 必填 | 数据变化时的回调函数 |
| immediate | Boolean | false | 是否立即以当前值执行handler |
| deep | Boolean | false | 是否深度观察对象内部值的变化 |
## 五、最佳实践
1. **命名规范**
- 监视属性通常以 `watch` 开头命名方法(如 `watchIsHotChange`
- 或者直接使用被监视的属性名
2. **使用场景**
- 数据变化需要执行异步操作时
- 数据变化需要执行开销较大的操作时
- 需要观察嵌套数据结构变化时
3. **性能考虑**
- 避免在handler中执行复杂同步操作
- 对于大对象谨慎使用deep watch
## 六、高级用法
### 1. 监视计算属性
```javascript
computed: {
weatherDesc() {
return this.isHot ? "炎热" : "凉爽";
}
},
watch: {
weatherDesc(newVal) {
console.log("天气描述变化:", newVal);
}
}
```
### 2. 监视路由变化
```javascript
watch: {
'$route'(to, from) {
console.log("路由变化", to.path, from.path);
}
}
```
### 3. 多属性监视
```javascript
watch: {
// 监视多个属性
'a.b.c': function(newVal) {
// 当a.b.c变化时执行
},
'x.y': 'someMethod' // 直接调用methods中的方法
}
```
## 七、与计算属性的比较
| 特性 | 计算属性 | 监视属性 |
| ------ | ------------ | ------------ |
| 目的 | 派生新数据 | 响应数据变化 |
| 缓存 | 有缓存 | 无缓存 |
| 异步 | 不支持 | 支持 |
| 返回值 | 必须返回 | 不需要返回 |
| 复杂度 | 适合简单计算 | 适合复杂逻辑 |
## 八、常见问题
1. **为什么handler没有被调用**
- 检查监视的属性名拼写是否正确
- 确认属性值确实发生了变化(对于对象/数组,可能需要深度监视)
2. **什么时候使用deep watch**
- 当需要检测对象内部属性变化时
- 注意性能影响,必要时可以指定具体路径如 `'obj.prop'`
3. **如何取消监视?**
- 使用 `vm.$watch()` 返回的取消函数:
```javascript
const unwatch = this.$watch('someProp', handler);
unwatch(); // 取消监视
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

View File

@ -0,0 +1,481 @@
!(function (t, e) {
"object" == typeof exports && "undefined" != typeof module
? (module.exports = e())
: "function" == typeof define && define.amd
? define(e)
: ((t = "undefined" != typeof globalThis ? globalThis : t || self).dayjs =
e());
})(this, function () {
"use strict";
var t = 1e3,
e = 6e4,
n = 36e5,
r = "millisecond",
i = "second",
s = "minute",
u = "hour",
a = "day",
o = "week",
c = "month",
f = "quarter",
h = "year",
d = "date",
l = "Invalid Date",
$ =
/^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/,
y =
/\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,
M = {
name: "en",
weekdays:
"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),
months:
"January_February_March_April_May_June_July_August_September_October_November_December".split(
"_"
),
ordinal: function (t) {
var e = ["th", "st", "nd", "rd"],
n = t % 100;
return "[" + t + (e[(n - 20) % 10] || e[n] || e[0]) + "]";
},
},
m = function (t, e, n) {
var r = String(t);
return !r || r.length >= e ? t : "" + Array(e + 1 - r.length).join(n) + t;
},
v = {
s: m,
z: function (t) {
var e = -t.utcOffset(),
n = Math.abs(e),
r = Math.floor(n / 60),
i = n % 60;
return (e <= 0 ? "+" : "-") + m(r, 2, "0") + ":" + m(i, 2, "0");
},
m: function t(e, n) {
if (e.date() < n.date()) return -t(n, e);
var r = 12 * (n.year() - e.year()) + (n.month() - e.month()),
i = e.clone().add(r, c),
s = n - i < 0,
u = e.clone().add(r + (s ? -1 : 1), c);
return +(-(r + (n - i) / (s ? i - u : u - i)) || 0);
},
a: function (t) {
return t < 0 ? Math.ceil(t) || 0 : Math.floor(t);
},
p: function (t) {
return (
{ M: c, y: h, w: o, d: a, D: d, h: u, m: s, s: i, ms: r, Q: f }[t] ||
String(t || "")
.toLowerCase()
.replace(/s$/, "")
);
},
u: function (t) {
return void 0 === t;
},
},
g = "en",
D = {};
D[g] = M;
var p = "$isDayjsObject",
S = function (t) {
return t instanceof _ || !(!t || !t[p]);
},
w = function t(e, n, r) {
var i;
if (!e) return g;
if ("string" == typeof e) {
var s = e.toLowerCase();
D[s] && (i = s), n && ((D[s] = n), (i = s));
var u = e.split("-");
if (!i && u.length > 1) return t(u[0]);
} else {
var a = e.name;
(D[a] = e), (i = a);
}
return !r && i && (g = i), i || (!r && g);
},
O = function (t, e) {
if (S(t)) return t.clone();
var n = "object" == typeof e ? e : {};
return (n.date = t), (n.args = arguments), new _(n);
},
b = v;
(b.l = w),
(b.i = S),
(b.w = function (t, e) {
return O(t, { locale: e.$L, utc: e.$u, x: e.$x, $offset: e.$offset });
});
var _ = (function () {
function M(t) {
(this.$L = w(t.locale, null, !0)),
this.parse(t),
(this.$x = this.$x || t.x || {}),
(this[p] = !0);
}
var m = M.prototype;
return (
(m.parse = function (t) {
(this.$d = (function (t) {
var e = t.date,
n = t.utc;
if (null === e) return new Date(NaN);
if (b.u(e)) return new Date();
if (e instanceof Date) return new Date(e);
if ("string" == typeof e && !/Z$/i.test(e)) {
var r = e.match($);
if (r) {
var i = r[2] - 1 || 0,
s = (r[7] || "0").substring(0, 3);
return n
? new Date(
Date.UTC(
r[1],
i,
r[3] || 1,
r[4] || 0,
r[5] || 0,
r[6] || 0,
s
)
)
: new Date(
r[1],
i,
r[3] || 1,
r[4] || 0,
r[5] || 0,
r[6] || 0,
s
);
}
}
return new Date(e);
})(t)),
this.init();
}),
(m.init = function () {
var t = this.$d;
(this.$y = t.getFullYear()),
(this.$M = t.getMonth()),
(this.$D = t.getDate()),
(this.$W = t.getDay()),
(this.$H = t.getHours()),
(this.$m = t.getMinutes()),
(this.$s = t.getSeconds()),
(this.$ms = t.getMilliseconds());
}),
(m.$utils = function () {
return b;
}),
(m.isValid = function () {
return !(this.$d.toString() === l);
}),
(m.isSame = function (t, e) {
var n = O(t);
return this.startOf(e) <= n && n <= this.endOf(e);
}),
(m.isAfter = function (t, e) {
return O(t) < this.startOf(e);
}),
(m.isBefore = function (t, e) {
return this.endOf(e) < O(t);
}),
(m.$g = function (t, e, n) {
return b.u(t) ? this[e] : this.set(n, t);
}),
(m.unix = function () {
return Math.floor(this.valueOf() / 1e3);
}),
(m.valueOf = function () {
return this.$d.getTime();
}),
(m.startOf = function (t, e) {
var n = this,
r = !!b.u(e) || e,
f = b.p(t),
l = function (t, e) {
var i = b.w(
n.$u ? Date.UTC(n.$y, e, t) : new Date(n.$y, e, t),
n
);
return r ? i : i.endOf(a);
},
$ = function (t, e) {
return b.w(
n
.toDate()
[t].apply(
n.toDate("s"),
(r ? [0, 0, 0, 0] : [23, 59, 59, 999]).slice(e)
),
n
);
},
y = this.$W,
M = this.$M,
m = this.$D,
v = "set" + (this.$u ? "UTC" : "");
switch (f) {
case h:
return r ? l(1, 0) : l(31, 11);
case c:
return r ? l(1, M) : l(0, M + 1);
case o:
var g = this.$locale().weekStart || 0,
D = (y < g ? y + 7 : y) - g;
return l(r ? m - D : m + (6 - D), M);
case a:
case d:
return $(v + "Hours", 0);
case u:
return $(v + "Minutes", 1);
case s:
return $(v + "Seconds", 2);
case i:
return $(v + "Milliseconds", 3);
default:
return this.clone();
}
}),
(m.endOf = function (t) {
return this.startOf(t, !1);
}),
(m.$set = function (t, e) {
var n,
o = b.p(t),
f = "set" + (this.$u ? "UTC" : ""),
l = ((n = {}),
(n[a] = f + "Date"),
(n[d] = f + "Date"),
(n[c] = f + "Month"),
(n[h] = f + "FullYear"),
(n[u] = f + "Hours"),
(n[s] = f + "Minutes"),
(n[i] = f + "Seconds"),
(n[r] = f + "Milliseconds"),
n)[o],
$ = o === a ? this.$D + (e - this.$W) : e;
if (o === c || o === h) {
var y = this.clone().set(d, 1);
y.$d[l]($),
y.init(),
(this.$d = y.set(d, Math.min(this.$D, y.daysInMonth())).$d);
} else l && this.$d[l]($);
return this.init(), this;
}),
(m.set = function (t, e) {
return this.clone().$set(t, e);
}),
(m.get = function (t) {
return this[b.p(t)]();
}),
(m.add = function (r, f) {
var d,
l = this;
r = Number(r);
var $ = b.p(f),
y = function (t) {
var e = O(l);
return b.w(e.date(e.date() + Math.round(t * r)), l);
};
if ($ === c) return this.set(c, this.$M + r);
if ($ === h) return this.set(h, this.$y + r);
if ($ === a) return y(1);
if ($ === o) return y(7);
var M = ((d = {}), (d[s] = e), (d[u] = n), (d[i] = t), d)[$] || 1,
m = this.$d.getTime() + r * M;
return b.w(m, this);
}),
(m.subtract = function (t, e) {
return this.add(-1 * t, e);
}),
(m.format = function (t) {
var e = this,
n = this.$locale();
if (!this.isValid()) return n.invalidDate || l;
var r = t || "YYYY-MM-DDTHH:mm:ssZ",
i = b.z(this),
s = this.$H,
u = this.$m,
a = this.$M,
o = n.weekdays,
c = n.months,
f = n.meridiem,
h = function (t, n, i, s) {
return (t && (t[n] || t(e, r))) || i[n].slice(0, s);
},
d = function (t) {
return b.s(s % 12 || 12, t, "0");
},
$ =
f ||
function (t, _e, n) {
var r = t < 12 ? "AM" : "PM";
return n ? r.toLowerCase() : r;
};
return r.replace(y, function (t, r) {
return (
r ||
(function (t) {
switch (t) {
case "YY":
return String(e.$y).slice(-2);
case "YYYY":
return b.s(e.$y, 4, "0");
case "M":
return a + 1;
case "MM":
return b.s(a + 1, 2, "0");
case "MMM":
return h(n.monthsShort, a, c, 3);
case "MMMM":
return h(c, a);
case "D":
return e.$D;
case "DD":
return b.s(e.$D, 2, "0");
case "d":
return String(e.$W);
case "dd":
return h(n.weekdaysMin, e.$W, o, 2);
case "ddd":
return h(n.weekdaysShort, e.$W, o, 3);
case "dddd":
return o[e.$W];
case "H":
return String(s);
case "HH":
return b.s(s, 2, "0");
case "h":
return d(1);
case "hh":
return d(2);
case "a":
return $(s, u, !0);
case "A":
return $(s, u, !1);
case "m":
return String(u);
case "mm":
return b.s(u, 2, "0");
case "s":
return String(e.$s);
case "ss":
return b.s(e.$s, 2, "0");
case "SSS":
return b.s(e.$ms, 3, "0");
case "Z":
return i;
}
return null;
})(t) ||
i.replace(":", "")
);
});
}),
(m.utcOffset = function () {
return 15 * -Math.round(this.$d.getTimezoneOffset() / 15);
}),
(m.diff = function (r, d, l) {
var $,
y = this,
M = b.p(d),
m = O(r),
v = (m.utcOffset() - this.utcOffset()) * e,
g = this - m,
D = function () {
return b.m(y, m);
};
switch (M) {
case h:
$ = D() / 12;
break;
case c:
$ = D();
break;
case f:
$ = D() / 3;
break;
case o:
$ = (g - v) / 6048e5;
break;
case a:
$ = (g - v) / 864e5;
break;
case u:
$ = g / n;
break;
case s:
$ = g / e;
break;
case i:
$ = g / t;
break;
default:
$ = g;
}
return l ? $ : b.a($);
}),
(m.daysInMonth = function () {
return this.endOf(c).$D;
}),
(m.$locale = function () {
return D[this.$L];
}),
(m.locale = function (t, e) {
if (!t) return this.$L;
var n = this.clone(),
r = w(t, e, !0);
return r && (n.$L = r), n;
}),
(m.clone = function () {
return b.w(this.$d, this);
}),
(m.toDate = function () {
return new Date(this.valueOf());
}),
(m.toJSON = function () {
return this.isValid() ? this.toISOString() : null;
}),
(m.toISOString = function () {
return this.$d.toISOString();
}),
(m.toString = function () {
return this.$d.toUTCString();
}),
M
);
})(),
k = _.prototype;
return (
(O.prototype = k),
[
["$ms", r],
["$s", i],
["$m", s],
["$H", u],
["$W", a],
["$M", c],
["$y", h],
["$D", d],
].forEach(function (t) {
k[t[1]] = function (e) {
return this.$g(e, t[0], t[1]);
};
}),
(O.extend = function (t, e) {
return t.$i || (t(e, _, O), (t.$i = !0)), O;
}),
(O.locale = w),
(O.isDayjs = S),
(O.unix = function (t) {
return O(1e3 * t);
}),
(O.en = D[g]),
(O.Ls = D),
(O.p = {}),
O
);
});

File diff suppressed because it is too large Load Diff