diff --git a/404.html b/404.html new file mode 100644 index 000000000..e0c562530 --- /dev/null +++ b/404.html @@ -0,0 +1,489 @@ +页面没有找到 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
Page not found

404

Page Not Found
+ + + + \ No newline at end of file diff --git a/about/index.html b/about/index.html new file mode 100644 index 000000000..6202bd06d --- /dev/null +++ b/about/index.html @@ -0,0 +1,583 @@ +about | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + +

评论
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/archives/2022/07/index.html b/archives/2022/07/index.html new file mode 100644 index 000000000..b2d112d5d --- /dev/null +++ b/archives/2022/07/index.html @@ -0,0 +1,498 @@ +七月 2022 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/archives/2022/08/index.html b/archives/2022/08/index.html new file mode 100644 index 000000000..172370bf6 --- /dev/null +++ b/archives/2022/08/index.html @@ -0,0 +1,498 @@ +八月 2022 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/archives/2022/09/index.html b/archives/2022/09/index.html new file mode 100644 index 000000000..0491969b7 --- /dev/null +++ b/archives/2022/09/index.html @@ -0,0 +1,498 @@ +九月 2022 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
文章总览 - 1
2022
数据结构绪论
数据结构绪论
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/archives/2022/12/index.html b/archives/2022/12/index.html new file mode 100644 index 000000000..d9e5124ae --- /dev/null +++ b/archives/2022/12/index.html @@ -0,0 +1,498 @@ +十二月 2022 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
文章总览 - 3
2022
栈与队列
栈与队列
线性表
线性表
Win10/11任务栏透明美化
Win10/11任务栏透明美化
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/archives/2022/index.html b/archives/2022/index.html new file mode 100644 index 000000000..608943035 --- /dev/null +++ b/archives/2022/index.html @@ -0,0 +1,498 @@ +2022 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/archives/2022/page/2/index.html b/archives/2022/page/2/index.html new file mode 100644 index 000000000..675e2abfd --- /dev/null +++ b/archives/2022/page/2/index.html @@ -0,0 +1,498 @@ +2022 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
文章总览 - 11
2022
关于cloudflare对网站搭建的使用
关于cloudflare对网站搭建的使用
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/archives/2023/01/index.html b/archives/2023/01/index.html new file mode 100644 index 000000000..6d1c65f10 --- /dev/null +++ b/archives/2023/01/index.html @@ -0,0 +1,498 @@ +一月 2023 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
文章总览 - 1
2023
JAVA学习笔记
JAVA学习笔记
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/archives/2023/03/index.html b/archives/2023/03/index.html new file mode 100644 index 000000000..9c6324be5 --- /dev/null +++ b/archives/2023/03/index.html @@ -0,0 +1,498 @@ +三月 2023 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
文章总览 - 1
2023
Windows/Ubuntu双系统安装教程
Windows/Ubuntu双系统安装教程
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/archives/2023/10/index.html b/archives/2023/10/index.html new file mode 100644 index 000000000..10e47ce38 --- /dev/null +++ b/archives/2023/10/index.html @@ -0,0 +1,498 @@ +十月 2023 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
文章总览 - 6
2023
JS拾遗笔记
JS拾遗笔记
JS事件循环
JS事件循环
深入了解Promise
深入了解Promise
Git 笔记
Git 笔记
Vite基础知识总结
Vite基础知识总结
REGEX in JavaScript
REGEX in JavaScript
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/archives/2023/11/index.html b/archives/2023/11/index.html new file mode 100644 index 000000000..3fb17d454 --- /dev/null +++ b/archives/2023/11/index.html @@ -0,0 +1,498 @@ +十一月 2023 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
文章总览 - 3
2023
Vue3笔记
Vue3笔记
Vue2笔记
Vue2笔记
前端知识笔记
前端知识笔记
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/archives/2023/12/index.html b/archives/2023/12/index.html new file mode 100644 index 000000000..bdcc692f8 --- /dev/null +++ b/archives/2023/12/index.html @@ -0,0 +1,498 @@ +十二月 2023 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
文章总览 - 3
2023
Intersection Observer
Intersection Observer
数据结构与算法笔记
数据结构与算法笔记
React笔记
React笔记
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/archives/2023/index.html b/archives/2023/index.html new file mode 100644 index 000000000..db0e2ffc6 --- /dev/null +++ b/archives/2023/index.html @@ -0,0 +1,498 @@ +2023 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
文章总览 - 14
2023
Intersection Observer
Intersection Observer
数据结构与算法笔记
数据结构与算法笔记
React笔记
React笔记
Vue3笔记
Vue3笔记
Vue2笔记
Vue2笔记
前端知识笔记
前端知识笔记
JS拾遗笔记
JS拾遗笔记
JS事件循环
JS事件循环
深入了解Promise
深入了解Promise
Git 笔记
Git 笔记
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/archives/2023/page/2/index.html b/archives/2023/page/2/index.html new file mode 100644 index 000000000..1e9e8f1e9 --- /dev/null +++ b/archives/2023/page/2/index.html @@ -0,0 +1,498 @@ +2023 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
文章总览 - 14
2023
Vite基础知识总结
Vite基础知识总结
REGEX in JavaScript
REGEX in JavaScript
Windows/Ubuntu双系统安装教程
Windows/Ubuntu双系统安装教程
JAVA学习笔记
JAVA学习笔记
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/archives/2024/01/index.html b/archives/2024/01/index.html new file mode 100644 index 000000000..7d49ce843 --- /dev/null +++ b/archives/2024/01/index.html @@ -0,0 +1,498 @@ +一月 2024 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
文章总览 - 2
2024
JS手写题大汇总
JS手写题大汇总
TypeSript笔记
TypeSript笔记
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/archives/2024/02/index.html b/archives/2024/02/index.html new file mode 100644 index 000000000..3beec05d2 --- /dev/null +++ b/archives/2024/02/index.html @@ -0,0 +1,498 @@ +二月 2024 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
文章总览 - 2
2024
LeetCode 算法笔记 Part2
LeetCode 算法笔记 Part2
LeetCode 算法笔记 Part1
LeetCode 算法笔记 Part1
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/archives/2024/03/index.html b/archives/2024/03/index.html new file mode 100644 index 000000000..ac7510fb3 --- /dev/null +++ b/archives/2024/03/index.html @@ -0,0 +1,498 @@ +三月 2024 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
文章总览 - 1
2024
LeetCode 算法笔记 Part3
LeetCode 算法笔记 Part3
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/archives/2024/08/index.html b/archives/2024/08/index.html new file mode 100644 index 000000000..0c900906e --- /dev/null +++ b/archives/2024/08/index.html @@ -0,0 +1,498 @@ +八月 2024 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
文章总览 - 1
2024
虚拟列表
虚拟列表
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/archives/2024/index.html b/archives/2024/index.html new file mode 100644 index 000000000..f49b0ee03 --- /dev/null +++ b/archives/2024/index.html @@ -0,0 +1,498 @@ +2024 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/archives/index.html b/archives/index.html new file mode 100644 index 000000000..96bcf4425 --- /dev/null +++ b/archives/index.html @@ -0,0 +1,498 @@ +归档 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/archives/page/2/index.html b/archives/page/2/index.html new file mode 100644 index 000000000..5e572e602 --- /dev/null +++ b/archives/page/2/index.html @@ -0,0 +1,498 @@ +归档 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
文章总览 - 31
2023
Vue2笔记
Vue2笔记
前端知识笔记
前端知识笔记
JS拾遗笔记
JS拾遗笔记
JS事件循环
JS事件循环
深入了解Promise
深入了解Promise
Git 笔记
Git 笔记
Vite基础知识总结
Vite基础知识总结
REGEX in JavaScript
REGEX in JavaScript
Windows/Ubuntu双系统安装教程
Windows/Ubuntu双系统安装教程
JAVA学习笔记
JAVA学习笔记
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/archives/page/3/index.html b/archives/page/3/index.html new file mode 100644 index 000000000..532cc8753 --- /dev/null +++ b/archives/page/3/index.html @@ -0,0 +1,498 @@ +归档 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/archives/page/4/index.html b/archives/page/4/index.html new file mode 100644 index 000000000..ba18f3b9c --- /dev/null +++ b/archives/page/4/index.html @@ -0,0 +1,498 @@ +归档 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
文章总览 - 31
2022
关于cloudflare对网站搭建的使用
关于cloudflare对网站搭建的使用
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/atom.xml b/atom.xml new file mode 100644 index 000000000..081a6eece --- /dev/null +++ b/atom.xml @@ -0,0 +1,912 @@ + + + 希亚的西红柿のBlog + https://npm.elemecdn.com/nova1751-sources/blogImg/icon.png + 分享学习与生活 + + + + 2024-08-06T15:25:28.752Z + http://blog.refrain.site/ + + + 希亚的西红柿 + + + + Hexo + + + 虚拟列表 + + http://blog.refrain.site/posts/300a8ca5/ + 2024-08-06T15:25:28.752Z + 2024-08-06T15:25:28.752Z + + + + + + + <div class="tip home"><p>唔~,好久没有更新博文了,一年前的这个时候用 Vue 实现过一版虚拟列表,不过现在已经忘光光了,故写一篇博文帮忙回忆与巩固一下虚拟列表的知识。</p> +</div> +<h2 id="前言"><a href="#前言" + + + + + + + + + + + + + + + + LeetCode 算法笔记 Part3 + + http://blog.refrain.site/posts/ffb66302/ + 2024-03-03T04:29:06.000Z + 2024-03-05T17:09:15.000Z + + + + + + + <div class="tip home"><p>本文主要记录了博主的 Leetcode 算法刷题记录,方便以后查询复习。</p> +</div> +<h2 id="图论"><a href="#图论" class="headerlink" + + + + + + + + + + + + + + + + LeetCode 算法笔记 Part2 + + http://blog.refrain.site/posts/b672a0f6/ + 2024-02-26T04:29:06.000Z + 2024-04-15T16:17:32.000Z + + + + + + + <div class="tip home"><p>本文主要记录了博主的 Leetcode 算法刷题记录,方便以后查询复习。</p> +</div> +<h2 id="链表"><a href="#链表" class="headerlink" + + + + + + + + + + + + + + + + LeetCode 算法笔记 Part1 + + http://blog.refrain.site/posts/1be7bb2b/ + 2024-02-25T04:29:06.000Z + 2024-03-29T16:59:12.000Z + + + + + + + <div class="tip home"><p>本文主要记录了博主的 Leetcode 算法刷题记录,方便以后查询复习。</p> +</div> +<h2 id="哈希表"><a href="#哈希表" class="headerlink" + + + + + + + + + + + + + + + + JS手写题大汇总 + + http://blog.refrain.site/posts/aac5e919/ + 2024-01-02T15:34:30.000Z + 2024-05-30T02:19:03.000Z + + + + + + + <div class="tip home"><p>本文主要记录了关于前端面试常考的手写代码题,常看常复习。</p> +</div> +<h2 id="JS-基础篇"><a href="#JS-基础篇" class="headerlink" title="JS 基础篇"></a>JS + + + + + + + + + + + + + + TypeSript笔记 + + http://blog.refrain.site/posts/1afd9955/ + 2024-01-01T09:12:26.000Z + 2024-01-01T09:48:40.000Z + + + + + + + <div class="tip info"><p>之前 TS 笔记由于放在长文章后面阅读体验不太好,故单独抽离出来一篇文章。</p> +</div> +<div class="tip home"><p>本文主要记录了 TS 的学习知识与笔记。</p> +</div> +<h2 + + + + + + + + + + + + + + + + + + Intersection Observer + + http://blog.refrain.site/posts/2afbd983/ + 2023-12-31T10:31:39.000Z + 2023-12-31T14:10:53.000Z + + + + + + + <div class="tip info"><p>早就想好好总结一下 Intersection Observer 这个 API,苦于一直没有时间,今天就来好好总结一下这个 API。</p> +</div> +<div class="tip home"><p>本篇文章将介绍现代 + + + + + + + + + + + + + + + + + + 数据结构与算法笔记 + + http://blog.refrain.site/posts/acc0017d/ + 2023-12-11T09:36:58.000Z + 2023-12-12T04:43:47.000Z + + + + + + + <div class="tip home"><p>本篇文章将介绍数据结构与算法的基本概念(前端版)。</p> +</div> +<div class="note info + + + + + + + + + + + + + + + + + + + + React笔记 + + http://blog.refrain.site/posts/a1605017/ + 2023-12-04T12:11:44.000Z + 2024-01-01T09:49:21.000Z + + + + + + + <h2 id="基础"><a href="#基础" class="headerlink" title="基础"></a>基础</h2><h3 id="语法基础与规范"><a href="#语法基础与规范" class="headerlink" + + + + + + + + + + + + + + + + + + + + Vue3笔记 + + http://blog.refrain.site/posts/3c94855e/ + 2023-11-25T09:57:47.000Z + 2023-12-31T14:10:53.000Z + + + + + + + <div class="tip home"><p>本文主要记录了关于 Vue3 的相关笔记,主要记录 Vue3 及其生态的使用与技巧,不定时更新。</p> +</div> +<h2 id="基础"><a href="#基础" class="headerlink" + + + + + + + + + + + + + + + + + + + + Vue2笔记 + + http://blog.refrain.site/posts/adf5c49/ + 2023-11-24T15:35:21.000Z + 2024-05-10T10:01:20.000Z + + + + + + + <div class="tip home"><p>本文主要记录了关于 Vue2 的相关笔记,主要记录 Vue2 的一些基础概念与知识,帮助理解 Vue3。</p> +</div> +<h2 id="Vue-的核心特性"><a href="#Vue-的核心特性" + + + + + + + + + + + + + + + + + + + + 前端知识笔记 + + http://blog.refrain.site/posts/fccf75e5/ + 2023-11-19T03:57:06.000Z + 2024-01-01T09:48:40.000Z + + + + + + + <div class="tip home"><p>本文主要记录了关于前端面试常考与常问的知识笔记,内容较多,不间断更新。</p> +</div> +<h2 id="CSS"><a href="#CSS" class="headerlink" + + + + + + + + + + + + + + JS拾遗笔记 + + http://blog.refrain.site/posts/b2b6ba06/ + 2023-10-23T04:17:34.000Z + 2023-11-24T06:43:50.000Z + + + + + + + <div class="tip home"><p>本文主要记录了 JS 基础以及 ES6 的相关笔记,随缘更新。</p> +</div> +<h2 id="JS-基础"><a href="#JS-基础" class="headerlink" title="JS 基础"></a>JS + + + + + + + + + + + + + + + + + + JS事件循环 + + http://blog.refrain.site/posts/35a88b44/ + 2023-10-20T06:07:28.000Z + 2023-11-21T13:24:05.000Z + + + + + + + <div class="tip home"><p>本文主要介绍了 JS 中的事件循环概念,详细讲解了 Browser 中的事件循环以及 Node.js 中的事件循环。</p> +</div> +<details class="folding-tag" + + + + + + + + + + + + + + + + + + 深入了解Promise + + http://blog.refrain.site/posts/2275c8/ + 2023-10-16T04:17:32.000Z + 2023-12-24T07:30:08.000Z + + + + + + + <div class="tip home"><p>本文主要讲解 Promise 的常用方法以及使用技巧,并实现如何手写一个 Promise。</p> +</div> +<h2 id="Promise-的出现"><a href="#Promise-的出现" + + + + + + + + + + + + + + + + + + Git 笔记 + + http://blog.refrain.site/posts/582b690b/ + 2023-10-13T08:49:02.000Z + 2024-03-02T14:10:48.000Z + + + + + + + <div class="tip home"><p>本文为个人自用 Git 笔记,记录了 Git 的常用命令,不定时更新(●’◡’●)。</p> +</div> +<h2 id="Git-常用命令"><a href="#Git-常用命令" class="headerlink" + + + + + + + + + + + + + + + + + + Vite基础知识总结 + + http://blog.refrain.site/posts/9614c7d1/ + 2023-10-08T12:48:33.000Z + 2024-01-01T09:48:40.000Z + + + + + + + <div class="tip home"><p>本文介绍了 Vite 的一些常用功能笔记,方便后续记忆与复习。<del>总有种写这个不如直接看文档的感觉QAQ</del></p> +</div> +<h2 id="Vite-工作基本原理"><a + + + + + + + + + + + + + + + + + + + + REGEX in JavaScript + + http://blog.refrain.site/posts/e89bd903/ + 2023-10-04T11:19:37.000Z + 2023-11-21T09:36:21.000Z + + + + + + + <div class="tip home"><p>本文主要介绍正则表达式的基本概念与用法,并对 JS 中的正则表达式的常用方法进行总结,方便后续记忆与复习。</p> +</div> +<details class="folding-tag" yellow><summary> + + + + + + + + + + + + + + + + + + Windows/Ubuntu双系统安装教程 + + http://blog.refrain.site/posts/d3748e5f/ + 2023-03-04T09:52:55.000Z + 2023-09-26T14:08:00.000Z + + + + + + + <div class="tip home"><p>本文主要记录笔者 PC 安装 Windows/Ubuntu 双系统的过程、Windows 平台下 VMware 虚拟机安装 Ubuntu 的配置过程、以及双系统 Ubuntu 卸载的注意事项。</p> +</div> +<div + + + + + + + + + + + + + + + + + + JAVA学习笔记 + + http://blog.refrain.site/posts/8d55d49a/ + 2023-01-17T06:44:22.000Z + 2023-03-07T06:44:22.000Z + + + + + + + <div class="tip home"><p>本篇文章将作为编程语言 <strong><span class='p red'>JAVA</span></strong> 的学习笔记,供以后查阅与复习使用。</p> +</div> +<div class="tip + + + + + + + + + + + + + + + + 栈与队列 + + http://blog.refrain.site/posts/6534ce06/ + 2022-12-31T06:18:56.000Z + 2023-01-09T06:18:56.000Z + + + 本人书写的数据结构教程 + + + + + + + + + + + + + + + + 线性表 + + http://blog.refrain.site/posts/40f24371/ + 2022-12-24T13:23:10.000Z + 2022-12-31T13:23:10.000Z + + + 本人书写的数据结构教程 + + + + + + + + + + + + + + Win10/11任务栏透明美化 + + http://blog.refrain.site/posts/b67f488/ + 2022-12-02T12:18:18.000Z + 2022-12-02T12:18:18.000Z + + + win10/11任务栏美化教程 + + + + + + + + + + + + + + + + 数据结构绪论 + + http://blog.refrain.site/posts/b06cc6ec/ + 2022-09-27T08:42:26.000Z + 2022-12-12T08:42:26.000Z + + + 本人书写的数据结构教程 + + + + + + + + + + + + + + 机器学习基本概念与知识 + + http://blog.refrain.site/posts/75378e04/ + 2022-08-20T02:42:24.000Z + 2022-08-20T02:42:24.000Z + + + 机器学习基础知识清单 + + + + + + + + + + + + 使用numpy实现k-means聚类算法 + + http://blog.refrain.site/posts/170bc017/ + 2022-08-17T01:03:20.000Z + 2022-08-17T01:03:20.000Z + + + + + + + <h2 id="k-means-算法基础原理"><a href="#k-means-算法基础原理" class="headerlink" title="k-means 算法基础原理"></a>k-means 算法基础原理</h2><blockquote> +<p>本文注重对 + + + + + + + + + + + + + + + + + + 使用Numpy实现k-Nearest-Neighbor算法 + + http://blog.refrain.site/posts/775eb342/ + 2022-08-14T06:08:58.000Z + 2022-08-14T06:08:58.000Z + + + + + + + <h2 id="KNN-算法基本原理"><a href="#KNN-算法基本原理" class="headerlink" title="KNN 算法基本原理"></a>KNN 算法基本原理</h2><p><strong>kNN 算法的核心思想是用距离最近的 k + + + + + + + + + + + + + + + + + + + + Python实现决策树(Decision Tree)算法 + + http://blog.refrain.site/posts/54c08517/ + 2022-08-07T06:13:25.000Z + 2022-08-07T06:13:25.000Z + + + + + + + <h2 id="决策树算法基础原理分析"><a href="#决策树算法基础原理分析" class="headerlink" title="决策树算法基础原理分析"></a>决策树算法基础原理分析</h2><h3 id="信息的不纯度-决定决策树分支的指标"><a + + + + + + + + + + + + + + + + + + Numpy实现逻辑回归(Logistic Regression)算法 + + http://blog.refrain.site/posts/355699d5/ + 2022-08-06T09:20:58.000Z + 2022-08-06T09:20:58.000Z + + + + + + + <blockquote> +<p>前言:本文旨在对如何使用 numpy 实现逻辑回归拟合的过程做具体分析,有关逻辑回归原理部分不做过多论述。</p> +</blockquote> +<h2 id="数据集准备"><a href="#数据集准备" class="headerlink" + + + + + + + + + + + + + + + + + + + + 线性回归 (Linear Regression) + + http://blog.refrain.site/posts/20642/ + 2022-07-22T06:59:02.000Z + 2022-07-22T06:59:02.000Z + + + <h3 id="利用-Numpy-实现简单的机器学习算法"><a href="#利用-Numpy-实现简单的机器学习算法" class="headerlink" title="利用 Numpy 实现简单的机器学习算法"></a>利用 Numpy 实现简单的机器学习算法</h3><blockquote> +<p>线性回归(Linear Regression) 可能是最流行的机器学习算法。线性回归就是要找一条直线,并且让这条直线尽可能地拟合散点图中的数据点。</p> +</blockquote> + + + + + + + + + + + + + + + + + + 关于cloudflare对网站搭建的使用 + + http://blog.refrain.site/posts/47341/ + 2022-07-18T10:54:13.000Z + 2022-07-18T10:54:13.000Z + + + <h2 id="为-hexo-博客添加自定义域名"><a href="#为-hexo-博客添加自定义域名" class="headerlink" title="为 hexo 博客添加自定义域名"></a>为 hexo 博客添加自定义域名</h2><blockquote> +<p>域名注册方面不做赘述,下面详细讲述利用 cloudflare 中 DNS 解析域名至 hexo 博客站的过程</p> +</blockquote> + + + + + + + + + + + + + diff --git a/categories/DNS/index.html b/categories/DNS/index.html new file mode 100644 index 000000000..f5bf096ef --- /dev/null +++ b/categories/DNS/index.html @@ -0,0 +1,498 @@ +分类: DNS | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
分类 - DNS
2022
关于cloudflare对网站搭建的使用
关于cloudflare对网站搭建的使用
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/categories/Front-End/index.html b/categories/Front-End/index.html new file mode 100644 index 000000000..73fb9ad2a --- /dev/null +++ b/categories/Front-End/index.html @@ -0,0 +1,498 @@ +分类: Front End | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
分类 - Front End
2023
前端知识笔记
前端知识笔记
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/categories/FrontEnd/index.html b/categories/FrontEnd/index.html new file mode 100644 index 000000000..dd0d05896 --- /dev/null +++ b/categories/FrontEnd/index.html @@ -0,0 +1,498 @@ +分类: FrontEnd | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
分类 - FrontEnd
2024
TypeSript笔记
TypeSript笔记
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/categories/Java/index.html b/categories/Java/index.html new file mode 100644 index 000000000..3cc04e500 --- /dev/null +++ b/categories/Java/index.html @@ -0,0 +1,498 @@ +分类: Java | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
分类 - Java
2023
JAVA学习笔记
JAVA学习笔记
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/categories/JavaScript/index.html b/categories/JavaScript/index.html new file mode 100644 index 000000000..414793c33 --- /dev/null +++ b/categories/JavaScript/index.html @@ -0,0 +1,498 @@ +分类: JavaScript | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
分类 - JavaScript
2024
JS手写题大汇总
JS手写题大汇总
2023
Intersection Observer
Intersection Observer
JS拾遗笔记
JS拾遗笔记
JS事件循环
JS事件循环
深入了解Promise
深入了解Promise
Vite基础知识总结
Vite基础知识总结
REGEX in JavaScript
REGEX in JavaScript
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/categories/LeetCode/index.html b/categories/LeetCode/index.html new file mode 100644 index 000000000..15e5f8856 --- /dev/null +++ b/categories/LeetCode/index.html @@ -0,0 +1,498 @@ +分类: LeetCode | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/categories/React/index.html b/categories/React/index.html new file mode 100644 index 000000000..bccd747c9 --- /dev/null +++ b/categories/React/index.html @@ -0,0 +1,498 @@ +分类: React | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
分类 - React
2024
虚拟列表
虚拟列表
2023
React笔记
React笔记
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/categories/Vue/index.html b/categories/Vue/index.html new file mode 100644 index 000000000..e5efcbf7e --- /dev/null +++ b/categories/Vue/index.html @@ -0,0 +1,498 @@ +分类: Vue | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
分类 - Vue
2023
Vue3笔记
Vue3笔记
Vue2笔记
Vue2笔记
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/categories/index.html b/categories/index.html new file mode 100644 index 000000000..567fa1bb4 --- /dev/null +++ b/categories/index.html @@ -0,0 +1,583 @@ +categories | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + +
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git "a/categories/\346\225\260\346\215\256\347\273\223\346\236\204/index.html" "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204/index.html" new file mode 100644 index 000000000..da998c5bd --- /dev/null +++ "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204/index.html" @@ -0,0 +1,498 @@ +分类: 数据结构 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
分类 - 数据结构
2023
数据结构与算法笔记
数据结构与算法笔记
2022
栈与队列
栈与队列
线性表
线性表
数据结构绪论
数据结构绪论
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git "a/categories/\346\234\272\345\231\250\345\255\246\344\271\240/index.html" "b/categories/\346\234\272\345\231\250\345\255\246\344\271\240/index.html" new file mode 100644 index 000000000..46e3301b6 --- /dev/null +++ "b/categories/\346\234\272\345\231\250\345\255\246\344\271\240/index.html" @@ -0,0 +1,498 @@ +分类: 机器学习 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git "a/categories/\347\231\276\345\256\235\347\256\261/index.html" "b/categories/\347\231\276\345\256\235\347\256\261/index.html" new file mode 100644 index 000000000..772bab542 --- /dev/null +++ "b/categories/\347\231\276\345\256\235\347\256\261/index.html" @@ -0,0 +1,498 @@ +分类: 百宝箱 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
分类 - 百宝箱
2023
Git 笔记
Git 笔记
Windows/Ubuntu双系统安装教程
Windows/Ubuntu双系统安装教程
2022
Win10/11任务栏透明美化
Win10/11任务栏透明美化
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/comments/index.html b/comments/index.html new file mode 100644 index 000000000..d4c149345 --- /dev/null +++ b/comments/index.html @@ -0,0 +1,625 @@ +留言板 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +

来自希亚的西红柿的留言:

有什么想问的?
有什么想说的?
有什么想吐槽的?
哪怕是有什么想吃的,都可以告诉我哦~

自动书记人偶竭诚为您服务!


评论
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/css/custom.css b/css/custom.css new file mode 100644 index 000000000..0bc353182 --- /dev/null +++ b/css/custom.css @@ -0,0 +1 @@ +body,html{font-family:"LXGW WenKai"}.card-clock-location,.card-clock-windDir,span.card-clock-weather{font-family:LXGW WenKai!important}.aplayer.aplayer-fixed.aplayer-narrow .aplayer-body{left:-66px!important}.aplayer.aplayer-fixed.aplayer-narrow .aplayer-body:hover{left:0!important}#footer-wrap .copyright{color:var(--font-color)}#web_bg{background-color:#ffdee9;background-image:linear-gradient(45deg,#ffdee9 0,#b5fffc 100%)}[data-theme=dark] #web_bg{background-color:#74ebd5;background-image:linear-gradient(180deg,#74ebd5 0,#9face6 100%)}#aside-content .card-widget,#content-inner>div:not([class]),#recent-posts>.recent-post-item,#sidebar #sidebar-menus,body #page-header.not-top-img #nav{background:rgba(255,255,255,.5)}@media screen and (max-width:768px){#aside-content .card-widget#card-toc,#sidebar #sidebar-menus{backdrop-filter:blur(10px)}}#nav .menus_items .menus_item .menus_item_child{background:rgba(255,255,255,.8)}[data-theme=dark] #nav .menus_items .menus_item .menus_item_child{background:rgba(0,0,0,.8)}[data-theme=dark] #aside-content .card-widget,[data-theme=dark] #content-inner>div:not([class]),[data-theme=dark] #recent-posts>.recent-post-item,[data-theme=dark] #sidebar #sidebar-menus,[data-theme=dark] body #page-header.not-top-img #nav{background:rgba(0,0,0,.5)}.fas.fa-envelope{color:#39c7f3}.fab.fa-bilibili{color:#00b3ef}.fas.fa-rss{color:#d68650}.fab.fa-telegram{color:#34a9e5}#footer-wrap .footer_custom_text img{vertical-align:middle}#footer-wrap .footer_custom_text a{line-height:1}:root{--cursor-arrow:url("pointer/Arrow.cur");--cursor-backrunning:url("pointer/Hand2.cur");--cursor-ban:url("pointer/NO.cur");--cursor-help:url("pointer/Help.cur");--cursor-link:url("pointer/Hand.cur");--cursor-input:url("pointer/IBeam.cur");--cursor-copy:url("pointer/Copy.cur");--docsearch-primary-color:#65b0ef!important}body,html{cursor:var(--cursor-arrow),auto!important}#article-container img{cursor:var(--cursor-backrunning),auto!important}a:hover{cursor:var(--cursor-link),auto!important}input:hover,textarea:hover{cursor:var(--cursor-input),auto!important}summary:hover{cursor:var(--cursor-help),auto!important}#article-container .code-expand-btn,#article-container figure.shiki .shiki-tools .expand,#twikoo svg,.aplayer .aplayer-info .aplayer-controller .aplayer-bar-wrap .aplayer-bar .aplayer-played .aplayer-thumb,.aplayer .aplayer-info .aplayer-music,.aplayer .aplayer-list ol li,.aplayer .aplayer-pic,.aplayer-time.aplayer-time-narrow svg,body .aplayer .aplayer-info .aplayer-controller .aplayer-bar-wrap,body .aplayer .aplayer-info .aplayer-controller .aplayer-volume-wrap,body .aplayer .aplayer-list ol li .aplayer-list-author,button:hover{cursor:var(--cursor-backrunning),auto!important}#footer-wrap a:hover{cursor:var(--cursor-link),auto!important}#pagination .page-number:hover{cursor:var(--cursor-link),auto!important}#nav .site-page:hover{cursor:var(--cursor-link),auto!important}#article-container .tabs>.nav-tabs>.tab.active button,.aplayer .aplayer-lrc .aplayer-lrc-contents{cursor:var(--cursor-arrow),auto!important}html>body :disabled{cursor:var(--cursor-ban),auto!important}.fas.fa-paste.copy-button{cursor:var(--cursor-copy),auto!important}::-webkit-scrollbar-thumb{background-color:#80c8f8;border-radius:4px;box-shadow:0 0 1px rgba(255,255,255,.01)}[data-theme=dark] ::-webkit-scrollbar-thumb{background-color:#2a2a2a}#nav .site-page:not(.child):after{border-radius:1.5px}#aside-content .card-categories ul.card-category-list li,#aside-content .card-info #card-info-btn,.aplayer .aplayer-list ol li.aplayer-list-light{border-radius:6px}#algolia-search .search-dialog .ais-Pagination .ais-Pagination-item .ais-Pagination-link,#aside-content #card-toc .toc-content .toc-link,#aside-content .aside-list>.aside-list-item .thumbnail,#aside-content .card-archives ul.card-archive-list li,#aside-content .card-archives ul.card-archive-list>.card-archive-list-item a,#aside-content .card-categories ul.card-category-list>.card-category-list-item a,#sidebar #sidebar-menus .menus_items .site-page,.article-sort-item-img,.layout>div:not(.recent-posts) .pagination .page-number{border-radius:6px}.pace .pace-progress{background-color:#80c8f8;border-radius:1px}.announcement_content a:hover{color:#fff;background:#ff7242}.announcement_content a{background:#e91e64;border-radius:6px}#my-aplayer .aplayer-body,.aplayer .aplayer-list ol li:hover,body .aplayer.aplayer-fixed{border-radius:0 6px 6px 0;overflow:hidden}#pagination.pagination-post,.aplayer .aplayer-list ol li:hover,.relatedPosts>.relatedPosts-list>div,body #post .post-copyright{border-radius:6px}#article-container code{border-radius:3px}body #post .post-copyright,body table td,body table th{border:1px solid #e1e1e1} \ No newline at end of file diff --git a/css/index.css b/css/index.css new file mode 100644 index 000000000..3130e8c77 --- /dev/null +++ b/css/index.css @@ -0,0 +1 @@ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}#article-container .flink .flink-item-desc,#article-container .flink .flink-item-name,#aside-content .card-archives ul.card-archive-list>.card-archive-list-item a span,#aside-content .card-categories ul.card-category-list>.card-category-list-item a span,#nav #blog-info,#pagination .next_info,#pagination .prev_info,#sidebar #sidebar-menus .menus_items .site-page,.limit-one-line,.site-data>a .headline{overflow:hidden;-o-text-overflow:ellipsis;text-overflow:ellipsis;white-space:nowrap}#article-container figure.gallery-group .gallery-group-name,#article-container figure.gallery-group p,#aside-content .aside-list>.aside-list-item .content>.comment,#aside-content .aside-list>.aside-list-item .content>.name,#aside-content .aside-list>.aside-list-item .content>.title,#post-info .post-title,#recent-posts>.recent-post-item>.recent-post-info>.article-title,#recent-posts>.recent-post-item>.recent-post-info>.content,.article-sort-item-title,.error404 #error-wrap .error-content .error-info .error_subtitle,.limit-more-line,.relatedPosts>.relatedPosts-list .content .title{display:-webkit-box;overflow:hidden;-webkit-box-orient:vertical}#article-container a.headerlink:after,#article-container h1:before,#article-container h2:before,#article-container h3:before,#article-container h4:before,#article-container h5:before,#article-container h6:before,#article-container hr:before,#post .post-copyright:before,#post .post-outdate-notice:before,.custom-hr:before,.fontawesomeIcon,.note:not(.no-icon)::before,.search-dialog hr:before{display:inline-block;font-weight:600;font-family:'Font Awesome 6 Free';text-rendering:auto;-webkit-font-smoothing:antialiased}#aside-content .card-widget,#recent-posts>.recent-post-item,.cardHover,.error404 #error-wrap .error-content,.layout>.recent-posts .pagination>:not(.space),.layout>div:first-child:not(.recent-posts){border-radius:8px;background:var(--card-bg);-webkit-box-shadow:var(--card-box-shadow);box-shadow:var(--card-box-shadow);-webkit-transition:all .3s;-moz-transition:all .3s;-o-transition:all .3s;-ms-transition:all .3s;transition:all .3s}#aside-content .card-widget:hover,#recent-posts>.recent-post-item:hover,.cardHover:hover,.error404 #error-wrap .error-content:hover,.layout>.recent-posts .pagination>:not(.space):hover,.layout>div:first-child:not(.recent-posts):hover{-webkit-box-shadow:var(--card-hover-box-shadow);box-shadow:var(--card-hover-box-shadow)}#aside-content .aside-list>.aside-list-item .thumbnail :first-child,#recent-posts>.recent-post-item .post_cover .post-bg,.article-sort-item-img :first-child,.error404 #error-wrap .error-content .error-img img,.imgHover{width:100%;height:100%;-webkit-transition:filter 375ms ease-in .2s,-webkit-transform .6s;-moz-transition:filter 375ms ease-in .2s,-moz-transform .6s;-o-transition:filter 375ms ease-in .2s,-o-transform .6s;-ms-transition:filter 375ms ease-in .2s,-ms-transform .6s;transition:filter 375ms ease-in .2s,transform .6s;object-fit:cover}#aside-content .aside-list>.aside-list-item .thumbnail :first-child:hover,#recent-posts>.recent-post-item .post_cover .post-bg:hover,.article-sort-item-img :first-child:hover,.error404 #error-wrap .error-content .error-img img:hover,.imgHover:hover{-webkit-transform:scale(1.1);-moz-transform:scale(1.1);-o-transform:scale(1.1);-ms-transform:scale(1.1);transform:scale(1.1)}#pagination .next-post:hover .cover,#pagination .prev-post:hover .cover,.postImgHover:hover .cover,.relatedPosts>.relatedPosts-list>div:hover .cover{opacity:.8;-webkit-transform:scale(1.1);-moz-transform:scale(1.1);-o-transform:scale(1.1);-ms-transform:scale(1.1);transform:scale(1.1)}#pagination .next-post .cover,#pagination .prev-post .cover,.postImgHover .cover,.relatedPosts>.relatedPosts-list>div .cover{position:absolute;width:100%;height:100%;opacity:.4;-webkit-transition:all .6s,filter 375ms ease-in .2s;-moz-transition:all .6s,filter 375ms ease-in .2s;-o-transition:all .6s,filter 375ms ease-in .2s;-ms-transition:all .6s,filter 375ms ease-in .2s;transition:all .6s,filter 375ms ease-in .2s;object-fit:cover}.category-lists ul,.list-beauty{list-style:none}.category-lists ul li,.list-beauty li{position:relative;padding:.12em .4em .12em 1.4em}.category-lists ul li:hover:before,.list-beauty li:hover:before{border-color:var(--pseudo-hover)}.category-lists ul li:before,.list-beauty li:before{position:absolute;top:.67em;left:0;width:.43em;height:.43em;border:.215em solid #49b1f5;border-radius:.43em;background:0 0;content:'';cursor:pointer;-webkit-transition:all .3s ease-out;-moz-transition:all .3s ease-out;-o-transition:all .3s ease-out;-ms-transition:all .3s ease-out;transition:all .3s ease-out}#article-container hr,.custom-hr,.search-dialog hr{position:relative;margin:40px auto;border:2px dashed var(--hr-border);width:calc(100% - 4px)}#article-container hr:hover:before,.custom-hr:hover:before,.search-dialog hr:hover:before{left:calc(95% - 20px)}#article-container hr:before,.custom-hr:before,.search-dialog hr:before{position:absolute;top:-10px;left:5%;z-index:1;color:var(--hr-before-color);content:'\f0c4';font-size:20px;line-height:1;-webkit-transition:all 1s ease-in-out;-moz-transition:all 1s ease-in-out;-o-transition:all 1s ease-in-out;-ms-transition:all 1s ease-in-out;transition:all 1s ease-in-out}.scroll-down-effects{-webkit-animation:scroll-down-effect 1.5s infinite;-moz-animation:scroll-down-effect 1.5s infinite;-o-animation:scroll-down-effect 1.5s infinite;-ms-animation:scroll-down-effect 1.5s infinite;animation:scroll-down-effect 1.5s infinite}.reward-main{-webkit-animation:donate_effcet .3s .1s ease both;-moz-animation:donate_effcet .3s .1s ease both;-o-animation:donate_effcet .3s .1s ease both;-ms-animation:donate_effcet .3s .1s ease both;animation:donate_effcet .3s .1s ease both}@-moz-keyframes scroll-down-effect{0%{top:0;opacity:.4}50%{top:-16px;opacity:1;-ms-filter:none;filter:none}100%{top:0;opacity:.4}}@-webkit-keyframes scroll-down-effect{0%{top:0;opacity:.4}50%{top:-16px;opacity:1;-ms-filter:none;filter:none}100%{top:0;opacity:.4}}@-o-keyframes scroll-down-effect{0%{top:0;opacity:.4}50%{top:-16px;opacity:1;-ms-filter:none;filter:none}100%{top:0;opacity:.4}}@keyframes scroll-down-effect{0%{top:0;opacity:.4}50%{top:-16px;opacity:1;-ms-filter:none;filter:none}100%{top:0;opacity:.4}}@-moz-keyframes header-effect{0%{opacity:0;-webkit-transform:translateY(-50px);-moz-transform:translateY(-50px);-o-transform:translateY(-50px);-ms-transform:translateY(-50px);transform:translateY(-50px)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes header-effect{0%{opacity:0;-webkit-transform:translateY(-50px);-moz-transform:translateY(-50px);-o-transform:translateY(-50px);-ms-transform:translateY(-50px);transform:translateY(-50px)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@-o-keyframes header-effect{0%{opacity:0;-webkit-transform:translateY(-50px);-moz-transform:translateY(-50px);-o-transform:translateY(-50px);-ms-transform:translateY(-50px);transform:translateY(-50px)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@keyframes header-effect{0%{opacity:0;-webkit-transform:translateY(-50px);-moz-transform:translateY(-50px);-o-transform:translateY(-50px);-ms-transform:translateY(-50px);transform:translateY(-50px)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@-moz-keyframes headerNoOpacity{0%{-webkit-transform:translateY(-50px);-moz-transform:translateY(-50px);-o-transform:translateY(-50px);-ms-transform:translateY(-50px);transform:translateY(-50px)}100%{-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes headerNoOpacity{0%{-webkit-transform:translateY(-50px);-moz-transform:translateY(-50px);-o-transform:translateY(-50px);-ms-transform:translateY(-50px);transform:translateY(-50px)}100%{-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@-o-keyframes headerNoOpacity{0%{-webkit-transform:translateY(-50px);-moz-transform:translateY(-50px);-o-transform:translateY(-50px);-ms-transform:translateY(-50px);transform:translateY(-50px)}100%{-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@keyframes headerNoOpacity{0%{-webkit-transform:translateY(-50px);-moz-transform:translateY(-50px);-o-transform:translateY(-50px);-ms-transform:translateY(-50px);transform:translateY(-50px)}100%{-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@-moz-keyframes bottom-top{0%{margin-top:50px;opacity:0}100%{margin-top:0;opacity:1;-ms-filter:none;filter:none}}@-webkit-keyframes bottom-top{0%{margin-top:50px;opacity:0}100%{margin-top:0;opacity:1;-ms-filter:none;filter:none}}@-o-keyframes bottom-top{0%{margin-top:50px;opacity:0}100%{margin-top:0;opacity:1;-ms-filter:none;filter:none}}@keyframes bottom-top{0%{margin-top:50px;opacity:0}100%{margin-top:0;opacity:1;-ms-filter:none;filter:none}}@-moz-keyframes titleScale{0%{opacity:0;-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}@-webkit-keyframes titleScale{0%{opacity:0;-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}@-o-keyframes titleScale{0%{opacity:0;-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}@keyframes titleScale{0%{opacity:0;-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}@-moz-keyframes search_close{0%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}100%{opacity:0;-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}}@-webkit-keyframes search_close{0%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}100%{opacity:0;-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}}@-o-keyframes search_close{0%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}100%{opacity:0;-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}}@keyframes search_close{0%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}100%{opacity:0;-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}}@-moz-keyframes to_show{0%{opacity:0}100%{opacity:1;-ms-filter:none;filter:none}}@-webkit-keyframes to_show{0%{opacity:0}100%{opacity:1;-ms-filter:none;filter:none}}@-o-keyframes to_show{0%{opacity:0}100%{opacity:1;-ms-filter:none;filter:none}}@keyframes to_show{0%{opacity:0}100%{opacity:1;-ms-filter:none;filter:none}}@-moz-keyframes to_hide{0%{opacity:1;-ms-filter:none;filter:none}100%{opacity:0}}@-webkit-keyframes to_hide{0%{opacity:1;-ms-filter:none;filter:none}100%{opacity:0}}@-o-keyframes to_hide{0%{opacity:1;-ms-filter:none;filter:none}100%{opacity:0}}@keyframes to_hide{0%{opacity:1;-ms-filter:none;filter:none}100%{opacity:0}}@-moz-keyframes ribbon_to_show{0%{opacity:0}100%{opacity:.6}}@-webkit-keyframes ribbon_to_show{0%{opacity:0}100%{opacity:.6}}@-o-keyframes ribbon_to_show{0%{opacity:0}100%{opacity:.6}}@keyframes ribbon_to_show{0%{opacity:0}100%{opacity:.6}}@-moz-keyframes avatar_turn_around{from{-webkit-transform:rotate(0);-moz-transform:rotate(0);-o-transform:rotate(0);-ms-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);-o-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes avatar_turn_around{from{-webkit-transform:rotate(0);-moz-transform:rotate(0);-o-transform:rotate(0);-ms-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);-o-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg)}}@-o-keyframes avatar_turn_around{from{-webkit-transform:rotate(0);-moz-transform:rotate(0);-o-transform:rotate(0);-ms-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);-o-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes avatar_turn_around{from{-webkit-transform:rotate(0);-moz-transform:rotate(0);-o-transform:rotate(0);-ms-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);-o-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg)}}@-moz-keyframes sub_menus{0%{opacity:0;-webkit-transform:translateY(10px);-moz-transform:translateY(10px);-o-transform:translateY(10px);-ms-transform:translateY(10px);transform:translateY(10px)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes sub_menus{0%{opacity:0;-webkit-transform:translateY(10px);-moz-transform:translateY(10px);-o-transform:translateY(10px);-ms-transform:translateY(10px);transform:translateY(10px)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@-o-keyframes sub_menus{0%{opacity:0;-webkit-transform:translateY(10px);-moz-transform:translateY(10px);-o-transform:translateY(10px);-ms-transform:translateY(10px);transform:translateY(10px)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@keyframes sub_menus{0%{opacity:0;-webkit-transform:translateY(10px);-moz-transform:translateY(10px);-o-transform:translateY(10px);-ms-transform:translateY(10px);transform:translateY(10px)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@-moz-keyframes donate_effcet{0%{opacity:0;-webkit-transform:translateY(-20px);-moz-transform:translateY(-20px);-o-transform:translateY(-20px);-ms-transform:translateY(-20px);transform:translateY(-20px)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes donate_effcet{0%{opacity:0;-webkit-transform:translateY(-20px);-moz-transform:translateY(-20px);-o-transform:translateY(-20px);-ms-transform:translateY(-20px);transform:translateY(-20px)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@-o-keyframes donate_effcet{0%{opacity:0;-webkit-transform:translateY(-20px);-moz-transform:translateY(-20px);-o-transform:translateY(-20px);-ms-transform:translateY(-20px);transform:translateY(-20px)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@keyframes donate_effcet{0%{opacity:0;-webkit-transform:translateY(-20px);-moz-transform:translateY(-20px);-o-transform:translateY(-20px);-ms-transform:translateY(-20px);transform:translateY(-20px)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@-moz-keyframes sidebarItem{0%{-webkit-transform:translateX(200px);-moz-transform:translateX(200px);-o-transform:translateX(200px);-ms-transform:translateX(200px);transform:translateX(200px)}100%{-webkit-transform:translateX(0);-moz-transform:translateX(0);-o-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}}@-webkit-keyframes sidebarItem{0%{-webkit-transform:translateX(200px);-moz-transform:translateX(200px);-o-transform:translateX(200px);-ms-transform:translateX(200px);transform:translateX(200px)}100%{-webkit-transform:translateX(0);-moz-transform:translateX(0);-o-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}}@-o-keyframes sidebarItem{0%{-webkit-transform:translateX(200px);-moz-transform:translateX(200px);-o-transform:translateX(200px);-ms-transform:translateX(200px);transform:translateX(200px)}100%{-webkit-transform:translateX(0);-moz-transform:translateX(0);-o-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}}@keyframes sidebarItem{0%{-webkit-transform:translateX(200px);-moz-transform:translateX(200px);-o-transform:translateX(200px);-ms-transform:translateX(200px);transform:translateX(200px)}100%{-webkit-transform:translateX(0);-moz-transform:translateX(0);-o-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}}:root{--global-font-size:15px;--global-bg:#fff;--font-color:#4c4948;--hr-border:#a4d8fa;--hr-before-color:#80c8f8;--search-bg:#f6f8fa;--search-input-color:#4c4948;--search-a-color:#4c4948;--preloader-bg:#37474f;--preloader-color:#fff;--tab-border-color:#f0f0f0;--tab-botton-bg:#f0f0f0;--tab-botton-color:#1f2d3d;--tab-button-hover-bg:#dcdcdc;--tab-button-active-bg:#fff;--card-bg:#fff;--sidebar-bg:#f6f8fa;--btn-hover-color:#ff7242;--btn-color:#fff;--btn-bg:#49b1f5;--text-bg-hover:rgba(73,177,245,0.7);--light-grey:#eee;--dark-grey:#cacaca;--white:#fff;--text-highlight-color:#1f2d3d;--blockquote-color:#6a737d;--blockquote-bg:rgba(73,177,245,0.1);--reward-pop:#f5f5f5;--toc-link-color:#666261;--card-box-shadow:0 3px 8px 6px rgba(7,17,27,0.05);--card-hover-box-shadow:0 3px 8px 6px rgba(7,17,27,0.09);--pseudo-hover:#ff7242;--headline-presudo:#a0a0a0;--scrollbar-color:#49b1f5;--default-bg-color:#49b1f5;--zoom-bg:#fff;--mark-bg:rgba(0,0,0,0.3)}body{position:relative;min-height:100%;background:var(--global-bg);color:var(--font-color);font-size:var(--global-font-size);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','Helvetica Neue',Lato,Roboto,'PingFang SC','Microsoft YaHei',sans-serif;line-height:2;-webkit-tap-highlight-color:transparent}::-webkit-scrollbar{width:5px;height:5px}::-webkit-scrollbar-thumb{background:var(--scrollbar-color)}::-webkit-scrollbar-track{background-color:transparent}*{scrollbar-width:thin;scrollbar-color:var(--scrollbar-color) transparent}input::placeholder{color:var(--font-color)}#web_bg{position:fixed;z-index:-999;width:100%;height:100%;background:0 0;background-attachment:local;background-position:center;background-size:cover;background-repeat:no-repeat}h1,h2,h3,h4,h5,h6{position:relative;margin:20px 0 14px;color:var(--text-highlight-color);font-weight:700}h1 code,h2 code,h3 code,h4 code,h5 code,h6 code{font-size:inherit!important}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.table-wrap{overflow-x:scroll;margin:0 0 20px}table{display:table;width:100%;border-spacing:0;border-collapse:collapse;empty-cells:show}table thead{background:rgba(153,169,191,.1)}table td,table th{padding:6px 12px;border:1px solid var(--light-grey);vertical-align:middle}::selection{background:#00c4b6;color:#f7f7f7}button{padding:0;outline:0;border:none;background:0 0;cursor:pointer;touch-action:manipulation}a{color:#99a9bf;text-decoration:none;word-wrap:break-word;-webkit-transition:all .2s;-moz-transition:all .2s;-o-transition:all .2s;-ms-transition:all .2s;transition:all .2s;overflow-wrap:break-word}a:hover{color:#49b1f5}.is-center{text-align:center}.pull-left{float:left}.pull-right{float:right}img:not([src]),img[src='']{opacity:0}img[data-lazy-src]:not(.loaded){filter:blur(8px) brightness(1)}img[data-lazy-src].error{filter:none}.img-alt{margin:-10px 0 10px;color:#858585}.img-alt:hover{text-decoration:none!important}blockquote{margin:0 0 20px;padding:12px 15px;border-left:3px solid #49b1f5;background-color:var(--blockquote-bg);color:var(--blockquote-color)}blockquote footer cite:before{padding:0 5px;content:'—'}blockquote>:last-child{margin-bottom:0!important}:root{--hl-color:#eff;--hl-bg:#212121;--hltools-bg:#1c1c1c;--hltools-color:rgba(238,255,255,0.8);--hlnumber-bg:#212121;--hlnumber-color:rgba(238,255,255,0.5);--hlscrollbar-bg:#353535;--hlexpand-bg:linear-gradient(180deg, rgba(33,33,33,0.6), rgba(33,33,33,0.9))}#article-container figure.highlight,#article-container pre{overflow:auto;margin:0 0 20px;padding:0;background:var(--hl-bg);color:var(--hl-color);line-height:1.6}#article-container code,#article-container pre{font-size:var(--global-font-size);font-family:consolas,Menlo,'PingFang SC','Microsoft YaHei',sans-serif!important}#article-container code{padding:2px 4px;background:rgba(27,31,35,.05);color:#f47466}#article-container pre{padding:10px 20px}#article-container pre code{padding:0;background:0 0;color:var(--hl-color);text-shadow:none}#article-container figure.highlight{position:relative}#article-container figure.highlight pre{margin:0;padding:8px 0;border:none}#article-container figure.highlight .caption,#article-container figure.highlight figcaption{padding:6px 0 2px 14px;font-size:var(--global-font-size);line-height:1em}#article-container figure.highlight .caption a,#article-container figure.highlight figcaption a{float:right;padding-right:10px;color:var(--hl-color)}#article-container figure.highlight .caption a:hover,#article-container figure.highlight figcaption a:hover{border-bottom-color:var(--hl-color)}#article-container figure.highlight.copy-true{-webkit-user-select:all;-moz-user-select:all;-ms-user-select:all;user-select:all}#article-container figure.highlight.copy-true>pre,#article-container figure.highlight.copy-true>table{display:block!important;opacity:0}#article-container .highlight-tools{position:relative;display:-webkit-box;display:-moz-box;display:-webkit-flex;display:-ms-flexbox;display:box;display:flex;-webkit-box-align:center;-moz-box-align:center;-o-box-align:center;-ms-flex-align:center;-webkit-align-items:center;align-items:center;overflow:hidden;min-height:24px;height:2.15em;background:var(--hltools-bg);color:var(--hltools-color);font-size:var(--global-font-size)}#article-container .highlight-tools.closed~*{display:none}#article-container .highlight-tools .expand{position:absolute;padding:.57em .7em;cursor:pointer;-webkit-transition:-webkit-transform .3s;-moz-transition:-moz-transform .3s;-o-transition:-o-transform .3s;-ms-transition:-ms-transform .3s;transition:transform .3s}#article-container .highlight-tools .expand+.code-lang{left:1.7em}#article-container .highlight-tools .expand.closed{-webkit-transition:all .3s;-moz-transition:all .3s;-o-transition:all .3s;-ms-transition:all .3s;transition:all .3s;-webkit-transform:rotate(-90deg)!important;-moz-transform:rotate(-90deg)!important;-o-transform:rotate(-90deg)!important;-ms-transform:rotate(-90deg)!important;transform:rotate(-90deg)!important}#article-container .highlight-tools .code-lang{position:absolute;left:14px;text-transform:uppercase;font-weight:700;font-size:1.15em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}#article-container .highlight-tools .copy-notice{position:absolute;right:2.4em;opacity:0;-webkit-transition:opacity .4s;-moz-transition:opacity .4s;-o-transition:opacity .4s;-ms-transition:opacity .4s;transition:opacity .4s}#article-container .highlight-tools .copy-button{position:absolute;right:14px;cursor:pointer;-webkit-transition:color .2s;-moz-transition:color .2s;-o-transition:color .2s;-ms-transition:color .2s;transition:color .2s}#article-container .highlight-tools .copy-button:hover{color:#49b1f5}#article-container .gutter{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}#article-container .gist table{width:auto}#article-container .gist table td{border:none}#article-container figure.highlight{margin:0 0 24px;border-radius:7px;-webkit-box-shadow:0 5px 10px 0 rgba(0,0,0,.4);box-shadow:0 5px 10px 0 rgba(0,0,0,.4);-webkit-transform:translateZ(0)}#article-container figure.highlight .highlight-tools:after{position:absolute;left:14px;width:12px;height:12px;border-radius:50%;background:#fc625d;-webkit-box-shadow:20px 0 #fdbc40,40px 0 #35cd4b;box-shadow:20px 0 #fdbc40,40px 0 #35cd4b;content:' '}#article-container figure.highlight .highlight-tools .expand{right:0}#article-container figure.highlight .highlight-tools .expand.closed{-webkit-transition:all .3s;-moz-transition:all .3s;-o-transition:all .3s;-ms-transition:all .3s;transition:all .3s;-webkit-transform:rotate(90deg)!important;-moz-transform:rotate(90deg)!important;-o-transform:rotate(90deg)!important;-ms-transform:rotate(90deg)!important;transform:rotate(90deg)!important}#article-container figure.highlight .highlight-tools .expand~.copy-notice{right:3.45em}#article-container figure.highlight .highlight-tools .expand~.copy-button{right:2.1em}#article-container figure.highlight .highlight-tools .code-lang{left:75px}#article-container .code-expand-btn{position:absolute;bottom:0;z-index:10;width:100%;background:var(--hlexpand-bg);text-align:center;font-size:var(--global-font-size);cursor:pointer}#article-container .code-expand-btn i{padding:6px 0;color:var(--hlnumber-color);-webkit-animation:code-expand-key 1.2s infinite;-moz-animation:code-expand-key 1.2s infinite;-o-animation:code-expand-key 1.2s infinite;-ms-animation:code-expand-key 1.2s infinite;animation:code-expand-key 1.2s infinite}#article-container .code-expand-btn.expand-done>i{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-o-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}#article-container .code-expand-btn.expand-done+pre,#article-container .code-expand-btn.expand-done+table{margin-bottom:1.8em}#article-container .code-expand-btn:not(.expand-done)~pre,#article-container .code-expand-btn:not(.expand-done)~table{overflow:hidden;height:360px}@-moz-keyframes code-expand-key{0%{opacity:.6}50%{opacity:.1}100%{opacity:.6}}@-webkit-keyframes code-expand-key{0%{opacity:.6}50%{opacity:.1}100%{opacity:.6}}@-o-keyframes code-expand-key{0%{opacity:.6}50%{opacity:.1}100%{opacity:.6}}@keyframes code-expand-key{0%{opacity:.6}50%{opacity:.1}100%{opacity:.6}}.error404 #error-wrap{position:absolute;top:50%;right:0;left:0;margin:0 auto;padding:60px 20px 0;max-width:1000px;-webkit-transform:translate(0,-50%);-moz-transform:translate(0,-50%);-o-transform:translate(0,-50%);-ms-transform:translate(0,-50%);transform:translate(0,-50%)}.error404 #error-wrap .error-content{overflow:hidden;margin:0 20px;height:360px}@media screen and (max-width:768px){.error404 #error-wrap .error-content{margin:0;height:500px}}.error404 #error-wrap .error-content .error-img{display:inline-block;overflow:hidden;width:50%;height:100%}@media screen and (max-width:768px){.error404 #error-wrap .error-content .error-img{width:100%;height:45%}}.error404 #error-wrap .error-content .error-img img{background-color:#49b1f5}.error404 #error-wrap .error-content .error-info{display:-webkit-inline-box;display:-moz-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-box;display:inline-flex;-webkit-box-orient:vertical;-moz-box-orient:vertical;-o-box-orient:vertical;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:center;-moz-box-pack:center;-o-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;-ms-flex-line-pack:center;-webkit-align-content:center;align-content:center;width:50%;height:100%;vertical-align:top;text-align:center}@media screen and (max-width:768px){.error404 #error-wrap .error-content .error-info{width:100%;height:55%}}.error404 #error-wrap .error-content .error-info .error_title{margin-top:-.6em;font-size:9em}@media screen and (max-width:768px){.error404 #error-wrap .error-content .error-info .error_title{font-size:8em}}.error404 #error-wrap .error-content .error-info .error_subtitle{margin-top:-3em;word-break:break-word;font-size:1.6em;-webkit-line-clamp:2}.error404+#rightside{display:none}.article-sort{margin-left:10px;padding-left:20px;border-left:2px solid #aadafa}.article-sort-title{position:relative;margin-left:10px;padding-bottom:20px;padding-left:20px;font-size:1.72em}.article-sort-title:hover:before{border-color:var(--pseudo-hover)}.article-sort-title:before{position:absolute;top:calc(((100% - 36px)/ 2));left:-9px;z-index:1;width:10px;height:10px;border:5px solid #49b1f5;border-radius:10px;background:var(--card-bg);content:'';line-height:10px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;-ms-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.article-sort-title:after{position:absolute;bottom:0;left:0;z-index:0;width:2px;height:1.5em;background:#aadafa;content:''}.article-sort-item{position:relative;display:-webkit-box;display:-moz-box;display:-webkit-flex;display:-ms-flexbox;display:box;display:flex;-webkit-box-align:center;-moz-box-align:center;-o-box-align:center;-ms-flex-align:center;-webkit-align-items:center;align-items:center;margin:0 0 20px 10px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;-ms-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.article-sort-item:hover:before{border-color:var(--pseudo-hover)}.article-sort-item:before{position:absolute;left:calc(-20px - 17px);width:6px;height:6px;border:3px solid #49b1f5;border-radius:6px;background:var(--card-bg);content:'';-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;-ms-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.article-sort-item.no-article-cover{height:80px}.article-sort-item.no-article-cover .article-sort-item-info{padding:0}.article-sort-item.year{font-size:1.43em}.article-sort-item.year:hover:before{border-color:#49b1f5}.article-sort-item.year:before{border-color:var(--pseudo-hover)}.article-sort-item-time{color:#858585;font-size:95%}.article-sort-item-time time{padding-left:6px;cursor:default}.article-sort-item-title{color:var(--font-color);font-size:1.1em;-webkit-transition:all .3s;-moz-transition:all .3s;-o-transition:all .3s;-ms-transition:all .3s;transition:all .3s;-webkit-line-clamp:2}.article-sort-item-title:hover{color:#49b1f5;-webkit-transform:translateX(10px);-moz-transform:translateX(10px);-o-transform:translateX(10px);-ms-transform:translateX(10px);transform:translateX(10px)}.article-sort-item-img{overflow:hidden;width:80px;height:80px}.article-sort-item-info{-webkit-box-flex:1;-moz-box-flex:1;-o-box-flex:1;box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;padding:0 16px}.category-lists .category-title{font-size:2.57em}@media screen and (max-width:768px){.category-lists .category-title{font-size:2em}}.category-lists .category-list{margin-bottom:0}.category-lists .category-list a{color:var(--font-color)}.category-lists .category-list a:hover{color:#49b1f5}.category-lists .category-list .category-list-count{margin-left:8px;color:#858585}.category-lists .category-list .category-list-count:before{content:'('}.category-lists .category-list .category-list-count:after{content:')'}.category-lists ul{padding:0 0 0 20px}.category-lists ul ul{padding-left:4px}.category-lists ul li{position:relative;margin:6px 0;padding:.12em .4em .12em 1.4em}#body-wrap{display:-webkit-box;display:-moz-box;display:-webkit-flex;display:-ms-flexbox;display:box;display:flex;-webkit-box-orient:vertical;-moz-box-orient:vertical;-o-box-orient:vertical;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;min-height:100vh}.layout{display:-webkit-box;display:-moz-box;display:-webkit-flex;display:-ms-flexbox;display:box;display:flex;-webkit-box-flex:1;-moz-box-flex:1;-o-box-flex:1;box-flex:1;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;margin:0 auto;padding:40px 15px;max-width:1200px;width:100%}@media screen and (max-width:900px){.layout{-webkit-box-orient:vertical;-moz-box-orient:vertical;-o-box-orient:vertical;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}}@media screen and (max-width:768px){.layout{padding:20px 5px}}@media screen and (min-width:2000px){.layout{max-width:70%}}.layout>div:first-child:not(.recent-posts){-webkit-align-self:flex-start;align-self:flex-start;-ms-flex-item-align:start;padding:50px 40px}@media screen and (max-width:768px){.layout>div:first-child:not(.recent-posts){padding:36px 14px}}.layout>div:first-child{width:74%;-webkit-transition:all .3s;-moz-transition:all .3s;-o-transition:all .3s;-ms-transition:all .3s;transition:all .3s}@media screen and (max-width:900px){.layout>div:first-child{width:100%!important}}.layout.hide-aside{max-width:1000px}@media screen and (min-width:2000px){.layout.hide-aside{max-width:1300px}}.layout.hide-aside>div{width:100%!important}.apple #page-header.full_page{background-attachment:scroll!important}.apple .avatar-img,.apple .flink-item-icon,.apple .recent-post-item{-webkit-transform:translateZ(0);-moz-transform:translateZ(0);-o-transform:translateZ(0);-ms-transform:translateZ(0);transform:translateZ(0)}#article-container .flink{margin-bottom:20px}#article-container .flink .flink-list{overflow:auto;padding:10px 10px 0;text-align:center}#article-container .flink .flink-list>.flink-list-item{position:relative;float:left;overflow:hidden;margin:15px 7px;width:calc(100% / 3 - 15px);height:90px;border-radius:8px;line-height:17px;-webkit-transform:translateZ(0)}@media screen and (max-width:1024px){#article-container .flink .flink-list>.flink-list-item{width:calc(50% - 15px)!important}}@media screen and (max-width:600px){#article-container .flink .flink-list>.flink-list-item{width:calc(100% - 15px)!important}}#article-container .flink .flink-list>.flink-list-item:hover .flink-item-icon{margin-left:-10px;width:0}#article-container .flink .flink-list>.flink-list-item:before{position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1;background:var(--text-bg-hover);content:'';-webkit-transition:-webkit-transform .3s ease-out;-moz-transition:-moz-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;-ms-transition:-ms-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:scale(0);-moz-transform:scale(0);-o-transform:scale(0);-ms-transform:scale(0);transform:scale(0)}#article-container .flink .flink-list>.flink-list-item:active:before,#article-container .flink .flink-list>.flink-list-item:focus:before,#article-container .flink .flink-list>.flink-list-item:hover:before{-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}#article-container .flink .flink-list>.flink-list-item a{color:var(--font-color);text-decoration:none}#article-container .flink .flink-list>.flink-list-item a .flink-item-icon{float:left;overflow:hidden;margin:15px 10px;width:60px;height:60px;border-radius:35px;-webkit-transition:width .3s ease-out;-moz-transition:width .3s ease-out;-o-transition:width .3s ease-out;-ms-transition:width .3s ease-out;transition:width .3s ease-out}#article-container .flink .flink-list>.flink-list-item a .flink-item-icon img{width:100%;height:100%;-webkit-transition:filter 375ms ease-in .2s,-webkit-transform .3s;-moz-transition:filter 375ms ease-in .2s,-moz-transform .3s;-o-transition:filter 375ms ease-in .2s,-o-transform .3s;-ms-transition:filter 375ms ease-in .2s,-ms-transform .3s;transition:filter 375ms ease-in .2s,transform .3s;object-fit:cover}#article-container .flink .flink-list>.flink-list-item a .img-alt{display:none}#article-container .flink .flink-item-name{padding:16px 10px 0 0;height:40px;font-weight:700;font-size:1.43em}#article-container .flink .flink-item-desc{padding:16px 10px 16px 0;height:50px;font-size:.93em}#article-container .flink .flink-name{margin-bottom:5px;font-weight:700;font-size:1.5em}#recent-posts>.recent-post-item:not(:first-child){margin-top:20px}#recent-posts>.recent-post-item{display:-webkit-box;display:-moz-box;display:-webkit-flex;display:-ms-flexbox;display:box;display:flex;-webkit-box-orient:horizontal;-moz-box-orient:horizontal;-o-box-orient:horizontal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-box-align:center;-moz-box-align:center;-o-box-align:center;-ms-flex-align:center;-webkit-align-items:center;align-items:center;overflow:hidden;height:16.8em}@media screen and (max-width:768px){#recent-posts>.recent-post-item{-webkit-box-orient:vertical;-moz-box-orient:vertical;-o-box-orient:vertical;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;height:auto}}#recent-posts>.recent-post-item:hover img.post-bg{-webkit-transform:scale(1.1);-moz-transform:scale(1.1);-o-transform:scale(1.1);-ms-transform:scale(1.1);transform:scale(1.1)}#recent-posts>.recent-post-item.ads-wrap{display:block!important;height:auto!important}#recent-posts>.recent-post-item .post_cover{overflow:hidden;width:42%;height:100%}@media screen and (max-width:768px){#recent-posts>.recent-post-item .post_cover{width:100%;height:230px}}#recent-posts>.recent-post-item .post_cover.right{-webkit-box-ordinal-group:1;-moz-box-ordinal-group:1;-o-box-ordinal-group:1;-ms-flex-order:1;-webkit-order:1;order:1}@media screen and (max-width:768px){#recent-posts>.recent-post-item .post_cover.right{-webkit-box-ordinal-group:0;-moz-box-ordinal-group:0;-o-box-ordinal-group:0;-ms-flex-order:0;-webkit-order:0;order:0}}#recent-posts>.recent-post-item>.recent-post-info{padding:0 40px;width:58%}@media screen and (max-width:768px){#recent-posts>.recent-post-item>.recent-post-info{padding:20px 20px 30px;width:100%}}#recent-posts>.recent-post-item>.recent-post-info.no-cover{width:100%}@media screen and (max-width:768px){#recent-posts>.recent-post-item>.recent-post-info.no-cover{padding:30px 20px}}#recent-posts>.recent-post-item>.recent-post-info>.article-title{color:var(--text-highlight-color);font-size:1.55em;line-height:1.4;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;-ms-transition:all .2s ease-in-out;transition:all .2s ease-in-out;-webkit-line-clamp:2}@media screen and (max-width:768px){#recent-posts>.recent-post-item>.recent-post-info>.article-title{font-size:1.43em}}#recent-posts>.recent-post-item>.recent-post-info>.article-title:hover{color:#49b1f5}#recent-posts>.recent-post-item>.recent-post-info>.article-meta-wrap{margin:6px 0;color:#858585;font-size:.9em}#recent-posts>.recent-post-item>.recent-post-info>.article-meta-wrap>.post-meta-date{cursor:default}#recent-posts>.recent-post-item>.recent-post-info>.article-meta-wrap .sticky{color:#ff7242}#recent-posts>.recent-post-item>.recent-post-info>.article-meta-wrap i{margin:0 4px 0 0}#recent-posts>.recent-post-item>.recent-post-info>.article-meta-wrap .fa-spinner{margin:0}#recent-posts>.recent-post-item>.recent-post-info>.article-meta-wrap .article-meta-label{padding-right:4px}#recent-posts>.recent-post-item>.recent-post-info>.article-meta-wrap .article-meta-separator{margin:0 6px}#recent-posts>.recent-post-item>.recent-post-info>.article-meta-wrap .article-meta-link{margin:0 4px}#recent-posts>.recent-post-item>.recent-post-info>.article-meta-wrap a{color:#858585}#recent-posts>.recent-post-item>.recent-post-info>.article-meta-wrap a:hover{color:#49b1f5;text-decoration:underline}#recent-posts>.recent-post-item>.recent-post-info>.content{-webkit-line-clamp:2}.tag-cloud-list a{display:inline-block;padding:0 8px;-webkit-transition:all .3s;-moz-transition:all .3s;-o-transition:all .3s;-ms-transition:all .3s;transition:all .3s}.tag-cloud-list a:hover{color:#49b1f5!important;-webkit-transform:scale(1.1);-moz-transform:scale(1.1);-o-transform:scale(1.1);-ms-transform:scale(1.1);transform:scale(1.1)}@media screen and (max-width:768px){.tag-cloud-list a{zoom:.85}}.tag-cloud-title{font-size:2.57em}@media screen and (max-width:768px){.tag-cloud-title{font-size:2em}}h1.page-title+.tag-cloud-list{text-align:left}#aside-content{width:26%}@media screen and (min-width:900px){#aside-content{padding-left:15px}}@media screen and (max-width:900px){#aside-content{width:100%}}#aside-content>.card-widget:first-child{margin-top:0}@media screen and (max-width:900px){#aside-content>.card-widget:first-child{margin-top:20px}}#aside-content .card-widget{position:relative;overflow:hidden;margin-top:20px;padding:20px 24px}#aside-content .card-info .author-info__name{font-weight:500;font-size:1.57em}#aside-content .card-info .author-info__description{margin-top:-.42em}#aside-content .card-info .card-info-data{margin:14px 0 4px}#aside-content .card-info .card-info-social-icons{margin:6px 0 -6px}#aside-content .card-info .card-info-social-icons .social-icon{margin:0 10px;color:var(--font-color);font-size:1.4em}#aside-content .card-info .card-info-social-icons i{-webkit-transition:all .3s;-moz-transition:all .3s;-o-transition:all .3s;-ms-transition:all .3s;transition:all .3s}#aside-content .card-info .card-info-social-icons i:hover{-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);-o-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg)}#aside-content .card-info #card-info-btn{display:block;margin-top:14px;background-color:var(--btn-bg);color:var(--btn-color);text-align:center;line-height:2.4}#aside-content .card-info #card-info-btn:hover{background-color:var(--btn-hover-color)}#aside-content .card-info #card-info-btn span{padding-left:10px}#aside-content .item-headline{padding-bottom:6px;font-size:1.2em}#aside-content .item-headline span{margin-left:6px}@media screen and (min-width:900px){#aside-content .sticky_layout{position:sticky;position:-webkit-sticky;top:20px;-webkit-transition:top .3s;-moz-transition:top .3s;-o-transition:top .3s;-ms-transition:top .3s;transition:top .3s}}#aside-content .card-tag-cloud a{display:inline-block;padding:0 4px}#aside-content .card-tag-cloud a:hover{color:#49b1f5!important}#aside-content .aside-list>span{display:block;margin-bottom:10px;text-align:center}#aside-content .aside-list>.aside-list-item{display:-webkit-box;display:-moz-box;display:-webkit-flex;display:-ms-flexbox;display:box;display:flex;-webkit-box-align:center;-moz-box-align:center;-o-box-align:center;-ms-flex-align:center;-webkit-align-items:center;align-items:center;padding:6px 0}#aside-content .aside-list>.aside-list-item:first-child{padding-top:0}#aside-content .aside-list>.aside-list-item:not(:last-child){border-bottom:1px dashed #f5f5f5}#aside-content .aside-list>.aside-list-item:last-child{padding-bottom:0}#aside-content .aside-list>.aside-list-item .thumbnail{overflow:hidden;width:4.2em;height:4.2em}#aside-content .aside-list>.aside-list-item .content{-webkit-box-flex:1;-moz-box-flex:1;-o-box-flex:1;box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;padding-left:10px;word-break:break-all}#aside-content .aside-list>.aside-list-item .content>.name{-webkit-line-clamp:1}#aside-content .aside-list>.aside-list-item .content>.name,#aside-content .aside-list>.aside-list-item .content>time{display:block;color:#858585;font-size:85%}#aside-content .aside-list>.aside-list-item .content>.comment,#aside-content .aside-list>.aside-list-item .content>.title{color:var(--font-color);font-size:95%;line-height:1.5;-webkit-line-clamp:2}#aside-content .aside-list>.aside-list-item .content>.comment:hover,#aside-content .aside-list>.aside-list-item .content>.title:hover{color:#49b1f5}#aside-content .aside-list>.aside-list-item.no-cover{min-height:4.4em}#aside-content .card-archives ul.card-archive-list,#aside-content .card-categories ul.card-category-list{margin:0;padding:0;list-style:none}#aside-content .card-archives ul.card-archive-list>.card-archive-list-item a,#aside-content .card-categories ul.card-category-list>.card-category-list-item a{display:-webkit-box;display:-moz-box;display:-webkit-flex;display:-ms-flexbox;display:box;display:flex;-webkit-box-orient:horizontal;-moz-box-orient:horizontal;-o-box-orient:horizontal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;padding:3px 10px;color:var(--font-color);-webkit-transition:all .4s;-moz-transition:all .4s;-o-transition:all .4s;-ms-transition:all .4s;transition:all .4s}#aside-content .card-archives ul.card-archive-list>.card-archive-list-item a:hover,#aside-content .card-categories ul.card-category-list>.card-category-list-item a:hover{padding:3px 17px;background-color:var(--text-bg-hover)}#aside-content .card-archives ul.card-archive-list>.card-archive-list-item a span:first-child,#aside-content .card-categories ul.card-category-list>.card-category-list-item a span:first-child{-webkit-box-flex:1;-moz-box-flex:1;-o-box-flex:1;box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}#aside-content .card-categories .card-category-list.child{padding:0 0 0 16px}#aside-content .card-categories .card-category-list>.parent>a .card-category-list-name{width:70%!important}#aside-content .card-categories .card-category-list>.parent>a .card-category-list-count{width:calc(100% - 70% - 20px);text-align:right}#aside-content .card-categories .card-category-list>.parent i{float:right;margin-right:-.5em;padding:.5em;-webkit-transition:-webkit-transform .3s;-moz-transition:-moz-transform .3s;-o-transition:-o-transform .3s;-ms-transition:-ms-transform .3s;transition:transform .3s;-webkit-transform:rotate(0);-moz-transform:rotate(0);-o-transform:rotate(0);-ms-transform:rotate(0);transform:rotate(0)}#aside-content .card-categories .card-category-list>.parent i.expand{-webkit-transform:rotate(-90deg);-moz-transform:rotate(-90deg);-o-transform:rotate(-90deg);-ms-transform:rotate(-90deg);transform:rotate(-90deg)}#aside-content .card-webinfo .webinfo .webinfo-item{display:-webkit-box;display:-moz-box;display:-webkit-flex;display:-ms-flexbox;display:box;display:flex;-webkit-box-align:center;-moz-box-align:center;-o-box-align:center;-ms-flex-align:center;-webkit-align-items:center;align-items:center;padding:2px 10px 0}#aside-content .card-webinfo .webinfo .webinfo-item div:first-child{-webkit-box-flex:1;-moz-box-flex:1;-o-box-flex:1;box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;padding-right:20px}@media screen and (min-width:901px){#aside-content #card-toc{right:0!important}}@media screen and (max-width:900px){#aside-content #card-toc{position:fixed;right:-100%;bottom:30px;z-index:100;max-width:380px;max-height:calc(100% - 60px);width:calc(100% - 80px);opacity:0;-webkit-transition:initial;-moz-transition:initial;-o-transition:initial;-ms-transition:initial;transition:initial;-webkit-transform-origin:right bottom;-moz-transform-origin:right bottom;-o-transform-origin:right bottom;-ms-transform-origin:right bottom;transform-origin:right bottom}}#aside-content #card-toc .toc-percentage{float:right;margin-top:-9px;color:#a9a9a9;font-style:italic;font-size:140%}#aside-content #card-toc .toc-content{overflow-y:scroll;overflow-y:overlay;margin:0 -24px;max-height:calc(100vh - 120px)}@media screen and (max-width:900px){#aside-content #card-toc .toc-content{max-height:calc(100vh - 140px)}}#aside-content #card-toc .toc-content>*{margin:0 20px!important}#aside-content #card-toc .toc-content>*>.toc-item>.toc-child{margin-left:10px;padding-left:10px;border-left:1px solid var(--dark-grey)}#aside-content #card-toc .toc-content:not(.is-expand) .toc-child{display:none}@media screen and (max-width:900px){#aside-content #card-toc .toc-content:not(.is-expand) .toc-child{display:block!important}}#aside-content #card-toc .toc-content:not(.is-expand) .toc-item.active .toc-child{display:block}#aside-content #card-toc .toc-content li,#aside-content #card-toc .toc-content ol{list-style:none}#aside-content #card-toc .toc-content>ol{padding:0!important}#aside-content #card-toc .toc-content ol{margin:0;padding-left:18px}#aside-content #card-toc .toc-content .toc-link{display:block;margin:4px 0;padding:1px 6px;color:var(--toc-link-color);-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;-ms-transition:all .2s ease-in-out;transition:all .2s ease-in-out}#aside-content #card-toc .toc-content .toc-link:hover{color:#49b1f5}#aside-content #card-toc .toc-content .toc-link.active{background:#00c4b6;color:#fff}#aside-content .sticky_layout:only-child>:first-child{margin-top:0}#aside-content .card-more-btn{float:right;color:inherit}#aside-content .card-more-btn:hover{-webkit-animation:more-btn-move 1s infinite;-moz-animation:more-btn-move 1s infinite;-o-animation:more-btn-move 1s infinite;-ms-animation:more-btn-move 1s infinite;animation:more-btn-move 1s infinite}#aside-content .card-announcement .item-headline i{color:red}.avatar-img{overflow:hidden;margin:0 auto;width:110px;height:110px;border-radius:70px}.avatar-img img{width:100%;height:100%;-webkit-transition:filter 375ms ease-in .2s,-webkit-transform .3s;-moz-transition:filter 375ms ease-in .2s,-moz-transform .3s;-o-transition:filter 375ms ease-in .2s,-o-transform .3s;-ms-transition:filter 375ms ease-in .2s,-ms-transform .3s;transition:filter 375ms ease-in .2s,transform .3s;object-fit:cover}.avatar-img img:hover{-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);-o-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg)}.site-data{display:table;width:100%;table-layout:fixed}.site-data>a{display:table-cell}.site-data>a div{-webkit-transition:all .3s;-moz-transition:all .3s;-o-transition:all .3s;-ms-transition:all .3s;transition:all .3s}.site-data>a:hover div{color:#49b1f5!important}.site-data>a .headline{color:var(--font-color)}.site-data>a .length-num{margin-top:-.32em;color:var(--text-highlight-color);font-size:1.4em}@media screen and (min-width:900px){html.hide-aside .layout{-webkit-box-pack:center;-moz-box-pack:center;-o-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center}html.hide-aside .layout>.aside-content{display:none}html.hide-aside .layout>div:first-child{width:80%}}.page .sticky_layout{display:-webkit-box;display:-moz-box;display:-webkit-flex;display:-ms-flexbox;display:box;display:flex;-webkit-box-orient:vertical;-moz-box-orient:vertical;-o-box-orient:vertical;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}@-moz-keyframes more-btn-move{0%,100%{-webkit-transform:translateX(0);-moz-transform:translateX(0);-o-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}50%{-webkit-transform:translateX(3px);-moz-transform:translateX(3px);-o-transform:translateX(3px);-ms-transform:translateX(3px);transform:translateX(3px)}}@-webkit-keyframes more-btn-move{0%,100%{-webkit-transform:translateX(0);-moz-transform:translateX(0);-o-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}50%{-webkit-transform:translateX(3px);-moz-transform:translateX(3px);-o-transform:translateX(3px);-ms-transform:translateX(3px);transform:translateX(3px)}}@-o-keyframes more-btn-move{0%,100%{-webkit-transform:translateX(0);-moz-transform:translateX(0);-o-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}50%{-webkit-transform:translateX(3px);-moz-transform:translateX(3px);-o-transform:translateX(3px);-ms-transform:translateX(3px);transform:translateX(3px)}}@keyframes more-btn-move{0%,100%{-webkit-transform:translateX(0);-moz-transform:translateX(0);-o-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}50%{-webkit-transform:translateX(3px);-moz-transform:translateX(3px);-o-transform:translateX(3px);-ms-transform:translateX(3px);transform:translateX(3px)}}@-moz-keyframes toc-open{0%{-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}100%{-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}@-webkit-keyframes toc-open{0%{-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}100%{-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}@-o-keyframes toc-open{0%{-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}100%{-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}@keyframes toc-open{0%{-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}100%{-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}@-moz-keyframes toc-close{0%{-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}100%{-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}}@-webkit-keyframes toc-close{0%{-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}100%{-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}}@-o-keyframes toc-close{0%{-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}100%{-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}}@keyframes toc-close{0%{-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}100%{-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}}#post-comment .comment-head{margin-bottom:20px}#post-comment .comment-head .comment-headline{display:inline-block;vertical-align:middle;font-weight:700;font-size:1.43em}#post-comment .comment-head #comment-switch{display:inline-block;float:right;margin:2px auto 0;padding:4px 16px;width:max-content;border-radius:8px;background:#f6f8fa}#post-comment .comment-head #comment-switch .first-comment{color:#49b1f5}#post-comment .comment-head #comment-switch .second-comment{color:#ff7242}#post-comment .comment-head #comment-switch .switch-btn{position:relative;display:inline-block;margin:-4px 8px 0;width:42px;height:22px;border-radius:34px;background-color:#49b1f5;vertical-align:middle;cursor:pointer;-webkit-transition:.4s;-moz-transition:.4s;-o-transition:.4s;-ms-transition:.4s;transition:.4s}#post-comment .comment-head #comment-switch .switch-btn:before{position:absolute;bottom:4px;left:4px;width:14px;height:14px;border-radius:50%;background-color:#fff;content:'';-webkit-transition:.4s;-moz-transition:.4s;-o-transition:.4s;-ms-transition:.4s;transition:.4s}#post-comment .comment-head #comment-switch .switch-btn.move{background-color:#ff7242}#post-comment .comment-head #comment-switch .switch-btn.move:before{-webkit-transform:translateX(20px);-moz-transform:translateX(20px);-o-transform:translateX(20px);-ms-transform:translateX(20px);transform:translateX(20px)}#post-comment .comment-wrap>div:nth-child(2){display:none}#footer{position:relative;background-color:#49b1f5;background-attachment:scroll;background-position:bottom;background-size:cover}#footer-wrap{position:relative;padding:40px 20px;color:var(--light-grey);text-align:center}#footer-wrap a{color:var(--light-grey)}#footer-wrap a:hover{text-decoration:underline}#footer-wrap .footer-separator{margin:0 4px}#footer-wrap .icp-icon{padding:0 4px;max-height:1.4em;width:auto;vertical-align:text-bottom}#page-header{position:relative;width:100%;background-color:#49b1f5;background-position:center center;background-size:cover;background-repeat:no-repeat;-webkit-transition:all .5s;-moz-transition:all .5s;-o-transition:all .5s;-ms-transition:all .5s;transition:all .5s}#page-header:not(.not-top-img):before{position:absolute;width:100%;height:100%;background-color:var(--mark-bg);content:''}#page-header.full_page{height:100vh;background-attachment:fixed}#page-header.full_page #site-info{position:absolute;top:43%;padding:0 10px;width:100%}#page-header #scroll-down .scroll-down-effects,#page-header #site-subtitle,#page-header #site-title{text-align:center;text-shadow:2px 2px 4px rgba(0,0,0,.15);line-height:1.5}#page-header #site-title{margin:0;color:var(--white);font-size:1.85em}@media screen and (min-width:768px){#page-header #site-title{font-size:2.85em}}#page-header #site-subtitle{color:var(--light-grey);font-size:1.15em}@media screen and (min-width:768px){#page-header #site-subtitle{font-size:1.72em}}#page-header #site_social_icons{display:none;margin:0 auto;width:300px;text-align:center}@media screen and (max-width:768px){#page-header #site_social_icons{display:block}}#page-header #site_social_icons .social-icon{margin:0 10px;color:var(--light-grey);text-shadow:2px 2px 4px rgba(0,0,0,.15);font-size:1.43em}#page-header #scroll-down{position:absolute;bottom:0;width:100%;cursor:pointer}#page-header #scroll-down .scroll-down-effects{position:relative;width:100%;color:var(--light-grey);font-size:30px}#page-header.not-home-page{height:400px}@media screen and (max-width:768px){#page-header.not-home-page{height:280px}}#page-header #page-site-info{position:absolute;top:200px;padding:0 10px;width:100%}@media screen and (max-width:768px){#page-header #page-site-info{top:140px}}#page-header.post-bg{height:400px}@media screen and (max-width:768px){#page-header.post-bg{height:360px}}#page-header #post-info{position:absolute;bottom:100px;padding:0 8%;width:100%;text-align:center}@media screen and (max-width:900px){#page-header #post-info{bottom:30px;text-align:left}}@media screen and (max-width:768px){#page-header #post-info{bottom:22px;padding:0 22px}}#page-header.not-top-img{margin-bottom:10px;height:60px;background:0}#page-header.not-top-img #nav{background:rgba(255,255,255,.8);-webkit-box-shadow:0 5px 6px -5px rgba(133,133,133,.6);box-shadow:0 5px 6px -5px rgba(133,133,133,.6)}#page-header.not-top-img #nav .site-name,#page-header.not-top-img #nav a{color:var(--font-color);text-shadow:none}#page-header.nav-fixed #nav{position:fixed;top:-60px;z-index:91;background:rgba(255,255,255,.8);-webkit-box-shadow:0 5px 6px -5px rgba(133,133,133,.6);box-shadow:0 5px 6px -5px rgba(133,133,133,.6);-webkit-transition:-webkit-transform .2s ease-in-out,opacity .2s ease-in-out;-moz-transition:-moz-transform .2s ease-in-out,opacity .2s ease-in-out;-o-transition:-o-transform .2s ease-in-out,opacity .2s ease-in-out;-ms-transition:-ms-transform .2s ease-in-out,opacity .2s ease-in-out;transition:transform .2s ease-in-out,opacity .2s ease-in-out}#page-header.nav-fixed #nav #blog-info{color:var(--font-color)}#page-header.nav-fixed #nav #blog-info:hover{color:#49b1f5}#page-header.nav-fixed #nav #blog-info .site-name{text-shadow:none}#page-header.nav-fixed #nav #toggle-menu,#page-header.nav-fixed #nav a{color:var(--font-color);text-shadow:none}#page-header.nav-fixed #nav #toggle-menu:hover,#page-header.nav-fixed #nav a:hover{color:#49b1f5}#page-header.nav-fixed.fixed #nav{top:0;-webkit-transition:all .5s;-moz-transition:all .5s;-o-transition:all .5s;-ms-transition:all .5s;transition:all .5s}#page-header.nav-visible:not(.fixed) #nav{-webkit-transition:all .5s;-moz-transition:all .5s;-o-transition:all .5s;-ms-transition:all .5s;transition:all .5s;-webkit-transform:translate3d(0,100%,0);-moz-transform:translate3d(0,100%,0);-o-transform:translate3d(0,100%,0);-ms-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}#page-header.nav-visible:not(.fixed)+.layout>.aside-content>.sticky_layout{top:70px;-webkit-transition:top .5s;-moz-transition:top .5s;-o-transition:top .5s;-ms-transition:top .5s;transition:top .5s}#page-header.fixed #nav{position:fixed}#page-header.fixed+.layout>.aside-content>.sticky_layout{top:70px;-webkit-transition:top .5s;-moz-transition:top .5s;-o-transition:top .5s;-ms-transition:top .5s;transition:top .5s}#page-header.fixed+.layout #card-toc .toc-content{max-height:calc(100vh - 170px)}#page h1.page-title{margin:8px 0 20px}#post>#post-info{margin-bottom:30px}#post>#post-info .post-title{padding-bottom:4px;border-bottom:1px solid var(--light-grey);color:var(--text-highlight-color)}#post>#post-info .post-title .post-edit-link{float:right}#post>#post-info #post-meta,#post>#post-info #post-meta a{color:#78818a}#post-info .post-title{margin-bottom:8px;color:var(--white);font-weight:400;font-size:2.5em;line-height:1.5;-webkit-line-clamp:3}@media screen and (max-width:768px){#post-info .post-title{font-size:2.1em}}#post-info .post-title .post-edit-link{padding-left:10px}#post-info #post-meta{color:var(--light-grey);font-size:95%}@media screen and (min-width:768px){#post-info #post-meta>.meta-secondline>span:first-child{display:none}}@media screen and (max-width:768px){#post-info #post-meta{font-size:90%}#post-info #post-meta>.meta-firstline,#post-info #post-meta>.meta-secondline{display:inline}}#post-info #post-meta .post-meta-separator{margin:0 5px}#post-info #post-meta .post-meta-icon{margin-right:4px}#post-info #post-meta .post-meta-label{margin-right:4px}#post-info #post-meta a{color:var(--light-grey);-webkit-transition:all .3s ease-out;-moz-transition:all .3s ease-out;-o-transition:all .3s ease-out;-ms-transition:all .3s ease-out;transition:all .3s ease-out}#post-info #post-meta a:hover{color:#49b1f5;text-decoration:underline}#nav{position:absolute;top:0;z-index:90;display:-webkit-box;display:-moz-box;display:-webkit-flex;display:-ms-flexbox;display:box;display:flex;-webkit-box-align:center;-moz-box-align:center;-o-box-align:center;-ms-flex-align:center;-webkit-align-items:center;align-items:center;padding:0 36px;width:100%;height:60px;font-size:1.3em;opacity:0;-webkit-transition:all .5s;-moz-transition:all .5s;-o-transition:all .5s;-ms-transition:all .5s;transition:all .5s}@media screen and (max-width:768px){#nav{padding:0 16px}}#nav.show{opacity:1;-ms-filter:none;filter:none}#nav #blog-info{-webkit-box-flex:1;-moz-box-flex:1;-o-box-flex:1;box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;color:var(--light-grey)}#nav #blog-info .site-icon{margin-right:6px;height:36px;vertical-align:middle}#nav #toggle-menu{display:none;padding:2px 0 0 6px;vertical-align:top}#nav #toggle-menu:hover{color:var(--white)}#nav a{color:var(--light-grey)}#nav a:hover{color:var(--white)}#nav .site-name{text-shadow:2px 2px 4px rgba(0,0,0,.15);font-weight:700}#nav .menus_items{display:inline}#nav .menus_items .menus_item{position:relative;display:inline-block;padding:0 0 0 14px}#nav .menus_items .menus_item:hover .menus_item_child{display:block}#nav .menus_items .menus_item:hover>a>i:last-child{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-o-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}#nav .menus_items .menus_item>a>i:last-child{padding:4px;-webkit-transition:-webkit-transform .3s;-moz-transition:-moz-transform .3s;-o-transition:-o-transform .3s;-ms-transition:-ms-transform .3s;transition:transform .3s}#nav .menus_items .menus_item .menus_item_child{position:absolute;right:0;display:none;margin-top:8px;padding:0;width:max-content;border-radius:5px;background-color:var(--sidebar-bg);-webkit-box-shadow:0 5px 20px -4px rgba(0,0,0,.5);box-shadow:0 5px 20px -4px rgba(0,0,0,.5);-webkit-animation:sub_menus .3s .1s ease both;-moz-animation:sub_menus .3s .1s ease both;-o-animation:sub_menus .3s .1s ease both;-ms-animation:sub_menus .3s .1s ease both;animation:sub_menus .3s .1s ease both}#nav .menus_items .menus_item .menus_item_child:before{position:absolute;top:-8px;left:0;width:100%;height:20px;content:''}#nav .menus_items .menus_item .menus_item_child li{list-style:none}#nav .menus_items .menus_item .menus_item_child li:hover{background:var(--text-bg-hover)}#nav .menus_items .menus_item .menus_item_child li:first-child{border-top-left-radius:5px;border-top-right-radius:5px}#nav .menus_items .menus_item .menus_item_child li:last-child{border-bottom-right-radius:5px;border-bottom-left-radius:5px}#nav .menus_items .menus_item .menus_item_child li a{display:inline-block;padding:8px 16px;width:100%;color:var(--font-color)!important;text-shadow:none!important}#nav.hide-menu #toggle-menu{display:inline-block!important}#nav.hide-menu #toggle-menu .site-page{font-size:inherit}#nav.hide-menu .menus_items{display:none}#nav.hide-menu #search-button span{display:none}#nav #search-button{display:inline;padding:0 0 0 14px}#nav .site-page{position:relative;padding-bottom:6px;text-shadow:1px 1px 2px rgba(0,0,0,.3);font-size:.78em;cursor:pointer}#nav .site-page:not(.child):after{position:absolute;bottom:0;left:0;z-index:-1;width:0;height:3px;background-color:#80c8f8;content:'';-webkit-transition:all .3s ease-in-out;-moz-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;-ms-transition:all .3s ease-in-out;transition:all .3s ease-in-out}#nav .site-page:not(.child):hover:after{width:100%}#pagination .pagination{margin-top:20px;text-align:center}#pagination .page-number.current{background:#00c4b6;color:var(--white)}#pagination .pagination-info{position:absolute;top:50%;padding:20px 40px;width:100%;-webkit-transform:translate(0,-50%);-moz-transform:translate(0,-50%);-o-transform:translate(0,-50%);-ms-transform:translate(0,-50%);transform:translate(0,-50%)}#pagination .next_info,#pagination .prev_info{color:var(--white);font-weight:500}#pagination .next-post .pagination-info{text-align:right}#pagination .pull-full{width:100%!important}#pagination .next-post .label,#pagination .prev-post .label{color:var(--light-grey);text-transform:uppercase;font-size:90%}#pagination .next-post,#pagination .prev-post{width:50%}@media screen and (max-width:768px){#pagination .next-post,#pagination .prev-post{width:100%}}#pagination .next-post a,#pagination .prev-post a{position:relative;display:block;overflow:hidden;height:150px}#pagination.pagination-post{overflow:hidden;margin-top:40px;width:100%;background:#000}.layout>.recent-posts .pagination>*{display:inline-block;margin:0 6px;width:2.5em;height:2.5em;line-height:2.5em}.layout>.recent-posts .pagination>:not(.space):hover{background:var(--btn-hover-color);color:var(--btn-color)}.layout>div:not(.recent-posts) .pagination .page-number{display:inline-block;margin:0 4px;min-width:24px;height:24px;text-align:center;line-height:24px;cursor:pointer}#article-container{word-wrap:break-word;overflow-wrap:break-word}#article-container a{color:#49b1f5}#article-container a:hover{text-decoration:underline}#article-container img{display:block;margin:0 auto 20px;max-width:100%;-webkit-transition:filter 375ms ease-in .2s;-moz-transition:filter 375ms ease-in .2s;-o-transition:filter 375ms ease-in .2s;-ms-transition:filter 375ms ease-in .2s;transition:filter 375ms ease-in .2s}#article-container p{margin:0 0 16px}#article-container iframe{margin:0 0 20px}#article-container kbd{margin:0 3px;padding:3px 5px;border:1px solid #b4b4b4;border-radius:3px;background-color:#f8f8f8;-webkit-box-shadow:0 1px 3px rgba(0,0,0,.25),0 2px 1px 0 rgba(255,255,255,.6) inset;box-shadow:0 1px 3px rgba(0,0,0,.25),0 2px 1px 0 rgba(255,255,255,.6) inset;color:#34495e;white-space:nowrap;font-weight:600;font-size:.9em;font-family:Monaco,'Ubuntu Mono',monospace;line-height:1em}#article-container a.headerlink:after{float:right;color:var(--headline-presudo);content:'\f1d8';font-size:.95em;-webkit-transition:all .3s;-moz-transition:all .3s;-o-transition:all .3s;-ms-transition:all .3s;transition:all .3s}#article-container a.headerlink:hover:after{color:var(--pseudo-hover)}#article-container a.headerlink:after{opacity:0}#article-container h1:hover a.headerlink:after,#article-container h2:hover a.headerlink:after,#article-container h3:hover a.headerlink:after,#article-container h4:hover a.headerlink:after,#article-container h5:hover a.headerlink:after,#article-container h6:hover a.headerlink:after{opacity:1;-ms-filter:none;filter:none}#article-container ol ol,#article-container ol ul,#article-container ul ol,#article-container ul ul{padding-left:20px}#article-container ol li,#article-container ul li{margin:4px 0}#article-container ol p,#article-container ul p{margin:0 0 8px}#article-container>:last-child{margin-bottom:0!important}#article-container hr{margin:20px 0}#article-container h1,#article-container h2,#article-container h3,#article-container h4,#article-container h5,#article-container h6{-webkit-transition:all .2s ease-out;-moz-transition:all .2s ease-out;-o-transition:all .2s ease-out;-ms-transition:all .2s ease-out;transition:all .2s ease-out}#article-container h1:before,#article-container h2:before,#article-container h3:before,#article-container h4:before,#article-container h5:before,#article-container h6:before{position:absolute;top:calc(50% - 7px);color:#fc6;content:'\f0e7';line-height:1;-webkit-transition:all .2s ease-out;-moz-transition:all .2s ease-out;-o-transition:all .2s ease-out;-ms-transition:all .2s ease-out;transition:all .2s ease-out}#article-container h1:hover:before,#article-container h2:hover:before,#article-container h3:hover:before,#article-container h4:hover:before,#article-container h5:hover:before,#article-container h6:hover:before{color:#49b1f5}#article-container h1{padding-left:32px}#article-container h1:before{margin-left:-26px;font-size:20px}#article-container h1:hover{padding-left:38px}#article-container h2{padding-left:30px}#article-container h2:before{margin-left:-24px;font-size:18px}#article-container h2:hover{padding-left:36px}#article-container h3{padding-left:28px}#article-container h3:before{margin-left:-22px;font-size:16px}#article-container h3:hover{padding-left:34px}#article-container h4{padding-left:26px}#article-container h4:before{margin-left:-20px;font-size:14px}#article-container h4:hover{padding-left:32px}#article-container h5{padding-left:24px}#article-container h5:before{margin-left:-18px;font-size:12px}#article-container h5:hover{padding-left:30px}#article-container h6{padding-left:24px}#article-container h6:before{margin-left:-18px;font-size:12px}#article-container h6:hover{padding-left:30px}#article-container ol p,#article-container ul p{margin:0 0 8px}#article-container li::marker{color:#49b1f5;font-weight:600;font-size:1.05em}#article-container li:hover::marker{color:var(--pseudo-hover)}#article-container ul>li{list-style-type:circle}#post .tag_share:after{display:block;clear:both;content:''}#post .tag_share .post-meta__tag-list{display:inline-block}#post .tag_share .post-meta__tags{display:inline-block;margin:8px 8px 8px 0;padding:0 12px;width:fit-content;border:1px solid #49b1f5;border-radius:12px;color:#49b1f5;font-size:.85em;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;-ms-transition:all .2s ease-in-out;transition:all .2s ease-in-out}#post .tag_share .post-meta__tags:hover{background:#49b1f5;color:var(--white)}#post .tag_share .post_share{display:inline-block;float:right;margin:8px 0 0;width:fit-content}#post .tag_share .post_share .social-share{font-size:.85em}#post .tag_share .post_share .social-share .social-share-icon{margin:0 4px;width:1.85em;height:1.85em;font-size:1.2em;line-height:1.85em}#post .post-copyright{position:relative;margin:40px 0 10px;padding:10px 16px;border:1px solid var(--light-grey);-webkit-transition:box-shadow .3s ease-in-out;-moz-transition:box-shadow .3s ease-in-out;-o-transition:box-shadow .3s ease-in-out;-ms-transition:box-shadow .3s ease-in-out;transition:box-shadow .3s ease-in-out}#post .post-copyright:before{position:absolute;top:2px;right:12px;color:#49b1f5;content:'\f1f9';font-size:1.3em}#post .post-copyright:hover{-webkit-box-shadow:0 0 8px 0 rgba(232,237,250,.6),0 2px 4px 0 rgba(232,237,250,.5);box-shadow:0 0 8px 0 rgba(232,237,250,.6),0 2px 4px 0 rgba(232,237,250,.5)}#post .post-copyright .post-copyright-meta{color:#49b1f5;font-weight:700}#post .post-copyright .post-copyright-info{padding-left:6px}#post .post-copyright .post-copyright-info a{text-decoration:underline;word-break:break-word}#post .post-copyright .post-copyright-info a:hover{text-decoration:none}#post .post-outdate-notice{position:relative;margin:0 0 20px;padding:.5em 1.2em;border-radius:3px;background-color:#ffe6e6;color:#f66;padding:.5em 1em .5em 2.6em;border-left:5px solid #ff8080}#post .post-outdate-notice:before{position:absolute;top:50%;left:.9em;color:#ff8080;content:'\f071';-webkit-transform:translateY(-50%);-moz-transform:translateY(-50%);-o-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)}#post .ads-wrap{margin:40px 0}.relatedPosts{margin-top:40px}.relatedPosts>.headline{margin-bottom:5px;font-weight:700;font-size:1.43em}.relatedPosts>.relatedPosts-list>div{position:relative;display:inline-block;overflow:hidden;margin:3px;width:calc(33.333% - 6px);height:200px;background:#000;vertical-align:bottom}@media screen and (max-width:768px){.relatedPosts>.relatedPosts-list>div{margin:2px;width:calc(50% - 4px);height:150px}}@media screen and (max-width:600px){.relatedPosts>.relatedPosts-list>div{width:calc(100% - 4px)}}.relatedPosts>.relatedPosts-list .content{position:absolute;top:50%;padding:0 20px;width:100%;-webkit-transform:translate(0,-50%);-moz-transform:translate(0,-50%);-o-transform:translate(0,-50%);-ms-transform:translate(0,-50%);transform:translate(0,-50%)}.relatedPosts>.relatedPosts-list .content .date{color:var(--light-grey);font-size:90%}.relatedPosts>.relatedPosts-list .content .title{color:var(--white);-webkit-line-clamp:2}.post-reward{position:relative;margin-top:80px;width:100%;text-align:center;pointer-events:none}.post-reward>*{pointer-events:auto}.post-reward .reward-button{display:inline-block;padding:4px 24px;background:var(--btn-bg);color:var(--btn-color);cursor:pointer}.post-reward:hover .reward-button{background:var(--btn-hover-color)}.post-reward:hover>.reward-main{display:block}.post-reward .reward-main{position:absolute;bottom:40px;left:0;z-index:100;display:none;padding:0 0 15px;width:100%}.post-reward .reward-main .reward-all{display:inline-block;margin:0;padding:20px 10px;border-radius:4px;background:var(--reward-pop)}.post-reward .reward-main .reward-all:before{position:absolute;bottom:-10px;left:0;width:100%;height:20px;content:''}.post-reward .reward-main .reward-all:after{position:absolute;right:0;bottom:2px;left:0;margin:0 auto;width:0;height:0;border-top:13px solid var(--reward-pop);border-right:13px solid transparent;border-left:13px solid transparent;content:''}.post-reward .reward-main .reward-all .reward-item{display:inline-block;padding:0 8px;list-style-type:none;vertical-align:top}.post-reward .reward-main .reward-all .reward-item img{width:130px;height:130px}.post-reward .reward-main .reward-all .reward-item .post-qr-code-desc{width:130px;color:#858585}#rightside{position:fixed;right:-48px;bottom:40px;z-index:100;opacity:0;-webkit-transition:all .5s;-moz-transition:all .5s;-o-transition:all .5s;-ms-transition:all .5s;transition:all .5s}#rightside #rightside-config-hide{height:0;opacity:0;-webkit-transition:-webkit-transform .4s;-moz-transition:-moz-transform .4s;-o-transition:-o-transform .4s;-ms-transition:-ms-transform .4s;transition:transform .4s;-webkit-transform:translate(45px,0);-moz-transform:translate(45px,0);-o-transform:translate(45px,0);-ms-transform:translate(45px,0);transform:translate(45px,0)}#rightside #rightside-config-hide.show{height:auto;opacity:1;-ms-filter:none;filter:none;-webkit-transform:translate(0,0);-moz-transform:translate(0,0);-o-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}#rightside #rightside-config-hide.status{height:auto;opacity:1;-ms-filter:none;filter:none}#rightside>div>a,#rightside>div>button{display:block;margin-bottom:5px;width:35px;height:35px;border-radius:5px;background-color:var(--btn-bg);color:var(--btn-color);text-align:center;font-size:16px;line-height:35px}#rightside>div>a:hover,#rightside>div>button:hover{background-color:var(--btn-hover-color)}#rightside #mobile-toc-button{display:none}@media screen and (max-width:900px){#rightside #mobile-toc-button{display:block}}@media screen and (max-width:900px){#rightside #hide-aside-btn{display:none}}#rightside #go-up .scroll-percent{display:none}#rightside #go-up.show-percent .scroll-percent{display:block}#rightside #go-up.show-percent .scroll-percent+i{display:none}#rightside #go-up:hover .scroll-percent{display:none}#rightside #go-up:hover .scroll-percent+i{display:block}#sidebar #menu-mask{position:fixed;z-index:102;display:none;width:100%;height:100%;background:rgba(0,0,0,.8)}#sidebar #sidebar-menus{position:fixed;top:0;right:-300px;z-index:103;overflow-x:hidden;overflow-y:auto;width:300px;height:100%;background:var(--sidebar-bg);-webkit-transition:all .5s;-moz-transition:all .5s;-o-transition:all .5s;-ms-transition:all .5s;transition:all .5s}#sidebar #sidebar-menus.open{-webkit-transform:translate3d(-100%,0,0);-moz-transform:translate3d(-100%,0,0);-o-transform:translate3d(-100%,0,0);-ms-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}#sidebar #sidebar-menus>.avatar-img{margin:20px auto}#sidebar #sidebar-menus .sidebar-site-data{padding:0 10px}#sidebar #sidebar-menus hr{margin:20px auto}#sidebar #sidebar-menus .menus_items{padding:0 10px 40px}#sidebar #sidebar-menus .menus_items .site-page{position:relative;display:block;padding:6px 30px 6px 22px;color:var(--font-color);font-size:1.15em}#sidebar #sidebar-menus .menus_items .site-page:hover{background:var(--text-bg-hover)}#sidebar #sidebar-menus .menus_items .site-page i:first-child{width:15%;text-align:left}#sidebar #sidebar-menus .menus_items .site-page.group>i:last-child{position:absolute;top:.78em;right:18px;-webkit-transition:-webkit-transform .3s;-moz-transition:-moz-transform .3s;-o-transition:-o-transform .3s;-ms-transition:-ms-transform .3s;transition:transform .3s}#sidebar #sidebar-menus .menus_items .site-page.group.hide>i:last-child{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-o-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}#sidebar #sidebar-menus .menus_items .site-page.group.hide+.menus_item_child{display:none}#sidebar #sidebar-menus .menus_items .menus_item_child{margin:0;list-style:none}#vcomment{font-size:1.1em}#vcomment .vbtn{border:none;background:var(--btn-bg);color:var(--btn-color)}#vcomment .vbtn:hover{background:var(--btn-hover-color)}#vcomment .vimg{-webkit-transition:all .3s;-moz-transition:all .3s;-o-transition:all .3s;-ms-transition:all .3s;transition:all .3s}#vcomment .vimg:hover{-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);-o-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg)}#vcomment .vcards .vcard .vcontent.expand:after,#vcomment .vcards .vcard .vcontent.expand:before{z-index:22}#waline-wrap{--waline-font-size:1.1em;--waline-theme-color:#49b1f5;--waline-active-color:#ff7242}#waline-wrap .wl-comment-actions>button:not(last-child){padding-right:4px}.fireworks{position:fixed;top:0;left:0;z-index:9999;pointer-events:none}.medium-zoom-image--opened{z-index:99999!important;margin:0!important}.medium-zoom-overlay{z-index:99999!important}.mermaid-wrap{margin:0 0 20px;text-align:center}.mermaid-wrap>svg{height:100%}.fb-comments iframe,.utterances{width:100%!important}#gitalk-container .gt-meta{margin:0 0 .8em;padding:6px 0 16px}.katex-wrap{overflow:auto}.katex-wrap::-webkit-scrollbar{display:none}mjx-container{overflow-x:auto;overflow-y:hidden;padding-bottom:4px;max-width:100%}mjx-container[display]{display:block!important;min-width:auto!important}mjx-container:not([display]){display:inline-grid!important}mjx-assistive-mml{right:0;bottom:0}.aplayer{color:#4c4948}#article-container .aplayer{margin:0 0 20px}#article-container .aplayer ol,#article-container .aplayer ul{margin:0;padding:0}#article-container .aplayer ol li,#article-container .aplayer ul li{margin:0;padding:0 15px}#article-container .aplayer ol li:before,#article-container .aplayer ul li:before{content:none}.snackbar-css{border-radius:5px!important}.abc-music-sheet{margin:0 0 20px;opacity:0;-webkit-transition:opacity .3s;-moz-transition:opacity .3s;-o-transition:opacity .3s;-ms-transition:opacity .3s;transition:opacity .3s}.abc-music-sheet.abcjs-container{opacity:1;-ms-filter:none;filter:none}@media screen and (max-width:768px){.fancybox__toolbar__column.is-middle{display:none}}#article-container .btn-center{margin:0 0 20px;text-align:center}#article-container .btn-beautify{display:inline-block;margin:0 4px 6px;padding:0 15px;background-color:var(--btn-beautify-color,#777);color:#fff;line-height:2}#article-container .btn-beautify.blue{--btn-beautify-color:#428bca}#article-container .btn-beautify.pink{--btn-beautify-color:#ff69b4}#article-container .btn-beautify.red{--btn-beautify-color:#f00}#article-container .btn-beautify.purple{--btn-beautify-color:#6f42c1}#article-container .btn-beautify.orange{--btn-beautify-color:#ff8c00}#article-container .btn-beautify.green{--btn-beautify-color:#5cb85c}#article-container .btn-beautify:hover{background-color:var(--btn-hover-color)}#article-container .btn-beautify i+span{margin-left:6px}#article-container .btn-beautify:not(.block)+.btn-beautify:not(.block){margin:0 4px 20px}#article-container .btn-beautify.block{display:block;margin:0 0 20px;width:fit-content;width:-moz-fit-content}#article-container .btn-beautify.block.center{margin:0 auto 20px}#article-container .btn-beautify.block.right{margin:0 0 20px auto}#article-container .btn-beautify.larger{padding:6px 15px}#article-container .btn-beautify:hover{text-decoration:none}#article-container .btn-beautify.outline{border:1px solid transparent;border-color:var(--btn-beautify-color,#777);background-color:transparent;color:var(--btn-beautify-color,#777)}#article-container .btn-beautify.outline:hover{background-color:var(--btn-beautify-color,#777)}#article-container .btn-beautify.outline:hover{color:#fff!important}#article-container figure.gallery-group{position:relative;float:left;overflow:hidden;margin:6px 4px;width:calc(50% - 8px);height:250px;border-radius:8px;background:#000;-webkit-transform:translate3d(0,0,0)}@media screen and (max-width:600px){#article-container figure.gallery-group{width:calc(100% - 8px)}}#article-container figure.gallery-group:hover img{opacity:.4;-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}#article-container figure.gallery-group:hover .gallery-group-name::after{-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}#article-container figure.gallery-group:hover p{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}#article-container figure.gallery-group img{position:relative;margin:0;max-width:none;width:calc(100% + 20px);height:250px;-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;-ms-backface-visibility:hidden;backface-visibility:hidden;opacity:.8;-webkit-transition:all .3s,filter 375ms ease-in .2s;-moz-transition:all .3s,filter 375ms ease-in .2s;-o-transition:all .3s,filter 375ms ease-in .2s;-ms-transition:all .3s,filter 375ms ease-in .2s;transition:all .3s,filter 375ms ease-in .2s;-webkit-transform:translate3d(-10px,0,0);-moz-transform:translate3d(-10px,0,0);-o-transform:translate3d(-10px,0,0);-ms-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0);object-fit:cover}#article-container figure.gallery-group figcaption{position:absolute;top:0;left:0;padding:30px;width:100%;height:100%;color:#fff;text-transform:uppercase;-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;-ms-backface-visibility:hidden;backface-visibility:hidden}#article-container figure.gallery-group figcaption>a{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1000;opacity:0}#article-container figure.gallery-group p{margin:0;padding:8px 0 0;letter-spacing:1px;font-size:1.1em;line-height:1.5;opacity:0;-webkit-transition:opacity .35s,-webkit-transform .35s;-moz-transition:opacity .35s,-moz-transform .35s;-o-transition:opacity .35s,-o-transform .35s;-ms-transition:opacity .35s,-ms-transform .35s;transition:opacity .35s,transform .35s;-webkit-transform:translate3d(100%,0,0);-moz-transform:translate3d(100%,0,0);-o-transform:translate3d(100%,0,0);-ms-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);-webkit-line-clamp:4}#article-container figure.gallery-group .gallery-group-name{position:relative;margin:0;padding:8px 0;font-weight:700;font-size:1.65em;line-height:1.5;-webkit-line-clamp:2}#article-container figure.gallery-group .gallery-group-name:after{position:absolute;bottom:0;left:0;width:100%;height:2px;background:#fff;content:'';-webkit-transition:-webkit-transform .35s;-moz-transition:-moz-transform .35s;-o-transition:-o-transform .35s;-ms-transition:-ms-transform .35s;transition:transform .35s;-webkit-transform:translate3d(-100%,0,0);-moz-transform:translate3d(-100%,0,0);-o-transform:translate3d(-100%,0,0);-ms-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}#article-container .gallery-group-main{overflow:auto;padding:0 0 16px}#article-container .gallery{margin:0 0 16px;text-align:center}#article-container .gallery .fj-gallery{opacity:0}#article-container .gallery .fj-gallery .img-alt{display:none}#article-container .gallery .fj-gallery.lazyload+button{display:inline-block}#article-container .gallery .fj-gallery .gallery-data{display:none}#article-container .gallery button{display:none;margin-top:25px;padding:10px;width:9em;border-radius:5px;background:var(--btn-bg);color:var(--btn-color);font-weight:700;font-size:1.1em;-webkit-transition:all .3s;-moz-transition:all .3s;-o-transition:all .3s;-ms-transition:all .3s;transition:all .3s}#article-container .gallery button>*{-webkit-transition:all .4s;-moz-transition:all .4s;-o-transition:all .4s;-ms-transition:all .4s;transition:all .4s}#article-container .gallery button i{width:0;opacity:0}#article-container .gallery button:hover{background:var(--btn-hover-color)}#article-container .gallery button:hover i{margin-left:2px;width:20px;opacity:1;-ms-filter:none;filter:none}blockquote.pullquote{position:relative;max-width:45%;font-size:110%}blockquote.pullquote.left{float:left;margin:1em .5em 0 0}blockquote.pullquote.right{float:right;margin:1em 0 0 .5em}.video-container{position:relative;overflow:hidden;margin-bottom:16px;padding-top:56.25%;height:0}.video-container iframe{position:absolute;top:0;left:0;margin-top:0;width:100%;height:100%}.hide-block>.hide-button,.hide-inline>.hide-button{display:inline-block;padding:5px 18px;background:#49b1f5;color:var(--white)}.hide-block>.hide-button:hover,.hide-inline>.hide-button:hover{background-color:var(--btn-hover-color)}.hide-block>.hide-button.open,.hide-inline>.hide-button.open{display:none}.hide-block>.hide-button.open+div,.hide-inline>.hide-button.open+div{display:block}.hide-block>.hide-button.open+span,.hide-inline>.hide-button.open+span{display:inline}.hide-block>.hide-content,.hide-inline>.hide-content{display:none}.hide-inline>.hide-button{margin:0 6px}.hide-inline>.hide-content{margin:0 6px}.hide-block{margin:0 0 16px}.toggle{margin-bottom:20px;border:1px solid #f0f0f0}.toggle>.toggle-button{padding:6px 15px;background:#f0f0f0;color:#1f2d3d;cursor:pointer}.toggle>.toggle-content{margin:30px 24px}#article-container .inline-img{display:inline;margin:0 3px;height:1.1em;vertical-align:text-bottom}.hl-label{padding:2px 4px;border-radius:3px;color:#fff}.hl-label.default{background-color:#777}.hl-label.blue{background-color:#428bca}.hl-label.pink{background-color:#ff69b4}.hl-label.red{background-color:red}.hl-label.purple{background-color:#6f42c1}.hl-label.orange{background-color:#ff8c00}.hl-label.green{background-color:#5cb85c}.note{position:relative;margin:0 0 20px;padding:15px;border-radius:3px}.note.icon-padding{padding-left:3em}.note>.note-icon{position:absolute;top:calc(50% - .5em);left:.8em;font-size:larger}.note.blue:not(.disabled){border-left-color:#428bca!important}.note.blue:not(.disabled).modern{border-left-color:transparent!important;color:#428bca}.note.blue:not(.disabled):not(.simple){background:#e3eef7!important}.note.blue>.note-icon{color:#428bca}.note.pink:not(.disabled){border-left-color:#ff69b4!important}.note.pink:not(.disabled).modern{border-left-color:transparent!important;color:#ff69b4}.note.pink:not(.disabled):not(.simple){background:#ffe9f4!important}.note.pink>.note-icon{color:#ff69b4}.note.red:not(.disabled){border-left-color:red!important}.note.red:not(.disabled).modern{border-left-color:transparent!important;color:red}.note.red:not(.disabled):not(.simple){background:#ffd9d9!important}.note.red>.note-icon{color:red}.note.purple:not(.disabled){border-left-color:#6f42c1!important}.note.purple:not(.disabled).modern{border-left-color:transparent!important;color:#6f42c1}.note.purple:not(.disabled):not(.simple){background:#e9e3f6!important}.note.purple>.note-icon{color:#6f42c1}.note.orange:not(.disabled){border-left-color:#ff8c00!important}.note.orange:not(.disabled).modern{border-left-color:transparent!important;color:#ff8c00}.note.orange:not(.disabled):not(.simple){background:#ffeed9!important}.note.orange>.note-icon{color:#ff8c00}.note.green:not(.disabled){border-left-color:#5cb85c!important}.note.green:not(.disabled).modern{border-left-color:transparent!important;color:#5cb85c}.note.green:not(.disabled):not(.simple){background:#e7f4e7!important}.note.green>.note-icon{color:#5cb85c}.note.simple{border:1px solid #eee;border-left-width:5px}.note.modern{border:1px solid transparent!important;background-color:#f5f5f5;color:#4c4948}.note.flat{border:initial;border-left:5px solid #eee;background-color:#f9f9f9;color:#4c4948}.note h2,.note h3,.note h4,.note h5,.note h6{margin-top:3px;margin-bottom:0;padding-top:0!important;border-bottom:initial}.note blockquote:first-child,.note img:first-child,.note ol:first-child,.note p:first-child,.note pre:first-child,.note table:first-child,.note ul:first-child{margin-top:0!important}.note blockquote:last-child,.note img:last-child,.note ol:last-child,.note p:last-child,.note pre:last-child,.note table:last-child,.note ul:last-child{margin-bottom:0!important}.note:not(.no-icon){padding-left:3em}.note:not(.no-icon)::before{position:absolute;top:calc(50% - .95em);left:.8em;font-size:larger}.note.default.flat{background:#f7f7f7}.note.default.modern{border-color:#e1e1e1;background:#f3f3f3;color:#666}.note.default.modern a:not(.btn){color:#666}.note.default.modern a:not(.btn):hover{color:#454545}.note.default:not(.modern){border-left-color:#777}.note.default:not(.modern) h2,.note.default:not(.modern) h3,.note.default:not(.modern) h4,.note.default:not(.modern) h5,.note.default:not(.modern) h6{color:#777}.note.default:not(.no-icon)::before{content:'\f0a9'}.note.default:not(.no-icon):not(.modern)::before{color:#777}.note.primary.flat{background:#f5f0fa}.note.primary.modern{border-color:#e1c2ff;background:#f3daff;color:#6f42c1}.note.primary.modern a:not(.btn){color:#6f42c1}.note.primary.modern a:not(.btn):hover{color:#453298}.note.primary:not(.modern){border-left-color:#6f42c1}.note.primary:not(.modern) h2,.note.primary:not(.modern) h3,.note.primary:not(.modern) h4,.note.primary:not(.modern) h5,.note.primary:not(.modern) h6{color:#6f42c1}.note.primary:not(.no-icon)::before{content:'\f055'}.note.primary:not(.no-icon):not(.modern)::before{color:#6f42c1}.note.info.flat{background:#eef7fa}.note.info.modern{border-color:#b3e5ef;background:#d9edf7;color:#31708f}.note.info.modern a:not(.btn){color:#31708f}.note.info.modern a:not(.btn):hover{color:#215761}.note.info:not(.modern){border-left-color:#428bca}.note.info:not(.modern) h2,.note.info:not(.modern) h3,.note.info:not(.modern) h4,.note.info:not(.modern) h5,.note.info:not(.modern) h6{color:#428bca}.note.info:not(.no-icon)::before{content:'\f05a'}.note.info:not(.no-icon):not(.modern)::before{color:#428bca}.note.success.flat{background:#eff8f0}.note.success.modern{border-color:#d0e6be;background:#dff0d8;color:#3c763d}.note.success.modern a:not(.btn){color:#3c763d}.note.success.modern a:not(.btn):hover{color:#32562c}.note.success:not(.modern){border-left-color:#5cb85c}.note.success:not(.modern) h2,.note.success:not(.modern) h3,.note.success:not(.modern) h4,.note.success:not(.modern) h5,.note.success:not(.modern) h6{color:#5cb85c}.note.success:not(.no-icon)::before{content:'\f058'}.note.success:not(.no-icon):not(.modern)::before{color:#5cb85c}.note.warning.flat{background:#fdf8ea}.note.warning.modern{border-color:#fae4cd;background:#fcf4e3;color:#8a6d3b}.note.warning.modern a:not(.btn){color:#8a6d3b}.note.warning.modern a:not(.btn):hover{color:#714f30}.note.warning:not(.modern){border-left-color:#f0ad4e}.note.warning:not(.modern) h2,.note.warning:not(.modern) h3,.note.warning:not(.modern) h4,.note.warning:not(.modern) h5,.note.warning:not(.modern) h6{color:#f0ad4e}.note.warning:not(.no-icon)::before{content:'\f06a'}.note.warning:not(.no-icon):not(.modern)::before{color:#f0ad4e}.note.danger.flat{background:#fcf1f2}.note.danger.modern{border-color:#ebcdd2;background:#f2dfdf;color:#a94442}.note.danger.modern a:not(.btn){color:#a94442}.note.danger.modern a:not(.btn):hover{color:#84333f}.note.danger:not(.modern){border-left-color:#d9534f}.note.danger:not(.modern) h2,.note.danger:not(.modern) h3,.note.danger:not(.modern) h4,.note.danger:not(.modern) h5,.note.danger:not(.modern) h6{color:#d9534f}.note.danger:not(.no-icon)::before{content:'\f056'}.note.danger:not(.no-icon):not(.modern)::before{color:#d9534f}#article-container .tabs{position:relative;margin:0 0 20px;border-right:1px solid var(--tab-border-color);border-bottom:1px solid var(--tab-border-color);border-left:1px solid var(--tab-border-color)}#article-container .tabs>.nav-tabs{display:-webkit-box;display:-moz-box;display:-webkit-flex;display:-ms-flexbox;display:box;display:flex;-webkit-box-lines:multiple;-moz-box-lines:multiple;-o-box-lines:multiple;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;margin:0;padding:0;background:var(--tab-botton-bg)}#article-container .tabs>.nav-tabs>.tab{margin:0;padding:0;list-style:none}@media screen and (max-width:768px){#article-container .tabs>.nav-tabs>.tab{-webkit-box-flex:1;-moz-box-flex:1;-o-box-flex:1;-ms-box-flex:1;box-flex:1;-webkit-flex-grow:1;flex-grow:1}}#article-container .tabs>.nav-tabs>.tab button{display:block;padding:8px 18px;width:100%;border-top:2px solid var(--tab-border-color);background:var(--tab-botton-bg);color:var(--tab-botton-color);line-height:2;-webkit-transition:all .4s;-moz-transition:all .4s;-o-transition:all .4s;-ms-transition:all .4s;transition:all .4s}#article-container .tabs>.nav-tabs>.tab button i{width:1.5em}#article-container .tabs>.nav-tabs>.tab.active button{border-top:2px solid #49b1f5;background:var(--tab-button-active-bg);cursor:default}#article-container .tabs>.nav-tabs>.tab:not(.active) button:hover{border-top:2px solid var(--tab-button-hover-bg);background:var(--tab-button-hover-bg)}#article-container .tabs>.tab-contents .tab-item-content{position:relative;display:none;padding:36px 24px}@media screen and (max-width:768px){#article-container .tabs>.tab-contents .tab-item-content{padding:24px 14px}}#article-container .tabs>.tab-contents .tab-item-content.active{display:block;-webkit-animation:tabshow .5s;-moz-animation:tabshow .5s;-o-animation:tabshow .5s;-ms-animation:tabshow .5s;animation:tabshow .5s}#article-container .tabs .tab-to-top{position:relative;display:block;margin:0 0 0 auto;color:#99a9bf}@-moz-keyframes tabshow{0%{-webkit-transform:translateY(15px);-moz-transform:translateY(15px);-o-transform:translateY(15px);-ms-transform:translateY(15px);transform:translateY(15px)}100%{-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes tabshow{0%{-webkit-transform:translateY(15px);-moz-transform:translateY(15px);-o-transform:translateY(15px);-ms-transform:translateY(15px);transform:translateY(15px)}100%{-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@-o-keyframes tabshow{0%{-webkit-transform:translateY(15px);-moz-transform:translateY(15px);-o-transform:translateY(15px);-ms-transform:translateY(15px);transform:translateY(15px)}100%{-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@keyframes tabshow{0%{-webkit-transform:translateY(15px);-moz-transform:translateY(15px);-o-transform:translateY(15px);-ms-transform:translateY(15px);transform:translateY(15px)}100%{-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}#article-container .timeline{margin:0 0 20px 10px;padding:14px 20px 5px;border-left:2px solid var(--timeline-color,#49b1f5)}#article-container .timeline.blue{--timeline-color:#428bca;--timeline-bg:rgba(66,139,202, 0.2)}#article-container .timeline.pink{--timeline-color:#ff69b4;--timeline-bg:rgba(255,105,180, 0.2)}#article-container .timeline.red{--timeline-color:#f00;--timeline-bg:rgba(255,0,0, 0.2)}#article-container .timeline.purple{--timeline-color:#6f42c1;--timeline-bg:rgba(111,66,193, 0.2)}#article-container .timeline.orange{--timeline-color:#ff8c00;--timeline-bg:rgba(255,140,0, 0.2)}#article-container .timeline.green{--timeline-color:#5cb85c;--timeline-bg:rgba(92,184,92, 0.2)}#article-container .timeline .timeline-item{margin:0 0 15px}#article-container .timeline .timeline-item:hover .item-circle:before{border-color:var(--timeline-color,#49b1f5)}#article-container .timeline .timeline-item.headline .timeline-item-title .item-circle>p{font-weight:600;font-size:1.2em}#article-container .timeline .timeline-item.headline .timeline-item-title .item-circle:before{left:-28px;border:4px solid var(--timeline-color,#49b1f5)}#article-container .timeline .timeline-item.headline:hover .item-circle:before{border-color:var(--pseudo-hover)}#article-container .timeline .timeline-item .timeline-item-title{position:relative}#article-container .timeline .timeline-item .item-circle:before{position:absolute;top:50%;left:-27px;width:6px;height:6px;border:3px solid var(--pseudo-hover);border-radius:50%;background:var(--card-bg);content:'';-webkit-transition:all .3s;-moz-transition:all .3s;-o-transition:all .3s;-ms-transition:all .3s;transition:all .3s;-webkit-transform:translate(0,-50%);-moz-transform:translate(0,-50%);-o-transform:translate(0,-50%);-ms-transform:translate(0,-50%);transform:translate(0,-50%)}#article-container .timeline .timeline-item .item-circle>p{margin:0 0 8px;font-weight:500}#article-container .timeline .timeline-item .timeline-item-content{position:relative;padding:12px 15px;border-radius:8px;background:var(--timeline-bg,#e4f3fd);font-size:.93em}#article-container .timeline .timeline-item .timeline-item-content>:last-child{margin-bottom:0}#article-container .timeline+.timeline{margin-top:-20px}[data-theme=dark]{--global-bg:#0d0d0d;--font-color:rgba(255,255,255,0.7);--hr-border:rgba(255,255,255,0.4);--hr-before-color:rgba(255,255,255,0.7);--search-bg:#121212;--search-input-color:rgba(255,255,255,0.7);--search-a-color:rgba(255,255,255,0.7);--preloader-bg:#0d0d0d;--preloader-color:rgba(255,255,255,0.7);--tab-border-color:#2c2c2c;--tab-botton-bg:#2c2c2c;--tab-botton-color:rgba(255,255,255,0.7);--tab-button-hover-bg:#383838;--tab-button-active-bg:#121212;--card-bg:#121212;--sidebar-bg:#121212;--btn-hover-color:#787878;--btn-color:rgba(255,255,255,0.7);--btn-bg:#1f1f1f;--text-bg-hover:#383838;--light-grey:rgba(255,255,255,0.7);--dark-grey:rgba(255,255,255,0.2);--white:rgba(255,255,255,0.9);--text-highlight-color:rgba(255,255,255,0.9);--blockquote-color:rgba(255,255,255,0.7);--blockquote-bg:#2c2c2c;--reward-pop:#2c2c2c;--toc-link-color:rgba(255,255,255,0.6);--hl-color:rgba(255,255,255,0.7);--hl-bg:#171717;--hltools-bg:#1a1a1a;--hltools-color:#90a4ae;--hlnumber-bg:#171717;--hlnumber-color:rgba(255,255,255,0.4);--hlscrollbar-bg:#1f1f1f;--hlexpand-bg:linear-gradient(180deg, rgba(23,23,23,0.6), rgba(23,23,23,0.9));--scrollbar-color:#1f1f1f;--timeline-bg:#1f1f1f;--zoom-bg:#121212;--mark-bg:rgba(0,0,0,0.6)}[data-theme=dark] #web_bg:before{position:absolute;width:100%;height:100%;background-color:rgba(0,0,0,.7);content:''}[data-theme=dark] #article-container code{background:#2c2c2c}[data-theme=dark] #article-container pre>code{background:#171717}[data-theme=dark] #article-container figure.highlight{-webkit-box-shadow:none;box-shadow:none}[data-theme=dark] #article-container .note code{background:rgba(27,31,35,.05)}[data-theme=dark] #article-container .aplayer{filter:brightness(.8)}[data-theme=dark] #article-container kbd{border-color:#696969;background-color:#525252;color:#e2f1ff}[data-theme=dark] #page-header.nav-fixed>#nav,[data-theme=dark] #page-header.not-top-img>#nav{background:rgba(18,18,18,.8);-webkit-box-shadow:0 5px 6px -5px rgba(133,133,133,0);box-shadow:0 5px 6px -5px rgba(133,133,133,0)}[data-theme=dark] #post-comment #comment-switch{background:#2c2c2c!important}[data-theme=dark] #post-comment #comment-switch .switch-btn{filter:brightness(.8)}[data-theme=dark] .note{filter:brightness(.8)}[data-theme=dark] #article-container iframe,[data-theme=dark] .ads-wrap,[data-theme=dark] .btn-beautify,[data-theme=dark] .error-img,[data-theme=dark] .gist,[data-theme=dark] .hide-button,[data-theme=dark] .hl-label,[data-theme=dark] .post-outdate-notice{filter:brightness(.8)}[data-theme=dark] img{filter:blur(0) brightness(.8)}[data-theme=dark] #aside-content .aside-list>.aside-list-item:not(:last-child){border-bottom:1px dashed rgba(255,255,255,.1)}[data-theme=dark] #gitalk-container{filter:brightness(.8)}[data-theme=dark] #gitalk-container svg{fill:rgba(255,255,255,.9)!important}[data-theme=dark] #disqusjs #dsqjs .dsqjs-no-comment,[data-theme=dark] #disqusjs #dsqjs .dsqjs-tab-active,[data-theme=dark] #disqusjs #dsqjs:focus,[data-theme=dark] #disqusjs #dsqjs:hover{color:rgba(255,255,255,.7)}[data-theme=dark] #disqusjs #dsqjs .dsqjs-order-label{background-color:#1f1f1f}[data-theme=dark] #disqusjs #dsqjs .dsqjs-post-body{color:rgba(255,255,255,.7)}[data-theme=dark] #disqusjs #dsqjs .dsqjs-post-body code,[data-theme=dark] #disqusjs #dsqjs .dsqjs-post-body pre{background:#2c2c2c}[data-theme=dark] #disqusjs #dsqjs .dsqjs-post-body blockquote{color:rgba(255,255,255,.7)}[data-theme=dark] #artitalk_main #lazy{background:#121212}[data-theme=dark] #operare_artitalk .c2{background:#121212}@media screen and (max-width:900px){[data-theme=dark] #card-toc{background:#1f1f1f}}.read-mode{--font-color:#4c4948;--readmode-light-color:#fff;--white:#4c4948;--light-grey:#4c4948;--gray:#d6dbdf;--hr-border:#d6dbdf;--hr-before-color:#b9c2c9;--highlight-bg:#f7f7f7;--exit-btn-bg:#c0c0c0;--exit-btn-color:#fff;--exit-btn-hover:#8d8d8d;--pseudo-hover:none}[data-theme=dark] .read-mode{--font-color:rgba(255,255,255,0.7);--readmode-light-color:#0d0d0d;--white:rgba(255,255,255,0.9);--light-grey:rgba(255,255,255,0.7);--gray:rgba(255,255,255,0.7);--hr-border:rgba(255,255,255,0.5);--hr-before-color:rgba(255,255,255,0.7);--highlight-bg:#171717;--exit-btn-bg:#1f1f1f;--exit-btn-color:rgba(255,255,255,0.9);--exit-btn-hover:#525252}.read-mode{background:var(--readmode-light-color)}.read-mode .exit-readmode{position:fixed;top:30px;right:30px;z-index:100;width:40px;height:40px;border-radius:8px;background:var(--exit-btn-bg);color:var(--exit-btn-color);font-size:16px;-webkit-transition:background .3s;-moz-transition:background .3s;-o-transition:background .3s;-ms-transition:background .3s;transition:background .3s}@media screen and (max-width:768px){.read-mode .exit-readmode{top:initial;bottom:30px}}.read-mode .exit-readmode:hover{background:var(--exit-btn-hover)}.read-mode #aside-content{display:none}.read-mode #page-header.post-bg{background:0 0!important}.read-mode #page-header.post-bg:before{opacity:0}.read-mode #page-header.post-bg>#post-info{text-align:center}.read-mode #post{margin:0 auto;background:0 0;-webkit-box-shadow:none;box-shadow:none}.read-mode #post:hover{-webkit-box-shadow:none;box-shadow:none}.read-mode>canvas{display:none!important}.read-mode #footer,.read-mode #nav,.read-mode #post>:not(#post-info):not(.post-content),.read-mode #rightside,.read-mode #web_bg,.read-mode .highlight-tools,.read-mode .not-top-img,.read-mode .post-outdate-notice{display:none!important}.read-mode #article-container a{color:#99a9bf}.read-mode #article-container .highlight:not(.js-file-line-container),.read-mode #article-container pre{background:var(--highlight-bg)!important}.read-mode #article-container .highlight:not(.js-file-line-container) *,.read-mode #article-container pre *{color:var(--font-color)!important}.read-mode #article-container figure.highlight{border-radius:0!important;-webkit-box-shadow:none!important;box-shadow:none!important}.read-mode #article-container figure.highlight>:not(.highlight-tools){display:block!important}.read-mode #article-container figure.highlight .line:before{color:var(--font-color)!important}.read-mode #article-container figure.highlight .hljs{background:var(--highlight-bg)!important}.read-mode #article-container h1,.read-mode #article-container h2,.read-mode #article-container h3,.read-mode #article-container h4,.read-mode #article-container h5,.read-mode #article-container h6{padding:0}.read-mode #article-container h1:before,.read-mode #article-container h2:before,.read-mode #article-container h3:before,.read-mode #article-container h4:before,.read-mode #article-container h5:before,.read-mode #article-container h6:before{content:''}.read-mode #article-container h1:hover,.read-mode #article-container h2:hover,.read-mode #article-container h3:hover,.read-mode #article-container h4:hover,.read-mode #article-container h5:hover,.read-mode #article-container h6:hover{padding:0}.read-mode #article-container li:hover:before,.read-mode #article-container ol:hover:before,.read-mode #article-container ul:hover:before{-webkit-transform:none!important;-moz-transform:none!important;-o-transform:none!important;-ms-transform:none!important;transform:none!important}.read-mode #article-container li:before,.read-mode #article-container ol:before{background:0 0!important;color:var(--font-color)!important}.read-mode #article-container ul>li:before{border-color:var(--gray)!important}.read-mode #article-container .tabs{border:2px solid var(--tab-border-color)}.read-mode #article-container .tabs>.nav-tabs{background:0 0}.read-mode #article-container .tabs>.nav-tabs>.tab{border-bottom:0}.read-mode #article-container .tabs>.nav-tabs>.tab button{border-top:none!important;background:0 0}.read-mode #article-container .tabs>.nav-tabs>.tab button:hover{background:0 0!important}.read-mode #article-container .tabs>.nav-tabs>.tab.active button{text-decoration:underline}.read-mode #article-container .tabs>.tab-contents .tab-item-content.active{-webkit-animation:none;-moz-animation:none;-o-animation:none;-ms-animation:none;animation:none}.read-mode #article-container code{color:var(--font-color)}.read-mode #article-container blockquote{border-color:var(--gray);background-color:var(--readmode-light-color)}.read-mode #article-container kbd{border:1px solid var(--gray);background-color:transparent;-webkit-box-shadow:none;box-shadow:none;color:var(--font-color)}.read-mode #article-container .hide-toggle{border:1px solid var(--gray)!important}.read-mode #article-container .btn-beautify,.read-mode #article-container .hide-button,.read-mode #article-container .hl-label{border:1px solid var(--gray)!important;background:var(--readmode-light-color)!important;color:var(--font-color)!important}.read-mode #article-container .note{border:2px solid var(--gray);border-left-color:var(--gray)!important;filter:none;background-color:var(--readmode-light-color)!important;color:var(--font-color)}.read-mode #article-container .note .note-icon,.read-mode #article-container .note:before{color:var(--font-color)}.search-dialog{position:fixed;top:10%;left:50%;z-index:1001;display:none;margin-left:-300px;padding:20px;width:600px;border-radius:8px;background:var(--search-bg);--search-height:100vh}@media screen and (max-width:768px){.search-dialog{top:0;left:0;margin:0;width:100%;height:100%;border-radius:0}}.search-dialog hr{margin:20px auto}.search-dialog .search-nav{margin:0 0 14px;color:#49b1f5;font-size:1.4em;line-height:1}.search-dialog .search-nav .search-dialog-title{margin-right:10px}.search-dialog .search-nav .search-close-button{float:right;color:#858585;-webkit-transition:color .2s ease-in-out;-moz-transition:color .2s ease-in-out;-o-transition:color .2s ease-in-out;-ms-transition:color .2s ease-in-out;transition:color .2s ease-in-out}.search-dialog .search-nav .search-close-button:hover{color:#49b1f5}.search-dialog hr{margin:20px auto}#search-mask{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1000;display:none;background:rgba(0,0,0,.6)} \ No newline at end of file diff --git a/css/pointer/Arrow.cur b/css/pointer/Arrow.cur new file mode 100644 index 000000000..8db3ab43a Binary files /dev/null and b/css/pointer/Arrow.cur differ diff --git a/css/pointer/Copy.cur b/css/pointer/Copy.cur new file mode 100644 index 000000000..51ba7902d Binary files /dev/null and b/css/pointer/Copy.cur differ diff --git a/css/pointer/Hand.cur b/css/pointer/Hand.cur new file mode 100644 index 000000000..fce73d116 Binary files /dev/null and b/css/pointer/Hand.cur differ diff --git a/css/pointer/Hand2.cur b/css/pointer/Hand2.cur new file mode 100644 index 000000000..f25b9a713 Binary files /dev/null and b/css/pointer/Hand2.cur differ diff --git a/css/pointer/Help.cur b/css/pointer/Help.cur new file mode 100644 index 000000000..9417e5b88 Binary files /dev/null and b/css/pointer/Help.cur differ diff --git a/css/pointer/IBeam.cur b/css/pointer/IBeam.cur new file mode 100644 index 000000000..582ad9816 Binary files /dev/null and b/css/pointer/IBeam.cur differ diff --git a/css/pointer/NO.cur b/css/pointer/NO.cur new file mode 100644 index 000000000..fbc8bc84c Binary files /dev/null and b/css/pointer/NO.cur differ diff --git a/css/var.css b/css/var.css new file mode 100644 index 000000000..e69de29bb diff --git a/drive/index.html b/drive/index.html new file mode 100644 index 000000000..267fbe548 --- /dev/null +++ b/drive/index.html @@ -0,0 +1,585 @@ +个人网盘 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + +
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/img/404.jpg b/img/404.jpg new file mode 100644 index 000000000..4bab3c3f2 Binary files /dev/null and b/img/404.jpg differ diff --git a/img/favicon.png b/img/favicon.png new file mode 100644 index 000000000..862ebe858 Binary files /dev/null and b/img/favicon.png differ diff --git a/img/friend_404.gif b/img/friend_404.gif new file mode 100644 index 000000000..91dd56a28 Binary files /dev/null and b/img/friend_404.gif differ diff --git a/index.html b/index.html new file mode 100644 index 000000000..e8d2d9017 --- /dev/null +++ b/index.html @@ -0,0 +1,690 @@ +希亚的西红柿のBlog - 分享学习与生活 + + + + + + + + + + + + + + + +
虚拟列表
LeetCode 算法笔记 Part3
LeetCode 算法笔记 Part2
LeetCode 算法笔记 Part1
JS手写题大汇总
TypeSript笔记
Intersection Observer
数据结构与算法笔记
React笔记
Vue3笔记
Vue2笔记
前端知识笔记
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/js/hideLrc.js b/js/hideLrc.js new file mode 100644 index 000000000..28468fe15 --- /dev/null +++ b/js/hideLrc.js @@ -0,0 +1 @@ +let meting_api="https://api.injahow.cn/meting/?server=:server&type=:type&id=:id&auth=:auth&r=:r";const observer=new MutationObserver(((e,r)=>{for(let r of e)"childList"===r.type&&removelrc()})),removelrc=()=>{const e=document.querySelector("#my-aplayer .aplayer-icon-lrc");e&&(observer.disconnect(),e.click())},observerConfig={childList:!0,subtree:!0};observer.observe(document,observerConfig); \ No newline at end of file diff --git a/js/main.js b/js/main.js new file mode 100644 index 000000000..42192e22e --- /dev/null +++ b/js/main.js @@ -0,0 +1 @@ +document.addEventListener("DOMContentLoaded",(function(){let t,e,n=!1;const o=n=>{const o=t=>{let e=0;return t.length&&Array.from(t).forEach((t=>{e+=t.offsetWidth})),e};if(n){const n=o(document.querySelector("#blog-info > a").children),i=o(document.getElementById("menus").children);t=n+i,e=document.getElementById("nav")}let i="";i=window.innerWidth<=768||t>e.offsetWidth-120,i?e.classList.add("hide-menu"):e.classList.remove("hide-menu")},i=()=>{btf.sidebarPaddingR(),document.body.style.overflow="hidden",btf.animateIn(document.getElementById("menu-mask"),"to_show 0.5s"),document.getElementById("sidebar-menus").classList.add("open"),n=!0},c=()=>{const t=document.body;t.style.overflow="",t.style.paddingRight="",btf.animateOut(document.getElementById("menu-mask"),"to_hide 0.5s"),document.getElementById("sidebar-menus").classList.remove("open"),n=!1},s=function(){const t=GLOBAL_CONFIG.highlight;if(!t)return;const{highlightCopy:e,highlightLang:n,highlightHeightLimit:o,plugin:i}=t,c=GLOBAL_CONFIG_SITE.isHighlightShrink,s=e||n||void 0!==c,a="highlighjs"===i?document.querySelectorAll("figure.highlight"):document.querySelectorAll('pre[class*="language-"]');if(!s&&!o||!a.length)return;const l="prismjs"===i,d=!0===c?"closed":"",r=void 0!==c?``:"",u=e?'
':"",m=t=>{const e=t.parentNode;e.classList.add("copy-true");const n=window.getSelection(),o=document.createRange(),i=l?"pre code":"table .code pre";o.selectNodeContents(e.querySelectorAll(`${i}`)[0]),n.removeAllRanges(),n.addRange(o);n.toString();((t,e)=>{if(document.queryCommandSupported&&document.queryCommandSupported("copy"))if(document.execCommand("copy"),void 0!==GLOBAL_CONFIG.Snackbar)btf.snackbarShow(GLOBAL_CONFIG.copy.success);else{const t=e.previousElementSibling;t.textContent=GLOBAL_CONFIG.copy.success,t.style.opacity=1,setTimeout((()=>{t.style.opacity=0}),700)}else void 0!==GLOBAL_CONFIG.Snackbar?btf.snackbarShow(GLOBAL_CONFIG.copy.noSupport):e.previousElementSibling.textContent=GLOBAL_CONFIG.copy.noSupport})(0,t.lastChild),n.removeAllRanges(),e.classList.remove("copy-true")},g=function(t){const e=t.target.classList;e.contains("expand")?(t=>{const e=[...t.parentNode.children].slice(1);t.firstChild.classList.toggle("closed"),btf.isHidden(e[e.length-1])?e.forEach((t=>{t.style.display="block"})):e.forEach((t=>{t.style.display="none"}))})(this):e.contains("copy-button")&&m(this)},h=function(){this.classList.toggle("expand-done")};function f(t,e,n){const i=document.createDocumentFragment();if(s){const e=document.createElement("div");e.className=`highlight-tools ${d}`,e.innerHTML=r+t+u,e.addEventListener("click",g),i.appendChild(e)}if(o&&e.offsetHeight>o+30){const t=document.createElement("div");t.className="code-expand-btn",t.innerHTML='',t.addEventListener("click",h),i.appendChild(t)}"hl"===n?e.insertBefore(i,e.firstChild):e.parentNode.insertBefore(i,e)}l?a.forEach((t=>{if(n){const e=`
${t.getAttribute("data-language")||"Code"}
`;btf.wrap(t,"figure",{class:"highlight"}),f(e,t)}else btf.wrap(t,"figure",{class:"highlight"}),f("",t)})):a.forEach((function(t){if(n){let e=t.getAttribute("class").split(" ")[1];"plain"!==e&&void 0!==e||(e="Code");f(`
${e}
`,t,"hl")}else f("",t,"hl")}))};const a=function(t){const e=t=>{let e="";const n=t=>t.replace(/"/g,""");return t.forEach((t=>{const o=t.alt?`alt="${n(t.alt)}"`:"",i=t.title?`title="${n(t.title)}"`:"";e+=``})),e},n=(t,n,o)=>{const i=o,c=n.length;return c>i?t.insertAdjacentHTML("beforeend",e(n.splice(0,i))):(t.insertAdjacentHTML("beforeend",e(n)),t.classList.remove("lazyload")),c>i?i:c},o=(t,o)=>{if(t.classList.contains("lazyload")){const e=t.getAttribute("data-limit");n(t,o,e);const i=()=>{const c=n(t,o,e);fjGallery(t,"appendImages",t.querySelectorAll(`.fj-gallery-item:nth-last-child(-n+${c})`)),btf.loadLightbox(t.querySelectorAll("img")),c{t.forEach((t=>{t.classList.contains("url")?(async t=>{const e=await fetch(t);return await e.json()})(t.textContent).then((e=>{o(t,e)})):o(t,JSON.parse(t.textContent))}))};window.fjGallery?i():(getCSS(`${GLOBAL_CONFIG.source.justifiedGallery.css}`),getScript(`${GLOBAL_CONFIG.source.justifiedGallery.js}`).then(i))},l=function(){const t=document.getElementById("rightside"),e=window.innerHeight+56;let n=0,o=!0;const i=document.getElementById("page-header"),c="undefined"!=typeof chatBtn,s=GLOBAL_CONFIG.percent.rightside;if(document.body.scrollHeight<=e)return void(t.style.cssText="opacity: 1; transform: translateX(-58px)");const a=btf.throttle((()=>{const a=window.scrollY||document.documentElement.scrollTop,l=(t=>{const e=t>n;return n=t,e})(a);a>56?(l?(i.classList.contains("nav-visible")&&i.classList.remove("nav-visible"),c&&!0===o&&(window.chatBtn.hide(),o=!1)):(i.classList.contains("nav-visible")||i.classList.add("nav-visible"),c&&!1===o&&(window.chatBtn.show(),o=!0)),i.classList.add("nav-fixed"),"0"===window.getComputedStyle(t).getPropertyValue("opacity")&&(t.style.cssText="opacity: 0.8; transform: translateX(-58px)")):(0===a&&i.classList.remove("nav-fixed","nav-visible"),t.style.cssText="opacity: ''; transform: ''"),s&&(t=>{const e=btf.getScrollPercent(t,document.body),n=document.getElementById("go-up");e<95?(n.classList.add("show-percent"),n.querySelector(".scroll-percent").textContent=e):n.classList.remove("show-percent")})(a),document.body.scrollHeight<=e&&(t.style.cssText="opacity: 0.8; transform: translateX(-58px)")}),200);window.scrollCollect=a,window.addEventListener("scroll",scrollCollect)},d=function(){const t=GLOBAL_CONFIG_SITE.isToc,e=GLOBAL_CONFIG.isAnchor,n=document.getElementById("article-container");if(!n||!t&&!e)return;let o,i,c,s,a;if(t){const t=document.getElementById("card-toc");i=t.getElementsByClassName("toc-content")[0],o=i.querySelectorAll(".toc-link"),s=t.querySelector(".toc-percentage"),a=i.classList.contains("is-expand"),window.mobileToc={open:()=>{t.style.cssText="animation: toc-open .3s; opacity: 1; right: 55px"},close:()=>{t.style.animation="toc-close .2s",setTimeout((()=>{t.style.cssText="opacity:''; animation: ''; right: ''"}),100)}},i.addEventListener("click",(t=>{t.preventDefault();const e=t.target.classList;if(e.contains("toc-content"))return;const n=e.contains("toc-link")?t.target:t.target.parentElement;btf.scrollToDest(btf.getEleTop(document.getElementById(decodeURI(n.getAttribute("href")).replace("#",""))),300),window.innerWidth<900&&window.mobileToc.close()})),c=t=>{const e=t.getBoundingClientRect().top,n=i.scrollTop;e>document.documentElement.clientHeight-100&&(i.scrollTop=n+150),e<100&&(i.scrollTop=n-150)}}const l=n.querySelectorAll("h1,h2,h3,h4,h5,h6");let d="";window.tocScrollFn=btf.throttle((()=>{const r=window.scrollY||document.documentElement.scrollTop;t&&GLOBAL_CONFIG.percent.toc&&(s.textContent=btf.getScrollPercent(r,n)),function(n){if(0===n)return!1;let s="",r="";if(l.forEach((function(t,e){if(n>btf.getEleTop(t)-80){const n=t.id;s=n?"#"+encodeURI(n):"",r=e}})),d!==r&&(e&&btf.updateAnchor(s),d=r,t)){if(i.querySelectorAll(".active").forEach((t=>{t.classList.remove("active")})),""===s)return;const t=o[r];if(t.classList.add("active"),setTimeout((()=>{c(t)}),0),a)return;let e=t.parentNode;for(;!e.matches(".toc");e=e.parentNode)e.matches("li")&&e.classList.add("active")}}(r)}),100),window.addEventListener("scroll",tocScrollFn)},r=t=>{if(!window.themeChange)return;const e=e=>window.themeChange[e](t);Object.keys(window.themeChange).forEach((t=>{["disqus","disqusjs"].includes(t)?setTimeout((()=>e(t)),300):e(t)}))},u=()=>{const t=document.body;t.classList.add("read-mode");const e=document.createElement("button");e.type="button",e.className="fas fa-sign-out-alt exit-readmode",t.appendChild(e);const n=()=>{t.classList.remove("read-mode"),e.remove(),e.removeEventListener("click",n)};e.addEventListener("click",n)},m=()=>{const t="dark"===document.documentElement.getAttribute("data-theme")?"light":"dark";"dark"===t?(activateDarkMode(),saveToLocal.set("theme","dark",2),void 0!==GLOBAL_CONFIG.Snackbar&&btf.snackbarShow(GLOBAL_CONFIG.Snackbar.day_to_night)):(activateLightMode(),saveToLocal.set("theme","light",2),void 0!==GLOBAL_CONFIG.Snackbar&&btf.snackbarShow(GLOBAL_CONFIG.Snackbar.night_to_day)),r(t)},g=t=>{const e=document.getElementById("rightside-config-hide").classList;e.toggle("show"),t.classList.contains("show")&&(e.add("status"),setTimeout((()=>{e.remove("status")}),300)),t.classList.toggle("show")},h=()=>{btf.scrollToDest(0,500)},f=()=>{const t=document.documentElement.classList,e=t.contains("hide-aside")?"show":"hide";saveToLocal.set("aside-status",e,2),t.toggle("hide-aside")},p=()=>{"0"===window.getComputedStyle(document.getElementById("card-toc")).getPropertyValue("opacity")?window.mobileToc.open():window.mobileToc.close()},L=()=>{window.chatBtnFn()};document.getElementById("rightside").addEventListener("click",(function(t){const e=t.target.id?t.target:t.target.parentNode;switch(e.id){case"go-up":h();break;case"rightside_config":g(e);break;case"mobile-toc-button":p();break;case"readmode":u();break;case"darkmode":m();break;case"hide-aside-btn":f();break;case"chat-btn":L()}}));const y=()=>{document.querySelectorAll("#sidebar-menus .site-page.group").forEach((function(t){t.addEventListener("click",(function(){this.classList.toggle("hide")}))}))},b=function(){document.querySelectorAll("#article-container .tab > button").forEach((function(t){t.addEventListener("click",(function(t){const e=this,n=e.parentNode;if(!n.classList.contains("active")){const t=n.parentNode.nextElementSibling,o=btf.siblings(n,".active")[0];o&&o.classList.remove("active"),n.classList.add("active");const i=e.getAttribute("data-href").replace("#","");[...t.children].forEach((t=>{t.id===i?t.classList.add("active"):t.classList.remove("active")}));const c=t.querySelectorAll(`#${i} .fj-gallery`);c.length>0&&btf.initJustifiedGallery(c)}}))}))},v=()=>{document.querySelectorAll("#article-container .tabs .tab-to-top").forEach((function(t){t.addEventListener("click",(function(){btf.scrollToDest(btf.getEleTop(btf.getParents(this,".tabs")),300)}))}))},E=function(t){t.forEach((t=>{const e=t.getAttribute("datetime");t.textContent=btf.diffDate(e,!0),t.style.display="inline"}))};window.refreshFn=function(){o(!0),e.classList.add("show"),GLOBAL_CONFIG_SITE.isPost?(void 0!==GLOBAL_CONFIG.noticeOutdate&&function(){const t=GLOBAL_CONFIG.noticeOutdate,e=btf.diffDate(GLOBAL_CONFIG_SITE.postUpdate);if(e>=t.limitDay){const n=document.createElement("div");n.className="post-outdate-notice",n.textContent=t.messagePrev+" "+e+" "+t.messageNext;const o=document.getElementById("article-container");"top"===t.position?o.insertBefore(n,o.firstChild):o.appendChild(n)}}(),GLOBAL_CONFIG.relativeDate.post&&E(document.querySelectorAll("#post-meta time"))):(GLOBAL_CONFIG.relativeDate.homepage&&E(document.querySelectorAll("#recent-posts time")),GLOBAL_CONFIG.runtime&&(()=>{const t=document.getElementById("runtimeshow");if(t){const e=t.getAttribute("data-publishDate");t.textContent=`${btf.diffDate(e)} ${GLOBAL_CONFIG.runtime}`}})(),(()=>{const t=document.getElementById("last-push-date");if(t){const e=t.getAttribute("data-lastPushDate");t.textContent=btf.diffDate(e,!0)}})(),function(){const t=document.querySelectorAll("#aside-cat-list .card-category-list-item.parent i");t.length&&t.forEach((function(t){t.addEventListener("click",(function(t){t.preventDefault(),this.classList.toggle("expand");const e=this.parentNode.nextElementSibling;btf.isHidden(e)?e.style.display="block":e.style.display="none"}))}))}()),d(),GLOBAL_CONFIG_SITE.isHome&&(()=>{const t=document.getElementById("scroll-down");t&&t.addEventListener("click",(function(){btf.scrollToDest(document.getElementById("content-inner").offsetTop,300)}))})(),s(),GLOBAL_CONFIG.isPhotoFigcaption&&document.querySelectorAll("#article-container img").forEach((function(t){const e=t.parentNode,n=t.title||t.alt;if(n&&!e.parentNode.classList.contains("justified-gallery")){const o=document.createElement("div");o.className="img-alt is-center",o.textContent=n,e.insertBefore(o,t.nextSibling)}})),l();const t=document.querySelectorAll("#article-container .fj-gallery");t.length&&a(t),btf.loadLightbox(document.querySelectorAll("#article-container img:not(.no-lightbox)")),(()=>{const t=document.querySelectorAll("#article-container :not(.highlight) > table, #article-container > table");t.length&&t.forEach((t=>{btf.wrap(t,"div",{class:"table-wrap"})}))})(),function(){const t=document.querySelectorAll("#article-container .hide-button");t.length&&t.forEach((function(t){t.addEventListener("click",(function(t){this.classList.add("open");const e=this.nextElementSibling.querySelectorAll(".fj-gallery");e.length&&btf.initJustifiedGallery(e)}))}))}(),b(),v(),function(){let t=!1;const e=document.querySelector("#comment-switch > .switch-btn");e&&e.addEventListener("click",(function(){this.classList.toggle("move"),document.querySelectorAll("#post-comment > .comment-wrap > div").forEach((function(t){btf.isHidden(t)?t.style.cssText="display: block;animation: tabshow .5s":t.style.cssText="display: none;animation: ''"})),t||"function"!=typeof loadOtherComment||(t=!0,loadOtherComment())}))}(),document.getElementById("toggle-menu").addEventListener("click",(()=>{i()}))},refreshFn(),window.addEventListener("resize",(()=>{o(!1),btf.isHidden(document.getElementById("toggle-menu"))&&n&&c()})),document.getElementById("menu-mask").addEventListener("click",(t=>{c()})),y(),GLOBAL_CONFIG.islazyload&&(window.lazyLoadInstance=new LazyLoad({elements_selector:"img",threshold:0,data_src:"lazy-src"})),void 0!==GLOBAL_CONFIG.copyright&&(()=>{const t=GLOBAL_CONFIG.copyright;document.body.oncopy=e=>{e.preventDefault();const n=window.getSelection(0).toString();let o=n;return n.length>t.limitCount&&(o=`${n}\n\n\n${t.languages.author}\n${t.languages.link}${window.location.href}\n${t.languages.source}\n${t.languages.info}`),e.clipboardData?e.clipboardData.setData("text",o):window.clipboardData.setData("text",o)}})(),GLOBAL_CONFIG.autoDarkmode&&window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",(t=>{void 0===saveToLocal.get("theme")&&(t.matches?r("dark"):r("light"))}))})); \ No newline at end of file diff --git a/js/search/algolia.js b/js/search/algolia.js new file mode 100644 index 000000000..ef3f8251e --- /dev/null +++ b/js/search/algolia.js @@ -0,0 +1 @@ +window.addEventListener("load",(()=>{const e=document.getElementById("search-mask"),t=document.querySelector("#algolia-search .search-dialog"),a=()=>{const a=document.body.style;a.width="100%",a.overflow="hidden",btf.animateIn(e,"to_show 0.5s"),btf.animateIn(t,"titleScale 0.5s"),setTimeout((()=>{document.querySelector("#algolia-search .ais-SearchBox-input").focus()}),100),document.addEventListener("keydown",(function e(t){"Escape"===t.code&&(n(),document.removeEventListener("keydown",e))})),i(),window.addEventListener("resize",i)},n=()=>{const a=document.body.style;a.width="",a.overflow="",btf.animateOut(t,"search_close .5s"),btf.animateOut(e,"to_hide 0.5s"),window.removeEventListener("resize",i)},i=()=>{window.innerWidth<768&&t.style.setProperty("--search-height",window.innerHeight+"px")},s=()=>{document.querySelector("#search-button > .search").addEventListener("click",a)},o=e=>{if(""===e)return"";const t=e.indexOf("");let a=t-30,n=t+120,i="",s="";a<=0?(a=0,n=140):i="...",n>e.length?n=e.length:s="...";return i+e.substring(a,n)+s},l=GLOBAL_CONFIG.algolia;if(!(l.appId&&l.apiKey&&l.indexName))return console.error("Algolia setting is invalid!");const r=instantsearch({indexName:l.indexName,searchClient:algoliasearch(l.appId,l.apiKey),searchFunction(e){e.state.query&&e.search()}}),c=instantsearch.widgets.configure({hitsPerPage:5}),d=instantsearch.widgets.searchBox({container:"#algolia-search-input",showReset:!1,showSubmit:!1,placeholder:GLOBAL_CONFIG.algolia.languages.input_placeholder,showLoadingIndicator:!0}),h=instantsearch.widgets.hits({container:"#algolia-hits",templates:{item(e){const t=e.permalink?e.permalink:GLOBAL_CONFIG.root+e.path,a=e._highlightResult,n=a.contentStripTruncate?o(a.contentStripTruncate.value):a.contentStrip?o(a.contentStrip.value):a.content?o(a.content.value):"";return`\n \n ${a.title.value||"no-title"}\n

${n}

\n
`},empty:function(e){return'
'+GLOBAL_CONFIG.algolia.languages.hits_empty.replace(/\$\{query}/,e.query)+"
"}}}),g=instantsearch.widgets.stats({container:"#algolia-info > .algolia-stats",templates:{text:function(e){return`
${GLOBAL_CONFIG.algolia.languages.hits_stats.replace(/\$\{hits}/,e.nbHits).replace(/\$\{time}/,e.processingTimeMS)}`}}}),u=instantsearch.widgets.poweredBy({container:"#algolia-info > .algolia-poweredBy"}),p=instantsearch.widgets.pagination({container:"#algolia-pagination",totalPages:5,templates:{first:'',last:'',previous:'',next:''}});r.addWidgets([c,d,h,g,u,p]),r.start(),s(),e.addEventListener("click",n),document.querySelector("#algolia-search .search-close-button").addEventListener("click",n),window.addEventListener("pjax:complete",(()=>{!btf.isHidden(e)&&n(),s()})),window.pjax&&r.on("render",(()=>{window.pjax.refresh(document.getElementById("algolia-hits"))}))})); \ No newline at end of file diff --git a/js/search/local-search.js b/js/search/local-search.js new file mode 100644 index 000000000..7c6ecc50f --- /dev/null +++ b/js/search/local-search.js @@ -0,0 +1 @@ +class LocalSearch{constructor({path:e="",unescape:t=!1,top_n_per_article:n=1}){this.path=e,this.unescape=t,this.top_n_per_article=n,this.isfetched=!1,this.datas=null}getIndexByWord(e,t,n=!1){const s=[],o=new Set;return n||(t=t.toLowerCase()),e.forEach((e=>{if(this.unescape){const t=document.createElement("div");t.innerText=e,e=t.innerHTML}const i=e.length;if(0===i)return;let r=0,a=-1;for(n||(e=e.toLowerCase());(a=t.indexOf(e,r))>-1;)s.push({position:a,word:e}),o.add(e),r=a+i})),s.sort(((e,t)=>e.position!==t.position?e.position-t.position:t.word.length-e.word.length)),[s,o]}mergeIntoSlice(e,t,n){let s=n[0],{position:o,word:i}=s;const r=[],a=new Set;for(;o+i.length<=t&&0!==n.length;){a.add(i),r.push({position:o,length:i.length});const e=o+i.length;for(n.shift();0!==n.length&&(s=n[0],o=s.position,i=s.word,e>o);)n.shift()}return{hits:r,start:e,end:t,count:a.size}}highlightKeyword(e,t){let n="",s=t.start;for(const{position:o,length:i}of t.hits)n+=e.substring(s,o),s=o+i,n+=`${e.substr(o,i)}`;return n+=e.substring(s,t.end),n}getResultItems(e){const t=[];return this.datas.forEach((({title:n,content:s,url:o})=>{const[i,r]=this.getIndexByWord(e,n),[a,c]=this.getIndexByWord(e,s),l=new Set([...r,...c]).size,h=i.length+a.length;if(0===h)return;const d=[];0!==i.length&&d.push(this.mergeIntoSlice(0,n.length,i));let u=[];for(;0!==a.length;){const e=a[0],{position:t}=e,n=Math.max(0,t-20),o=Math.min(s.length,t+100);u.push(this.mergeIntoSlice(n,o,a))}u.sort(((e,t)=>e.count!==t.count?t.count-e.count:e.hits.length!==t.hits.length?t.hits.length-e.hits.length:e.start-t.start));const g=parseInt(this.top_n_per_article,10);g>=0&&(u=u.slice(0,g));let p="";(o=new URL(o,location.origin)).searchParams.append("highlight",e.join(" ")),0!==d.length?p+=`
${this.highlightKeyword(n,d[0])}`:p+=`",t.push({item:p,id:t.length,hitCount:h,includedCount:l})})),t}fetchData(){const e=!this.path.endsWith("json");fetch(this.path).then((e=>e.text())).then((t=>{this.isfetched=!0,this.datas=e?[...(new DOMParser).parseFromString(t,"text/xml").querySelectorAll("entry")].map((e=>({title:e.querySelector("title").textContent,content:e.querySelector("content").textContent,url:e.querySelector("url").textContent}))):JSON.parse(t),this.datas=this.datas.filter((e=>e.title)).map((e=>(e.title=e.title.trim(),e.content=e.content?e.content.trim().replace(/<[^>]+>/g,""):"",e.url=decodeURIComponent(e.url).replace(/\/{2,}/g,"/"),e))),window.dispatchEvent(new Event("search:loaded"))}))}highlightText(e,t,n){const s=e.nodeValue;let o=t.start;const i=[];for(const{position:e,length:r}of t.hits){const t=document.createTextNode(s.substring(o,e));o=e+r;const a=document.createElement("mark");a.className=n,a.appendChild(document.createTextNode(s.substr(e,r))),i.push(t,a)}e.nodeValue=s.substring(o,t.end),i.forEach((t=>{e.parentNode.insertBefore(t,e)}))}highlightSearchWords(e){const t=new URL(location.href).searchParams.get("highlight"),n=t?t.split(" "):[];if(!n.length||!e)return;const s=document.createTreeWalker(e,NodeFilter.SHOW_TEXT,null),o=[];for(;s.nextNode();)s.currentNode.parentNode.matches("button, select, textarea, .mermaid")||o.push(s.currentNode);o.forEach((e=>{const[t]=this.getIndexByWord(n,e.nodeValue);if(!t.length)return;const s=this.mergeIntoSlice(0,e.nodeValue.length,t);this.highlightText(e,s,"search-keyword")}))}}window.addEventListener("load",(()=>{const{path:e,top_n_per_article:t,unescape:n,languages:s}=GLOBAL_CONFIG.localSearch,o=new LocalSearch({path:e,top_n_per_article:t,unescape:n}),i=document.querySelector("#local-search-input input"),r=document.getElementById("local-search-stats-wrap"),a=document.getElementById("loading-status"),c=()=>{if(!o.isfetched)return;const e=i.value.trim().toLowerCase();""!==e&&(a.innerHTML='');const t=e.split(/[-\s]+/),n=document.getElementById("local-search-results");let c=[];if(e.length>0&&(c=o.getResultItems(t)),1===t.length&&""===t[0])n.classList.add("no-result"),n.textContent="";else if(0===c.length)n.textContent="",r.innerHTML=`
${s.hits_empty.replace(/\$\{query}/,e)}
`;else{c.sort(((e,t)=>e.includedCount!==t.includedCount?t.includedCount-e.includedCount:e.hitCount!==t.hitCount?t.hitCount-e.hitCount:t.id-e.id));const e=s.hits_stats.replace(/\$\{hits}/,c.length);n.classList.remove("no-result"),n.innerHTML=`
${c.map((e=>e.item)).join("")}
`,r.innerHTML=`
${e}
`,window.pjax&&window.pjax.refresh(n)}a.textContent=""};let l=!1;const h=document.getElementById("search-mask"),d=document.querySelector("#local-search .search-dialog"),u=()=>{window.innerWidth<768&&d.style.setProperty("--search-height",window.innerHeight+"px")},g=()=>{const e=document.body.style;e.width="100%",e.overflow="hidden",btf.animateIn(h,"to_show 0.5s"),btf.animateIn(d,"titleScale 0.5s"),setTimeout((()=>{i.focus()}),300),l||(!o.isfetched&&o.fetchData(),i.addEventListener("input",c),l=!0),document.addEventListener("keydown",(function e(t){"Escape"===t.code&&(p(),document.removeEventListener("keydown",e))})),u(),window.addEventListener("resize",u)},p=()=>{const e=document.body.style;e.width="",e.overflow="",btf.animateOut(d,"search_close .5s"),btf.animateOut(h,"to_hide 0.5s"),window.removeEventListener("resize",u)},m=()=>{document.querySelector("#search-button > .search").addEventListener("click",g)};window.addEventListener("search:loaded",(()=>{const e=document.getElementById("loading-database");e.nextElementSibling.style.display="block",e.remove()})),m(),document.querySelector("#local-search .search-close-button").addEventListener("click",p),h.addEventListener("click",p),GLOBAL_CONFIG.localSearch.preload&&o.fetchData(),o.highlightSearchWords(document.getElementById("article-container")),window.addEventListener("pjax:complete",(()=>{!btf.isHidden(h)&&p(),o.highlightSearchWords(document.getElementById("article-container")),m()}))})); \ No newline at end of file diff --git a/js/tw_cn.js b/js/tw_cn.js new file mode 100644 index 000000000..8c7050f62 --- /dev/null +++ b/js/tw_cn.js @@ -0,0 +1 @@ +document.addEventListener("DOMContentLoaded",(function(){const{defaultEncoding:t,translateDelay:e,msgToTraditionalChinese:n,msgToSimplifiedChinese:a}=GLOBAL_CONFIG.translate,o=GLOBAL_CONFIG.Snackbar;let l=t;const c="translate-chn-cht";let d,i=void 0===saveToLocal.get(c)?t:Number(saveToLocal.get("translate-chn-cht"));const r=void 0!==o;function h(){document.documentElement.lang=1===i?"zh-TW":"zh-CN"}function s(t){return""===t||null==t?"":1===l&&2===i?function(t){let e="";const n="万与丑专业丛东丝丢两严丧个丬丰临为丽举么义乌乐乔习乡书买乱争于亏云亘亚产亩亲亵亸亿仅从仑仓仪们价众优伙会伛伞伟传伤伥伦伧伪伫体余佣佥侠侣侥侦侧侨侩侪侬俣俦俨俩俪俭债倾偬偻偾偿傥傧储傩儿兑兖党兰关兴兹养兽冁内冈册写军农冢冯冲决况冻净凄凉凌减凑凛几凤凫凭凯击凼凿刍划刘则刚创删别刬刭刽刿剀剂剐剑剥剧劝办务劢动励劲劳势勋勐勚匀匦匮区医华协单卖卢卤卧卫却卺厂厅历厉压厌厍厕厢厣厦厨厩厮县参叆叇双发变叙叠叶号叹叽吁后吓吕吗吣吨听启吴呒呓呕呖呗员呙呛呜咏咔咙咛咝咤咴咸哌响哑哒哓哔哕哗哙哜哝哟唛唝唠唡唢唣唤唿啧啬啭啮啰啴啸喷喽喾嗫呵嗳嘘嘤嘱噜噼嚣嚯团园囱围囵国图圆圣圹场坂坏块坚坛坜坝坞坟坠垄垅垆垒垦垧垩垫垭垯垱垲垴埘埙埚埝埯堑堕塆墙壮声壳壶壸处备复够头夸夹夺奁奂奋奖奥妆妇妈妩妪妫姗姜娄娅娆娇娈娱娲娴婳婴婵婶媪嫒嫔嫱嬷孙学孪宁宝实宠审宪宫宽宾寝对寻导寿将尔尘尧尴尸尽层屃屉届属屡屦屿岁岂岖岗岘岙岚岛岭岳岽岿峃峄峡峣峤峥峦崂崃崄崭嵘嵚嵛嵝嵴巅巩巯币帅师帏帐帘帜带帧帮帱帻帼幂幞干并广庄庆庐庑库应庙庞废庼廪开异弃张弥弪弯弹强归当录彟彦彻径徕御忆忏忧忾怀态怂怃怄怅怆怜总怼怿恋恳恶恸恹恺恻恼恽悦悫悬悭悯惊惧惨惩惫惬惭惮惯愍愠愤愦愿慑慭憷懑懒懔戆戋戏戗战戬户扎扑扦执扩扪扫扬扰抚抛抟抠抡抢护报担拟拢拣拥拦拧拨择挂挚挛挜挝挞挟挠挡挢挣挤挥挦捞损捡换捣据捻掳掴掷掸掺掼揸揽揿搀搁搂搅携摄摅摆摇摈摊撄撑撵撷撸撺擞攒敌敛数斋斓斗斩断无旧时旷旸昙昼昽显晋晒晓晔晕晖暂暧札术朴机杀杂权条来杨杩杰极构枞枢枣枥枧枨枪枫枭柜柠柽栀栅标栈栉栊栋栌栎栏树栖样栾桊桠桡桢档桤桥桦桧桨桩梦梼梾检棂椁椟椠椤椭楼榄榇榈榉槚槛槟槠横樯樱橥橱橹橼檐檩欢欤欧歼殁殇残殒殓殚殡殴毁毂毕毙毡毵氇气氢氩氲汇汉污汤汹沓沟没沣沤沥沦沧沨沩沪沵泞泪泶泷泸泺泻泼泽泾洁洒洼浃浅浆浇浈浉浊测浍济浏浐浑浒浓浔浕涂涌涛涝涞涟涠涡涢涣涤润涧涨涩淀渊渌渍渎渐渑渔渖渗温游湾湿溃溅溆溇滗滚滞滟滠满滢滤滥滦滨滩滪漤潆潇潋潍潜潴澜濑濒灏灭灯灵灾灿炀炉炖炜炝点炼炽烁烂烃烛烟烦烧烨烩烫烬热焕焖焘煅煳熘爱爷牍牦牵牺犊犟状犷犸犹狈狍狝狞独狭狮狯狰狱狲猃猎猕猡猪猫猬献獭玑玙玚玛玮环现玱玺珉珏珐珑珰珲琎琏琐琼瑶瑷璇璎瓒瓮瓯电画畅畲畴疖疗疟疠疡疬疮疯疱疴痈痉痒痖痨痪痫痴瘅瘆瘗瘘瘪瘫瘾瘿癞癣癫癯皑皱皲盏盐监盖盗盘眍眦眬着睁睐睑瞒瞩矫矶矾矿砀码砖砗砚砜砺砻砾础硁硅硕硖硗硙硚确硷碍碛碜碱碹磙礼祎祢祯祷祸禀禄禅离秃秆种积称秽秾稆税稣稳穑穷窃窍窑窜窝窥窦窭竖竞笃笋笔笕笺笼笾筑筚筛筜筝筹签简箓箦箧箨箩箪箫篑篓篮篱簖籁籴类籼粜粝粤粪粮糁糇紧絷纟纠纡红纣纤纥约级纨纩纪纫纬纭纮纯纰纱纲纳纴纵纶纷纸纹纺纻纼纽纾线绀绁绂练组绅细织终绉绊绋绌绍绎经绐绑绒结绔绕绖绗绘给绚绛络绝绞统绠绡绢绣绤绥绦继绨绩绪绫绬续绮绯绰绱绲绳维绵绶绷绸绹绺绻综绽绾绿缀缁缂缃缄缅缆缇缈缉缊缋缌缍缎缏缐缑缒缓缔缕编缗缘缙缚缛缜缝缞缟缠缡缢缣缤缥缦缧缨缩缪缫缬缭缮缯缰缱缲缳缴缵罂网罗罚罢罴羁羟羡翘翙翚耢耧耸耻聂聋职聍联聩聪肃肠肤肷肾肿胀胁胆胜胧胨胪胫胶脉脍脏脐脑脓脔脚脱脶脸腊腌腘腭腻腼腽腾膑臜舆舣舰舱舻艰艳艹艺节芈芗芜芦苁苇苈苋苌苍苎苏苘苹茎茏茑茔茕茧荆荐荙荚荛荜荞荟荠荡荣荤荥荦荧荨荩荪荫荬荭荮药莅莜莱莲莳莴莶获莸莹莺莼萚萝萤营萦萧萨葱蒇蒉蒋蒌蓝蓟蓠蓣蓥蓦蔷蔹蔺蔼蕲蕴薮藁藓虏虑虚虫虬虮虽虾虿蚀蚁蚂蚕蚝蚬蛊蛎蛏蛮蛰蛱蛲蛳蛴蜕蜗蜡蝇蝈蝉蝎蝼蝾螀螨蟏衅衔补衬衮袄袅袆袜袭袯装裆裈裢裣裤裥褛褴襁襕见观觃规觅视觇览觉觊觋觌觍觎觏觐觑觞触觯詟誉誊讠计订讣认讥讦讧讨让讪讫训议讯记讱讲讳讴讵讶讷许讹论讻讼讽设访诀证诂诃评诅识诇诈诉诊诋诌词诎诏诐译诒诓诔试诖诗诘诙诚诛诜话诞诟诠诡询诣诤该详诧诨诩诪诫诬语诮误诰诱诲诳说诵诶请诸诹诺读诼诽课诿谀谁谂调谄谅谆谇谈谊谋谌谍谎谏谐谑谒谓谔谕谖谗谘谙谚谛谜谝谞谟谠谡谢谣谤谥谦谧谨谩谪谫谬谭谮谯谰谱谲谳谴谵谶谷豮贝贞负贠贡财责贤败账货质贩贪贫贬购贮贯贰贱贲贳贴贵贶贷贸费贺贻贼贽贾贿赀赁赂赃资赅赆赇赈赉赊赋赌赍赎赏赐赑赒赓赔赕赖赗赘赙赚赛赜赝赞赟赠赡赢赣赪赵赶趋趱趸跃跄跖跞践跶跷跸跹跻踊踌踪踬踯蹑蹒蹰蹿躏躜躯车轧轨轩轪轫转轭轮软轰轱轲轳轴轵轶轷轸轹轺轻轼载轾轿辀辁辂较辄辅辆辇辈辉辊辋辌辍辎辏辐辑辒输辔辕辖辗辘辙辚辞辩辫边辽达迁过迈运还这进远违连迟迩迳迹适选逊递逦逻遗遥邓邝邬邮邹邺邻郁郄郏郐郑郓郦郧郸酝酦酱酽酾酿释里鉅鉴銮錾钆钇针钉钊钋钌钍钎钏钐钑钒钓钔钕钖钗钘钙钚钛钝钞钟钠钡钢钣钤钥钦钧钨钩钪钫钬钭钮钯钰钱钲钳钴钵钶钷钸钹钺钻钼钽钾钿铀铁铂铃铄铅铆铈铉铊铋铍铎铏铐铑铒铕铗铘铙铚铛铜铝铞铟铠铡铢铣铤铥铦铧铨铪铫铬铭铮铯铰铱铲铳铴铵银铷铸铹铺铻铼铽链铿销锁锂锃锄锅锆锇锈锉锊锋锌锍锎锏锐锑锒锓锔锕锖锗错锚锜锞锟锠锡锢锣锤锥锦锨锩锫锬锭键锯锰锱锲锳锴锵锶锷锸锹锺锻锼锽锾锿镀镁镂镃镆镇镈镉镊镌镍镎镏镐镑镒镕镖镗镙镚镛镜镝镞镟镠镡镢镣镤镥镦镧镨镩镪镫镬镭镮镯镰镱镲镳镴镶长门闩闪闫闬闭问闯闰闱闲闳间闵闶闷闸闹闺闻闼闽闾闿阀阁阂阃阄阅阆阇阈阉阊阋阌阍阎阏阐阑阒阓阔阕阖阗阘阙阚阛队阳阴阵阶际陆陇陈陉陕陧陨险随隐隶隽难雏雠雳雾霁霉霭靓静靥鞑鞒鞯鞴韦韧韨韩韪韫韬韵页顶顷顸项顺须顼顽顾顿颀颁颂颃预颅领颇颈颉颊颋颌颍颎颏颐频颒颓颔颕颖颗题颙颚颛颜额颞颟颠颡颢颣颤颥颦颧风飏飐飑飒飓飔飕飖飗飘飙飚飞飨餍饤饥饦饧饨饩饪饫饬饭饮饯饰饱饲饳饴饵饶饷饸饹饺饻饼饽饾饿馀馁馂馃馄馅馆馇馈馉馊馋馌馍馎馏馐馑馒馓馔馕马驭驮驯驰驱驲驳驴驵驶驷驸驹驺驻驼驽驾驿骀骁骂骃骄骅骆骇骈骉骊骋验骍骎骏骐骑骒骓骔骕骖骗骘骙骚骛骜骝骞骟骠骡骢骣骤骥骦骧髅髋髌鬓魇魉鱼鱽鱾鱿鲀鲁鲂鲄鲅鲆鲇鲈鲉鲊鲋鲌鲍鲎鲏鲐鲑鲒鲓鲔鲕鲖鲗鲘鲙鲚鲛鲜鲝鲞鲟鲠鲡鲢鲣鲤鲥鲦鲧鲨鲩鲪鲫鲬鲭鲮鲯鲰鲱鲲鲳鲴鲵鲶鲷鲸鲹鲺鲻鲼鲽鲾鲿鳀鳁鳂鳃鳄鳅鳆鳇鳈鳉鳊鳋鳌鳍鳎鳏鳐鳑鳒鳓鳔鳕鳖鳗鳘鳙鳛鳜鳝鳞鳟鳠鳡鳢鳣鸟鸠鸡鸢鸣鸤鸥鸦鸧鸨鸩鸪鸫鸬鸭鸮鸯鸰鸱鸲鸳鸴鸵鸶鸷鸸鸹鸺鸻鸼鸽鸾鸿鹀鹁鹂鹃鹄鹅鹆鹇鹈鹉鹊鹋鹌鹍鹎鹏鹐鹑鹒鹓鹔鹕鹖鹗鹘鹚鹛鹜鹝鹞鹟鹠鹡鹢鹣鹤鹥鹦鹧鹨鹩鹪鹫鹬鹭鹯鹰鹱鹲鹳鹴鹾麦麸黄黉黡黩黪黾龙历志制一台皋准复猛钟注范签",a="萬與醜專業叢東絲丟兩嚴喪個爿豐臨為麗舉麼義烏樂喬習鄉書買亂爭於虧雲亙亞產畝親褻嚲億僅從侖倉儀們價眾優夥會傴傘偉傳傷倀倫傖偽佇體餘傭僉俠侶僥偵側僑儈儕儂俁儔儼倆儷儉債傾傯僂僨償儻儐儲儺兒兌兗黨蘭關興茲養獸囅內岡冊寫軍農塚馮衝決況凍淨淒涼淩減湊凜幾鳳鳧憑凱擊氹鑿芻劃劉則剛創刪別剗剄劊劌剴劑剮劍剝劇勸辦務勱動勵勁勞勢勳猛勩勻匭匱區醫華協單賣盧鹵臥衛卻巹廠廳曆厲壓厭厙廁廂厴廈廚廄廝縣參靉靆雙發變敘疊葉號歎嘰籲後嚇呂嗎唚噸聽啟吳嘸囈嘔嚦唄員咼嗆嗚詠哢嚨嚀噝吒噅鹹呱響啞噠嘵嗶噦嘩噲嚌噥喲嘜嗊嘮啢嗩唕喚呼嘖嗇囀齧囉嘽嘯噴嘍嚳囁嗬噯噓嚶囑嚕劈囂謔團園囪圍圇國圖圓聖壙場阪壞塊堅壇壢壩塢墳墜壟壟壚壘墾坰堊墊埡墶壋塏堖塒塤堝墊垵塹墮壪牆壯聲殼壺壼處備複夠頭誇夾奪奩奐奮獎奧妝婦媽嫵嫗媯姍薑婁婭嬈嬌孌娛媧嫻嫿嬰嬋嬸媼嬡嬪嬙嬤孫學孿寧寶實寵審憲宮寬賓寢對尋導壽將爾塵堯尷屍盡層屭屜屆屬屢屨嶼歲豈嶇崗峴嶴嵐島嶺嶽崠巋嶨嶧峽嶢嶠崢巒嶗崍嶮嶄嶸嶔崳嶁脊巔鞏巰幣帥師幃帳簾幟帶幀幫幬幘幗冪襆幹並廣莊慶廬廡庫應廟龐廢廎廩開異棄張彌弳彎彈強歸當錄彠彥徹徑徠禦憶懺憂愾懷態慫憮慪悵愴憐總懟懌戀懇惡慟懨愷惻惱惲悅愨懸慳憫驚懼慘懲憊愜慚憚慣湣慍憤憒願懾憖怵懣懶懍戇戔戲戧戰戩戶紮撲扡執擴捫掃揚擾撫拋摶摳掄搶護報擔擬攏揀擁攔擰撥擇掛摯攣掗撾撻挾撓擋撟掙擠揮撏撈損撿換搗據撚擄摑擲撣摻摜摣攬撳攙擱摟攪攜攝攄擺搖擯攤攖撐攆擷擼攛擻攢敵斂數齋斕鬥斬斷無舊時曠暘曇晝曨顯晉曬曉曄暈暉暫曖劄術樸機殺雜權條來楊榪傑極構樅樞棗櫪梘棖槍楓梟櫃檸檉梔柵標棧櫛櫳棟櫨櫟欄樹棲樣欒棬椏橈楨檔榿橋樺檜槳樁夢檮棶檢欞槨櫝槧欏橢樓欖櫬櫚櫸檟檻檳櫧橫檣櫻櫫櫥櫓櫞簷檁歡歟歐殲歿殤殘殞殮殫殯毆毀轂畢斃氈毿氌氣氫氬氳彙漢汙湯洶遝溝沒灃漚瀝淪滄渢溈滬濔濘淚澩瀧瀘濼瀉潑澤涇潔灑窪浹淺漿澆湞溮濁測澮濟瀏滻渾滸濃潯濜塗湧濤澇淶漣潿渦溳渙滌潤澗漲澀澱淵淥漬瀆漸澠漁瀋滲溫遊灣濕潰濺漵漊潷滾滯灩灄滿瀅濾濫灤濱灘澦濫瀠瀟瀲濰潛瀦瀾瀨瀕灝滅燈靈災燦煬爐燉煒熗點煉熾爍爛烴燭煙煩燒燁燴燙燼熱煥燜燾煆糊溜愛爺牘犛牽犧犢強狀獷獁猶狽麅獮獰獨狹獅獪猙獄猻獫獵獼玀豬貓蝟獻獺璣璵瑒瑪瑋環現瑲璽瑉玨琺瓏璫琿璡璉瑣瓊瑤璦璿瓔瓚甕甌電畫暢佘疇癤療瘧癘瘍鬁瘡瘋皰屙癰痙癢瘂癆瘓癇癡癉瘮瘞瘺癟癱癮癭癩癬癲臒皚皺皸盞鹽監蓋盜盤瞘眥矓著睜睞瞼瞞矚矯磯礬礦碭碼磚硨硯碸礪礱礫礎硜矽碩硤磽磑礄確鹼礙磧磣堿镟滾禮禕禰禎禱禍稟祿禪離禿稈種積稱穢穠穭稅穌穩穡窮竊竅窯竄窩窺竇窶豎競篤筍筆筧箋籠籩築篳篩簹箏籌簽簡籙簀篋籜籮簞簫簣簍籃籬籪籟糴類秈糶糲粵糞糧糝餱緊縶糸糾紆紅紂纖紇約級紈纊紀紉緯紜紘純紕紗綱納紝縱綸紛紙紋紡紵紖紐紓線紺絏紱練組紳細織終縐絆紼絀紹繹經紿綁絨結絝繞絰絎繪給絢絳絡絕絞統綆綃絹繡綌綏絛繼綈績緒綾緓續綺緋綽緔緄繩維綿綬繃綢綯綹綣綜綻綰綠綴緇緙緗緘緬纜緹緲緝縕繢緦綞緞緶線緱縋緩締縷編緡緣縉縛縟縝縫縗縞纏縭縊縑繽縹縵縲纓縮繆繅纈繚繕繒韁繾繰繯繳纘罌網羅罰罷羆羈羥羨翹翽翬耮耬聳恥聶聾職聹聯聵聰肅腸膚膁腎腫脹脅膽勝朧腖臚脛膠脈膾髒臍腦膿臠腳脫腡臉臘醃膕齶膩靦膃騰臏臢輿艤艦艙艫艱豔艸藝節羋薌蕪蘆蓯葦藶莧萇蒼苧蘇檾蘋莖蘢蔦塋煢繭荊薦薘莢蕘蓽蕎薈薺蕩榮葷滎犖熒蕁藎蓀蔭蕒葒葤藥蒞蓧萊蓮蒔萵薟獲蕕瑩鶯蓴蘀蘿螢營縈蕭薩蔥蕆蕢蔣蔞藍薊蘺蕷鎣驀薔蘞藺藹蘄蘊藪槁蘚虜慮虛蟲虯蟣雖蝦蠆蝕蟻螞蠶蠔蜆蠱蠣蟶蠻蟄蛺蟯螄蠐蛻蝸蠟蠅蟈蟬蠍螻蠑螿蟎蠨釁銜補襯袞襖嫋褘襪襲襏裝襠褌褳襝褲襇褸襤繈襴見觀覎規覓視覘覽覺覬覡覿覥覦覯覲覷觴觸觶讋譽謄訁計訂訃認譏訐訌討讓訕訖訓議訊記訒講諱謳詎訝訥許訛論訩訟諷設訪訣證詁訶評詛識詗詐訴診詆謅詞詘詔詖譯詒誆誄試詿詩詰詼誠誅詵話誕詬詮詭詢詣諍該詳詫諢詡譸誡誣語誚誤誥誘誨誑說誦誒請諸諏諾讀諑誹課諉諛誰諗調諂諒諄誶談誼謀諶諜謊諫諧謔謁謂諤諭諼讒諮諳諺諦謎諞諝謨讜謖謝謠謗諡謙謐謹謾謫譾謬譚譖譙讕譜譎讞譴譫讖穀豶貝貞負貟貢財責賢敗賬貨質販貪貧貶購貯貫貳賤賁貰貼貴貺貸貿費賀貽賊贄賈賄貲賃賂贓資賅贐賕賑賚賒賦賭齎贖賞賜贔賙賡賠賧賴賵贅賻賺賽賾贗讚贇贈贍贏贛赬趙趕趨趲躉躍蹌蹠躒踐躂蹺蹕躚躋踴躊蹤躓躑躡蹣躕躥躪躦軀車軋軌軒軑軔轉軛輪軟轟軲軻轤軸軹軼軤軫轢軺輕軾載輊轎輈輇輅較輒輔輛輦輩輝輥輞輬輟輜輳輻輯轀輸轡轅轄輾轆轍轔辭辯辮邊遼達遷過邁運還這進遠違連遲邇逕跡適選遜遞邐邏遺遙鄧鄺鄔郵鄒鄴鄰鬱郤郟鄶鄭鄆酈鄖鄲醞醱醬釅釃釀釋裏钜鑒鑾鏨釓釔針釘釗釙釕釷釺釧釤鈒釩釣鍆釹鍚釵鈃鈣鈈鈦鈍鈔鍾鈉鋇鋼鈑鈐鑰欽鈞鎢鉤鈧鈁鈥鈄鈕鈀鈺錢鉦鉗鈷缽鈳鉕鈽鈸鉞鑽鉬鉭鉀鈿鈾鐵鉑鈴鑠鉛鉚鈰鉉鉈鉍鈹鐸鉶銬銠鉺銪鋏鋣鐃銍鐺銅鋁銱銦鎧鍘銖銑鋌銩銛鏵銓鉿銚鉻銘錚銫鉸銥鏟銃鐋銨銀銣鑄鐒鋪鋙錸鋱鏈鏗銷鎖鋰鋥鋤鍋鋯鋨鏽銼鋝鋒鋅鋶鐦鐧銳銻鋃鋟鋦錒錆鍺錯錨錡錁錕錩錫錮鑼錘錐錦鍁錈錇錟錠鍵鋸錳錙鍥鍈鍇鏘鍶鍔鍤鍬鍾鍛鎪鍠鍰鎄鍍鎂鏤鎡鏌鎮鎛鎘鑷鐫鎳鎿鎦鎬鎊鎰鎔鏢鏜鏍鏰鏞鏡鏑鏃鏇鏐鐔钁鐐鏷鑥鐓鑭鐠鑹鏹鐙鑊鐳鐶鐲鐮鐿鑔鑣鑞鑲長門閂閃閆閈閉問闖閏闈閑閎間閔閌悶閘鬧閨聞闥閩閭闓閥閣閡閫鬮閱閬闍閾閹閶鬩閿閽閻閼闡闌闃闠闊闋闔闐闒闕闞闤隊陽陰陣階際陸隴陳陘陝隉隕險隨隱隸雋難雛讎靂霧霽黴靄靚靜靨韃鞽韉韝韋韌韍韓韙韞韜韻頁頂頃頇項順須頊頑顧頓頎頒頌頏預顱領頗頸頡頰頲頜潁熲頦頤頻頮頹頷頴穎顆題顒顎顓顏額顳顢顛顙顥纇顫顬顰顴風颺颭颮颯颶颸颼颻飀飄飆飆飛饗饜飣饑飥餳飩餼飪飫飭飯飲餞飾飽飼飿飴餌饒餉餄餎餃餏餅餑餖餓餘餒餕餜餛餡館餷饋餶餿饞饁饃餺餾饈饉饅饊饌饢馬馭馱馴馳驅馹駁驢駔駛駟駙駒騶駐駝駑駕驛駘驍罵駰驕驊駱駭駢驫驪騁驗騂駸駿騏騎騍騅騌驌驂騙騭騤騷騖驁騮騫騸驃騾驄驏驟驥驦驤髏髖髕鬢魘魎魚魛魢魷魨魯魴魺鮁鮃鯰鱸鮋鮓鮒鮊鮑鱟鮍鮐鮭鮚鮳鮪鮞鮦鰂鮜鱠鱭鮫鮮鮺鯗鱘鯁鱺鰱鰹鯉鰣鰷鯀鯊鯇鮶鯽鯒鯖鯪鯕鯫鯡鯤鯧鯝鯢鯰鯛鯨鯵鯴鯔鱝鰈鰏鱨鯷鰮鰃鰓鱷鰍鰒鰉鰁鱂鯿鰠鼇鰭鰨鰥鰩鰟鰜鰳鰾鱈鱉鰻鰵鱅鰼鱖鱔鱗鱒鱯鱤鱧鱣鳥鳩雞鳶鳴鳲鷗鴉鶬鴇鴆鴣鶇鸕鴨鴞鴦鴒鴟鴝鴛鴬鴕鷥鷙鴯鴰鵂鴴鵃鴿鸞鴻鵐鵓鸝鵑鵠鵝鵒鷳鵜鵡鵲鶓鵪鶤鵯鵬鵮鶉鶊鵷鷫鶘鶡鶚鶻鶿鶥鶩鷊鷂鶲鶹鶺鷁鶼鶴鷖鸚鷓鷚鷯鷦鷲鷸鷺鸇鷹鸌鸏鸛鸘鹺麥麩黃黌黶黷黲黽龍歷誌製壹臺臯準復勐鐘註範籤";for(let o=0;o1e4&&-1!==a.indexOf(t.charAt(o))?e+=n.charAt(a.indexOf(t.charAt(o))):e+=t.charAt(o);return e}(t):2===l&&1===i?function(t){let e="";const n="万与丑专业丛东丝丢两严丧个丬丰临为丽举么义乌乐乔习乡书买乱争于亏云亘亚产亩亲亵亸亿仅从仑仓仪们价众优伙会伛伞伟传伤伥伦伧伪伫体余佣佥侠侣侥侦侧侨侩侪侬俣俦俨俩俪俭债倾偬偻偾偿傥傧储傩儿兑兖党兰关兴兹养兽冁内冈册写军农冢冯冲决况冻净凄凉凌减凑凛几凤凫凭凯击凼凿刍划刘则刚创删别刬刭刽刿剀剂剐剑剥剧劝办务劢动励劲劳势勋勐勚匀匦匮区医华协单卖卢卤卧卫却卺厂厅历厉压厌厍厕厢厣厦厨厩厮县参叆叇双发变叙叠叶号叹叽吁后吓吕吗吣吨听启吴呒呓呕呖呗员呙呛呜咏咔咙咛咝咤咴咸哌响哑哒哓哔哕哗哙哜哝哟唛唝唠唡唢唣唤唿啧啬啭啮啰啴啸喷喽喾嗫呵嗳嘘嘤嘱噜噼嚣嚯团园囱围囵国图圆圣圹场坂坏块坚坛坜坝坞坟坠垄垅垆垒垦垧垩垫垭垯垱垲垴埘埙埚埝埯堑堕塆墙壮声壳壶壸处备复够头夸夹夺奁奂奋奖奥妆妇妈妩妪妫姗姜娄娅娆娇娈娱娲娴婳婴婵婶媪嫒嫔嫱嬷孙学孪宁宝实宠审宪宫宽宾寝对寻导寿将尔尘尧尴尸尽层屃屉届属屡屦屿岁岂岖岗岘岙岚岛岭岳岽岿峃峄峡峣峤峥峦崂崃崄崭嵘嵚嵛嵝嵴巅巩巯币帅师帏帐帘帜带帧帮帱帻帼幂幞干并广庄庆庐庑库应庙庞废庼廪开异弃张弥弪弯弹强归当录彟彦彻径徕御忆忏忧忾怀态怂怃怄怅怆怜总怼怿恋恳恶恸恹恺恻恼恽悦悫悬悭悯惊惧惨惩惫惬惭惮惯愍愠愤愦愿慑慭憷懑懒懔戆戋戏戗战戬户扎扑扦执扩扪扫扬扰抚抛抟抠抡抢护报担拟拢拣拥拦拧拨择挂挚挛挜挝挞挟挠挡挢挣挤挥挦捞损捡换捣据捻掳掴掷掸掺掼揸揽揿搀搁搂搅携摄摅摆摇摈摊撄撑撵撷撸撺擞攒敌敛数斋斓斗斩断无旧时旷旸昙昼昽显晋晒晓晔晕晖暂暧札术朴机杀杂权条来杨杩杰极构枞枢枣枥枧枨枪枫枭柜柠柽栀栅标栈栉栊栋栌栎栏树栖样栾桊桠桡桢档桤桥桦桧桨桩梦梼梾检棂椁椟椠椤椭楼榄榇榈榉槚槛槟槠横樯樱橥橱橹橼檐檩欢欤欧歼殁殇残殒殓殚殡殴毁毂毕毙毡毵氇气氢氩氲汇汉污汤汹沓沟没沣沤沥沦沧沨沩沪沵泞泪泶泷泸泺泻泼泽泾洁洒洼浃浅浆浇浈浉浊测浍济浏浐浑浒浓浔浕涂涌涛涝涞涟涠涡涢涣涤润涧涨涩淀渊渌渍渎渐渑渔渖渗温游湾湿溃溅溆溇滗滚滞滟滠满滢滤滥滦滨滩滪漤潆潇潋潍潜潴澜濑濒灏灭灯灵灾灿炀炉炖炜炝点炼炽烁烂烃烛烟烦烧烨烩烫烬热焕焖焘煅煳熘爱爷牍牦牵牺犊犟状犷犸犹狈狍狝狞独狭狮狯狰狱狲猃猎猕猡猪猫猬献獭玑玙玚玛玮环现玱玺珉珏珐珑珰珲琎琏琐琼瑶瑷璇璎瓒瓮瓯电画畅畲畴疖疗疟疠疡疬疮疯疱疴痈痉痒痖痨痪痫痴瘅瘆瘗瘘瘪瘫瘾瘿癞癣癫癯皑皱皲盏盐监盖盗盘眍眦眬着睁睐睑瞒瞩矫矶矾矿砀码砖砗砚砜砺砻砾础硁硅硕硖硗硙硚确硷碍碛碜碱碹磙礼祎祢祯祷祸禀禄禅离秃秆种积称秽秾稆税稣稳穑穷窃窍窑窜窝窥窦窭竖竞笃笋笔笕笺笼笾筑筚筛筜筝筹签简箓箦箧箨箩箪箫篑篓篮篱簖籁籴类籼粜粝粤粪粮糁糇紧絷纟纠纡红纣纤纥约级纨纩纪纫纬纭纮纯纰纱纲纳纴纵纶纷纸纹纺纻纼纽纾线绀绁绂练组绅细织终绉绊绋绌绍绎经绐绑绒结绔绕绖绗绘给绚绛络绝绞统绠绡绢绣绤绥绦继绨绩绪绫绬续绮绯绰绱绲绳维绵绶绷绸绹绺绻综绽绾绿缀缁缂缃缄缅缆缇缈缉缊缋缌缍缎缏缐缑缒缓缔缕编缗缘缙缚缛缜缝缞缟缠缡缢缣缤缥缦缧缨缩缪缫缬缭缮缯缰缱缲缳缴缵罂网罗罚罢罴羁羟羡翘翙翚耢耧耸耻聂聋职聍联聩聪肃肠肤肷肾肿胀胁胆胜胧胨胪胫胶脉脍脏脐脑脓脔脚脱脶脸腊腌腘腭腻腼腽腾膑臜舆舣舰舱舻艰艳艹艺节芈芗芜芦苁苇苈苋苌苍苎苏苘苹茎茏茑茔茕茧荆荐荙荚荛荜荞荟荠荡荣荤荥荦荧荨荩荪荫荬荭荮药莅莜莱莲莳莴莶获莸莹莺莼萚萝萤营萦萧萨葱蒇蒉蒋蒌蓝蓟蓠蓣蓥蓦蔷蔹蔺蔼蕲蕴薮藁藓虏虑虚虫虬虮虽虾虿蚀蚁蚂蚕蚝蚬蛊蛎蛏蛮蛰蛱蛲蛳蛴蜕蜗蜡蝇蝈蝉蝎蝼蝾螀螨蟏衅衔补衬衮袄袅袆袜袭袯装裆裈裢裣裤裥褛褴襁襕见观觃规觅视觇览觉觊觋觌觍觎觏觐觑觞触觯詟誉誊讠计订讣认讥讦讧讨让讪讫训议讯记讱讲讳讴讵讶讷许讹论讻讼讽设访诀证诂诃评诅识诇诈诉诊诋诌词诎诏诐译诒诓诔试诖诗诘诙诚诛诜话诞诟诠诡询诣诤该详诧诨诩诪诫诬语诮误诰诱诲诳说诵诶请诸诹诺读诼诽课诿谀谁谂调谄谅谆谇谈谊谋谌谍谎谏谐谑谒谓谔谕谖谗谘谙谚谛谜谝谞谟谠谡谢谣谤谥谦谧谨谩谪谫谬谭谮谯谰谱谲谳谴谵谶谷豮贝贞负贠贡财责贤败账货质贩贪贫贬购贮贯贰贱贲贳贴贵贶贷贸费贺贻贼贽贾贿赀赁赂赃资赅赆赇赈赉赊赋赌赍赎赏赐赑赒赓赔赕赖赗赘赙赚赛赜赝赞赟赠赡赢赣赪赵赶趋趱趸跃跄跖跞践跶跷跸跹跻踊踌踪踬踯蹑蹒蹰蹿躏躜躯车轧轨轩轪轫转轭轮软轰轱轲轳轴轵轶轷轸轹轺轻轼载轾轿辀辁辂较辄辅辆辇辈辉辊辋辌辍辎辏辐辑辒输辔辕辖辗辘辙辚辞辩辫边辽达迁过迈运还这进远违连迟迩迳迹适选逊递逦逻遗遥邓邝邬邮邹邺邻郁郄郏郐郑郓郦郧郸酝酦酱酽酾酿释里鉅鉴銮錾钆钇针钉钊钋钌钍钎钏钐钑钒钓钔钕钖钗钘钙钚钛钝钞钟钠钡钢钣钤钥钦钧钨钩钪钫钬钭钮钯钰钱钲钳钴钵钶钷钸钹钺钻钼钽钾钿铀铁铂铃铄铅铆铈铉铊铋铍铎铏铐铑铒铕铗铘铙铚铛铜铝铞铟铠铡铢铣铤铥铦铧铨铪铫铬铭铮铯铰铱铲铳铴铵银铷铸铹铺铻铼铽链铿销锁锂锃锄锅锆锇锈锉锊锋锌锍锎锏锐锑锒锓锔锕锖锗错锚锜锞锟锠锡锢锣锤锥锦锨锩锫锬锭键锯锰锱锲锳锴锵锶锷锸锹锺锻锼锽锾锿镀镁镂镃镆镇镈镉镊镌镍镎镏镐镑镒镕镖镗镙镚镛镜镝镞镟镠镡镢镣镤镥镦镧镨镩镪镫镬镭镮镯镰镱镲镳镴镶长门闩闪闫闬闭问闯闰闱闲闳间闵闶闷闸闹闺闻闼闽闾闿阀阁阂阃阄阅阆阇阈阉阊阋阌阍阎阏阐阑阒阓阔阕阖阗阘阙阚阛队阳阴阵阶际陆陇陈陉陕陧陨险随隐隶隽难雏雠雳雾霁霉霭靓静靥鞑鞒鞯鞴韦韧韨韩韪韫韬韵页顶顷顸项顺须顼顽顾顿颀颁颂颃预颅领颇颈颉颊颋颌颍颎颏颐频颒颓颔颕颖颗题颙颚颛颜额颞颟颠颡颢颣颤颥颦颧风飏飐飑飒飓飔飕飖飗飘飙飚飞飨餍饤饥饦饧饨饩饪饫饬饭饮饯饰饱饲饳饴饵饶饷饸饹饺饻饼饽饾饿馀馁馂馃馄馅馆馇馈馉馊馋馌馍馎馏馐馑馒馓馔馕马驭驮驯驰驱驲驳驴驵驶驷驸驹驺驻驼驽驾驿骀骁骂骃骄骅骆骇骈骉骊骋验骍骎骏骐骑骒骓骔骕骖骗骘骙骚骛骜骝骞骟骠骡骢骣骤骥骦骧髅髋髌鬓魇魉鱼鱽鱾鱿鲀鲁鲂鲄鲅鲆鲇鲈鲉鲊鲋鲌鲍鲎鲏鲐鲑鲒鲓鲔鲕鲖鲗鲘鲙鲚鲛鲜鲝鲞鲟鲠鲡鲢鲣鲤鲥鲦鲧鲨鲩鲪鲫鲬鲭鲮鲯鲰鲱鲲鲳鲴鲵鲶鲷鲸鲹鲺鲻鲼鲽鲾鲿鳀鳁鳂鳃鳄鳅鳆鳇鳈鳉鳊鳋鳌鳍鳎鳏鳐鳑鳒鳓鳔鳕鳖鳗鳘鳙鳛鳜鳝鳞鳟鳠鳡鳢鳣鸟鸠鸡鸢鸣鸤鸥鸦鸧鸨鸩鸪鸫鸬鸭鸮鸯鸰鸱鸲鸳鸴鸵鸶鸷鸸鸹鸺鸻鸼鸽鸾鸿鹀鹁鹂鹃鹄鹅鹆鹇鹈鹉鹊鹋鹌鹍鹎鹏鹐鹑鹒鹓鹔鹕鹖鹗鹘鹚鹛鹜鹝鹞鹟鹠鹡鹢鹣鹤鹥鹦鹧鹨鹩鹪鹫鹬鹭鹯鹰鹱鹲鹳鹴鹾麦麸黄黉黡黩黪黾龙历志制一台皋准复猛钟注范签",a="萬與醜專業叢東絲丟兩嚴喪個爿豐臨為麗舉麼義烏樂喬習鄉書買亂爭於虧雲亙亞產畝親褻嚲億僅從侖倉儀們價眾優夥會傴傘偉傳傷倀倫傖偽佇體餘傭僉俠侶僥偵側僑儈儕儂俁儔儼倆儷儉債傾傯僂僨償儻儐儲儺兒兌兗黨蘭關興茲養獸囅內岡冊寫軍農塚馮衝決況凍淨淒涼淩減湊凜幾鳳鳧憑凱擊氹鑿芻劃劉則剛創刪別剗剄劊劌剴劑剮劍剝劇勸辦務勱動勵勁勞勢勳猛勩勻匭匱區醫華協單賣盧鹵臥衛卻巹廠廳曆厲壓厭厙廁廂厴廈廚廄廝縣參靉靆雙發變敘疊葉號歎嘰籲後嚇呂嗎唚噸聽啟吳嘸囈嘔嚦唄員咼嗆嗚詠哢嚨嚀噝吒噅鹹呱響啞噠嘵嗶噦嘩噲嚌噥喲嘜嗊嘮啢嗩唕喚呼嘖嗇囀齧囉嘽嘯噴嘍嚳囁嗬噯噓嚶囑嚕劈囂謔團園囪圍圇國圖圓聖壙場阪壞塊堅壇壢壩塢墳墜壟壟壚壘墾坰堊墊埡墶壋塏堖塒塤堝墊垵塹墮壪牆壯聲殼壺壼處備複夠頭誇夾奪奩奐奮獎奧妝婦媽嫵嫗媯姍薑婁婭嬈嬌孌娛媧嫻嫿嬰嬋嬸媼嬡嬪嬙嬤孫學孿寧寶實寵審憲宮寬賓寢對尋導壽將爾塵堯尷屍盡層屭屜屆屬屢屨嶼歲豈嶇崗峴嶴嵐島嶺嶽崠巋嶨嶧峽嶢嶠崢巒嶗崍嶮嶄嶸嶔崳嶁脊巔鞏巰幣帥師幃帳簾幟帶幀幫幬幘幗冪襆幹並廣莊慶廬廡庫應廟龐廢廎廩開異棄張彌弳彎彈強歸當錄彠彥徹徑徠禦憶懺憂愾懷態慫憮慪悵愴憐總懟懌戀懇惡慟懨愷惻惱惲悅愨懸慳憫驚懼慘懲憊愜慚憚慣湣慍憤憒願懾憖怵懣懶懍戇戔戲戧戰戩戶紮撲扡執擴捫掃揚擾撫拋摶摳掄搶護報擔擬攏揀擁攔擰撥擇掛摯攣掗撾撻挾撓擋撟掙擠揮撏撈損撿換搗據撚擄摑擲撣摻摜摣攬撳攙擱摟攪攜攝攄擺搖擯攤攖撐攆擷擼攛擻攢敵斂數齋斕鬥斬斷無舊時曠暘曇晝曨顯晉曬曉曄暈暉暫曖劄術樸機殺雜權條來楊榪傑極構樅樞棗櫪梘棖槍楓梟櫃檸檉梔柵標棧櫛櫳棟櫨櫟欄樹棲樣欒棬椏橈楨檔榿橋樺檜槳樁夢檮棶檢欞槨櫝槧欏橢樓欖櫬櫚櫸檟檻檳櫧橫檣櫻櫫櫥櫓櫞簷檁歡歟歐殲歿殤殘殞殮殫殯毆毀轂畢斃氈毿氌氣氫氬氳彙漢汙湯洶遝溝沒灃漚瀝淪滄渢溈滬濔濘淚澩瀧瀘濼瀉潑澤涇潔灑窪浹淺漿澆湞溮濁測澮濟瀏滻渾滸濃潯濜塗湧濤澇淶漣潿渦溳渙滌潤澗漲澀澱淵淥漬瀆漸澠漁瀋滲溫遊灣濕潰濺漵漊潷滾滯灩灄滿瀅濾濫灤濱灘澦濫瀠瀟瀲濰潛瀦瀾瀨瀕灝滅燈靈災燦煬爐燉煒熗點煉熾爍爛烴燭煙煩燒燁燴燙燼熱煥燜燾煆糊溜愛爺牘犛牽犧犢強狀獷獁猶狽麅獮獰獨狹獅獪猙獄猻獫獵獼玀豬貓蝟獻獺璣璵瑒瑪瑋環現瑲璽瑉玨琺瓏璫琿璡璉瑣瓊瑤璦璿瓔瓚甕甌電畫暢佘疇癤療瘧癘瘍鬁瘡瘋皰屙癰痙癢瘂癆瘓癇癡癉瘮瘞瘺癟癱癮癭癩癬癲臒皚皺皸盞鹽監蓋盜盤瞘眥矓著睜睞瞼瞞矚矯磯礬礦碭碼磚硨硯碸礪礱礫礎硜矽碩硤磽磑礄確鹼礙磧磣堿镟滾禮禕禰禎禱禍稟祿禪離禿稈種積稱穢穠穭稅穌穩穡窮竊竅窯竄窩窺竇窶豎競篤筍筆筧箋籠籩築篳篩簹箏籌簽簡籙簀篋籜籮簞簫簣簍籃籬籪籟糴類秈糶糲粵糞糧糝餱緊縶糸糾紆紅紂纖紇約級紈纊紀紉緯紜紘純紕紗綱納紝縱綸紛紙紋紡紵紖紐紓線紺絏紱練組紳細織終縐絆紼絀紹繹經紿綁絨結絝繞絰絎繪給絢絳絡絕絞統綆綃絹繡綌綏絛繼綈績緒綾緓續綺緋綽緔緄繩維綿綬繃綢綯綹綣綜綻綰綠綴緇緙緗緘緬纜緹緲緝縕繢緦綞緞緶線緱縋緩締縷編緡緣縉縛縟縝縫縗縞纏縭縊縑繽縹縵縲纓縮繆繅纈繚繕繒韁繾繰繯繳纘罌網羅罰罷羆羈羥羨翹翽翬耮耬聳恥聶聾職聹聯聵聰肅腸膚膁腎腫脹脅膽勝朧腖臚脛膠脈膾髒臍腦膿臠腳脫腡臉臘醃膕齶膩靦膃騰臏臢輿艤艦艙艫艱豔艸藝節羋薌蕪蘆蓯葦藶莧萇蒼苧蘇檾蘋莖蘢蔦塋煢繭荊薦薘莢蕘蓽蕎薈薺蕩榮葷滎犖熒蕁藎蓀蔭蕒葒葤藥蒞蓧萊蓮蒔萵薟獲蕕瑩鶯蓴蘀蘿螢營縈蕭薩蔥蕆蕢蔣蔞藍薊蘺蕷鎣驀薔蘞藺藹蘄蘊藪槁蘚虜慮虛蟲虯蟣雖蝦蠆蝕蟻螞蠶蠔蜆蠱蠣蟶蠻蟄蛺蟯螄蠐蛻蝸蠟蠅蟈蟬蠍螻蠑螿蟎蠨釁銜補襯袞襖嫋褘襪襲襏裝襠褌褳襝褲襇褸襤繈襴見觀覎規覓視覘覽覺覬覡覿覥覦覯覲覷觴觸觶讋譽謄訁計訂訃認譏訐訌討讓訕訖訓議訊記訒講諱謳詎訝訥許訛論訩訟諷設訪訣證詁訶評詛識詗詐訴診詆謅詞詘詔詖譯詒誆誄試詿詩詰詼誠誅詵話誕詬詮詭詢詣諍該詳詫諢詡譸誡誣語誚誤誥誘誨誑說誦誒請諸諏諾讀諑誹課諉諛誰諗調諂諒諄誶談誼謀諶諜謊諫諧謔謁謂諤諭諼讒諮諳諺諦謎諞諝謨讜謖謝謠謗諡謙謐謹謾謫譾謬譚譖譙讕譜譎讞譴譫讖穀豶貝貞負貟貢財責賢敗賬貨質販貪貧貶購貯貫貳賤賁貰貼貴貺貸貿費賀貽賊贄賈賄貲賃賂贓資賅贐賕賑賚賒賦賭齎贖賞賜贔賙賡賠賧賴賵贅賻賺賽賾贗讚贇贈贍贏贛赬趙趕趨趲躉躍蹌蹠躒踐躂蹺蹕躚躋踴躊蹤躓躑躡蹣躕躥躪躦軀車軋軌軒軑軔轉軛輪軟轟軲軻轤軸軹軼軤軫轢軺輕軾載輊轎輈輇輅較輒輔輛輦輩輝輥輞輬輟輜輳輻輯轀輸轡轅轄輾轆轍轔辭辯辮邊遼達遷過邁運還這進遠違連遲邇逕跡適選遜遞邐邏遺遙鄧鄺鄔郵鄒鄴鄰鬱郤郟鄶鄭鄆酈鄖鄲醞醱醬釅釃釀釋裏钜鑒鑾鏨釓釔針釘釗釙釕釷釺釧釤鈒釩釣鍆釹鍚釵鈃鈣鈈鈦鈍鈔鍾鈉鋇鋼鈑鈐鑰欽鈞鎢鉤鈧鈁鈥鈄鈕鈀鈺錢鉦鉗鈷缽鈳鉕鈽鈸鉞鑽鉬鉭鉀鈿鈾鐵鉑鈴鑠鉛鉚鈰鉉鉈鉍鈹鐸鉶銬銠鉺銪鋏鋣鐃銍鐺銅鋁銱銦鎧鍘銖銑鋌銩銛鏵銓鉿銚鉻銘錚銫鉸銥鏟銃鐋銨銀銣鑄鐒鋪鋙錸鋱鏈鏗銷鎖鋰鋥鋤鍋鋯鋨鏽銼鋝鋒鋅鋶鐦鐧銳銻鋃鋟鋦錒錆鍺錯錨錡錁錕錩錫錮鑼錘錐錦鍁錈錇錟錠鍵鋸錳錙鍥鍈鍇鏘鍶鍔鍤鍬鍾鍛鎪鍠鍰鎄鍍鎂鏤鎡鏌鎮鎛鎘鑷鐫鎳鎿鎦鎬鎊鎰鎔鏢鏜鏍鏰鏞鏡鏑鏃鏇鏐鐔钁鐐鏷鑥鐓鑭鐠鑹鏹鐙鑊鐳鐶鐲鐮鐿鑔鑣鑞鑲長門閂閃閆閈閉問闖閏闈閑閎間閔閌悶閘鬧閨聞闥閩閭闓閥閣閡閫鬮閱閬闍閾閹閶鬩閿閽閻閼闡闌闃闠闊闋闔闐闒闕闞闤隊陽陰陣階際陸隴陳陘陝隉隕險隨隱隸雋難雛讎靂霧霽黴靄靚靜靨韃鞽韉韝韋韌韍韓韙韞韜韻頁頂頃頇項順須頊頑顧頓頎頒頌頏預顱領頗頸頡頰頲頜潁熲頦頤頻頮頹頷頴穎顆題顒顎顓顏額顳顢顛顙顥纇顫顬顰顴風颺颭颮颯颶颸颼颻飀飄飆飆飛饗饜飣饑飥餳飩餼飪飫飭飯飲餞飾飽飼飿飴餌饒餉餄餎餃餏餅餑餖餓餘餒餕餜餛餡館餷饋餶餿饞饁饃餺餾饈饉饅饊饌饢馬馭馱馴馳驅馹駁驢駔駛駟駙駒騶駐駝駑駕驛駘驍罵駰驕驊駱駭駢驫驪騁驗騂駸駿騏騎騍騅騌驌驂騙騭騤騷騖驁騮騫騸驃騾驄驏驟驥驦驤髏髖髕鬢魘魎魚魛魢魷魨魯魴魺鮁鮃鯰鱸鮋鮓鮒鮊鮑鱟鮍鮐鮭鮚鮳鮪鮞鮦鰂鮜鱠鱭鮫鮮鮺鯗鱘鯁鱺鰱鰹鯉鰣鰷鯀鯊鯇鮶鯽鯒鯖鯪鯕鯫鯡鯤鯧鯝鯢鯰鯛鯨鯵鯴鯔鱝鰈鰏鱨鯷鰮鰃鰓鱷鰍鰒鰉鰁鱂鯿鰠鼇鰭鰨鰥鰩鰟鰜鰳鰾鱈鱉鰻鰵鱅鰼鱖鱔鱗鱒鱯鱤鱧鱣鳥鳩雞鳶鳴鳲鷗鴉鶬鴇鴆鴣鶇鸕鴨鴞鴦鴒鴟鴝鴛鴬鴕鷥鷙鴯鴰鵂鴴鵃鴿鸞鴻鵐鵓鸝鵑鵠鵝鵒鷳鵜鵡鵲鶓鵪鶤鵯鵬鵮鶉鶊鵷鷫鶘鶡鶚鶻鶿鶥鶩鷊鷂鶲鶹鶺鷁鶼鶴鷖鸚鷓鷚鷯鷦鷲鷸鷺鸇鷹鸌鸏鸛鸘鹺麥麩黃黌黶黷黲黽龍歷誌製壹臺臯準復勐鐘註範籤";for(let o=0;o1e4&&-1!==n.indexOf(t.charAt(o))?e+=a.charAt(n.indexOf(t.charAt(o))):e+=t.charAt(o);return e}(t):t}function u(t){let e;e="object"==typeof t?t.childNodes:document.body.childNodes;for(let t=0;t0||n===d||(""!==n.title&&null!=n.title&&(n.title=s(n.title)),""!==n.alt&&null!=n.alt&&(n.alt=s(n.alt)),""!==n.placeholder&&null!=n.placeholder&&(n.placeholder=s(n.placeholder)),"INPUT"===n.tagName&&""!==n.value&&"text"!==n.type&&"hidden"!==n.type&&(n.value=s(n.value)),3===n.nodeType?n.data=s(n.data):u(n))}}function f(){1===i?(l=1,i=2,d.textContent=n,r&&btf.snackbarShow(o.cht_to_chs)):2===i&&(l=2,i=1,d.textContent=a,r&&btf.snackbarShow(o.chs_to_cht)),saveToLocal.set(c,i,2),h(),u()}function m(){d=document.getElementById("translateLink"),d&&(l!==i&&(d.textContent=1===i?a:n,h(),setTimeout(u,e)),d.addEventListener("click",f,!1))}m(),document.addEventListener("pjax:complete",m)})); \ No newline at end of file diff --git a/js/utils.js b/js/utils.js new file mode 100644 index 000000000..1a4b6f524 --- /dev/null +++ b/js/utils.js @@ -0,0 +1 @@ +const btf={debounce:function(t,e,n){let o;return function(){const i=this,a=arguments,r=n&&!o;clearTimeout(o),o=setTimeout((function(){o=null,n||t.apply(i,a)}),e),r&&t.apply(i,a)}},throttle:function(t,e,n){let o,i,a,r=0;n||(n={});const s=function(){r=!1===n.leading?0:(new Date).getTime(),o=null,t.apply(i,a),o||(i=a=null)};return function(){const l=(new Date).getTime();r||!1!==n.leading||(r=l);const c=e-(l-r);i=this,a=arguments,c<=0||c>e?(o&&(clearTimeout(o),o=null),r=l,t.apply(i,a),o||(i=a=null)):o||!1===n.trailing||(o=setTimeout(s,c))}},sidebarPaddingR:()=>{const t=window.innerWidth,e=document.body.clientWidth,n=t-e;t!==e&&(document.body.style.paddingRight=n+"px")},snackbarShow:(t,e=!1,n=2e3)=>{const{position:o,bgLight:i,bgDark:a}=GLOBAL_CONFIG.Snackbar,r="light"===document.documentElement.getAttribute("data-theme")?i:a;Snackbar.show({text:t,backgroundColor:r,showAction:e,duration:n,pos:o,customClass:"snackbar-css"})},diffDate:(t,e=!1)=>{const n=new Date,o=new Date(t),i=n.getTime()-o.getTime(),a=36e5,r=24*a,{dateSuffix:s}=GLOBAL_CONFIG;if(!e)return parseInt(i/r);const l=i/2592e6,c=i/r,d=i/a,u=i/6e4;return l>12?o.toISOString().slice(0,10):l>=1?`${parseInt(l)} ${s.month}`:c>=1?`${parseInt(c)} ${s.day}`:d>=1?`${parseInt(d)} ${s.hour}`:u>=1?`${parseInt(u)} ${s.min}`:s.just},loadComment:(t,e)=>{if("IntersectionObserver"in window){const n=new IntersectionObserver((t=>{t[0].isIntersecting&&(e(),n.disconnect())}),{threshold:[0]});n.observe(t)}else e()},scrollToDest:(t,e=500)=>{const n=window.pageYOffset,o=document.getElementById("page-header").classList.contains("fixed");if((n>t||o)&&(t-=70),"scrollBehavior"in document.documentElement.style)return void window.scrollTo({top:t,behavior:"smooth"});let i=null;t=+t,window.requestAnimationFrame((function o(a){i=i||a;const r=a-i;n{t.style.display="block",t.style.animation=e},animateOut:(t,e)=>{t.addEventListener("animationend",(function e(){t.style.display="",t.style.animation="",t.removeEventListener("animationend",e)})),t.style.animation=e},getParents:(t,e)=>{for(;t&&t!==document;t=t.parentNode)if(t.matches(e))return t;return null},siblings:(t,e)=>[...t.parentNode.children].filter((n=>e?n!==t&&n.matches(e):n!==t)),wrap:(t,e,n)=>{const o=document.createElement(e);for(const[t,e]of Object.entries(n))o.setAttribute(t,e);t.parentNode.insertBefore(o,t),o.appendChild(t)},unwrap:t=>{const e=t.parentNode;e&&e!==document.body&&e.replaceChild(t,e)},isHidden:t=>0===t.offsetHeight&&0===t.offsetWidth,getEleTop:t=>{let e=t.offsetTop,n=t.offsetParent;for(;null!==n;)e+=n.offsetTop,n=n.offsetParent;return e},loadLightbox:t=>{const e=GLOBAL_CONFIG.lightbox;"mediumZoom"===e&&mediumZoom(t,{background:"var(--zoom-bg)"}),"fancybox"===e&&(t.forEach((t=>{if("A"!==t.parentNode.tagName){const e=t.dataset.lazySrc||t.src,n=t.title||t.alt||"";btf.wrap(t,"a",{href:e,"data-fancybox":"gallery","data-caption":n,"data-thumb":e})}})),window.fancyboxRun||(Fancybox.bind("[data-fancybox]",{Hash:!1,Thumbs:{showOnStart:!1},Images:{Panzoom:{maxScale:4}},Carousel:{transition:"slide"},Toolbar:{display:{left:["infobar"],middle:["zoomIn","zoomOut","toggle1to1","rotateCCW","rotateCW","flipX","flipY"],right:["slideshow","thumbs","close"]}}}),window.fancyboxRun=!0))},initJustifiedGallery:function(t){const e=t=>{btf.isHidden(t)||fjGallery(t,{itemSelector:".fj-gallery-item",rowHeight:t.getAttribute("data-rowHeight"),gutter:4,onJustify:function(){this.$container.style.opacity="1"}})};0===Array.from(t).length?e(t):t.forEach((t=>{e(t)}))},updateAnchor:t=>{if(t!==window.location.hash){t||(t=location.pathname);const e=GLOBAL_CONFIG_SITE.title;window.history.replaceState({url:location.href,title:e},e,t)}},getScrollPercent:(t,e)=>{const n=e.clientHeight,o=document.documentElement.clientHeight,i=(t-e.offsetTop)/(n>o?n-o:document.documentElement.scrollHeight-o),a=Math.round(100*i);return a>100?100:a<=0?0:a},addModeChange:(t,e)=>{window.themeChange&&window.themeChange[t]||(window.themeChange={...window.themeChange,[t]:e})}}; \ No newline at end of file diff --git a/link/index.html b/link/index.html new file mode 100644 index 000000000..0534a5715 --- /dev/null +++ b/link/index.html @@ -0,0 +1,583 @@ +link | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + +

评论
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/music/index.html b/music/index.html new file mode 100644 index 000000000..6ba665035 --- /dev/null +++ b/music/index.html @@ -0,0 +1,598 @@ +My Music List | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + +

Witch On The Holy Night🎉

+
+

My Favorite Music List❤️

+
+


+ +

评论
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/page/2/index.html b/page/2/index.html new file mode 100644 index 000000000..c3a45c03f --- /dev/null +++ b/page/2/index.html @@ -0,0 +1,721 @@ +希亚的西红柿のBlog - 分享学习与生活 + + + + + + + + + + + + + + + +
JS拾遗笔记
JS事件循环
深入了解Promise
Git 笔记
Vite基础知识总结
REGEX in JavaScript
Windows/Ubuntu双系统安装教程
JAVA学习笔记
栈与队列
线性表
Win10/11任务栏透明美化
数据结构绪论
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/page/3/index.html b/page/3/index.html new file mode 100644 index 000000000..4511195e9 --- /dev/null +++ b/page/3/index.html @@ -0,0 +1,661 @@ +希亚的西红柿のBlog - 分享学习与生活 + + + + + + + + + + + + + + + +
机器学习基本概念与知识
使用numpy实现k-means聚类算法
使用Numpy实现k-Nearest-Neighbor算法
Python实现决策树(Decision Tree)算法
Numpy实现逻辑回归(Logistic Regression)算法
线性回归 (Linear Regression)
关于cloudflare对网站搭建的使用
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/posts/170bc017/index.html b/posts/170bc017/index.html new file mode 100644 index 000000000..024cd3732 --- /dev/null +++ b/posts/170bc017/index.html @@ -0,0 +1,753 @@ +使用numpy实现k-means聚类算法 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + + + +

使用numpy实现k-means聚类算法

k-means 算法基础原理

+

本文注重对 k-means 算法的实现作出分析,基础原理不作赘述。

+
+

k-means 算法的特点

1. k-means 算法的基本概念:

+
    +
  • k 代表分为几个簇
  • +
  • means 表示寻求新质心点的时候采用求均值的方法
  • +
+

2. k-means 算法的特点:

+
    +
  • k-means 算法与 knn 算法类似,都是需要“聚类”
  • +
  • k-means 算法是无监督学习算法的一种
  • +
+

k-means 算法的实现步骤:

    +
  1. 随机选取 K 个对象,并以它们为质心;
  2. +
  3. 计算数据集样本点到质心的距离;
  4. +
  5. 根据样本点距离质心的距离将其分簇(类),距离哪个近,划分到哪个簇(类);
  6. +
  7. 以簇内所有样本点的均值重新计算质心,,然后重复第二步,直到划分的簇(类)不在变化后停止。
  8. +
+

k-means 算法代码实现

模块的导入与 K 均值聚类算法类的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import random
+
+import numpy as np
+from matplotlib import pyplot as plt
+
+
+class KMeans:
+    """
+    K-means clustering(K均值聚类)
+    """
+
+    def __init__(self, n_clusters: int, iterations=100, eps=1e-3):
+        """
+        Args:
+            n_clusters (int): 聚类类别数.
+            iterations (int, optional): 迭代次数, 默认为100.
+            eps (float, optional): 中心点最小更新量, 默认为1e-3.
+        """
+        self.n_clusters, self.iterations, self.eps, self.centers = n_clusters, iterations, eps, None
+
+    def fit(self, X: np.ndarray):
+        """
+        Args:
+            X (np.ndarray): 输入
+        """
+        # 随机选择k个点作为中心点
+        self.centers = X[random.sample(range(len(X)), self.n_clusters)] # range(len(X))返回一个迭代对象 random.sample截取一段数字返回一个列表
+
+        for _ in range(self.iterations):
+            y_pred = self(X)
+
+            # 各类别的均值作为新的中心点,
+            centers = np.stack(
+                [
+                    # 存在元素属于类别i则计算类别i所有点的均值,否则随机选择一个点作为类别i的均值
+                    np.mean(X[y_pred == i], axis=0) if np.any(y_pred == i) else random.choice(X) for i in range(self.n_clusters)  # np.any()用于判读真假,
+                ]
+            )
+
+            # 中心点最大更新值小于eps则停止迭代
+            if np.abs(self.centers - centers).max() < self.eps:
+                break
+
+            # 将更新后的均值作为各类别中心点
+            self.centers = centers
+
+    def __call__(self, X: np.ndarray):
+        return np.array([np.argmin(np.linalg.norm(self.centers - x, axis=1)) for x in X])  # 每一点类别为最近的中心点类别,返回下标
+

随机生成测试集

1
2
3
4
def load_data(n_samples_per_class=200, n_classes=5):
+    X = np.concatenate([np.random.randn(n_samples_per_class, 2) + 3 * np.random.randn(2) for _ in range(n_classes)])
+    y = np.concatenate([np.full(n_samples_per_class, label) for label in range(n_classes)])
+    return X, y
+

画出图像,实现聚类算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
if __name__ == "__main__":
+    n_classes = 5
+    X, y = load_data(n_classes=n_classes)
+
+    plt.figure(figsize=[12, 6])
+    plt.subplot(1, 2, 1)
+    plt.title("Ground Truth")
+    for label in range(n_classes):
+        plt.scatter(X[y == label, 0], X[y == label, 1], marker=".")
+
+    kmeans = KMeans(n_clusters=n_classes)
+    kmeans.fit(X)
+    y_pred = kmeans(X)
+
+    plt.subplot(1, 2, 2)
+    plt.title("Clustering")
+    for label in range(n_classes):
+        plt.scatter(X[y_pred == label, 0], X[y_pred == label, 1], marker=".")
+
+    plt.scatter(kmeans.centers[:, 0], kmeans.centers[:, 1], marker="*")
+
+    plt.show()
+

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#!/user/bin/env python3
+# -*- coding: utf-8 -*-
+
+import random
+
+import numpy as np
+from matplotlib import pyplot as plt
+
+
+class KMeans:
+    """
+    K-means clustering(K均值聚类)
+    """
+
+    def __init__(self, n_clusters: int, iterations=100, eps=1e-3):
+        """
+        Args:
+            n_clusters (int): 聚类类别数.
+            iterations (int, optional): 迭代次数, 默认为100.
+            eps (float, optional): 中心点最小更新量, 默认为1e-3.
+        """
+        self.n_clusters, self.iterations, self.eps, self.centers = n_clusters, iterations, eps, None
+
+    def fit(self, X: np.ndarray):
+        """
+        Args:
+            X (np.ndarray): 输入
+        """
+        # 随机选择k个点作为中心点
+        self.centers = X[random.sample(range(len(X)), self.n_clusters)] # range(len(X))返回一个迭代对象 random.sample截取一段数字返回一个列表
+
+        for _ in range(self.iterations):
+            y_pred = self(X)
+
+            # 各类别的均值作为新的中心点,
+            centers = np.stack(
+                [
+                    # 存在元素属于类别i则计算类别i所有点的均值,否则随机选择一个点作为类别i的均值
+                    np.mean(X[y_pred == i], axis=0) if np.any(y_pred == i) else random.choice(X) for i in range(self.n_clusters)  # np.any()用于判读真假,
+                ]
+            )
+
+            # 中心点最大更新值小于eps则停止迭代
+            if np.abs(self.centers - centers).max() < self.eps:
+                break
+
+            # 将更新后的均值作为各类别中心点
+            self.centers = centers
+
+    def __call__(self, X: np.ndarray):
+        return np.array([np.argmin(np.linalg.norm(self.centers - x, axis=1)) for x in X])  # 每一点类别为最近的中心点类别,返回下标
+
+
+def load_data(n_samples_per_class=200, n_classes=5):
+    X = np.concatenate([np.random.randn(n_samples_per_class, 2) + 3 * np.random.randn(2) for _ in range(n_classes)])
+    y = np.concatenate([np.full(n_samples_per_class, label) for label in range(n_classes)])
+    return X, y
+
+
+if __name__ == "__main__":
+    n_classes = 5
+    X, y = load_data(n_classes=n_classes)
+
+    plt.figure(figsize=[12, 6])
+    plt.subplot(1, 2, 1)
+    plt.title("Ground Truth")
+    for label in range(n_classes):
+        plt.scatter(X[y == label, 0], X[y == label, 1], marker=".")
+
+    kmeans = KMeans(n_clusters=n_classes)
+    kmeans.fit(X)
+    y_pred = kmeans(X)
+
+    plt.subplot(1, 2, 2)
+    plt.title("Clustering")
+    for label in range(n_classes):
+        plt.scatter(X[y_pred == label, 0], X[y_pred == label, 1], marker=".")
+
+    plt.scatter(kmeans.centers[:, 0], kmeans.centers[:, 1], marker="*")
+
+    plt.show()
+
+

源码参考自 https://github.com/luokn/ml/blob/master/src/kmeans.py

+
+
文章作者: 希亚的西红柿
文章链接: http://blog.refrain.site/posts/170bc017/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
+ + + + \ No newline at end of file diff --git a/posts/1afd9955/index.html b/posts/1afd9955/index.html new file mode 100644 index 000000000..2d6b3d092 --- /dev/null +++ b/posts/1afd9955/index.html @@ -0,0 +1,1455 @@ +TypeSript笔记 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + + + +

TypeSript笔记

之前 TS 笔记由于放在长文章后面阅读体验不太好,故单独抽离出来一篇文章。

+
+

本文主要记录了 TS 的学习知识与笔记。

+
+

基本类型

TS 有以下几种数据类型:

+
    +
  • 基础类型:包括boolean(布尔值)、number(数字)、string(字符串)、null(空值)、undefined(未定义值)、bigint(大整型)和symbol(符号)等。这些类型和 JavaScript 的基本类型基本一致,只是 TS 在编译时会检查变量的类型是否匹配。例如:

    +
    1
    2
    3
    4
    5
    6
    7
    let isDone: boolean = false; // 声明一个布尔类型的变量
    +let age: number = 18; // 声明一个数字类型的变量
    +let name: string = "Alice"; // 声明一个字符串类型的变量
    +let x: null = null; // 声明一个空值类型的变量
    +let y: undefined = undefined; // 声明一个未定义值类型的变量
    +let a: bigint = 2172141653n; // 定义一个大整型变量
    +let z: symbol = Symbol("key"); // 声明一个符号类型的变量
    +
  • +
  • 数组类型:用来表示一组相同类型的数据。TS 有两种方式可以定义数组类型,一种是在元素类型后面加上[],另一种是使用泛型Array<元素类型>。例如:

    +
    1
    2
    let arr1: number[] = [1, 2, 3]; // 声明一个数字类型的数组
    +let arr2: Array<number> = [4, 5, 6]; // 声明一个数字类型的数组,使用泛型
    +
  • +
  • 元组类型:用来表示一个已知元素数量和类型的数组,各元素的类型不必相同,但是对应位置的类型需要相同。例如:

    +
    1
    2
    3
    let tuple: [string, number]; // 声明一个元组类型的变量
    +tuple = ["Bob", 20]; // 赋值正确,字符串和数字类型分别对应
    +tuple = [20, "Bob"]; // 赋值错误,类型不匹配
    +
  • +
  • 枚举类型:用来定义一组有名字的常数,可以方便地访问和使用。TS 支持数字枚举和字符串枚举,还可以使用const关键字定义常量枚举,以提高性能。例如:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    enum Color {
    +  Red,
    +  Green,
    +  Blue,
    +} // 声明一个数字枚举类型
    +let c: Color = Color.Blue; // 赋值正确,c的值为2
    +console.log(c); // 输出2
    +
    +enum Direction {
    +  Up = "UP",
    +  Down = "DOWN",
    +  Left = "LEFT",
    +  Right = "RIGHT",
    +} // 声明一个字符串枚举类型
    +let d: Direction = Direction.Left; // 赋值正确,d的值为'LEFT'
    +console.log(d); // 输出'LEFT'
    +
    +const enum Month {
    +  Jan,
    +  Feb,
    +  Mar,
    +} // 声明一个常量枚举类型
    +let m: Month = Month.Feb; // 赋值正确,m的值为1
    +console.log(m); // 输出1
    +
  • +
  • any 类型:用来表示任意类型的数据,可以赋值给任何类型的变量,也可以接受任何类型的赋值。这样可以避免类型检查的错误,但是也会失去类型检查的好处。例如:

    +
    1
    2
    3
    let a: any = 1; // 声明一个任意类型的变量
    +a = "hello"; // 赋值正确,可以赋值为字符串类型
    +a = true; // 赋值正确,可以赋值为布尔类型
    +
  • +
  • void 类型:用来表示没有任何类型,一般用于标识函数的返回值类型,表示该函数没有返回值。例如:

    +
    1
    2
    3
    4
    function sayHello(): void {
    +  // 声明一个返回值为void类型的函数
    +  console.log("Hello");
    +}
    +
  • +
  • never 类型:用来表示永远不会出现的值的类型,例如抛出异常或无限循环的函数的返回值类型。例如:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    function error(message: string): never {
    +  // 声明一个返回值为never类型的函数
    +  throw new Error(message); // 抛出异常
    +}
    +
    +function loop(): never {
    +  // 声明一个返回值为never类型的函数
    +  while (true) {} // 无限循环
    +}
    +
  • +
  • unknown 类型unknown类型和any类型有些相似,但是更加安全,因为它不允许对未经类型检查的值进行任何操作,除非使用类型断言或类型收缩来缩小范围。例如:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    let u: unknown = "Hello"; // 声明一个unknown类型的变量
    +u = 10; // 赋值正确,可以赋值为任何类型
    +console.log(u + 1); // 错误,不能对unknown类型进行运算
    +console.log((u as number) + 1); // 正确,使用类型断言缩小范围
    +if (typeof u === "number") {
    +  // 正确,使用类型收缩缩小范围
    +  console.log(u + 1);
    +}
    +
  • +
+
查看 never 类型与 void 类型的区别 +
+

TS 中的 never 类型和 void 类型是两种特殊的类型,它们的用法和含义有一些区别:

  • never 类型:用来表示永远不会出现的值的类型,例如抛出异常或无限循环的函数的返回值类型。never类型是任何类型的子类型,也就是说never类型的值可以赋值给任何类型的变量,但是没有类型的值可以赋值给never类型的变量(除了never本身)。这意味着never类型可以用来进行详尽的类型检查,避免出现不可能的情况。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    function error(message: string): never {
    +  // 声明一个返回值为never类型的函数
    +  throw new Error(message); // 抛出异常
    +}
    +
    +function loop(): never {
    +  // 声明一个返回值为never类型的函数
    +  while (true) {} // 无限循环
    +}
    +
    +function check(x: string | number) {
    +  switch (typeof x) {
    +    case "string":
    +      // do something
    +      break;
    +    case "number":
    +      // do something
    +      break;
    +    default:
    +      const never: never = x; // 错误,x的类型不可能是never
    +    // do something
    +  }
    +}
  • void 类型:用来表示没有任何类型,一般用于标识函数的返回值类型,表示该函数没有返回值。void 类型的变量只能赋值为undefinednull(在严格模式下,只能赋值为undefined)。void类型的作用是避免不小心使用了空指针导致的错误,和 C 语言中的void是类似的。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function sayHello(): void {
    +  // 声明一个返回值为void类型的函数
    +  console.log("Hello");
    +}
    +
    +let x: void = undefined; // 声明一个void类型的变量
    +x = null; // 赋值正确,如果不是严格模式
    +x = 1; // 错误,不能赋值为其他类型
    +console.log(x); // 输出undefined或null
+
+
+
查看 any 类型与 unknown 类型的区别 +
+

TSany类型与unknown类型是两种特殊的类型,它们都可以接受任何类型的值,但它们之间有一些重要的区别。下面我将从以下几个方面来讲解这两种类型的特点和用法,以及它们的异同:

  • 定义和赋值any类型是TS中最宽泛的类型,它表示任意类型的值,可以赋值给任何类型的变量,也可以接受任何类型的值。unknown类型是 TS 3.0 中引入的一种新的类型,它表示未知类型的值,也可以赋值给任何类型的变量,也可以接受任何类型的值。例如:

    1
    2
    3
    4
    5
    6
    let a: any; // 定义一个any类型的变量a
    +let b: unknown; // 定义一个unknown类型的变量b
    +a = 1; // 可以给a赋值为数字
    +a = "hello"; // 可以给a赋值为字符串
    +b = 2; // 可以给b赋值为数字
    +b = "world"; // 可以给b赋值为字符串
  • 操作和访问any类型的变量可以进行任何操作和访问,不会有类型检查的错误,但这也会导致一些潜在的问题,比如访问不存在的属性或方法,或者调用不合法的参数。unknown 类型的变量则不能进行任何操作和访问,除非进行类型断言或类型收缩,否则会有类型检查的错误,这样可以保证类型的安全性。例如:

    1
    2
    3
    4
    5
    6
    let a: any;
    +let b: unknown;
    +a.foo(); // 可以调用任意的方法,不会报错,但可能运行时出错
    +a + 1; // 可以进行任意的运算,不会报错,但可能得到意外的结果
    +b.foo(); // 不能调用任意的方法,会报错:Object is of type 'unknown'
    +b + 1; // 不能进行任意的运算,会报错:Object is of type 'unknown'
  • 赋值给其他类型any类型的变量可以赋值给任何类型的变量,不会有类型检查的错误,但这也会导致一些潜在的问题,比如赋值给不兼容的类型,或者覆盖了原有的类型信息。unknown类型的变量则只能赋值给any类型或unknown类型的变量,否则会有类型检查的错误,这样可以保证类型的一致性。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    let a: any;
    +let b: unknown;
    +let c: number;
    +let d: string;
    +c = a; // 可以把any类型赋值给number类型,不会报错,但可能赋值不合法的值
    +d = a; // 可以把any类型赋值给string类型,不会报错,但可能赋值不合法的值
    +c = b; // 不能把unknown类型赋值给number类型,会报错:Type 'unknown' is not assignable to type 'number'
    +d = b; // 不能把unknown类型赋值给string类型,会报错:Type 'unknown' is not assignable to type 'string'
  • 类型断言:类型断言是一种告诉编译器我们比它更了解类型的方式,它可以让我们强制把一个类型转换为另一个类型,但这也有一定的风险,比如断言不合法的类型,或者忽略了一些类型检查。any类型的变量可以使用类型断言转换为任何类型,不会有类型检查的错误,但这也会导致一些潜在的问题,比如断言错误的类型,或者丢失了类型信息。unknown类型的变量则可以使用类型断言转换为任何类型,但这需要我们明确地指定要转换的类型,这样可以保证类型的正确性。例如:

    1
    2
    3
    4
    5
    6
    let a: any;
    +let b: unknown;
    +let c = a as number; // 可以把any类型断言为number类型,不会报错,但可能断言错误的类型
    +let d = a as string; // 可以把any类型断言为string类型,不会报错,但可能断言错误的类型
    +let e = b as number; // 可以把unknown类型断言为number类型,不会报错,但需要明确指定类型
    +let f = b as string; // 可以把unknown类型断言为string类型,不会报错,但需要明确指定类型
  • 类型收缩:类型收缩是一种让编译器自动推断出更具体的类型的方式,它可以让我们根据一些条件判断来缩小类型的范围,从而进行一些操作和访问。any类型的变量不能使用类型收缩,因为它已经是最宽泛的类型,没有更具体的类型可以推断出来。unknown类型的变量则可以使用类型收缩,通过一些类型保护的方法,比如typeofinstanceofin等,来推断出更具体的类型,从而进行一些操作和访问。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    let a: any;
    +let b: unknown;
    +if (typeof a === "number") {
    +  a.toFixed(2); // 不能使用类型收缩,会报错:Object is of type 'any'
    +}
    +if (typeof b === "number") {
    +  b.toFixed(2); // 可以使用类型收缩,不会报错,推断出b是number类型
    +}
+
+
+
查看 ts 中的元组 tuple +
+

TS 中的元组是一种特殊的数组,它可以存储不同类型的元素,并且元素的个数和类型在定义时就已经确定了。元组的语法格式如下:

1
let tuple_name: [type1, type2, ..., typeN] = [value1, value2, ..., valueN];

例如,我们可以定义一个元组,包含一个字符串和一个数字:

1
let mytuple: [string, number] = ["Hello", 42];

元组的元素可以通过索引来访问,索引从 0 开始,例如:

1
2
console.log(mytuple[0]); // 输出 "Hello"
+console.log(mytuple[1]); // 输出 42

元组的长度和类型都是固定的,所以不能越界访问或修改元素,也不能添加或删除超出范围的元素。否则,TS 编译器会报错。但是,我们可以对元组的元素进行更新操作,例如:

1
2
mytuple[0] = "World"; // 更新第一个元素
+console.log(mytuple[0]); // 输出 "World"

元组还有一些与其相关的方法,主要有以下几种:

  • push():向元组的末尾添加一个新元素,返回新的长度。注意,这个方法会改变原来的元组,而且添加的元素必须是元组中已有类型的联合类型。
  • pop():从元组的末尾移除一个元素,返回被移除的元素。注意,这个方法会改变原来的元组。
  • concat():连接两个元组,返回一个新的元组。注意,这个方法不会改变原来的元组,而且连接后的元组的类型必须是两个元组的类型的联合类型。
  • slice():从元组中截取一部分元素,返回一个新的元组。注意,这个方法不会改变原来的元组,而且截取后的元组的类型必须是原来元组的类型的联合类型。

下面是一些使用这些方法的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let mytuple: [string, number] = ["Hello", 42];
+let yourtuple: [string, number] = ["true", 100];
+
+mytuple.push("World"); // 添加一个字符串元素
+console.log(mytuple); // 输出 ["Hello", 42, "World"]
+
+let last = mytuple.pop(); // 移除最后一个元素
+console.log(last); // 输出 "World"
+console.log(mytuple); // 输出 ["Hello", 42]
+
+let newtuple = mytuple.concat(yourtuple); // 连接两个元组
+console.log(newtuple); // 输出 ["Hello", 42, 'true', 100]
+
+let subtuple = newtuple.slice(1, 3); // 截取一部分元组
+console.log(subtuple); // 输出 [42, 'true']
+
+
+

枚举类型

枚举类型是一种在 TypeScript 中定义一组带名字的常量的方式。枚举可以清晰地表达意图或创建一组有区别的用例。TypeScript 支持基于数字和基于字符串的枚举。

+
1
2
3
4
5
6
7
enum Direction {
+  Up,
+  Down = 10,
+  Left,
+  Right,
+}
+console.log(Direction.Up, Direction.Down, Direction.Left, Direction.Right); // 0 10 11 12
+
    +
  • 数字枚举:每个成员都有一个数字值,可以是常量或计算出来的。如果没有初始化器,第一个成员的值为 0,后面的成员依次递增。例如:

    +
    1
    2
    3
    4
    5
    6
    enum Direction {
    +  Up, // 0
    +  Down, // 1
    +  Left, // 2
    +  Right, // 3
    +}
    +
  • +
  • 字符串枚举:每个成员都必须用字符串字面量或另一个字符串枚举成员初始化。字符串枚举没有自增长的行为,但可以提供一个运行时有意义的并且可读的值。例如:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    enum Direction {
    +  Up = "UP",
    +  Down = "DOWN",
    +  Left = "LEFT",
    +  Right = "RIGHT",
    +}
    +console.log(Direction["Right"], Direction.Up); // RIGHT UP
    +// 后续也需要设置字符串
    +enum Direction {
    +  Up = "UP",
    +  Down, // error TS1061: Enum member must have initializer
    +  Left, // error TS1061: Enum member must have initializer
    +  Right, // error TS1061: Enum member must have initializer
    +}
    +
  • +
  • 异构枚举:可以混合字符串和数字成员,但不建议这么做。例如:

    +
    1
    2
    3
    4
    enum BooleanLikeHeterogeneousEnum {
    +  No = 0,
    +  Yes = "YES",
    +}
    +
  • +
  • 常量枚举:使用const enum关键字定义,只能使用常量枚举表达式初始化成员,不能包含计算或动态的值。常量枚举在编译阶段会被删除,不会生成任何代码。例如:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const enum Direction {
    +  Up,
    +  Down,
    +  Left,
    +  Right,
    +}
    +
    +let directions = [
    +  Direction.Up,
    +  Direction.Down,
    +  Direction.Left,
    +  Direction.Right,
    +]; // 编译后变为 [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */]
    +
  • +
  • 联合枚举和枚举成员类型:当所有枚举成员都是字面量类型时(不带有初始值或者初始化为字符串或数字字面量),枚举成员本身就是类型,而枚举类型本身就是每个成员的联合类型。这样可以实现更精确的类型检查和约束。例如:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    enum ShapeKind {
    +  Circle,
    +  Square,
    +}
    +
    +interface Circle {
    +  kind: ShapeKind.Circle; // 只能是 ShapeKind.Circle 类型
    +  radius: number;
    +}
    +
    +interface Square {
    +  kind: ShapeKind.Square; // 只能是 ShapeKind.Square 类型
    +  sideLength: number;
    +}
    +
    +let c: Circle = {
    +  kind: ShapeKind.Square, // Error! 类型不匹配
    +  radius: 100,
    +};
    +
  • +
  • 运行时的枚举:枚举是在运行时真正存在的对象,可以作为参数传递给函数或从函数返回。例如:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    enum E {
    +  X,
    +  Y,
    +  Z,
    +}
    +
    +function f(obj: { X: number }) {
    +  return obj.X;
    +}
    +
    +// Works, since 'E' has a property named 'X' which is a number.
    +f(E);
    +
  • +
  • 反向映射:数字枚举成员具有反向映射,可以根据枚举值得到对应的名字。例如:

    +
    1
    2
    3
    4
    5
    6
    enum Enum {
    +  A,
    +}
    +
    +let a = Enum.A;
    +let nameOfA = Enum[a]; // "A"
    +
  • +
+
查看枚举的本质 +
+

一个枚举的案例如下

1
2
3
4
5
6
7
8
enum Direction {
+  Up,
+  Down,
+  Left,
+  Right,
+}
+console.log(Direction.Up === 0); // true
+console.log(Direction[0], typeof Direction[0]); // Up string

编译后的js代码

1
2
3
4
5
6
7
8
9
10
11
12
var Direction;
+(function (Direction) {
+  Direction[(Direction["Up"] = 0)] = "Up";
+  Direction[(Direction["Down"] = 1)] = "Down";
+  Direction[(Direction["Left"] = 2)] = "Left";
+  Direction[(Direction["Right"] = 3)] = "Right";
+})(Direction || (Direction = {}));
+(function (Direction) {
+  Direction[(Direction["Center"] = 1)] = "Center";
+})(Direction || (Direction = {}));
+console.log(Direction.Up === 0); // true
+console.log(Direction[0], typeof Direction[0]); // Up string
+
+
+

接口

ts 中的接口是一种用来描述对象的形状(shape)的语法,它可以规定对象的属性和方法,以及它们的类型。接口可以让我们在编写代码时进行类型检查,避免出现类型错误。接口也可以提高代码的可读性和可维护性,让我们更清楚地知道对象的结构和功能。

+

ts 中的接口有以下几个特点:

+
    +
  • 可选属性:接口中的属性可以用?标记为可选,表示这个属性可以存在也可以不存在。例如:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    interface Person {
    +  name: string; // 必须属性
    +  age?: number; // 可选属性
    +}
    +
    +let p1: Person = { name: "Alice" }; // 合法,age可以省略
    +let p2: Person = { name: "Bob", age: 18 }; // 合法,age可以存在
    +let p3: Person = { name: "Charlie", gender: "male" }; // 非法,gender不是接口定义的属性
    +
  • +
  • 只读属性:接口中的属性可以用readonly标记为只读,表示这个属性只能在对象创建时赋值,不能再修改。例如:

    +
    1
    2
    3
    4
    5
    6
    7
    interface Point {
    +  readonly x: number; // 只读属性
    +  readonly y: number; // 只读属性
    +}
    +
    +let p1: Point = { x: 10, y: 20 }; // 合法,创建时赋值
    +p1.x = 30; // 非法,不能修改只读属性
    +
  • +
  • 函数类型:接口中可以定义函数的类型,即参数列表和返回值类型。例如:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    interface SearchFunc {
    +  (source: string, subString: string): boolean; // 函数类型
    +}
    +
    +let mySearch: SearchFunc; // 定义一个变量符合函数类型
    +mySearch = function (src, sub) {
    +  // 实现一个函数符合函数类型
    +  let result = src.search(sub);
    +  return result > -1;
    +};
    +
  • +
  • 索引类型:接口中可以定义索引的类型,即通过[]访问对象的类型。索引可以是数字或字符串。例如:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    interface StringArray {
    +  [index: number]: string; // 索引类型
    +}
    +
    +let myArray: StringArray; // 定义一个变量符合索引类型
    +myArray = ["Bob", "Fred"]; // 赋值一个数组符合索引类型
    +
    +let myStr: string = myArray[0]; // 访问数组元素符合索引类型
    +
  • +
  • 类类型:接口中可以定义类的类型,即类的构造函数和实例方法。例如:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    interface ClockInterface {
    +  currentTime: Date; // 实例属性
    +  setTime(d: Date): void; // 实例方法
    +}
    +
    +class Clock implements ClockInterface {
    +  // 类实现接口
    +  currentTime: Date;
    +  constructor(h: number, m: number) {
    +    this.currentTime = new Date();
    +    this.currentTime.setHours(h);
    +    this.currentTime.setMinutes(m);
    +  }
    +  setTime(d: Date) {
    +    this.currentTime = d;
    +  }
    +}
    +
  • +
  • 继承接口:接口之间可以相互继承,从而拥有父接口的属性和方法。一个接口也可以继承多个接口,实现多重继承。例如:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    interface Shape {
    +  color: string; // 父接口属性
    +}
    +
    +interface PenStroke {
    +  penWidth: number; // 父接口属性
    +}
    +
    +interface Square extends Shape, PenStroke {
    +  // 子接口继承两个父接口
    +  sideLength: number; // 子接口属性
    +}
    +
    +let square = {} as Square; // 定义一个变量符合子接口类型
    +square.color = "blue"; // 赋值父接口Shape的属性
    +square.sideLength = 10; // 赋值子接口Square的属性
    +square.penWidth = 5.0; // 赋值父接口PenStroke的属性
    +
  • +
+

类(class)

TS中的类的基本使用与TS相同,只不过引入了类型。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Car {
+  // 字段
+  engine: string;
+
+  // 构造函数
+  constructor(engine: string) {
+    this.engine = engine;
+  }
+
+  // 方法
+  disp(): void {
+    console.log("发动机为 :   " + this.engine);
+  }
+}
+

TS相对于JS中的class也添加了一些更高阶的类的特性。

+

访问修饰符

TS引入访问修饰符,类似JAVA。值得注意的是,访问修饰符的特性是TS本身的规范,JS并不能实现类似访问修饰符的特性。

+
    +
  • public:公开,可以自由的访问类程序里定义的成员。
  • +
  • private:私有,只能在类的内部进行访问。
  • +
  • protected:受保护,除了在该类的内部可以访问,还可以在子类中仍然可以访问。
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class Animal {
+  public name: string; // 公开的,可以在任何地方访问
+  private age: number; // 私有的,只能在类的内部访问
+  protected color: string; // 受保护的,可以在类的内部和子类中访问
+  readonly species: string; // 只读的,只能在声明时或构造函数中赋值
+
+  constructor(name: string, age: number, color: string, species: string) {
+    this.name = name;
+    this.age = age;
+    this.color = color;
+    this.species = species;
+  }
+
+  // 访问器,用来获取或设置私有或受保护的成员
+  get Age() {
+    return this.age;
+  }
+
+  set Age(value: number) {
+    if (value > 0) {
+      this.age = value;
+    }
+  }
+
+  get Color() {
+    return this.color;
+  }
+
+  set Color(value: string) {
+    this.color = value;
+  }
+}
+
+class Cat extends Animal {
+  constructor(name: string, age: number, color: string) {
+    super(name, age, color, "cat"); // 调用父类的构造函数
+  }
+
+  // 重写父类的方法
+  get Color() {
+    return "The color of this cat is " + super.Color; // 访问父类的受保护成员
+  }
+}
+
+let animal = new Animal("Tom", 3, "black", "dog");
+console.log(animal.name); // 可以访问公开成员
+// console.log(animal.age); // 错误,不能访问私有成员
+console.log(animal.Age); // 可以通过访问器访问私有成员
+// console.log(animal.color); // 错误,不能访问受保护成员
+console.log(animal.Color); // 可以通过访问器访问受保护成员
+console.log(animal.species); // 可以访问只读成员
+// animal.species = "bird"; // 错误,不能修改只读成员
+
+let cat = new Cat("Jerry", 2, "white");
+console.log(cat.name); // 可以访问公开成员
+// console.log(cat.age); // 错误,不能访问私有成员
+console.log(cat.Age); // 可以通过访问器访问私有成员
+// console.log(cat.color); // 错误,不能访问受保护成员
+console.log(cat.Color); // 可以通过访问器访问受保护成员,注意这里调用的是子类重写的方法
+console.log(cat.species); // 可以访问只读成员
+// cat.species = "mouse"; // 错误,不能修改只读成员
+

TS还引入了属性访问修饰符readonly。注意:readonly只能用于修饰属性,可以配合classclassfield写法例如:

+
1
2
3
4
5
6
7
8
9
10
11
class Person {
+  public readonly name: string; // 公有的只读属性
+  private readonly age: number; // 私有的只读属性
+  protected readonly gender: string; // 受保护的只读属性
+
+  constructor(name: string, age: number, gender: string) {
+    this.name = name;
+    this.age = age;
+    this.gender = gender;
+  }
+}
+
+

抽象类

TS 中的抽象类是一种特殊的类,它不能被直接实例化,只能作为其他类的基类来提供通用的属性和方法的定义。抽象类主要用于定义一组相关的类的共同结构和行为,以及强制子类实现特定的方法。抽象类用 abstract 关键字修饰,抽象类中的抽象方法也用 abstract 关键字修饰,抽象方法没有具体的实现,只有声明。抽象类可以有构造器,也可以有非抽象的属性和方法。抽象类的子类必须实现抽象类中的所有抽象方法,否则也会成为抽象类。
以下是一个 TS 中抽象类的代码示例:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 定义一个抽象类Animal,它有一个name属性,一个构造器,一个sayHello方法和一个抽象的eat方法
+abstract class Animal {
+  name: string;
+  constructor(name: string) {
+    this.name = name;
+  }
+  sayHello(): void {
+    console.log(`Hello, I am ${this.name}`);
+  }
+  abstract eat(): void; // 抽象方法,没有实现
+}
+
+// 定义一个子类Dog,它继承了Animal类,它必须实现Animal类中的抽象方法eat
+class Dog extends Animal {
+  constructor(name: string) {
+    super(name); // 调用父类的构造器
+  }
+  eat(): void {
+    // 实现抽象方法
+    console.log(`${this.name} is eating bones`);
+  }
+}
+
+// 定义一个子类Cat,它继承了Animal类,它必须实现Animal类中的抽象方法eat
+class Cat extends Animal {
+  constructor(name: string) {
+    super(name); // 调用父类的构造器
+  }
+  eat(): void {
+    // 实现抽象方法
+    console.log(`${this.name} is eating fish`);
+  }
+}
+
+// 创建一个Dog对象和一个Cat对象
+let dog = new Dog("Tommy");
+let cat = new Cat("Kitty");
+
+// 调用它们的方法
+dog.sayHello(); // Hello, I am Tommy
+dog.eat(); // Tommy is eating bones
+cat.sayHello(); // Hello, I am Kitty
+cat.eat(); // Kitty is eating fish
+
+// 不能创建一个Animal对象,因为Animal是抽象类
+let animal = new Animal("Jack"); // 编译错误,不能实例化抽象类
+

函数

    +
  1. ts类型定义

    +
    1
    2
    3
    4
    5
    6
    7
    // 方式一,函数类型的对象字面量,可用于函数重载
    +type LongHand = {
    +  (a: number): number;
    +};
    +
    +// 方式二,函数类型的别名,只能定义一个函数的类型,而不能定义多个函数的类型,不可用于函数重载
    +type ShortHand = (a: number) => number;
    +

    重载签名的作用是为了让 TS 编译器能够正确地推断函数的参数类型和返回类型,从而提供更好的类型检查和代码提示。如果没有重载签名,函数依旧能够发挥作用,但是 TS 编译器可能无法识别函数的参数类型和返回类型,导致类型错误或警告。例如,如果你在 TS 中使用以下的代码:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 没有重载签名的函数
    +function add(x: any, y: any): any {
    +  // 函数体
    +  if (typeof x === "string" && typeof y === "string") {
    +    // 字符串相拼接
    +    return x + y;
    +  } else if (typeof x === "number" && typeof y === "number") {
    +    // 数字相加
    +    return x + y;
    +  } else {
    +    // 抛出错误
    +    throw new Error("Invalid arguments");
    +  }
    +}
    +
    +// 调用函数
    +let a = add("Hello", "World"); // a的类型是any
    +let b = add(1, 2); // b的类型是any
    +let c = add("1", 2); // c的类型是any,但是会抛出错误
    +

    你会发现,变量 a、b 和 c 的类型都是 any,这意味着 TS 编译器无法确定它们的具体类型,也就无法提供类型检查和代码提示。例如,如果你想对 a 进行字符串操作,或者对 b 进行数学运算,TS 编译器可能会提示你这样做是不安全的,因为它们可能不是你期望的类型。而如果你使用重载签名,TS 编译器就能够根据你传入的参数类型,推断出函数的返回类型,从而提供更好的类型检查和代码提示。例如,如果你在 TS 中使用以下的代码:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // 有重载签名的函数
    +function add(x: string, y: string): string;
    +function add(x: number, y: number): number;
    +
    +function add(x: any, y: any): any {
    +  // 函数体
    +  if (typeof x === "string" && typeof y === "string") {
    +    // 字符串相拼接
    +    return x + y;
    +  } else if (typeof x === "number" && typeof y === "number") {
    +    // 数字相加
    +    return x + y;
    +  } else {
    +    // 抛出错误
    +    throw new Error("Invalid arguments");
    +  }
    +}
    +
    +// 调用函数
    +let a = add("Hello", "World"); // a的类型是string
    +let b = add(1, 2); // b的类型是number
    +let c = add("1", 2); // c的类型是any,但是会抛出错误
    +

    你会发现,变量 a 的类型是 string,变量 b 的类型是 number,这意味着 TS 编译器能够确定它们的具体类型,也就能够提供类型检查和代码提示。例如,如果你想对 a 进行字符串操作,或者对 b 进行数学运算,TS 编译器就不会提示你这样做是不安全的,因为它们是你期望的类型。而变量 c 的类型仍然是 any,因为你传入了不匹配的参数类型,这时候 TS 编译器会提示你函数的重载签名没有匹配到你的参数组合,也就提醒你可能会出现错误。

    +
  2. +
  3. 可选参数与默认参数

    +
      +
    • 可选参数必须跟在必须参数后面的例子:

      +
      1
      2
      3
      4
      5
      6
      7
      function greet(name: string, greeting?: string) {
      +  // name是必须参数,greeting是可选参数
      +  console.log(greeting ? `${greeting}, ${name}!` : `Hello, ${name}!`);
      +}
      +
      +greet("Alice"); // Hello, Alice!
      +greet("Bob", "Hi"); // Hi, Bob!
      +
    • +
    • 有默认值的参数可以放在必须参数的前面或后面的例子:

      +
      1
      2
      3
      4
      5
      6
      7
      8
      function add(x: number = 0, y: number) {
      +  // x是有默认值的参数,y是必须参数
      +  return x + y;
      +}
      +
      +console.log(add(1, 2)); // 3
      +console.log(add(undefined, 3)); // 3
      +console.log(add(4)); // 报错,缺少必须参数y
      +
    • +
    • 有默认值的参数如果放在必须参数的后面,那么这样函数的签名和可选参数就是一样的:

      +
      1
      2
      3
      4
      5
      6
      7
      8
      function multiply(x: number, y: number = 1) {
      +  // x是必须参数,y是有默认值的参数
      +  return x * y;
      +}
      +
      +console.log(multiply(2, 3)); // 6
      +console.log(multiply(4)); // 4
      +console.log(multiply(5, undefined)); // 5
      +
    • +
    +

    这个函数的签名和function multiply(x: number, y?: number)是一样的。

    +
  4. +
+

泛型

泛型是一种编程范式,它允许在程序中定义形式类型参数,然后在泛型实例化时使用实际类型参数来替换形式类型参数。泛型可以提高代码的复用性和类型安全性,让程序更灵活和通用。

+

TS 中的泛型有以下几种应用场景:

+
    +
  • 泛型函数:可以定义一个函数,它的参数和返回值的类型由一个类型变量来表示,这样就可以适用于不同的类型,而不需要重复编写相同逻辑的函数。例如:

    +
    1
    2
    3
    function identity<T>(arg: T): T {
    +  return arg;
    +}
    +

    这个函数可以接受任何类型的参数,并返回相同类型的值。我们可以在调用时指定泛型参数的实际类型,如identity<string>("hello"),或者让 TS 自动推断类型,如identity(42)

    +
  • +
  • 泛型接口:可以定义一个接口,它的属性或方法的类型由一个或多个类型变量来表示,这样就可以定义通用的数据结构或契约。例如:

    +
    1
    2
    3
    4
    interface MyArray<T> extends Array<T> {
    +  first: T | undefined;
    +  last: T | undefined;
    +}
    +

    这个接口继承了数组接口,并添加了两个属性,它们的类型都是泛型参数 T。我们可以实现这个接口,并指定 T 的实际类型,如class StringArray implements MyArray<string>

    +
  • +
  • 泛型类:可以定义一个类,它的属性或方法的类型由一个或多个类型变量来表示,这样就可以创建通用的类。例如:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class GetMin<T> {
    +  arr: T[] = [];
    +  add(ele: T) {
    +    this.arr.push(ele);
    +  }
    +  min(): T {
    +    let min = this.arr[0];
    +    this.arr.forEach(function (value) {
    +      if (value < min) {
    +        min = value;
    +      }
    +    });
    +    return min;
    +  }
    +}
    +

    这个类可以创建一个存储任何类型元素的数组,并提供一个方法返回最小值。我们可以创建这个类的实例,并指定 T 的实际类型,如let gm1 = new GetMin<number>()

    +
  • +
  • 泛型约束:可以使用 extends 关键字来限制泛型参数的范围,使之只能是某个类型或其子类型。这样就可以在泛型中使用一些特定的属性或方法。例如:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    interface Point {
    +  x: number;
    +  y: number;
    +}
    +
    +function toArray<T extends Point>(a: T, b: T): T[] {
    +  return [a, b];
    +}
    +

    这个函数只能接受 Point 或其子类型作为参数,并返回相同类型的数组。我们不能传入其他类型的参数,如toArray(1, 2)会报错。

    +
  • +
+

高级类型

当然可以,我会给你一些 TS 中高级类型的例子,你可以参考一下:

+
    +
  • 交叉类型(Intersection Types)的例子:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // 定义两个接口
    +interface A {
    +  name: string;
    +  age: number;
    +}
    +
    +interface B {
    +  gender: "male" | "female";
    +  hobby: string;
    +}
    +
    +// 使用交叉类型将两个接口合并为一个类型
    +type C = A & B;
    +
    +// 创建一个C类型的对象
    +let c: C = {
    +  name: "Tom",
    +  age: 20,
    +  gender: "male",
    +  hobby: "basketball",
    +};
    +
  • +
  • 联合类型(Union Types)的例子:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 定义一个联合类型,表示一个值可以是number或string
    +type D = number | string;
    +
    +// 使用联合类型作为函数参数的类型
    +function print(d: D) {
    +  // 使用typeof类型保护来判断d的具体类型
    +  if (typeof d === "number") {
    +    console.log("The number is " + d);
    +  } else if (typeof d === "string") {
    +    console.log("The string is " + d);
    +  }
    +}
    +
    +// 调用函数,传入不同类型的值
    +print(10); // The number is 10
    +print("Hello"); // The string is Hello
    +
  • +
  • 字面量类型(Literal Types)的例子:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 定义一个字符串字面量类型,表示一个值只能是'hello'或'world'
    +type E = "hello" | "world";
    +
    +// 定义一个数字字面量类型,表示一个值只能是1或2
    +type F = 1 | 2;
    +
    +// 定义一个布尔字面量类型,表示一个值只能是true
    +type G = true;
    +
    +// 定义一个模板字面量类型,表示一个值只能是'Hello ${string}'
    +type H = `Hello ${string}`;
    +
    +// 使用字面量类型创建变量
    +let e: E = "hello"; // OK
    +let f: F = 2; // OK
    +let g: G = true; // OK
    +let h: H = `Hello world`; // OK
    +
  • +
  • 索引类型(Indexed Types)的例子:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    // 定义一个接口
    +interface I {
    +  name: string;
    +  age: number;
    +  gender: "male" | "female";
    +}
    +
    +// 使用索引类型查询操作符得到I的所有属性名的类型
    +type J = keyof I; // J = 'name' | 'age' | 'gender'
    +
    +// 使用索引访问操作符得到I的某个属性的类型
    +type K = I["name"]; // K = string
    +
    +// 使用泛型约束和索引类型实现一个获取对象属性值的函数
    +function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
    +  return obj[key];
    +}
    +
    +// 创建一个I类型的对象
    +let i: I = {
    +  name: "Alice",
    +  age: 18,
    +  gender: "female",
    +};
    +
    +// 调用函数,传入对象和属性名
    +let name = getProp(i, "name"); // name的类型是string,值是'Alice'
    +let age = getProp(i, "age"); // age的类型是number,值是18
    +
  • +
  • 映射类型(Mapped Types)的例子:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    // 定义一个接口
    +interface L {
    +  name: string;
    +  age: number;
    +  gender: "male" | "female";
    +}
    +
    +// 使用映射类型将L的所有属性变为可选的
    +type M = {
    +  [P in keyof L]?: L[P];
    +};
    +
    +// 使用映射类型将L的所有属性变为只读的
    +type N = {
    +  readonly [P in keyof L]: L[P];
    +};
    +
    +// 使用内置的映射类型Pick从L中选择部分属性组成一个新的类型
    +type O = Pick<L, "name" | "gender">;
    +
    +// 使用映射类型创建变量
    +let m: M = {
    +  name: "Bob",
    +}; // OK,只有name属性,其他属性可选
    +
    +let n: N = {
    +  name: "Bob",
    +  age: 20,
    +  gender: "male",
    +}; // OK,所有属性只读
    +
    +n.name = "Tom"; // Error,不能修改只读属性
    +
    +let o: O = {
    +  name: "Bob",
    +  gender: "male",
    +}; // OK,只有name和gender属性,其他属性不存在
    +
  • +
  • 条件类型(Conditional Types):

    +
    1
    T extends U ? X : Y
    +

    上面的意思就是,如果 T 是 U 的子集,就是类型 X,否则为类型 Y

    +
  • +
  • 类型别名(Type Aliases)

    +

    类型别名是一种给一个类型起一个新的名字的方式,它可以让你更方便地引用这个类型,或者给这个类型添加一些语义。类型别名使用 type 关键字来定义,例如 type Name = string 表示给 string 类型起了一个别名叫 Name ,之后你就可以用 Name 来代替 string 了。

    +

    其次,你需要知道什么是泛型(Generics)。泛型是一种在定义类型时使用一个或多个类型参数的方式,它可以让你创建一些适用于任意类型的通用类型,而不是限定于某个具体的类型。泛型使用 <T> 这样的语法来表示类型参数,其中 T 是一个占位符,可以用任何合法的标识符来替换。例如 Array<T> 表示一个泛型数组类型,它可以存放任意类型的元素,例如 Array<number> 表示一个数字数组, Array<string> 表示一个字符串数组。

    +

    那么,类型别名可以是泛型吗?答案是肯定的,类型别名可以使用泛型来定义一些通用的类型,这样就可以让类型别名更灵活和复用。例如,你给出的这个类型别名:

    +
    1
    type Container<T> = { value: T };
    +

    它就是一个泛型类型别名,它表示一个容器类型,它有一个属性叫 value ,这个属性的类型是由类型参数 T 决定的。这样,你就可以用这个类型别名来创建不同类型的容器,例如:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    // 创建一个容器,它的value属性是一个数字
    +let numContainer: Container<number> = { value: 10 };
    +
    +// 创建一个容器,它的value属性是一个字符串
    +let strContainer: Container<string> = { value: "Hello" };
    +
    +// 创建一个容器,它的value属性是一个布尔值
    +let boolContainer: Container<boolean> = { value: true };
    +

    这些容器都是使用同一个类型别名 Container<T> 来定义的,只是类型参数 T 不同而已。这样,你就可以用一个类型别名来表示多种可能性,而不需要为每种情况都定义一个新的类型。

    +
    查看 keyof 与 infer 关键字的介绍 +
    +
    1. inferTypeScript 2.8 中引入的一个关键字,它可以用在条件类型的 extends 子句中,用来推断某个类型变量的具体类型。infer 的作用是在真实分支中引用此推断类型变量,从而获取待推断的类型。infer 可以用在多种场合,例如:

      • 推断函数的参数和返回值类型

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        // 定义一个类型,用来获取函数的参数类型
        +type ParamType<T> = T extends (...args: infer P) => any ? P : never;
        +
        +// 定义一个类型,用来获取函数的返回值类型
        +type ReturnType<T> = T extends (...args: any) => infer R ? R : never;
        +
        +// 测试
        +type Fn = (a: number, b: string) => number;
        +
        +type P = ParamType<Fn>; // [number, string]
        +type R = ReturnType<Fn>; // number
      • 推断对象的属性类型

        1
        2
        3
        4
        5
        6
        7
        8
        // 定义一个类型,用来获取对象的属性类型
        +type PropType<T, K extends keyof T> = T[K] extends infer U ? U : never;
        +
        +// 测试
        +type Obj = { a: string; b: number };
        +
        +type A = PropType<Obj, "a">; // string
        +type B = PropType<Obj, "b">; // number
      • 推断联合类型的成员类型

        1
        2
        3
        4
        5
        // 定义一个类型,用来获取联合类型的成员类型
        +type UnionType<T> = T extends infer U ? U : never;
        +
        +// 测试
        +type U = UnionType<string | number>; // string | number
    2. keyofTypeScript 中的一个关键字,它可以从一个对象类型中提取出它的所有属性名,组成一个字符串或数字的字面量联合类型。例如:

      1
      2
      3
      4
      5
      6
      7
      type Person = {
      +  name: string;
      +  age: number;
      +  gender: string;
      +};
      +
      +type P = keyof Person; // "name" | "age" | "gender"

      keyof 的作用是可以让我们在类型层面上操作对象的属性,比如限制访问对象的某些属性,或者映射对象的属性类型。keyof 也可以和泛型、索引类型、映射类型等一起使用,实现更多的类型操作。下面是一些 keyof 的常见用法:

      • 使用[]访问对象属性的类型:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        type Person = {
        +  name: string;
        +  age: number;
        +  gender: string;
        +};
        +
        +type Name = Person["name"]; // string
        +type Age = Person["age"]; // number
        +type Gender = Person["gender"]; // string
      • 使用extends限制泛型参数的属性:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        type Person = {
        +  name: string;
        +  age: number;
        +  gender: string;
        +};
        +
        +function getProperty<T, K extends keyof T>(obj: T, key: K) {
        +  return obj[key];
        +}
        +
        +let p: Person = { name: "Alice", age: 20, gender: "female" };
        +
        +let name = getProperty(p, "name"); // string
        +let age = getProperty(p, "age"); // number
        +let gender = getProperty(p, "gender"); // string
        +// let hobby = getProperty(p, "hobby"); // Error: Type '"hobby"' is not assignable to type '"name" | "age" | "gender"'
      • 使用in遍历对象的属性:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        type Person = {
        +  name: string;
        +  age: number;
        +  gender: string;
        +};
        +
        +type Keys = keyof Person; // "name" | "age" | "gender"
        +
        +type PersonRecord = {
        +  [P in Keys]: string;
        +};
        +
        +// 等价于
        +// type PersonRecord = {
        +//   name: string;
        +//   age: string;
        +//   gender: string;
        +// };
    +
    +
    +
  • +
+
文章作者: 希亚的西红柿
文章链接: http://blog.refrain.site/posts/1afd9955/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/posts/1be7bb2b/index.html b/posts/1be7bb2b/index.html new file mode 100644 index 000000000..c35b9e916 --- /dev/null +++ b/posts/1be7bb2b/index.html @@ -0,0 +1,1505 @@ +LeetCode 算法笔记 Part1 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + + +

LeetCode 算法笔记 Part1

本文主要记录了博主的 Leetcode 算法刷题记录,方便以后查询复习。

+
+

哈希表

两数之和

LeetCode 原题链接:1. 两数之和

+
+

这道题首先考虑的是通过两层for循环遍历查询,但这样有很多无意义的查找。因此我们可以采用哈希表来减少查找的时间复杂度,此时查找顺序是往后而不是往前。注意使用Map对象来减少查询之前数据的时间复杂度,这里不能使用WeakMap,因为WeakMap只能使用对象作为键。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
+ * @param {number[]} nums
+ * @param {number} target
+ * @return {number[]}
+ */
+const twoSum = (nums, target) => {
+  for (let i = 0; i < nums.length; i++) {
+    for (let j = i + 1; j < nums.length; j++) {
+      if (nums[i] + nums[j] === target) {
+        return [i, j];
+      }
+    }
+  }
+};
+
+/**
+ * @param {number[]} nums
+ * @param {number} target
+ * @return {number[]}
+ */
+const twoSum = (nums, target) => {
+  const map = new Map();
+  for (let i = 0; i < nums.length; i++) {
+    if (map.has(target - nums[i])) {
+      return [map.get(target - nums[i]), i];
+    }
+    if (!map.has(nums[i])) {
+      map.set(nums[i], i);
+    }
+  }
+};
+
+console.log(twoSum([2, 7, 11, 15], 9));
+// [ 0, 1 ]
+

字母异位词分组

LeetCode 原题链接:49. 字母异位词分组

+
+

建立一个判断某一字符串是否是同一组的函数,再遍历数组,填充键-数组Map对象,然后利用Map对象的相关方法返回实例。关键在于如何判断是同一种类型的字符串,一种是将字符串转为数组后排序,然后生成唯一的key,排序算法时间复杂度 $O(nlogn)$ 另一种方法是使用一个26位的数组来构造一个字符串的唯一键,来实现匹配的目的,注意使用的是charCodeAt方法,此种方法的时间复杂度为 $O(n)$,$n$ 为字符串的长度。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
+ * @param {string[]} strs
+ * @return {string[][]}
+ */
+const groupAnagrams = (strs) => {
+  const valToKey = (str) => {
+    const strArr = Array.from(str);
+    return strArr.sort().toString();
+  };
+  const map = new Map();
+  for (const str of strs) {
+    const key = valToKey(str);
+    if (!map.has(key)) {
+      map.set(key, [str]);
+    } else {
+      map.get(key).push(str);
+    }
+  }
+  return Array.from(map.values());
+};
+
+/**
+ * @param {string[]} strs
+ * @return {string[][]}
+ */
+const groupAnagrams = (strs) => {
+  const map = new Map();
+  const valToKey = (str) => {
+    const keyArr = new Array(26).fill(0);
+    for (const char of str) {
+      keyArr[char.charCodeAt() - "a".charCodeAt()]++;
+    }
+    return keyArr.toString();
+  };
+  for (const str of strs) {
+    const key = valToKey(str);
+    if (map.has(key)) {
+      map.get(key).push(str);
+    } else {
+      map.set(key, [str]);
+    }
+  }
+  return Array.from(map.values());
+};
+
+console.log(groupAnagrams(["eat", "tea", "tan", "ate", "nat", "bat"]));
+// [ [ 'eat', 'tea', 'ate' ], [ 'tan', 'nat' ], [ 'bat' ] ]
+

最长连续序列

LeetCode 原题链接:128. 最长连续序列

+
+

首先采用以数组为参数构建set,在建立每个元素哈希的同时去重,后续遍历在set集合中展开,然后找到每段区间的起始位置并求其最大值,即存在比起小的数字即set.has(num - 1)这种情况需要跳过,因为必然会从比num - 1开始遍历的区间的长度要小。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
+ * @param {number[]} nums
+ * @return {number}
+ */
+const longestConsecutive = (nums) => {
+  const set = new Set(nums);
+  let maxLength = 0;
+  for (let num of set) {
+    if (set.has(num - 1)) {
+      continue;
+    } else {
+      let count = 1;
+      while (set.has(++num)) {
+        count++;
+      }
+      maxLength = Math.max(maxLength, count);
+    }
+  }
+  return maxLength;
+};
+
+console.log(longestConsecutive([0, 3, 7, 2, 5, 8, 4, 6, 0, 1]));
+// 9
+

总结

在 JS 中,哈希表主要采用MapSet这两种内置的数据结构。合理使用这两种数据结构可以极大地方便查找元素,从而降低算法的时间复杂度。对于Map对象来说更是可以把键与值关联起来存储,方便算法的改进,还可以用于实际需求,如分组的运用。

+

双指针

移动零

LeetCode 原题链接:283. 移动零

+
+

代码中left指针指向已处理好的数组的末尾后一个元素,即指向现数组的第一个0,当right指针指向非零数字的时候就将非零数字提到前面然后进行交换。核心思路为left指针始终指向第一个0元素,当right指针指向遇到非零元素时left指针才会变化。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
+ * @param {number[]} nums
+ * @return {void} Do not return anything, modify nums in-place instead.
+ */
+const moveZeroes = (nums) => {
+  let left = 0,
+    right = 0;
+  while (right < nums.length) {
+    if (nums[right] !== 0) {
+      if (right > left) {
+        [nums[left], nums[right]] = [nums[right], nums[left]];
+      }
+      left++;
+    }
+    right++;
+  }
+};
+
+const nums = [0, 1, 0, 3, 12];
+moveZeroes(nums);
+console.log(nums);
+// [ 1, 3, 12, 0, 0 ]
+

盛水最多的容器

LeetCode 原题链接:11. 盛最多水的容器

+
+

双指针向内遍历,由于横向长度的变小,想要大于之前的水量,只能遇到更高的容器壁,所以小的容器壁一边需要往内收缩,不断更新最大值,最后得到相应的结果。感觉核心的思想还是贪心。🤔

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
+ * @param {number[]} height
+ * @return {number}
+ */
+const maxArea = (height) => {
+  let max = 0,
+    left = 0,
+    right = height.length - 1;
+  while (left < right) {
+    max =
+      height[left] > height[right]
+        ? Math.max(max, (right - left) * height[right--])
+        : Math.max(max, (right - left) * height[left++]);
+  }
+  return max;
+};
+
+console.log(maxArea([1, 8, 6, 2, 5, 4, 8, 3, 7]));
+// 49
+

三数之和

LeetCode 原题链接:15. 三数之和

+
+

这道题目的难点在于去重。将数组进行排序是一个很巧妙的做法,一方面可以便于第一层i使用一次后去重,第二个是便于lr双指针向内遍历去重,同时在nums[i] > 0的时候可以退出循环,避免无用的计算。做这道题重点在于两次去重的过程,值得仔细理解。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
+ * @param {number[]} nums
+ * @return {number[][]}
+ */
+const threeSum = (nums) => {
+  if (!nums || nums.length < 3) {
+    return [];
+  }
+  const res = [];
+  nums.sort((a, b) => a - b);
+  for (let i = 0; i < nums.length; i++) {
+    if (nums[i] > 0) {
+      break;
+    }
+    while (i > 0 && nums[i] === nums[i - 1]) {
+      i++;
+    }
+    let left = i + 1,
+      right = nums.length - 1;
+    while (left < right) {
+      const sum = nums[i] + nums[left] + nums[right];
+      if (sum === 0) {
+        res.push([nums[i], nums[left], nums[right]]);
+        while (nums[left] === nums[left + 1]) {
+          left++;
+        }
+        while (nums[right] === nums[right - 1]) {
+          right--;
+        }
+        left++;
+        right--;
+      } else if (sum > 0) {
+        right--;
+      } else {
+        left++;
+      }
+    }
+  }
+  return res;
+};
+
+console.log(threeSum([-1, 0, 1, 2, -1, -4]));
+// [ [ -1, -1, 2 ], [ -1, 0, 1 ] ]
+

接雨水

LeetCode 原题链接:42. 接雨水

+
+

接雨水似乎是写烂了的动态规划?🤔 下面也列出了三种方法,分别是:

+
    +
  • 动态规划双向遍历得到左右数组,然后逐个元素求解。
  • +
  • 单调栈求解。注意栈中存储的是数组的索引
  • +
  • 动态规划 + 双指针,两边取最小求解,leftMaxrightMax那个最小就确定了那个最小的值。
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
/**
+ * @param {number[]} height
+ * @return {number}
+ */
+const trap = (height) => {
+  if (height.length < 3) {
+    return 0;
+  }
+  const n = height.length;
+  const leftMaxArr = new Array(n);
+  leftMaxArr[0] = height[0];
+  leftMaxArr[n - 1] = height[n - 1];
+  for (let i = 1; i < n - 1; i++) {
+    leftMaxArr[i] = Math.max(leftMaxArr[i - 1], height[i - 1]);
+  }
+
+  const rightMaxArr = new Array(n);
+  rightMaxArr[0] = height[0];
+  rightMaxArr[n - 1] = height[n - 1];
+  for (let i = n - 2; i > 0; i--) {
+    rightMaxArr[i] = Math.max(rightMaxArr[i + 1], height[i + 1]);
+  }
+
+  let res = 0;
+  for (let i = 1; i < n - 1; i++) {
+    const num = Math.min(leftMaxArr[i], rightMaxArr[i]);
+    if (num > height[i]) {
+      res += Math.abs(num - height[i]);
+    }
+  }
+  return res;
+};
+
+/**
+ * @param {number[]} height
+ * @return {number}
+ */
+const trap = (height) => {
+  let res = 0;
+  const stack = [];
+  for (let i = 0; i < height.length; i++) {
+    while (stack.length && height[i] > height[stack[stack.length - 1]]) {
+      const top = stack.pop();
+      if (!stack.length) {
+        break;
+      }
+      const left = stack[stack.length - 1];
+      const curHeight = Math.min(height[left], height[i]) - height[top];
+      const curWidth = i - left - 1;
+      res += curWidth * curHeight;
+    }
+    stack.push(i);
+  }
+  return res;
+};
+
+/**
+ * @param {number[]} height
+ * @return {number}
+ */
+const trap = (height) => {
+  let res = 0;
+  let left = 0,
+    right = height.length - 1;
+  let leftMax = 0,
+    rightMax = 0;
+  while (left < right) {
+    leftMax = Math.max(height[left], leftMax);
+    rightMax = Math.max(height[right], rightMax);
+    if (leftMax < rightMax) {
+      res += leftMax - height[left];
+      left++;
+    } else {
+      res += rightMax - height[right];
+      right--;
+    }
+  }
+  return res;
+};
+
+console.log(trap([0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]));
+// 6
+

总结

双指针向内收缩对于特殊情况的元素遍历有很大的帮助,对于两个或两个以上元素的遍历,都可以考虑双指针的做法。

+

滑动窗口

无重复字符的最长字串

LeetCode 原题链接:3. 无重复字符的最长子串

+
+

利用set集合与滑动窗口解决,右扩张,左收缩,遍历数组获得最长长度。有点像滑动窗口 + 哈希表 + 双指针的结合。🤩

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
+ * @param {string} s
+ * @return {number}
+ */
+const lengthOfLongestSubstring = (s) => {
+  const set = new Set();
+  let i = 0,
+    maxLength = 0;
+  for (const char of s) {
+    while (set.has(char)) {
+      set.delete(s[i++]);
+    }
+    set.add(char);
+    maxLength = Math.max(maxLength, set.size);
+  }
+  return maxLength;
+};
+
+console.log(lengthOfLongestSubstring("abcabcbb"));
+// 3
+

找到字符串中所有字母的异位词

+

主要采取长度为26位的数组生成唯一的key值,用来滑动窗口进行匹配。注意对一个元素进行处理或者对最后增减做一个条件判断。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/**
+ * @param {string} s
+ * @param {string} p
+ * @return {number[]}
+ */
+const findAnagrams = (s, p) => {
+  if (p.length > s.length) {
+    return [];
+  }
+  const pKeyArr = new Array(26).fill(0);
+  for (const char of p) {
+    pKeyArr[char.charCodeAt() - "a".charCodeAt()]++;
+  }
+  const pKey = pKeyArr.toString();
+  const winArr = new Array(26).fill(0);
+  for (let i = 0; i < p.length; i++) {
+    winArr[s[i].charCodeAt() - "a".charCodeAt()]++;
+  }
+  const res = [];
+  for (let i = 0; i < s.length - p.length + 1; i++) {
+    if (winArr.toString() === pKey) {
+      res.push(i);
+    }
+    if (i + p.length < s.length) {
+      winArr[s[i].charCodeAt() - "a".charCodeAt()]--;
+      winArr[s[i + p.length].charCodeAt() - "a".charCodeAt()]++;
+    }
+  }
+  return res;
+};
+
+/**
+ * @param {string} s
+ * @param {string} p
+ * @return {number[]}
+ */
+const findAnagrams = (s, p) => {
+  if (s.length < p.length) {
+    return [];
+  }
+  const keyArr = new Array(26).fill(0);
+  const valArr = new Array(26).fill(0);
+  const res = [];
+  for (let i = 0; i < p.length; i++) {
+    keyArr[p[i].charCodeAt() - "a".charCodeAt()]++;
+    valArr[s[i].charCodeAt() - "a".charCodeAt()]++;
+  }
+  const key = keyArr.toString();
+  if (valArr.toString() === key) {
+    res.push(0);
+  }
+
+  for (let i = p.length; i < s.length; i++) {
+    valArr[s[i].charCodeAt() - "a".charCodeAt()]++;
+    valArr[s[i - p.length].charCodeAt() - "a".charCodeAt()]--;
+    if (valArr.toString() === key) {
+      res.push(i - p.length + 1);
+    }
+  }
+  return res;
+};
+
+console.log("cbaebabacd", "abc");
+// [ 0, 6 ]
+

总结

滑动窗口分为静态大小的窗口和动态大小的窗口,前者每次都需要变化,大小固定;后者一般为右扩张左收缩,大小不固定。

+

子串

和为 k 的子数组

LeetCode 原题链接:560. 和为 k 的子数组

+
+

注意是连续的子数组,可以采用两层for循环,内层for循环往回遍历。或者使用哈希表,注意哈希表的思路比较巧妙,遍历到当前元素的时候,采取看之前的Map对象中是否存在当前和减去目标值,即map.has(pre - k),存在即计数增加,注意需要设置0的初始值const map = new Map([[0, 1]])。暴力做法可帮助理解第二种做法 🦀。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
+ * @param {number[]} nums
+ * @param {number} k
+ * @return {number}
+ */
+const subarraySum = (nums, k) => {
+  let count = 0;
+  for (let i = 0; i < nums.length; i++) {
+    let sum = 0;
+    for (let j = i; j >= 0; j--) {
+      sum += nums[j];
+      if (sum === k) {
+        count++;
+      }
+    }
+  }
+  return count;
+};
+
+/**
+ * @param {number[]} nums
+ * @param {number} k
+ * @return {number}
+ */
+const subarraySum = (nums, k) => {
+  let count = 0,
+    pre = 0;
+  const map = new Map([[0, 1]]);
+  for (const num of nums) {
+    pre += num;
+    if (map.has(pre - k)) {
+      count += map.get(pre - k);
+    }
+    if (map.has(pre)) {
+      map.set(pre, map.get(pre) + 1);
+    } else {
+      map.set(pre, 1);
+    }
+  }
+  return count;
+};
+
+console.log(subarraySum([1, 1, 1], 2));
+// 2
+

滑动窗口最大值

+

有点类似单调栈?🤔 采用单调队列求解,但需要注意无论是队列还是栈,数组中存放的都是下标,推入的元素需要根据题目条件来做处理。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
+ * @param {number[]} nums
+ * @param {number} k
+ * @return {number[]}
+ */
+const maxSlidingWindow = (nums, k) => {
+  const queue = [];
+  for (let i = 0; i < k; i++) {
+    while (queue.length && nums[i] >= nums[queue[queue.length - 1]]) {
+      queue.pop();
+    }
+    queue.push(i);
+  }
+  const res = [nums[queue[0]]];
+  for (let i = k; i < nums.length; i++) {
+    while (queue.length && nums[i] >= nums[queue[queue.length - 1]]) {
+      queue.pop();
+    }
+    queue.push(i);
+    while (queue[0] <= i - k) {
+      queue.shift();
+    }
+    res.push(nums[queue[0]]);
+  }
+  return res;
+};
+
+console.log(maxSlidingWindow([1, 3, -1, -3, 5, 3, 6, 7], 3));
+// [ 3, 3, 5, 5, 6, 7 ]
+

最小覆盖字串

LeetCode 原题链接:76. 最小覆盖字串

+
+

采用哈希表计数,然后采用不定长度滑动窗口求解,右扩张,左收缩,哈希表计数是关键中的关键。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
+ * @param {string} s
+ * @param {string} t
+ * @return {string}
+ */
+const minWindow = (s, t) => {
+  const map = new Map();
+  for (const char of t) {
+    map.set(char, map.has(char) ? map.get(char) + 1 : 1);
+  }
+  // 注意 count 不是 t 的长度而是不同字母的个数,即 Map 对象的大小
+  let count = map.size,
+    i = 0,
+    j = 0,
+    res = "";
+  while (j < s.length) {
+    if (map.has(s[j])) {
+      map.set(s[j], map.get(s[j]) - 1);
+      if (map.get(s[j]) === 0) {
+        count--;
+        if (count === 0) {
+          while (true) {
+            if (map.has(s[i])) {
+              if (map.get(s[i]) === 0) {
+                break;
+              } else {
+                map.set(s[i], map.get(s[i]) + 1);
+              }
+            }
+            i++;
+          }
+
+          if (j - i < res.length || res === "") {
+            res = s.slice(i, j + 1);
+          }
+          map.set(s[i], map.get(s[i]) + 1);
+          count++;
+          i++;
+        }
+      }
+    }
+    j++;
+  }
+  return res;
+};
+
+console.log(minWindow("ADOBECODEBANC", "ABC"));
+// BANC
+

总结

子串很多都与哈希表相关,注意总结题目经验求解,很多情况下可能用到滑动窗口,单调栈与单调队列的思想也很重要。

+

普通数组

最大子数组和

LeetCode 原题链接:53. 最大子数组和

+
+

比较简单的动态规划题目,若前面的pre小于 0 则直接舍去,防止拖累变小,感觉也有点贪心的思想在里面。🤔

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
+ * @param {number[]} nums
+ * @return {number}
+ */
+const maxSubArray = (nums) => {
+  let pre = 0,
+    maxAns = nums[0];
+  for (const num of nums) {
+    pre = Math.max(pre + num, num);
+    maxAns = Math.max(pre, maxAns);
+  }
+  return maxAns;
+};
+
+console.log(maxSubArray([-2, 1, -3, 4, -1, 2, 1, -5, 4]));
+// 6
+

合并区间

LeetCode 原题链接:56. 合并区间

+
+

简单的模拟策略但需要注意排序的时候,在前面的右侧值不一定最大,所以需要取二者的最大值。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
+ * @param {number[][]} intervals
+ * @return {number[][]}
+ */
+const merge = (intervals) => {
+  intervals.sort((a, b) => a[0] - b[0]);
+  const res = [];
+  for (const interval of intervals) {
+    if (res.length === 0 || interval[0] > res[res.length - 1][1]) {
+      res.push(interval);
+    } else {
+      res[res.length - 1][1] = Math.max(res[res.length - 1][1], interval[1]);
+    }
+  }
+  return res;
+};
+
+console.log(
+  merge([
+    [1, 3],
+    [2, 6],
+    [8, 10],
+    [15, 18],
+  ])
+);
+// [ [ 1, 6 ], [ 8, 10 ], [ 15, 18 ] ]
+

轮转数组

LeetCode 原题链接:189. 轮转数组

+
+

可采用额外数组存储,或者数组反转进行解决。第一种方法比较常规,第二种方法技巧性强但便于记忆与理解。😊

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
操作结果
原始数组1 2 3 4 5 6 7
翻转所有元素7 6 5 4 3 2 1
翻转 $ [0, k\bmod n - 1] $ 区间的元素5 6 7 4 3 2 1
翻转 $ [k\bmod n, n - 1] $ 区间的元素5 6 7 1 2 3 4
+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
+ * @param {number[]} nums
+ * @param {number} k
+ * @return {void} Do not return anything, modify nums in-place instead.
+ */
+const rotate = (nums, k) => {
+  k = k % nums.length;
+  const newArr = new Array(nums.length);
+  for (let i = 0; i < nums.length; i++) {
+    newArr[(i + k) % nums.length] = nums[i];
+  }
+  for (let i = 0; i < nums.length; i++) {
+    nums[i] = newArr[i];
+  }
+};
+
+/**
+ * @param {number[]} nums
+ * @param {number} k
+ * @return {void} Do not return anything, modify nums in-place instead.
+ */
+const rotate = (nums, k) => {
+  k = k % nums.length;
+  const reverse = (i, j) => {
+    while (i < j) {
+      const temp = nums[i];
+      nums[i] = nums[j];
+      nums[j] = temp;
+      i++;
+      j--;
+    }
+  };
+  reverse(0, nums.length - 1);
+  reverse(0, k - 1);
+  reverse(k, nums.length - 1);
+};
+
+console.log(rotate([1, 2, 3, 4, 5, 6, 7], 3));
+// [
+//   5, 6, 7, 1,
+//   2, 3, 4
+// ]
+

除自身以外数组的乘积

LeetCode 原题链接:238. 除自身以外数组的乘积

+
+

类似接雨水双向数组,可采取遍历的方式进行反向计算。可以最大程度上节省空间。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
+ * @param {number[]} nums
+ * @return {number[]}
+ */
+const productExceptSelf = (nums) => {
+  const leftArr = new Array(nums.length).fill(1);
+  const rightArr = new Array(nums.length).fill(1);
+  for (let i = 1; i < nums.length; i++) {
+    leftArr[i] = leftArr[i - 1] * nums[i - 1];
+  }
+  for (let i = nums.length - 2; i >= 0; i--) {
+    rightArr[i] = rightArr[i + 1] * nums[i + 1];
+  }
+  const res = new Array(nums.length);
+  for (let i = 0; i < nums.length; i++) {
+    res[i] = leftArr[i] * rightArr[i];
+  }
+  return res;
+};
+
+/**
+ * @param {number[]} nums
+ * @return {number[]}
+ */
+const productExceptSelf = (nums) => {
+  const ansArr = new Array(nums.length).fill(1);
+  for (let i = 1; i < nums.length; i++) {
+    ansArr[i] = ansArr[i - 1] * nums[i - 1];
+  }
+  let right = 1;
+  for (let i = nums.length - 1; i >= 0; i--) {
+    ansArr[i] = right * ansArr[i];
+    right *= nums[i];
+  }
+  return ansArr;
+};
+
+console.log(productExceptSelf([1, 2, 3, 4]));
+// [ 24, 12, 8, 6 ]
+

缺失的第一个正数

LeetCode 原题链接:41. 缺失的第一个正数

+
+

原地哈希,将数组的值i与下标i - 1相对应,没有对应的元素证明是第一个缺失的正整数。注意第三个判断条件nums[i] !== nums[nums[i] - 1]添加一个nums判断可以应对数组中重复值的情况。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
+ * @param {number[]} nums
+ * @return {number}
+ */
+const firstMissingPositive = (nums) => {
+  let n = nums.length;
+  for (let i = 0; i < n; i++) {
+    if (nums[i] <= 0) {
+      nums[i] = n + 1;
+    }
+  }
+
+  for (let i = 0; i < n; i++) {
+    let num = Math.abs(nums[i]);
+    if (num <= n) {
+      nums[num - 1] = -Math.abs(nums[num - 1]);
+    }
+  }
+
+  for (let i = 0; i < n; i++) {
+    if (nums[i] > 0) {
+      return i + 1;
+    }
+  }
+  return n + 1;
+};
+
+/**
+ * @param {number[]} nums
+ * @return {number}
+ */
+const firstMissingPositive = (nums) => {
+  for (let i = 0; i < nums.length; i++) {
+    while (
+      nums[i] > 0 &&
+      nums[i] <= nums.length &&
+      nums[i] !== nums[nums[i] - 1]
+    ) {
+      const temp = nums[nums[i] - 1];
+      nums[nums[i] - 1] = nums[i];
+      nums[i] = temp;
+    }
+  }
+
+  for (let i = 0; i < nums.length; i++) {
+    if (nums[i] !== i + 1) {
+      return i + 1;
+    }
+  }
+  return nums.length + 1;
+};
+
+console.log(firstMissingPositive([3, 4, -1, 1]));
+// 2
+

总结

数组的技巧性比较强,一般会与模拟,动态规划,哈希表结合,需要灵活使用。

+

矩阵

矩阵置零

LeetCode 原题链接: 73. 矩阵置零

+
+

常规的遍历标记然后取值,可以优化一下空间复杂度

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
+ * @param {number[][]} matrix
+ * @return {void} Do not return anything, modify matrix in-place instead.
+ */
+const setZeroes = (matrix) => {
+  const rowSet = new Set();
+  const colSet = new Set();
+  for (let i = 0; i < matrix.length; i++) {
+    for (let j = 0; j < matrix[0].length; j++) {
+      if (matrix[i][j] === 0) {
+        if (!rowSet.has(i)) {
+          rowSet.add(i);
+        }
+        if (!colSet.has(j)) {
+          colSet.add(j);
+        }
+      }
+    }
+  }
+  rowSet.forEach((item) => {
+    for (let i = 0; i < matrix[0].length; i++) {
+      matrix[item][i] = 0;
+    }
+  });
+  colSet.forEach((item) => {
+    for (let i = 0; i < matrix.length; i++) {
+      matrix[i][item] = 0;
+    }
+  });
+};
+
+const matrix = [
+  [0, 1, 2, 0],
+  [3, 4, 5, 2],
+  [1, 3, 1, 5],
+];
+setZeroes(matrix);
+console.log(matrix);
+// [
+//   [0, 0, 0, 0],
+//   [0, 4, 5, 0],
+//   [0, 3, 1, 0],
+// ];
+

螺旋矩阵

LeetCode 原题链接:54. 螺旋矩阵

+
+

核心思路为模拟,设置上下左右 4 个变量便于理解,for循环的时候采用变量命名的方式提醒自己是行还是列,同时注意left <= right && top <= bottomleft < right && top < bottom这两个判断条件的使用。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
+ * @param {number[][]} matrix
+ * @return {number[]}
+ */
+const spiralOrder = (matrix) => {
+  if (!matrix.length || !matrix[0].length) {
+    return [];
+  }
+
+  let left = 0,
+    right = matrix[0].length - 1,
+    top = 0,
+    bottom = matrix.length - 1;
+  const res = [];
+  while (left <= right && top <= bottom) {
+    for (let col = left; col <= right; col++) {
+      res.push(matrix[top][col]);
+    }
+    for (let row = top + 1; row <= bottom; row++) {
+      res.push(matrix[row][right]);
+    }
+    if (left < right && top < bottom) {
+      for (let col = right - 1; col > left; col--) {
+        res.push(matrix[bottom][col]);
+      }
+      for (let row = bottom; row > top; row--) {
+        res.push(matrix[row][left]);
+      }
+    }
+    [left, right, top, bottom] = [left + 1, right - 1, top + 1, bottom - 1];
+  }
+  return res;
+};
+
+const matrix = [
+  [1, 2, 3],
+  [4, 5, 6],
+  [7, 8, 9],
+];
+console.log(spiralOrder(matrix));
+// [
+//   1, 2, 3, 6, 9,
+//   8, 7, 4, 5
+// ]
+

旋转图像

LeetCode 原题链接:48. 旋转图像

+
+

可以采用额外空间来一一对应,或者采用特殊技巧,上下反转后再对角线反转。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
+ * @param {number[][]} matrix
+ * @return {void} Do not return anything, modify matrix in-place instead.
+ */
+const rotate = (matrix) => {
+  const n = matrix.length;
+  const matrix_new = new Array(n).fill(0).map(() => new Array(n).fill(0));
+  for (let i = 0; i < n; i++) {
+    for (let j = 0; j < n; j++) {
+      matrix_new[j][n - i - 1] = matrix[i][j];
+    }
+  }
+  for (let i = 0; i < n; i++) {
+    for (let j = 0; j < n; j++) {
+      matrix[i][j] = matrix_new[i][j];
+    }
+  }
+};
+
+/**
+ * @param {number[][]} matrix
+ * @return {void} Do not return anything, modify matrix in-place instead.
+ */
+const rotate = (matrix) => {
+  const length = matrix.length;
+  const halfLen = Math.floor(length / 2);
+  for (let i = 0; i < halfLen; i++) {
+    for (let j = 0; j < length; j++) {
+      const temp = matrix[i][j];
+      matrix[i][j] = matrix[length - i - 1][j];
+      matrix[length - i - 1][j] = temp;
+    }
+  }
+  for (let i = 0; i < length; i++) {
+    for (let j = 0; j < i; j++) {
+      const temp = matrix[i][j];
+      matrix[i][j] = matrix[j][i];
+      matrix[j][i] = temp;
+    }
+  }
+};
+
+const matrix = [
+  [1, 2, 3],
+  [4, 5, 6],
+  [7, 8, 9],
+];
+rotate(matrix);
+console.log(matrix);
+// [
+//   [7, 4, 1],
+//   [8, 5, 2],
+//   [9, 6, 3],
+// ];
+

搜索二维矩阵

LeetCode 原题链接:240. 搜索二维矩阵

+
+

定位到右上角,利用矩阵递增的性质,实现 Z 字形查找

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
+ * @param {number[][]} matrix
+ * @param {number} target
+ * @return {boolean}
+ */
+const searchMatrix = (matrix, target) => {
+  let x = 0,
+    y = matrix[0].length - 1;
+  while (x < matrix.length && y >= 0) {
+    if (target < matrix[x][y]) {
+      y--;
+    } else if (target > matrix[x][y]) {
+      x++;
+    } else {
+      return true;
+    }
+  }
+  return false;
+};
+
+console.log(
+  searchMatrix(
+    [
+      [1, 4, 7, 11, 15],
+      [2, 5, 8, 12, 19],
+      [3, 6, 9, 16, 22],
+      [10, 13, 14, 17, 24],
+      [18, 21, 23, 26, 30],
+    ],
+    5
+  )
+);
+// true
+

总结

矩阵的题目比较考验技巧与空间思维,需要严格分清行坐标与列坐标,写的时候需要思路清晰,一步一步来。

+
文章作者: 希亚的西红柿
文章链接: http://blog.refrain.site/posts/1be7bb2b/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
+ + + + \ No newline at end of file diff --git a/posts/20642/index.html b/posts/20642/index.html new file mode 100644 index 000000000..b6b22b040 --- /dev/null +++ b/posts/20642/index.html @@ -0,0 +1,670 @@ +线性回归 (Linear Regression) | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + + + + +

线性回归 (Linear Regression)

利用 Numpy 实现简单的机器学习算法

+

线性回归(Linear Regression) 可能是最流行的机器学习算法。线性回归就是要找一条直线,并且让这条直线尽可能地拟合散点图中的数据点。

+
+ +

前言

+

线性回归作为高中数学统计题比较重要的内容,先对其原理与推导过程不做详细讨论,重点通过代码来运用 python 模块实现线性回归算法。

+
+

线性回归算法的简单介绍

线性模型:给定由 d 个属性描述的示例,线性模型试图学得一个通过属性的线性组合来进行预测的函数。

+

线性回归试图学得一个线性模型以尽可能准确的预测实值输出标记,公式: $ f(x) = w^Tx+b $
$ f(x_i) = wx_i + b $,使得 $ f(x_i) ≈ y_i $

+

实现线性回归算法的目标与方法

我们的任务就是求出 w 和 b,可用均方误差最小化的方法,基于均方误差最小化来进行模型求解的方法称为最小二乘法,在线性回归中,最小二乘法就是试图找到一条直线,使得所有样本数据点到达直线的欧氏距离最小。总距离是所有数据点的垂直距离的平方和。其思想是通过最小化这个平方误差或距离来拟合模型。

+

代码具体实现过程

定义线性回归函数

+
    +
  • np.mean()函数主要起对矩阵求均值的作用
  • +
  • for 循环中利用最小二乘法的基本公式求出分子与分母,进而求出回归直线的斜率
  • +
  • 值得注意的是分母转化为浮点数来保证计算的精确性
  • +
+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def fitSLR(X, Y):
+  X_avg = np.mean(X)  # 求取括号内数组矩阵的均值
+  Y_avg = np.mean(Y)
+  n = len(X)  # 得到括号内列表长度即元素的个数
+  # 定义分子与分母
+  fen_zi = 0
+  fen_mu = 0
+  # 核心算法
+  for i in range(0, n):
+      fen_zi += (X[i] - X_avg) * (Y[i] - Y_avg)
+      fen_mu += (X[i] - X_avg) ** 2
+  b1 = fen_zi / float(fen_mu)  # 计算w,即直线的斜率
+  b0 = Y_avg - b1 * X_avg
+  return b0, b1
+

引入具体的数据

1
2
X = [1.5, 0.8, 2.6, 1.0, 0.6, 2.8, 1.2, 0.9, 0.4, 1.3, 1.2, 2.0, 1.6, 1.8, 2.2]
+Y = [3.1, 1.9, 4.2, 2.3, 1.6, 4.9, 2.8, 2.1, 1.4, 2.4, 2.4, 3.8, 3.0, 3.4, 4.0]
+

调用函数计算出斜率与截距

1
2
3
b0, b1 = fitSLR(X, Y)
+print('w = ', b1)
+print('b = ', b0)
+

生成画板,画图图像

+
    +
  • 两点确定一条直线作出回归直线
  • +
  • plt.plot()'r'代表回归直线是红色的意思
  • +
+
+
1
2
3
4
5
6
7
8
9
10
11
# 生成画板
+plt.figure()
+# 画散点图
+plt.scatter(X, Y)
+X_min = min(X)
+X_max = max(X)
+Y_min = b0 + b1 * X_min
+Y_max = b0 + b1 * X_max
+
+plt.plot([X_min, X_max], [Y_min, Y_max], 'r')
+plt.show()
+

画出图像


完整代码:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import numpy as np
+from matplotlib import pyplot as plt
+
+
+def fitSLR(X, Y):
+    X_avg = np.mean(X)  # 求取括号内数组矩阵的均值
+    Y_avg = np.mean(Y)
+    n = len(X)  # 得到括号内列表长度即元素的个数
+    # 定义分子与分母
+    fen_zi = 0
+    fen_mu = 0
+    # 核心算法
+    for i in range(0, n):
+        fen_zi += (X[i] - X_avg) * (Y[i] - Y_avg)
+        fen_mu += (X[i] - X_avg) ** 2
+    b1 = fen_zi / float(fen_mu)  # 计算w,即直线的斜率
+    b0 = Y_avg - b1 * X_avg
+    return b0, b1
+
+
+# 定义X,Y列表
+X = [1.5, 0.8, 2.6, 1.0, 0.6, 2.8, 1.2, 0.9, 0.4, 1.3, 1.2, 2.0, 1.6, 1.8, 2.2]
+Y = [3.1, 1.9, 4.2, 2.3, 1.6, 4.9, 2.8, 2.1, 1.4, 2.4, 2.4, 3.8, 3.0, 3.4, 4.0]
+
+b0, b1 = fitSLR(X, Y)
+print('w = ', b1)
+print('b = ', b0)
+
+# 生成画板
+plt.figure()
+# 画散点图
+plt.scatter(X, Y)
+X_min = min(X)
+X_max = max(X)
+Y_min = b0 + b1 * X_min
+Y_max = b0 + b1 * X_max
+
+plt.plot([X_min, X_max], [Y_min, Y_max], 'r')
+plt.show()
+
文章作者: 希亚的西红柿
文章链接: http://blog.refrain.site/posts/20642/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
+ + + + \ No newline at end of file diff --git a/posts/2275c8/index.html b/posts/2275c8/index.html new file mode 100644 index 000000000..d1a431180 --- /dev/null +++ b/posts/2275c8/index.html @@ -0,0 +1,1418 @@ +深入了解Promise | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + + + +

深入了解Promise

本文主要讲解 Promise 的常用方法以及使用技巧,并实现如何手写一个 Promise。

+
+

Promise 的出现

回调地狱问题

在传统的 JS 编程中始终存在一个问题,即当当前回调函数的执行依赖于上一个回调函数的执行结果的时候,会形成回调函数层层嵌套的问题,严重影响代码的可读性与可维护性,这种现象一般称之为回调地狱

+
+

下面为示例代码,回调地狱的一个比较常见的情景为ajax请求,即下一个请求的是否发起依赖于上一个请求的结果。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let xhr = new XMLHttpRequest();
+xhr.open(
+  "get",
+  "https://v0.yiketianqi.com/api?unescape=1&version=v61&appid=82294778&appsecret=4PKVFula&city=%E5%8C%97%E4%BA%AC"
+);
+xhr.send();
+xhr.onreadystatechange = function () {
+  if (xhr.readyState === 4) {
+    if (xhr.status >= 200 && xhr.status < 300) {
+      console.log(xhr.responseText);
+
+      //伪代码....
+      let xhr = new XMLHttpRequest();
+      xhr.open("get", "http://www.xx.com?a" + xhr.responseText);
+      xhr.send();
+      xhr.onreadystatechange = function () {
+        if (xhr.readyState === 4) {
+          if (xhr.status >= 200 && xhr.status < 300) {
+            console.log(xhr.responseText);
+          }
+        }
+      };
+    }
+  }
+};
+

Promise 的出现

    +
  1. Promise是什么?其解决了什么问题?
    Promise 是异步编程的一种解决方案,比传统的解决方案回调函数更合理、更强大。ES6 将其写进了语言标准,统一了用法,原生提供了 Promise 对象。指定回调函数的方式也变得更加灵活易懂,也解决了异步回调地狱的问题旧方案是单纯使用回调函数,常见的异步操作有:定时器、fs 模块、ajax、数据库操作。从语法上说,Promise 是一个构造函数;从功能上说,Promise 对象用来封装一个异步操作并可以获取其成功/失败的结果值。
  2. +
  3. Promise设计的核心理念是什么?
      +
    • Promise的角度来说,是将状态改变与回调函数彻底区分开。
    • +
    • 从应用Promise的角度来说,以ajax请求数据为例,则是将数据请求与数据处理区分开。
    • +
    +
  4. +
+

Promise 的实际应用

案例 1:利用 promise 来进行读取文件操作

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//1.普通文件读取方式
+const fs = require("fs");
+
+//2.直接利用readfile来进行读取
+/* fs.readFile(__dirname + '/data.txt',(err,data)=>{
+    if(err) throw err;
+    console.log(data.toString());
+}) */
+
+//3.利用promise来实现文件的读取
+const p = new Promise((resolve, reject) => {
+  fs.readFile(__dirname + "/data.txt", (err, data) => {
+    if (err) {
+      reject(err);
+    } else {
+      resolve(data);
+    }
+  });
+});
+
+p.then(
+  (value) => {
+    console.log(value.toString());
+  },
+  (reason) => {
+    console.log(reason);
+  }
+);
+

案例 2:利用 promise 进行 ajax 请求

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<body>
+  <button>发送ajax请求</button>
+  <script>
+    //1.获取DOM元素对象
+    let btn = document.querySelector("button");
+    //2.绑定事件
+    btn.onclick = function () {
+      //3.创建promise实例对象
+      const p = new Promise((resolve, reject) => {
+        //4.创建ajax实例对象
+        const xhr = new XMLHttpRequest();
+        //5.打开请求
+        xhr.open(
+          "get",
+          "https://www.yiketianqi.com/free/day?appid=82294778&appsecret=4PKVFula&unescape=1"
+        );
+        //6.发送请求
+        xhr.send();
+        //7.利用onreadystatechange事件
+        xhr.onreadystatechange = function () {
+          //8.判断
+          if (xhr.readyState == 4) {
+            if (xhr.status == 200) {
+              resolve(xhr.responseText);
+            } else {
+              reject(xhr.response);
+            }
+          }
+        };
+      });
+      p.then(
+        (value) => {
+          console.log(JSON.parse(value));
+        },
+        (reason) => {
+          console.log("获取信息失败");
+        }
+      );
+    };
+  </script>
+</body>
+

案例 3:利用 promise 进行数据库操作

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
const mongoose = require("mongoose");
+
+new Promise((resolve, reject) => {
+  mongoose.connect("mongodb://127.0.0.1/project");
+  mongoose.connection.on("open", () => {
+    //连接成功的情况
+    resolve();
+  });
+
+  mongoose.connection.on("error", () => {
+    //连接失败的情况
+    reject();
+  });
+}).then(
+  (value) => {
+    //创建结构
+    const NoteSchema = new mongoose.Schema({
+      title: String,
+      content: String,
+    });
+    //创建模型
+    const NoteModel = mongoose.model("notes", NoteSchema);
+
+    //读取操作
+    NoteModel.find().then(
+      (value) => {
+        console.log(value);
+      },
+      (reason) => {
+        console.log(reason);
+      }
+    );
+  },
+  (reason) => {
+    console.log("连接失败");
+  }
+);
+

案例 4:封装一个函数,作用是读取文件

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const fs = require("fs");
+
+function ReadFileFun(path) {
+  return new Promise((resolve, reject) => {
+    fs.readFile(path, (err, data) => {
+      //判断
+      if (err) {
+        reject(err);
+      } else {
+        resolve(data);
+      }
+    });
+  });
+}
+
+ReadFileFun("./data.txt").then(
+  (value) => {
+    console.log(value.toString());
+  },
+  (reason) => {
+    console.log(reason);
+  }
+);
+
+

node 中的 promisify

+
    +
  • promisify (只能在 NodeJS 环境中使用)
  • +
  • promisifyutil 模块中的一个方法 utilnodeJS 的内置模块
  • +
  • 作用: 返回一个新的函数, 函数是 promise 风格的.
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
const util = require("util");
+const fs = require("fs");
+//通过 fs.readFile 创建一个新的函数
+const mineReadFile = util.promisify(fs.readFile);
+
+mineReadFile("./resource/2.html").then(
+  (value) => {
+    console.log(value.toString());
+  },
+  (reason) => {
+    console.log(reason);
+  }
+);
+
查看 promisify 的手写实现 +
+

看到 promisify 这个函数对其内部实现机制比较感兴趣,那我们就来手写一下。

  1. 首先promisify函数得返回一个函数,同时返回的这个函数应该返回一个Promise对象,根据这两点我们把函数的基本结构搭建起来。

    1
    2
    3
    4
    5
    const promisify = () => {
    +  return function () {
    +    return new Promise((resolve, reject) => {});
    +  };
    +};
  2. 利用返回的这个Promise对象,可以使用then方法进行回调的处理,即原来函数的回调决定这Promise状态的改变以及执行,所以需要将改变Promise对象的状态的回调函数传入参数中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /**
    + * @param {(...args) => void} func
    + * @returns {(...args) => Promise<any}
    + */
    +const promisify = (func) => {
    +  return function (...params) {
    +    return new Promise((resolve, reject) => {
    +      func.call(this, ...params, (err, data) => {
    +        if (err) reject(err);
    +        else resolve(data);
    +      });
    +    });
    +  };
    +};
+
+
+

Promise 的实例方法

then

注意点:通过then返回的promise的对象的状态由谁决定。

+
    +
  • 若返回非promise对象或者什么都不返回则,状态为fullfilled
  • +
  • 若函数执行过程中抛出错误,则状态为rejected
  • +
  • 若返回一个promise对象,则状态与返回的promise对象保持一致。
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const p = new Promise((resolve, reject) => {
+  resolve("ok");
+});
+
+let result = p.then(
+  (value) => {
+    throw "错误";
+  },
+  (reason) => {
+    console.log(reason);
+  }
+);
+
+console.log(result);
+

catch

能够穿透捕获Promise中的错误,其实相当于Promise.prototype.then(undefined, onRejected)

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let p = new Promise((resolve, reject) => {
+  //resolve('success');
+  reject("error");
+});
+
+p.catch((reason) => {
+  console.log(reason);
+});
+
+//then方法中不是必须传入两个参数,可以只传递成功时的回调函数
+//也可以单独使用catch来指定失败的回调函数
+
+//异常(错误)穿透
+//当如果有多个需要执行的成功时的回调函数,可以不需要每一次都写失败回调,可以统一最后利用catch
+//当如果promise对象的状态为reject的话,会一直向下穿透直到catch方法
+p.then((value) => {
+  console.log(value);
+})
+  .then((value) => {
+    console.log(value);
+  })
+  .catch((reason) => {
+    console.log(reason);
+  });
+

finally

finally 是在 ES9(ES2018)中新增的一个特性:表示无论 Promise 对象变成 fufilled 还是 rejected 状态,最终都会被执行。
finally 方法中的回调函数是不接受参数的,因为无论前面是 fulfilled 状态还是 rejected 状态, 它都是执行。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
const p = new Promise((resolve, reject) => {
+  // resolve('ok');
+  reject("error");
+});
+p.then((res) => {
+  console.log(res);
+})
+  .catch((err) => {
+    console.log(err);
+  })
+  .finally(() => {
+    console.log("finally");
+  });
+

Promise 类静态方法

resolve

    +
  1. 当参数不是Promise对象的时候,返回promise状态为fullfilled
  2. +
  3. 当参数是Promise对象的时候,返回的Promise实例的属性由该Promise实例决定。
  4. +
+
1
2
3
4
5
6
7
8
9
let p3 = Promise.resolve(
+  new Promise((resolve, reject) => {
+    resolve("success");
+  })
+);
+console.log(p3);
+
+let p4 = Promise.resolve(Promise.resolve(Promise.resolve("OK")));
+console.log(p4);
+

reject

始终返回状态为rejectedPromise对象

+
1
2
console.log(Promise.reject(123));
+console.log(Promise.reject(Promise.resolve("ok")));
+

all

接受一个Promise对象的数组,如果数组中的Promise对象的状态都是fullfilled,则返回的也是一个状态为fullfilled对象,PromiseResult的值为每个成功值的数组。若存在状态为rejected的对象则返回一个状态为rejectedPromise对象,PromiseResult为第一个状态为rejectedPromise对象。

+
1
2
3
4
5
6
7
let p1 = new Promise((resolve, reject) => {
+  resolve("ok");
+});
+let p2 = Promise.resolve("hello");
+let p3 = Promise.resolve("oh yeah");
+let result = Promise.all([p1, p2, p3]);
+console.log(result);
+

race

all方法类似都是接受一个Promise对象的数组,但其状态与结果由数组中最先改变的Promise对象决定。

+
1
2
3
4
5
6
7
8
9
let p1 = new Promise((resolve, reject) => {
+  setTimeout(() => {
+    resolve("ok");
+  }, 2000);
+});
+let p2 = Promise.resolve("success");
+let p3 = Promise.resolve("oh hou");
+let result = Promise.race([p1, p2, p3]);
+console.log(result);
+

allSettled

all方法类似,当数组内所有promise的状态都确定后执行成功的回调。基本上只有成功的回调。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function ajax(url) {
+  return new Promise((resolve, reject) => {
+    let xhr = new XMLHttpRequest();
+    xhr.open("get", url, true);
+    xhr.send();
+    xhr.onreadystatechange = function () {
+      if (xhr.readyState === 4) {
+        if (xhr.status >= 200 && xhr.status < 300) {
+          resolve(xhr.responseText);
+        } else {
+          reject(xhr.responseText);
+        }
+      }
+    };
+  });
+}
+

any

all方法正好相反,只要参数实例有一个变成 fulfilled 状态,包装实例就会变成 fulfiilled 状态;
如果所有参数实例都变成 rejected,包装实例就会变成 rejected 状态,结果为一个AggregateError对象。

+
1
2
3
4
5
6
7
8
9
10
let p1 = new Promise((resolve, reject) =>{" "}
+{setTimeout(() => {
+  resolve("ok");
+}, 1000)}
+) let p2 = new Promise((resolve, reject) => {setTimeout(() => {
+  resolve("okk");
+}, 2000)}) let p3 = new Promise((resolve, reject) => {setTimeout(() => {
+  reject("error");
+}, 3000)}) Promise.any([p1, p2, p3]).then(res => {console.log(res)}).catch(err
+=> {console.log("error")})
+

关键问题

中断 Promise 链

终止Promise链条,有且仅有一种方式,即返回一个状态为pendingpromise对象。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
new Promise((resolve, reject) => {
+  resolve(111);
+})
+  .then((value) => {
+    console.log(value);
+    console.log(222);
+    //
+    // return false;
+    // throw '出错啦';
+    //有且只有一种方式 返回一个pending状态的promise对象
+    return new Promise((resolve, reject) => {});
+  })
+  .then((value) => {
+    console.log(333);
+  })
+  .then((value) => {
+    console.log(444);
+  })
+  .catch((reason) => {
+    console.log(reason);
+  });
+

修改 Promise 的状态

1
2
3
4
5
6
7
8
9
10
11
12
13
//如何修改 promise 对象状态
+let p = new Promise((resolve, reject) => {
+  //1. resolve
+  // resolve('success');
+  //2. reject
+  // reject('error');
+  //3. 抛出错误 异常
+  // throw '出问题啦! 你说出这样的话  你没有良心!!';
+  // 状态的改变只有一次
+  resolve("ok");
+  reject("error");
+});
+console.log(p);
+

Promise 串联多个任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
new Promise((resolve, reject) => {
+  console.log(111);
+  reject();
+})
+  .then((value) => {
+    console.log(222);
+  })
+  .then((value) => {
+    console.log(value);
+  })
+  .then(
+    (value) => {
+      console.log(value);
+    },
+    (reason) => {
+      console.error(reason);
+    }
+  );
+

执行顺序分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
setTimeout(() => {
+  console.log("0");
+}, 0);
+new Promise((resolve, reject) => {
+  console.log("1");
+  resolve();
+})
+  .then(() => {
+    console.log("2");
+    new Promise((resolve, reject) => {
+      console.log("3");
+      resolve();
+    })
+      .then(() => {
+        console.log("4");
+      })
+      .then(() => {
+        console.log("5");
+      });
+  })
+  .then(() => {
+    console.log("6");
+  });
+
+new Promise((resolve, reject) => {
+  console.log("7");
+  resolve();
+}).then(() => {
+  console.log("8");
+});
+
+// 我们来分析一下这段代码执行结果的打印顺序,注意宏队列只会在微队列为空后才会执行
+// 1. 同步执行:1 7
+// 2. 异步队列中的宏队列与微队列出队列,进入执行栈执行:2 3 8
+// 3. 4 比 6 先进微队列,:4 6
+// 4. 5 出微队列后为空,宏队列开始执行:5 0
+

使用 JS 手写一个 promise

由于注释写的比较清除就不分步写了,注意Promise类的静态方法race等,接受的是一个可迭代对象,同时注意在写的时候所用到的技巧,foreach以及数组的原生方法。
值得注意的地方有:

+
    +
  • _resolve的单独处理,处理嵌套的Promise对象。
  • +
  • 静态方法接受可迭代对象的方法大致相同,但注意空数组的时候返回什么。
  • +
  • 注意原生实现的时候then方法不能使用箭头函数定义,会出现this指向无法指向实例对象的问题。
  • +
+
+

原生实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
// 首先使用一个自执行函数,避免全局污染
+((window) => {
+  // 注意这里不能使用箭头函数,箭头函数不能用作构造函数
+  function Promise(executor) {
+    this.PromiseState = "pending";
+    this.PromiseResult = undefined;
+    // 定义储存回调函数的数组
+    this.callbacks = [];
+    const _resolve = (value) => {
+      // 注意 Promise 的状态只能更改一次
+      if (this.PromiseState !== "pending") return;
+      // 这里需要对值是 promise 对象进行格外处理
+      if (value instanceof Promise) {
+        value.then(_resolve, _reject);
+      } else {
+        this.PromiseState = "fullfilled";
+        this.PromiseResult = value;
+        this.callbacks.forEach((callback) => {
+          callback.onResolved();
+        });
+      }
+    };
+    const _reject = (reason) => {
+      // 注意 Promise 的状态只能更改一次
+      if (this.PromiseState !== "pending") return;
+      this.PromiseState = "rejected";
+      this.PromiseResult = reason;
+      this.callbacks.forEach((callback) => {
+        callback.onRejected();
+      });
+    };
+    try {
+      executor(_resolve, _reject);
+    } catch (error) {
+      _reject(error);
+    }
+  }
+  Object.assign(Promise.prototype, {
+    // 在这里我们需要明确,then 方法需要返回一个 Promise 对象,并且需要对 onResolved 与 onRejected 是否是一个函数
+    then(onResolved, onRejected) {
+      if (!(onResolved instanceof Function)) onResolved = (value) => value;
+      if (!(onRejected instanceof Function))
+        onRejected = (reason) => {
+          throw reason;
+        };
+      return new Promise((resolve, reject) => {
+        // 抽离统一处理的方法
+        const __common = (callback) => {
+          // 通过 setTimeout 模拟异步回调的方法
+          setTimeout(() => {
+            // 还需要考虑到出错的情况
+            try {
+              // 注意我们现在需要通过这个的返回结果获取 then 方法返回的 Promise 的状态
+              const result = callback(this.PromiseResult);
+              if (result instanceof Promise) {
+                // 如果返回的是 Promise 对象,则状态由 Promise 对象决定,则将状态的改变手段,交予该对象的 then 的回调
+                result.then(resolve, reject);
+              } else {
+                resolve(result);
+              }
+            } catch (error) {
+              reject(error);
+            }
+          });
+        };
+        // 这里 then 方法返回了一个 Promise 对象,其状态应该是由 onResolved 或 onRejected 的执行结果决定的,这里我们首先先判断执行哪一个
+        if (this.PromiseState === "fullfilled") {
+          __common(onResolved);
+        } else if (this.PromiseState === "rejected") {
+          __common(onRejected);
+        }
+        // 同时,这里只考虑到,状态改变后执行回调的情况,没有考虑回调定义在状态改变之前的情况,这里我们来考虑 pending 的情况
+        else if (this.PromiseState === "pending") {
+          this.callbacks.push({
+            onResolved: () => __common(onResolved),
+            onRejected: () => __common(onRejected),
+          });
+        }
+      });
+    },
+    catch(onRejected) {
+      return this.then(undefined, onRejected);
+    },
+    finally(callback) {
+      // finally 方法是无论如何都会执行并返回一个等效的 Promise 对象
+      return this.then(
+        (value) => {
+          return Promise.resolve(callback()).then(() => value);
+        },
+        (reason) => {
+          return Promise.resolve(callback()).then(() => {
+            throw reason;
+          });
+        }
+      );
+    },
+  });
+
+  // 定义类身上的静态方法
+  // 定义 resolve 方法
+  Promise.resolve = (value) => {
+    return new Promise((resolve, reject) => {
+      if (value instanceof Promise) value.then(resolve, reject);
+      else resolve(value);
+    });
+  };
+  // 定义 reject 方法
+  Promise.reject = (reason) => {
+    return new Promise((resolve, reject) => {
+      reject(reason);
+    });
+  };
+  // 定义 all 方法
+  Promise.all = (iterable) => {
+    if (!iterable[Symbol.iterator]) {
+      throw new TypeError("Argument must be iterable");
+    }
+    return new Promise((resolve, reject) => {
+      // all 方法则为,当所有状态为 fullfilled 的才会返回成功,有 rejected 就会返回失败
+      // 将可迭代对象转换为数组
+      const promises = Array.from(iterable);
+      // 如果数组为空,直接返回一个已成功的Promise对象,值为一个空数组
+      if (promises.length === 0) {
+        return resolve([]);
+      }
+      const length = promises.length;
+      let arr = new Array(length);
+      let count = 0;
+      for (let i = 0; i < length; i++) {
+        if (promises[i] instanceof Promise) {
+          promises[i].then((value) => {
+            arr[i] = value;
+            count++;
+            if (count === length) resolve(arr);
+          }, reject);
+        } else {
+          arr[i] = promises[i];
+          count++;
+          if (count === length) resolve(arr);
+        }
+      }
+    });
+  };
+  Promise.race = (iterable) => {
+    if (!iterable[Symbol.iterator]) {
+      throw new TypeError("Argument must be iterable");
+    }
+    return new Promise((resolve, reject) => {
+      const promises = Array.from(iterable);
+      if (promises.length === 0) {
+        return;
+      }
+      for (let i = 0; i < promises.length; i++) {
+        if (promises[i] instanceof Promise) {
+          promises[i].then(resolve, reject);
+        } else {
+          resolve(promises[i]);
+        }
+      }
+    });
+  };
+  // promise.allSettled方法
+  Promise.allSettled = function (iterable) {
+    // 参数必须是一个可迭代对象
+    if (!iterable[Symbol.iterator]) {
+      throw new TypeError("Argument must be iterable");
+    }
+    // 返回一个新的promise对象
+    return new Promise((resolve, reject) => {
+      // 将可迭代对象转换为数组
+      const promises = Array.from(iterable);
+      // 如果数组为空,直接返回一个已完成的promise,值为一个空数组
+      if (promises.length === 0) {
+        resolve([]);
+        return;
+      }
+      // 定义一个结果数组,用来存放每个promise的状态和值或原因
+      const results = new Array(promises.length);
+      // 定义一个计数器,用来记录已完成的promise的数量
+      let count = 0;
+      // 遍历每个promise
+      promises.forEach((promise, index) => {
+        // 将非promise对象转换为promise对象
+        Promise.resolve(promise)
+          // 如果成功,将结果对象存入数组,并增加计数器
+          .then((value) => {
+            results[index] = { status: "fulfilled", value };
+            count++;
+            // 如果所有的promise都完成了,就返回一个已完成的promise,值为结果数组
+            if (count === promises.length) {
+              resolve(results);
+            }
+          })
+          // 如果失败,同样将结果对象存入数组,并增加计数器
+          .catch((reason) => {
+            results[index] = { status: "rejected", reason };
+            count++;
+            // 如果所有的promise都完成了,就返回一个已完成的promise,值为结果数组
+            if (count === promises.length) {
+              resolve(results);
+            }
+          });
+      });
+    });
+  };
+
+  // promise.any方法
+  Promise.any = function (iterable) {
+    // 参数必须是一个可迭代对象
+    if (!iterable[Symbol.iterator]) {
+      throw new TypeError("Argument must be iterable");
+    }
+    // 返回一个新的promise对象
+    return new Promise((resolve, reject) => {
+      // 将可迭代对象转换为数组
+      const promises = Array.from(iterable);
+      // 如果数组为空,直接返回一个已拒绝的promise,原因为一个空的AggregateError
+      if (promises.length === 0) {
+        reject(new AggregateError([]));
+        return;
+      }
+      // 定义一个错误数组,用来存放每个promise的失败原因
+      const errors = new Array(promises.length);
+      // 定义一个计数器,用来记录已拒绝的promise的数量
+      let count = 0;
+      // 遍历每个promise
+      promises.forEach((promise, index) => {
+        // 将非promise对象转换为promise对象
+        Promise.resolve(promise)
+          // 如果成功,直接返回一个已完成的promise,值为该promise的值
+          .then((value) => {
+            resolve(value);
+          })
+          // 如果失败,将失败原因存入错误数组,并增加计数器
+          .catch((reason) => {
+            errors[index] = reason;
+            count++;
+            // 如果所有的promise都拒绝了,就返回一个已拒绝的promise,原因为一个包含错误数组的AggregateError
+            if (count === promises.length) {
+              reject(new AggregateError(errors));
+            }
+          });
+      });
+    });
+  };
+  // 使用自定义的 Promise 替换掉默认的 Promise
+  window.Promise = Promise;
+})(window);
+

class 实现

原生的都写出来了只能说class版本有手就行,注意class field写法的方法仍然属于实例属性,class field的主要设计目的就是为了解决在类里面使用this的种种不便,精简了部分代码。

+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
class Promise {
+  constructor(executor) {
+    this.PromiseState = "pending";
+    this.PromiseResult = undefined;
+    this.callbacks = [];
+    const _resolve = (value) => {
+      if (this.PromiseState !== "pending") return;
+      if (value instanceof Promise) value.then(_resolve, _reject);
+      else {
+        this.PromiseState = "fullfilled";
+        this.PromiseResult = value;
+        this.callbacks.forEach((callback) => callback.onResolved());
+      }
+    };
+    const _reject = (reason) => {
+      if (this.PromiseState !== "pending") return;
+      this.PromiseState = "rejected";
+      this.PromiseResult = reason;
+      this.callbacks.forEach((callback) => callback.onRejected());
+    };
+    try {
+      executor(_resolve, _reject);
+    } catch (error) {
+      _reject(error);
+    }
+  }
+  then(onResolved, onRejected) {
+    if (!(onResolved instanceof Function)) onResolved = (value) => value;
+    if (!(onRejected instanceof Function))
+      onRejected = (reason) => {
+        throw reason;
+      };
+    return new Promise((resolve, reject) => {
+      const _common = (callback) => {
+        setTimeout(() => {
+          try {
+            const result = callback(this.PromiseResult);
+            if (result instanceof Promise) result.then(resolve, reject);
+            else resolve(result);
+          } catch (error) {
+            reject(error);
+          }
+        });
+      };
+      if (this.PromiseState === "fullfilled") {
+        _common(onResolved);
+      } else if (this.PromiseState === "rejected") {
+        _common(onRejected);
+      } else if (this.PromiseState === "pending") {
+        this.callbacks.push({
+          onResolved: () => _common(onResolved),
+          onRejected: () => _common(onRejected),
+        });
+      }
+    });
+  }
+  catch(onRejected) {
+    return this.then(undefined, onRejected);
+  }
+  finally(callback) {
+    return this.then(
+      (value) => Promise.resolve(callback()).then(() => value),
+      (reason) =>
+        Promise.resolve(callback()).then(() => {
+          throw reason;
+        })
+    );
+  }
+  static resolve(value) {
+    return new Promise((resolve, reject) => {
+      if (value instanceof Promise) value.then(resolve, reject);
+      else {
+        resolve(value);
+      }
+    });
+  }
+  static reject(reason) {
+    return new Promise((resolve, reject) => {
+      reject(reason);
+    });
+  }
+  static all(iterables) {
+    if (!iterables[Symbol.iterator]) {
+      throw new TypeError(
+        `TypeError: ${typeof iterables} ${iterables} is not iterable (cannot read property Symbol(Symbol.iterator))`
+      );
+    }
+    return new Promise((resolve, reject) => {
+      const promises = Array.from(iterables);
+      if (promises.length === 0) return resolve([]);
+      const results = new Array(promises.length);
+      let count = 0;
+      promises.forEach((promise, index) => {
+        Promise.resolve(promise).then((value) => {
+          results[index] = value;
+          count++;
+          if (count === promises.length) resolve(results);
+        }, reject);
+      });
+    });
+  }
+  static race(iterables) {
+    if (!iterables[Symbol.iterator]) {
+      throw new TypeError(
+        `TypeError: ${typeof iterables} ${iterables} is not iterable (cannot read property Symbol(Symbol.iterator))`
+      );
+    }
+    return new Promise((resolve, reject) => {
+      const promises = Array.from(iterables);
+      promises.forEach((promise) => {
+        Promise.resolve(promise).then(resolve, reject);
+      });
+    });
+  }
+  static allSettled(iterables) {
+    if (!iterables[Symbol.iterator]) {
+      throw new TypeError(
+        `TypeError: ${typeof iterables} ${iterables} is not iterable (cannot read property Symbol(Symbol.iterator))`
+      );
+    }
+    return new Promise((resolve, reject) => {
+      const promises = Array.from(iterables);
+      if (promises.length === 0) return resolve([]);
+      const results = new Array(promises.length);
+      let count = 0;
+      promises.forEach((promise, index) => {
+        Promise.resolve(promise).then(
+          (value) => {
+            results[index] = { status: "fullfilled", value };
+            count++;
+            if (count === promises.length) resolve(results);
+          },
+          (reason) => {
+            results[index] = { status: "rejected", reason };
+            count++;
+            if (count === promises.length) resolve(results);
+          }
+        );
+      });
+    });
+  }
+  static any(iterables) {
+    if (!iterables[Symbol.iterator]) {
+      throw new TypeError(
+        `TypeError: ${typeof iterables} ${iterables} is not iterable (cannot read property Symbol(Symbol.iterator))`
+      );
+    }
+    return new Promise((resolve, reject) => {
+      const promises = Array.from(iterables);
+      if (promises.length === 0) return reject(new AggregateError([]));
+      const results = new Array(promises.length);
+      let count = 0;
+      promises.forEach((promise, index) => {
+        Promise.resolve(promise).then(resolve, (reason) => {
+          results[index] = reason;
+          count++;
+          if (count === promises.length) reject(new AggregateError(results));
+        });
+      });
+    });
+  }
+}
+
文章作者: 希亚的西红柿
文章链接: http://blog.refrain.site/posts/2275c8/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
+ + + + \ No newline at end of file diff --git a/posts/2afbd983/index.html b/posts/2afbd983/index.html new file mode 100644 index 000000000..251a62372 --- /dev/null +++ b/posts/2afbd983/index.html @@ -0,0 +1,914 @@ +Intersection Observer | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + + + +

Intersection Observer

早就想好好总结一下 Intersection Observer 这个 API,苦于一直没有时间,今天就来好好总结一下这个 API。

+
+

本篇文章将介绍现代 WebAPI Intersection Observer(元素交集观察器)。

+
+

背景

交叉观察器 API(Intersection Observer API)提供了一种异步检测目标元素与祖先元素或顶级文档的视口相交情况变化的方法。
过去,要检测一个元素是否可见或者两个元素是否相交并不容易,很多解决办法不可靠或性能很差。然而,随着互联网的发展,这种需求却与日俱增,比如,下面这些情况都需要用到相交检测:

+
    +
  • 在页面滚动时“懒加载”图像或其他内容。
  • +
  • 实现“无限滚动”网站,在滚动过程中加载和显示越来越多的内容,这样用户就不必翻页了。
  • +
  • 报告广告的可见度,以便计算广告收入。
  • +
  • 根据用户是否能看到结果来决定是否执行任务或动画进程。
  • +
+

交叉观察器 API 可令代码注册一个回调函数,当特定元素进入或退出与另一元素(或视口)的交集时,或者当两个元素之间的交集发生指定变化时,该函数就会被执行。这样,网站就不再需要在主线程上做任何事情来监视这种元素交集,浏览器也可以根据自己的需要优化交集管理。

+

由于该方法是异步的,所以不会有Element.getBoundingClientRect()等方法的性能问题。

+
+

基本使用

初始化对象IntersectionObserver对象。

+
1
const observer = new IntersectionObserver(callback, options);
+

可以看到该配置函数有两个配置项:callbackoptions。我们分别来探讨这两个选项有什么作用:

+
    +
  1. options
    options对象有三个配置选项。

    +
      +
    • root:用作视口的元素,用于检查目标的可见性。必须是目标的祖先。如果未指定或为 null,则默认为浏览器视口。
    • +
    • rootMargin:根周围的边距。其值可以类似于 CSS margin 属性,例如 “10px 20px 30px 40px”(上、右、下、左)。这些值可以是百分比。在计算交叉点之前,这组值用于增大或缩小根元素边框的每一侧。默认值为全零。下面会详细讲解。
    • +
    • threshold:一个数字或一个数字数组,表示目标可见度达到多少百分比时,观察器的回调就应该执行。如果只想在能见度超过 50% 时检测,可以使用 0.5 的值。如果希望每次能见度超过 25% 时都执行回调,则需要指定数组 [0, 0.25, 0.5, 0.75, 1]。默认值为 0(这意味着只要有一个像素可见,回调就会运行)。值为 1.0 意味着在每个像素都可见之前,阈值不会被认为已通过。
    • +
    +
    1
    2
    3
    4
    5
    const options = {
    +  root: document.querySelector("#scrollArea"),
    +  rootMargin: "0px",
    +  threshold: 1.0,
    +};
    +
  2. +
  3. callback
    callback作为交集的回调函数,执行特定的行为。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    const callback = (entries, observer) => {
    +  entries.forEach((entry) => {
    +    // 每个条目描述一个目标元素观测点的交叉变化:
    +    //   entry.boundingClientRect
    +    //   entry.intersectionRatio
    +    //   entry.intersectionRect
    +    //   entry.isIntersecting
    +    //   entry.rootBounds
    +    //   entry.target
    +    //   entry.time
    +  });
    +};
    +
    +// 定义回调函数
    +const callback = (entries, observer) => {
    +  // 遍历所有的交叉记录
    +  for (let entry of entries) {
    +    // 如果目标元素和根元素有交叉
    +    if (entry.isIntersecting) {
    +      // 打印交叉比例
    +      console.log(entry.intersectionRatio);
    +      // 停止观察
    +      observer.unobserve(entry.target);
    +    }
    +  }
    +};
    +

    请留意,你注册的回调函数将会在主线程中被执行。所以该函数执行速度要尽可能的快。如果需要执行任何耗时的操作,请使用 Window.requestIdleCallback()

    +
    +
  4. +
+

配置项详解

root(根元素)

在跟踪元素与容器的交集之前,我们需要知道容器是什么。这个容器就是交集根,或根元素。它可以是文档中作为要观察元素的祖先的特定元素,也可以是 null,即使用文档的视口作为容器。

注意一般视口用的较多,由于大多数子元素一开始就相交,所以很少使用。判断与父元素相交。

+

+

threshold(阈值)

交叉观察器 API 使用阈值,而不是报告目标元素可见度的每一个微小变化。创建观察器时,可以提供一个或多个数值,代表目标元素可见度的百分比。然后,API 只报告超过这些阈值的可见性变化。

+

例如,如果希望每次目标元素的可见度向后或向前越过每个 25% 的标记时都能得到通知,可以在创建观察器时指定数组 [0, 0.25, 0.5, 0.75, 1] 作为阈值列表。

+

rootMargin

给根元素添加margin边距实现收缩的效果
正值矩形向外扩张,负值矩形向内收缩。

+

结合下面这个例子进行理解

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<!DOCTYPE html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Document</title>
+    <style>
+      .red-box {
+        width: 50px;
+        height: 50px;
+        background-color: red;
+        position: absolute;
+        left: 200px;
+      }
+      .outer {
+        width: 100px;
+        height: 100px;
+        background-color: cyan;
+        position: relative;
+      }
+      * {
+        margin: 0;
+        padding: 0;
+        box-sizing: border-box;
+      }
+      body {
+        width: 7000px;
+        height: 7000px;
+        padding-top: 2000px;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="outer"><div class="red-box"></div></div>
+
+    <script>
+      // 创建一个交叉观察器
+      let observer = new IntersectionObserver(callback, {
+        root: document.querySelector(".outer"), // 根元素是视口
+        rootMargin: "100px -50px 100px 100px", // 根元素的边界框偏移量
+        threshold: 0, // 交叉比例阈值
+      });
+
+      // 选择目标元素
+      let target = document.querySelector(".red-box");
+
+      // 开始观察目标元素
+      observer.observe(target);
+
+      // 定义回调函数
+      function callback(entries, observer) {
+        console.log(1);
+        // 遍历所有的交叉记录
+        for (let entry of entries) {
+          console.log(2);
+          // 如果目标元素和根元素有交叉
+          if (entry.isIntersecting) {
+            // 打印交叉比例
+            console.log(entry.intersectionRatio);
+            // 停止观察
+            // observer.unobserve(entry.target);
+          }
+        }
+      }
+    </script>
+  </body>
+</html>
+

callback

    +
  1. 页面渲染后会执行一次,无论是否相交。
  2. +
  3. 每次进入和离开各执行一次。
  4. +
+

API

实例属性

同上options选项。

+

实例方法

    +
  1. disconnect:通知所有对象的监视。
    1
    observer.disconnect();
  2. +
  3. observe:观察一个对象。
  4. +
  5. unobserve:停止观察。
  6. +
  7. takeRecords
    takeRecords 方法的用途是在停止观察之前获取所有未处理的交叉记录,以便在回调函数中处理它们。这个方法返回一个 IntersectionObserverEntry 对象数组,每个对象包含目标元素与根每次的相交信息。
    如果你使用回调来监视这些更改,则无需调用此方法。调用此方法会清除挂起的相交状态列表,因此不会运行回调。

    比较鸡肋很少用到。

    +
    +
  8. +
+

IntersectionObserverEntry

属性:

+
    +
  • boundingClientRect:返回目标元素的平面矩形。
  • +
  • intersectionRatio:相交多少。
  • +
  • intersectionRect:相交的部分的平面矩形。
  • +
  • isIntersecting:是否相交。
  • +
  • rootBounds:使用rootMargin计算后的平面矩阵。
  • +
  • target:目标元素。
  • +
  • time:交集状态发生改变的时候的时间戳。
  • +
+

基本用例

    +
  1. 图片懒加载

    +
      +
    • 工具函数

      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      import { ref } from "vue";
      +const useIntersectionObserver = (
      +  target: HTMLElement,
      +  callback: (isIntersecting: boolean) => void
      +): (() => void) => {
      +  const observer = ref<IntersectionObserver | null>(null);
      +
      +  const handleIntersect: IntersectionObserverCallback = (entries) => {
      +    // console.log('test')
      +    const isIntersecting = entries[0].isIntersecting;
      +    callback(isIntersecting);
      +  };
      +
      +  const observeElement = () => {
      +    if (target && observer.value) {
      +      observer.value.observe(target);
      +    }
      +  };
      +
      +  const unobserveElement = () => {
      +    if (target && observer.value) {
      +      observer.value.unobserve(target);
      +    }
      +  };
      +
      +  const createObserver = () => {
      +    if (target) {
      +      observer.value = new IntersectionObserver(handleIntersect, {
      +        // 配置对象,指定根元素为视口,阈值为0.1
      +        root: null,
      +        threshold: 0.1,
      +      });
      +      observeElement();
      +    }
      +  };
      +
      +  createObserver();
      +
      +  return unobserveElement;
      +};
      +
      +export default useIntersectionObserver;
      +
    • +
    • 指令封装

      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      export const lazyPlugin: Plugin = {
      +  install(app: App) {
      +    app.directive("img-lazy", {
      +      mounted(el, binding) {
      +        const stop = useIntersectionObserver(el, (isIntersecting) => {
      +          if (isIntersecting) {
      +            // console.log('test')
      +            el.src = binding.value;
      +            stop();
      +          }
      +        });
      +      },
      +    });
      +  },
      +};
      +
    • +
    +
  2. +
  3. 无限滚动
    底层元素进入视口就开始加载

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    <!DOCTYPE html>
    +<html lang="en">
    +  <head>
    +    <meta charset="UTF-8" />
    +    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    +    <title>Infinite Scrolling Example</title>
    +    <style>
    +      .container {
    +        max-width: 800px;
    +        margin: auto;
    +      }
    +
    +      .post {
    +        border: 1px solid gray;
    +        padding: 20px;
    +        margin: 20px;
    +      }
    +
    +      .loading {
    +        text-align: center;
    +        font-size: 24px;
    +      }
    +    </style>
    +  </head>
    +  <body>
    +    <h1>Infinite Scrolling Example</h1>
    +    <!-- 创建一个容器元素,用来存放内容 -->
    +    <div class="container">
    +      <!-- 创建一个占位元素,用来触发无限滚动 -->
    +      <div id="sentinel"></div>
    +      <!-- 创建一个加载提示元素 -->
    +      <div class="loading">Loading...</div>
    +    </div>
    +
    +    <script>
    +      // 获取容器元素和占位元素
    +      const container = document.querySelector(".container");
    +      const sentinel = document.querySelector("#sentinel");
    +
    +      // 定义一个变量,用来存储当前加载的页数
    +      let page = 1;
    +
    +      // 定义一个函数,用来模拟从服务器获取数据
    +      function getData(page) {
    +        // 返回一个Promise对象,用来异步处理数据
    +        return new Promise((resolve, reject) => {
    +          // 使用setTimeout模拟网络延迟
    +          setTimeout(() => {
    +            // 使用fetch API请求一个随机用户生成器的API,传入页数参数
    +            fetch(`https://randomuser.me/api/?page=${page}&results=10`)
    +              .then((response) => response.json()) // 把响应转换为JSON格式
    +              .then((data) => resolve(data.results)) // 把数据传给resolve函数
    +              .catch((error) => reject(error)); // 如果出错,把错误传给reject函数
    +          }, 1000);
    +        });
    +      }
    +
    +      // 定义一个函数,用来渲染数据到页面上
    +      function renderData(data) {
    +        // 遍历数据数组
    +        data.forEach((item) => {
    +          // 创建一个div元素,用来存放每个用户的信息
    +          const post = document.createElement("div");
    +          // 给div元素添加post类名
    +          post.classList.add("post");
    +          // 给div元素添加内容,使用模板字符串插入用户的姓名、邮箱和头像等信息
    +          post.innerHTML = `
    +            <h2>${item.name.first} ${item.name.last}</h2>
    +            <p>${item.email}</p>
    +            <img src="${item.picture.large}" alt="${item.name.first}">
    +            `;
    +          // 把div元素插入到容器元素的最后一个子元素之前,也就是占位元素之前
    +          container.insertBefore(post, sentinel);
    +        });
    +      }
    +
    +      // 创建一个IntersectionObserver对象,传入一个回调函数和一个配置对象
    +      const observer = new IntersectionObserver(
    +        (entries) => {
    +          // 如果占位元素与视口相交
    +          if (entries[0].isIntersecting) {
    +            // 调用getData函数,传入当前的页数,获取数据
    +            getData(page)
    +              .then((data) => {
    +                // 调用renderData函数,渲染数据到页面上
    +                renderData(data);
    +                // 页数加一,准备下一次加载
    +                page++;
    +              })
    +              .catch((error) => {
    +                // 如果出错,打印错误信息
    +                console.error(error);
    +              });
    +          }
    +        },
    +        {
    +          // 配置对象,指定根元素为视口,阈值为1
    +          root: null,
    +          threshold: 1,
    +        }
    +      );
    +
    +      // 调用observe方法,传入占位元素,开始观察它
    +      observer.observe(sentinel);
    +    </script>
    +  </body>
    +</html>
    +
    无限滚动的另一种方式就是触底加载 +
    +
    • scrollHeight 是一个元素内容高度的度量,包含由于溢出导致的视图中不可见内容。也就是说,如果一个元素的内容超出了它的可视区域,那么它的 scrollHeight 就会大于它的可视区域的高度。scrollHeight 的值等于元素的最小高度,使得所有的内容都能在不使用垂直滚动条的情况下显示在视图中。scrollHeight 的计算方式和 clientHeight 相同:它包含了元素的内边距,但不包括边框、外边距或水平滚动条(如果有的话)。它也可以包括伪元素(如 ::before::after)的高度。

    • clientHeight 是元素内部的高度(单位像素),包含内边距,但不包括水平滚动条、边框和外边距。也就是说,它是元素的可视区域的高度。如果元素的内容没有超出可视区域,那么它的 clientHeight 就等于它的 scrollHeight

    • scrollTop 是获取或设置一个元素的内容垂直滚动的像素数。也就是说,它是元素的内容顶部和元素的可视区域顶部之间的距离。当元素的内容没有滚动时,它的 scrollTop 为 0。当元素的内容向下滚动时,它的 scrollTop 会增加,直到滚动到内容的底部。

    这三个属性的关系可以用下面的公式表示:

    • 当元素的内容没有滚动时,scrollTop = 0clientHeight = scrollHeight
    • 当元素的内容向下滚动时,scrollTop > 0clientHeight < scrollHeight
    • 当元素的内容滚动到底部时,scrollTop + clientHeight = scrollHeight

    这三个属性可以用来实现一些常见的功能,例如:

    • 判断一个元素是否可以滚动,可以检查它的 scrollHeight 是否大于它的 clientHeight
    • 判断一个元素是否已经滚动到底部,可以检查它的 scrollTop + clientHeight 是否等于或接近它的 scrollHeight
    • 判断一个用户是否已经阅读了一个文本,可以检查一个文本框的 scrollTop + clientHeight 是否等于它的 scrollHeight
    +
    +
    +
  4. +
+
文章作者: 希亚的西红柿
文章链接: http://blog.refrain.site/posts/2afbd983/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
+ + + + \ No newline at end of file diff --git a/posts/300a8ca5/index.html b/posts/300a8ca5/index.html new file mode 100644 index 000000000..e9cf800c4 --- /dev/null +++ b/posts/300a8ca5/index.html @@ -0,0 +1,1160 @@ +虚拟列表 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + + +

虚拟列表

唔~,好久没有更新博文了,一年前的这个时候用 Vue 实现过一版虚拟列表,不过现在已经忘光光了,故写一篇博文帮忙回忆与巩固一下虚拟列表的知识。

+
+

前言

本文对虚拟列表出现的原因与解决的问题不再介绍,着重讲解虚拟列表实现的思路以及原理。

+
+

技术栈

    +
  • React
  • +
  • Vite
  • +
  • TS
  • +
+

预览链接

+

问题分析

基本思路

    +
  1. 虚拟列表的基本结构:

    +
      +
    • 容器层(container):整个虚拟列表的包裹层,滚动事件是在这个包裹层出发的。
    • +
    • 幻影层(phantom):容器层的子元素,用于撑开整个容器,获得原生的滚动条展示,不渲染内容。
    • +
    • 内容层(content):容器层的子元素,用于渲染列表内容,列表元素动态变化。
    • +
    +
  2. +
  3. 虚拟列表的滚动效果实现:
    首先我们知道,虚拟列表的视口的渲染元素是动态增减的,那么其平滑的滚动效果是如何出现的?这里有一个误区需要注意,虚拟列表的滚动效果就是原生的滚动效果,虚拟列表并不参与scrollTop属性的设置,而是通过CSStransform属性来实现的。
    这里有两个基本点需要了解

    +
      +
    1. 虚拟列表对小于等于元素高度的滚动是直接体现的原生的滚动效果。
    2. +
    3. 虚拟列表对大于元素高度的滚动是通过CSStransform属性来实现的,具体体现为元素消失在视口的时候,会对内容层的transform属性增减一个元素的高度,实现内容与滚动的一致性。
    4. +
    +
  4. +
+

细节分析

    +
  1. 容器高度分析
      +
    1. 容器层的高度:容器层的高度由幻影层的高度撑开,幻影层的高度依赖于需要渲染数据的总量与高度。
    2. +
    3. 渲染区域的高度:即视口高度,由元素的clientHeight获取。
    4. +
    +
  2. +
  3. 渲染的数据的数量
      +
    1. 渲染的数据的数量:渲染的数据的数量由渲染区域的高度与元素的高度计算得出。
    2. +
    3. 总数据的数量:即数据的长度。
    4. +
    +
  4. +
  5. 可视数据索引的计算方式
      +
    1. 开始索引:开始索引的计算方式为Math.floor(scrollTop / itemHeight)
    2. +
    3. 结束索引:开始索引加上可视渲染数据的个数。
    4. +
    +
  6. +
+

what can i say, 多说无益,下面直接上代码。

+
+

代码实现

虚拟列表的基本结构

    +
  1. 首先搭建虚拟列表的基本结构

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    const VirtualList1: React.FC<VirtualList1Props> = (props) => {
    +  <div
    +    ref={list}
    +    className="infinite-list-container"
    +    onScroll={handleScroll}
    +  >
    +    {/* 最外层容器,获取 scrollTop 属性,并监听滚动事件 */}
    +    <div
    +      className="infinite-list-phantom"
    +      style={{ height: `${listHeight}px` }}
    +    ></div>
    +    {/* 用于将最外层容器的高度撑开,获取滚动效果 */}
    +    <div
    +      className="infinite-list"
    +      style={{ transform: `translateY(${startOffset}px` }}
    +    >
    +      {/* 实际的渲染层,动态增减元素渲染,减少过多 dom 元素渲染带来的性能损耗 */}
    +      {visibleData.map((item) => (
    +        <div
    +          className="infinite-list-item"
    +          style={{ height: itemSize, lineHeight: itemSize + 'px' }}
    +          key={item.id}
    +        >
    +          {item.value}
    +        </div>
    +      ))}
    +    </div>
    +  </div>;
    +};
    +
  2. +
  3. 声名需要用到的变量

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    // 原数据与列表项的高度
    +const { listData = [], itemSize = 200 } = props;
    +// 视口的高度
    +const [screenHeight, setSceenHeight] = useState(0);
    +// 渲染数据起始索引
    +const [startIndex, setStartIndex] = useState(0);
    +// 渲染数据结束索引
    +const [endIndex, setEndIndex] = useState(0);
    +// 实际渲染数据的 2D 位移偏移量
    +const [startOffset, setStartOffset] = useState(0);
    +
    +// 最外层容器,获取 scrollTop 属性,并监听滚动事件
    +const list = useRef<HTMLDivElement>(null);
    +
    +// 内层撑开外层容器的高度,由数据量和元素高度决定
    +const listHeight = useMemo(
    +  () => listData.length * itemSize,
    +  [listData.length, itemSize]
    +);
    +// 可视区域渲染元素数据的数量,由视口高度与元素高度决定
    +const visibleCount = useMemo(
    +  () => Math.ceil(screenHeight / itemSize),
    +  [screenHeight, itemSize]
    +);
    +// 实际渲染数据的列表,由总数据与起始索引与结束索引决定
    +const visibleData = useMemo(
    +  () => listData.slice(startIndex, Math.min(listData.length, endIndex)),
    +  [startIndex, endIndex, listData]
    +);
    +
  4. +
  5. 处理元素渲染的副作用

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // 保证 endIndex 与 startIndex的值同步
    +useEffect(() => {
    +  setEndIndex(startIndex + visibleCount);
    +}, [visibleCount, startIndex]);
    +// 在视口发生 Resize 事件的时候需要重新设置高度,出发 visibleCount 数量的变化
    +useEffect(() => {
    +  const element = list.current;
    +  setSceenHeight(element?.clientHeight ?? 0);
    +  const resizeObserver = new ResizeObserver((entries) => {
    +    for (const entry of entries) {
    +      setSceenHeight(entry.contentRect.height);
    +    }
    +  });
    +
    +  if (element) {
    +    resizeObserver.observe(element);
    +  }
    +
    +  return () => {
    +    if (element) {
    +      resizeObserver.unobserve(element);
    +    }
    +  };
    +}, []);
    +
  6. +
  7. 监听滚动事件

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    const handleScroll = () => {
    +  // 获取外部元素的 scrollTop 属性
    +  const scrollTop = list.current?.scrollTop ?? 0;
    +  // 计算出当前可视数据的起始 index ,注意需要向下取整,由于元素的不完全展示
    +  const startIndexValue = Math.floor(scrollTop / itemSize);
    +  setStartIndex(startIndexValue);
    +  // 小于等于元素高度的滚动位移的体现由原生的 scrollTop 属性体现,反之由内容元素的 2D 位移与 scrollTop属性共同实现
    +  setStartOffset(scrollTop - (scrollTop % itemSize));
    +};
    +
  8. +
  9. 完整代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    import React, { useEffect, useMemo, useRef, useState } from 'react';
    +import './index.css';
    +
    +export interface ItemType {
    +  id: number;
    +  value: number;
    +}
    +interface VirtualList1Props {
    +  listData: ItemType[];
    +  itemSize: number;
    +}
    +
    +const VirtualList1: React.FC<VirtualList1Props> = (props) => {
    +  // 原数据与列表项的高度
    +  const { listData = [], itemSize = 200 } = props;
    +  // 视口的高度
    +  const [screenHeight, setSceenHeight] = useState(0);
    +  // 渲染数据起始索引
    +  const [startIndex, setStartIndex] = useState(0);
    +  // 渲染数据结束索引
    +  const [endIndex, setEndIndex] = useState(0);
    +  // 实际渲染数据的 2D 位移偏移量
    +  const [startOffset, setStartOffset] = useState(0);
    +
    +  // 最外层容器,获取 scrollTop 属性,并监听滚动事件
    +  const list = useRef<HTMLDivElement>(null);
    +
    +  // 内层撑开外层容器的高度,由数据量和元素高度决定
    +  const listHeight = useMemo(
    +    () => listData.length * itemSize,
    +    [listData.length, itemSize]
    +  );
    +  // 可视区域渲染元素数据的数量,由视口高度与元素高度决定
    +  const visibleCount = useMemo(
    +    () => Math.ceil(screenHeight / itemSize),
    +    [screenHeight, itemSize]
    +  );
    +  // 实际渲染数据的列表,由总数据与起始索引与结束索引决定
    +  const visibleData = useMemo(
    +    () => listData.slice(startIndex, Math.min(listData.length, endIndex)),
    +    [startIndex, endIndex, listData]
    +  );
    +
    +  // 保证 endIndex 与 startIndex的值同步
    +  useEffect(() => {
    +    setEndIndex(startIndex + visibleCount);
    +  }, [visibleCount, startIndex]);
    +  // 在视口发生 Resize 事件的时候需要重新设置高度,出发 visibleCount 数量的变化
    +  useEffect(() => {
    +    const element = list.current;
    +    setSceenHeight(element?.clientHeight ?? 0);
    +    const resizeObserver = new ResizeObserver((entries) => {
    +      for (const entry of entries) {
    +        setSceenHeight(entry.contentRect.height);
    +      }
    +    });
    +
    +    if (element) {
    +      resizeObserver.observe(element);
    +    }
    +
    +    return () => {
    +      if (element) {
    +        resizeObserver.unobserve(element);
    +      }
    +    };
    +  }, []);
    +
    +  const handleScroll = () => {
    +    // 获取外部元素的 scrollTop 属性
    +    const scrollTop = list.current?.scrollTop ?? 0;
    +    // 计算出当前可视数据的起始 index ,注意需要向下取整,由于元素的不完全展示
    +    const startIndexValue = Math.floor(scrollTop / itemSize);
    +    setStartIndex(startIndexValue);
    +    // 小于等于元素高度的滚动位移的体现由原生的 scrollTop 属性体现,反之由内容元素的 2D 位移与 scrollTop属性共同实现
    +    setStartOffset(scrollTop - (scrollTop % itemSize));
    +  };
    +
    +  return (
    +    <div
    +      ref={list}
    +      className="infinite-list-container"
    +      onScroll={handleScroll}
    +    >
    +      {/* 最外层容器,获取 scrollTop 属性,并监听滚动事件 */}
    +      <div
    +        className="infinite-list-phantom"
    +        style={{ height: `${listHeight}px` }}
    +      ></div>
    +      {/* 用于将最外层容器的高度撑开,获取滚动效果 */}
    +      <div
    +        className="infinite-list"
    +        style={{ transform: `translateY(${startOffset}px` }}
    +      >
    +        {/* 实际的渲染层,动态增减元素渲染,减少过多 dom 元素渲染带来的性能损耗 */}
    +        {visibleData.map((item) => (
    +          <div
    +            className="infinite-list-item"
    +            style={{ height: itemSize, lineHeight: itemSize + 'px' }}
    +            key={item.id}
    +          >
    +            {item.value}
    +          </div>
    +        ))}
    +      </div>
    +    </div>
    +  );
    +};
    +
    +export default VirtualList1;
    +
  10. +
+

对不定高元素的渲染的处理

在上面的虚拟列表的实现中我们假定了列表项的高度是固定的,但在很多场景中,由于元素的内容高度与宽度不定,可能会造成列表元素的高度的不一致性,这需要一种能支持动态高度的虚拟列表。

+
+

实现动态虚拟列表的难点,在于如何正确的计算起始索引与结束索引并正确的展示元素。

+
+

这里我们为了实现一个动态的虚拟列表,采用现行用预估高度渲染,然后渲染后动态更新高度的手段。
这里核心的一点在于,在滚动下滚的时候动态更新高度数组,上滚的时候就可以正确的计算出起始索引与结束索引,从而正常展示元素。下面上代码

+
    +
  1. 获取实际渲染元素列表的 DOM 实例,用于动态更新元素的高度

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const items = useRef<HTMLDivElement>(null);
    +
    +<div
    +  ref={items}
    +  className="infinite-list"
    +  style={{ transform: `translateY(${startOffset}px)` }}
    +>
    +  {visibleData.map((item) => (
    +    <div className="infinite-list-item" id={item.id} key={item.id}>
    +      {item.value}
    +    </div>
    +  ))}
    +</div>;
    +
  2. +
  3. 维护positions数组,并初始化。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const [positions, setPositions] = useState<PositionType[]>([]);
    +
    +useEffect(
    +  () =>
    +    setPositions(
    +      listData.map((_, index) => ({
    +        index,
    +        height: estimatedItemSize,
    +        top: index * estimatedItemSize,
    +        bottom: (index + 1) * estimatedItemSize,
    +      }))
    +    ),
    +  [estimatedItemSize, listData]
    +);
    +
  4. +
  5. 每次组件更新的时候都需要更新整个位置数组的对象

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    useEffect(() => {
    +  updateItemSize(startIndex);
    +});
    +
    +const updateItemSize = (startIndexValue: number) => {
    +  const itemlist = items.current?.children;
    +  if (itemlist) {
    +    const elementList = Array.from(itemlist);
    +    const newPositions = [...positions];
    +    for (const item of elementList) {
    +      const rect = item.getBoundingClientRect();
    +      const height = rect.height;
    +      const index = Number(item.id.slice(1));
    +      const oldHeight = positions[index].height;
    +      const diffValue = oldHeight - height;
    +      if (diffValue) {
    +        newPositions[index].bottom -= diffValue;
    +        newPositions[index].height = height;
    +        for (let i = index + 1; i < newPositions.length; i++) {
    +          newPositions[i].top = newPositions[i - 1].bottom;
    +          newPositions[i].bottom -= diffValue;
    +        }
    +        setPositions(newPositions);
    +      }
    +    }
    +  }
    +  setStartOffset(
    +    !startIndexValue ? 0 : positions[startIndexValue - 1]?.bottom
    +  );
    +};
    +
  6. +
  7. 对滚动事件的处理

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    const handleScroll = () => {
    +  const scrollTop = list.current?.scrollTop ?? 0;
    +  const startIndexValue = getStartIndex(scrollTop);
    +  setStartIndex(startIndexValue);
    +};
    +
    +// 二分法查找起始索引
    +const getStartIndex = (value: number) => {
    +  let start = 0;
    +  let end = positions.length - 1;
    +  let tempIndex = null;
    +  while (start <= end) {
    +    const midIndex = Math.floor((start + end) / 2);
    +    const midValue = positions[midIndex].bottom;
    +    if (midValue === value) {
    +      return midIndex + 1;
    +    } else if (midValue < value) {
    +      start = midIndex + 1;
    +    } else if (midValue > value) {
    +      if (tempIndex === null || tempIndex > midIndex) {
    +        tempIndex = midIndex;
    +      }
    +      end = end - 1;
    +    }
    +  }
    +  return tempIndex ?? 0;
    +};
    +
  8. +
  9. 完整代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    import React, { useEffect, useMemo, useRef, useState } from 'react';
    +import './index.css';
    +
    +export interface ItemType {
    +  id: string;
    +  value: string;
    +}
    +export interface PositionType {
    +  index: number;
    +  height: number;
    +  top: number;
    +  bottom: number;
    +}
    +interface VirtualList2Props {
    +  listData: ItemType[];
    +  estimatedItemSize: number;
    +}
    +
    +const VirtualList2: React.FC<VirtualList2Props> = (props) => {
    +  const { listData = [], estimatedItemSize } = props;
    +  const [screenHeight, setSceenHeight] = useState(0);
    +  const [startOffset, setStartOffset] = useState(0);
    +  const [startIndex, setStartIndex] = useState(0);
    +  const [endIndex, setEndIndex] = useState(0);
    +  const [positions, setPositions] = useState<PositionType[]>([]);
    +
    +  const list = useRef<HTMLDivElement>(null);
    +  const items = useRef<HTMLDivElement>(null);
    +
    +  const listHeight = useMemo(
    +    () => positions[positions.length - 1]?.bottom ?? 0,
    +    [positions]
    +  );
    +  // 根据预估高度计算渲染数量,这里应该使用数量偏小的数量防止展示不全
    +  const visibleCount = useMemo(
    +    () => Math.ceil(screenHeight / estimatedItemSize),
    +    [screenHeight, estimatedItemSize]
    +  );
    +  const visibleData = useMemo(
    +    () => listData.slice(startIndex, Math.min(listData.length, endIndex)),
    +    [startIndex, endIndex, listData]
    +  );
    +
    +  useEffect(() => {
    +    setEndIndex(startIndex + visibleCount);
    +  }, [visibleCount, startIndex]);
    +  useEffect(() => {
    +    const element = list.current;
    +    setSceenHeight(element?.clientHeight ?? 0);
    +    const resizeObserver = new ResizeObserver((entries) => {
    +      for (const entry of entries) {
    +        setSceenHeight(entry.contentRect.height);
    +      }
    +    });
    +    if (element) {
    +      resizeObserver.observe(element);
    +    }
    +    return () => {
    +      if (element) {
    +        resizeObserver.unobserve(element);
    +      }
    +    };
    +  }, []);
    +  // 初始化位置数组
    +  useEffect(
    +    () =>
    +      setPositions(
    +        listData.map((_, index) => ({
    +          index,
    +          height: estimatedItemSize,
    +          top: index * estimatedItemSize,
    +          bottom: (index + 1) * estimatedItemSize,
    +        }))
    +      ),
    +    [estimatedItemSize, listData]
    +  );
    +  // 时刻更新位置数组,防止渲染不全
    +  useEffect(() => {
    +    updateItemSize(startIndex);
    +  });
    +  // 处理滚动事件,二分查找
    +  const handleScroll = () => {
    +    const scrollTop = list.current?.scrollTop ?? 0;
    +    const startIndexValue = getStartIndex(scrollTop);
    +    setStartIndex(startIndexValue);
    +  };
    +  // 更新位置数组
    +  const updateItemSize = (startIndexValue: number) => {
    +    const itemlist = items.current?.children;
    +    if (itemlist) {
    +      const elementList = Array.from(itemlist);
    +      const newPositions = [...positions];
    +      for (const item of elementList) {
    +        const rect = item.getBoundingClientRect();
    +        const height = rect.height;
    +        const index = Number(item.id.slice(1));
    +        const oldHeight = positions[index].height;
    +        const diffValue = oldHeight - height;
    +        if (diffValue) {
    +          newPositions[index].bottom -= diffValue;
    +          newPositions[index].height = height;
    +          for (let i = index + 1; i < newPositions.length; i++) {
    +            newPositions[i].top = newPositions[i - 1].bottom;
    +            newPositions[i].bottom -= diffValue;
    +          }
    +          setPositions(newPositions);
    +        }
    +      }
    +      setStartOffset(
    +        !startIndexValue ? 0 : newPositions[startIndexValue - 1]?.bottom
    +      );
    +    }
    +  };
    +  // 二分查找获取索引
    +  const getStartIndex = (value: number) => {
    +    let start = 0;
    +    let end = positions.length - 1;
    +    let tempIndex = null;
    +    while (start <= end) {
    +      const midIndex = Math.floor((start + end) / 2);
    +      const midValue = positions[midIndex].bottom;
    +      if (midValue === value) {
    +        return midIndex + 1;
    +      } else if (midValue < value) {
    +        start = midIndex + 1;
    +      } else if (midValue > value) {
    +        if (tempIndex === null || tempIndex > midIndex) {
    +          tempIndex = midIndex;
    +        }
    +        end = end - 1;
    +      }
    +    }
    +    return tempIndex ?? 0;
    +  };
    +
    +  return (
    +    <div
    +      ref={list}
    +      className="infinite-list-container"
    +      onScroll={handleScroll}
    +    >
    +      <div
    +        className="infinite-list-phantom"
    +        style={{ height: `${listHeight}px` }}
    +      ></div>
    +      <div
    +        ref={items}
    +        className="infinite-list"
    +        style={{ transform: `translateY(${startOffset}px)` }}
    +      >
    +        {visibleData.map((item) => (
    +          <div className="infinite-list-item" id={item.id} key={item.id}>
    +            {item.value}
    +          </div>
    +        ))}
    +      </div>
    +    </div>
    +  );
    +};
    +
    +export default VirtualList2;
    +
  10. +
+

对滚动过快白屏现象的处理

解决方法为上下都渲染一个缓冲区,防止滚动过快白屏。

+
+
    +
  1. 定义上下缓冲区的大小。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const {
    +  listData = [],
    +  itemSize = 200,
    +  estimatedItemSize,
    +  bufferScale = 1,
    +} = props;
    +
    +const belowCount = useMemo(
    +  () => Math.min(listData.length - endIndex, bufferScale * visibleCount),
    +  [endIndex, bufferScale, visibleCount, listData.length]
    +);
    +const visibleData = useMemo(() => {
    +  return listData.slice(startIndex - aboveCount, endIndex + belowCount);
    +}, [listData, startIndex, aboveCount, endIndex, belowCount]);
    +
  2. +
  3. 由于顶部多渲染了一些元素,所以整体 2D 位移的值需要减小

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    const updateItemSize = (startIndexValue: number) => {
    +  const itemlist = items.current?.children;
    +  if (itemlist) {
    +    const elementList = Array.from(itemlist);
    +    const newPositions = [...positions];
    +    for (const item of elementList) {
    +      const rect = item.getBoundingClientRect();
    +      const height = rect.height;
    +      const index = Number(item.id.slice(1));
    +      const oldHeight = positions[index].height;
    +      const diffValue = oldHeight - height;
    +      if (diffValue) {
    +        newPositions[index].bottom -= diffValue;
    +        newPositions[index].height = height;
    +        for (let i = index + 1; i < newPositions.length; i++) {
    +          newPositions[i].top = newPositions[i - 1].bottom;
    +          newPositions[i].bottom -= diffValue;
    +        }
    +        setPositions(newPositions);
    +      }
    +    }
    +    // 这里需要减去顶部元素把视口元素挤下去的距离
    +    if (startIndexValue) {
    +      const size =
    +        positions[startIndexValue]?.top -
    +          positions[startIndexValue - aboveCount]?.top ?? 0;
    +      setStartOffset(positions[startIndexValue - 1]?.bottom - size);
    +    } else {
    +      setStartOffset(0);
    +    }
    +  }
    +};
    +
  4. +
+

总结

至此,我们完成了一个较为完备的虚拟列表的实现。支持动态高度并能解决滚动过快的白屏问题。
但是对于图片类渲染后决定高度的情况我们没有优化,可以考虑后续来继续解决,这里有两种解决方案:

+
    +
  1. 在图片初始化的时候就固定图片的信息,固定宽高渲染,然后渲染。
  2. +
  3. 根据ResizeObserver对象或者img元素的onload事件来监听图片的加载,然后更新高度数据。
  4. +
+
文章作者: 希亚的西红柿
文章链接: http://blog.refrain.site/posts/300a8ca5/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
+ + + + \ No newline at end of file diff --git a/posts/355699d5/index.html b/posts/355699d5/index.html new file mode 100644 index 000000000..21493ef99 --- /dev/null +++ b/posts/355699d5/index.html @@ -0,0 +1,786 @@ +Numpy实现逻辑回归(Logistic Regression)算法 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + + + + +

Numpy实现逻辑回归(Logistic Regression)算法

+

前言:本文旨在对如何使用 numpy 实现逻辑回归拟合的过程做具体分析,有关逻辑回归原理部分不做过多论述。

+
+

数据集准备

准备用于二分类的数据集,可直接复制到对应的 txt 文件
-0.017612 14.053064 0 + -1.395634 4.662541 1 + -0.752157 6.538620 0 + -1.322371 7.152853 0 + 0.423363 11.054677 0 + 0.406704 7.067335 1 + 0.667394 12.741452 0 + -2.460150 6.866805 1 + 0.569411 9.548755 0 + -0.026632 10.427743 0 + 0.850433 6.920334 1 + 1.347183 13.175500 0 + 1.176813 3.167020 1 + -1.781871 9.097953 0 + -0.566606 5.749003 1 + 0.931635 1.589505 1 + -0.024205 6.151823 1 + -0.036453 2.690988 1 + -0.196949 0.444165 1 + 1.014459 5.754399 1 + 1.985298 3.230619 1 + -1.693453 -0.557540 1 + -0.576525 11.778922 0 + -0.346811 -1.678730 1 + -2.124484 2.672471 1 + 1.217916 9.597015 0 + -0.733928 9.098687 0 + -3.642001 -1.618087 1 + 0.315985 3.523953 1 + 1.416614 9.619232 0 + -0.386323 3.989286 1 + 0.556921 8.294984 1 + 1.224863 11.587360 0 + -1.347803 -2.406051 1 + 1.196604 4.951851 1 + 0.275221 9.543647 0 + 0.470575 9.332488 0 + -1.889567 9.542662 0 + -1.527893 12.150579 0 + -1.185247 11.309318 0 + -0.445678 3.297303 1 + 1.042222 6.105155 1 + -0.618787 10.320986 0 + 1.152083 0.548467 1 + 0.828534 2.676045 1 + -1.237728 10.549033 0 + -0.683565 -2.166125 1 + 0.229456 5.921938 1 + -0.959885 11.555336 0 + 0.492911 10.993324 0 + 0.184992 8.721488 0 + -0.355715 10.325976 0 + -0.397822 8.058397 0 + 0.824839 13.730343 0 + 1.507278 5.027866 1 + 0.099671 6.835839 1 + -0.344008 10.717485 0 + 1.785928 7.718645 1 + -0.918801 11.560217 0 + -0.364009 4.747300 1 + -0.841722 4.119083 1 + 0.490426 1.960539 1 + -0.007194 9.075792 0 + 0.356107 12.447863 0 + 0.342578 12.281162 0 + -0.810823 -1.466018 1 + 2.530777 6.476801 1 + 1.296683 11.607559 0 + 0.475487 12.040035 0 + -0.783277 11.009725 0 + 0.074798 11.023650 0 + -1.337472 0.468339 1 + -0.102781 13.763651 0 + -0.147324 2.874846 1 + 0.518389 9.887035 0 + 1.015399 7.571882 0 + -1.658086 -0.027255 1 + 1.319944 2.171228 1 + 2.056216 5.019981 1 + -0.851633 4.375691 1 + -1.510047 6.061992 0 + -1.076637 -3.181888 1 + 1.821096 10.283990 0 + 3.010150 8.401766 1 + -1.099458 1.688274 1 + -0.834872 -1.733869 1 + -0.846637 3.849075 1 + 1.400102 12.628781 0 + 1.752842 5.468166 1 + 0.078557 0.059736 1 + 0.089392 -0.715300 1 + 1.825662 12.693808 0 + 0.197445 9.744638 0 + 0.126117 0.922311 1 + -0.679797 1.220530 1 + 0.677983 2.556666 1 + 0.761349 10.693862 0 + -2.168791 0.143632 1 + 1.388610 9.341997 0 + 0.317029 14.739025 0

+

读取数据

构造出[[1,data,data],][result,]两种矩阵

+
1
2
3
4
5
6
7
8
9
10
11
12
from numpy import *
+filename = 'Resources/LogisticRegressionSet.txt'
+def loadDataSet():
+    dataMat = []
+    labelMat = [] # 构造两个空列表
+    fr = open(filename)
+    for line in fr.readlines():
+        lineArr = line.strip().split() # 切割元素
+        dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])])
+        # 前面的1,表示方程的常量。比如两个特征X1,X2,共需要三个参数,W1+W2*X1+W3*X2
+        labelMat.append(int(lineArr[2]))
+    return dataMat, labelMat
+

构造 Logistic(Sigmoid)函数

1
2
def sigmoid(inX):  # sigmoid函数
+    return 1.0 / (1 + exp(-inX))
+

构造梯度上升函数

普通梯度上升

通过矩阵乘法后算出差值反复迭代,利用梯度上升法求出结果,注意矩阵转置的意义

+
1
2
3
4
5
6
7
8
9
10
11
12
def gradAscent(dataMat, labelMat):  # 梯度上升求最优参数
+    dataMatrix = mat(dataMat)  # 将读取的数据转换为矩阵
+    classLabels = mat(labelMat).transpose()  # 将读取的数据转换为矩阵
+    m, n = shape(dataMatrix)
+    alpha = 0.001  # 设置梯度的阀值,该值越大梯度上升幅度越大
+    maxCycles = 500  # 设置迭代的次数,一般看实际数据进行设定,有些可能200次就够了
+    weights = ones((n, 1))  # 设置初始的参数,并都赋默认值为1。注意这里权重以矩阵形式表示三个参数。
+    for k in range(maxCycles):
+        h = sigmoid(dataMatrix * weights)
+        error = (classLabels - h)  # 求导后差值
+        weights = weights + alpha * dataMatrix.transpose() * error  # 迭代更新权重
+    return weights
+

随机梯度上升

与上述算法的区别在于只选择一行数据更新权重

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def stocGradAscent0(dataMat, labelMat):
+# 随机梯度上升,当数据量比较大时,每次迭代都选择全量数据进行计算,
+#计算量会非常大。所以采用每次迭代中一次只选择其中的一行数据进行更新权重。
+    dataMatrix = mat(dataMat)
+    classLabels = labelMat
+    m, n = shape(dataMatrix)
+    alpha = 0.01
+    maxCycles = 500
+    weights = ones((n, 1))
+    for k in range(maxCycles):
+        for i in range(m):  # 遍历计算每一行
+            h = sigmoid(sum(dataMatrix[i] * weights))
+            error = classLabels[i] - h
+            weights = weights + alpha * error * dataMatrix[i].transpose()
+    return weights
+

改进版随机梯度上升

该算法与上述算法的区别在于两点:

+
    +
  • 步长随迭代次数的增加而减小
  • +
  • 采用了随机抽样的方法
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def stocGradAscent1(dataMat, labelMat):
+    # 改进版随机梯度上升,在每次迭代中随机选择样本来更新权重,并且随迭代次数增加,权重变化越小。
+    dataMatrix = mat(dataMat)
+    classLabels = labelMat
+    m, n = shape(dataMatrix)
+    weights = ones((n, 1))
+    maxCycles = 500
+    for j in range(maxCycles):  # 迭代
+        dataIndex = [i for i in range(m)]
+        for i in range(m):  # 随机遍历每一行
+            alpha = 4 / (1 + j + i) + 0.0001  # 随迭代次数增加,权重变化越小。
+            randIndex = int(random.uniform(0, len(dataIndex)))  # 随机抽样
+            h = sigmoid(sum(dataMatrix[randIndex] * weights))
+            error = classLabels[randIndex] - h
+            weights = weights + alpha * error * dataMatrix[randIndex].transpose()
+            del (dataIndex[randIndex])  # 去除已经抽取的样本
+    return weights
+

画出图像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def plotBestFit(weights):  # 画出最终分类的图
+    import matplotlib.pyplot as plt
+    dataMat, labelMat = loadDataSet()
+    dataArr = array(dataMat)
+    n = shape(dataArr)[0]
+    xcord1 = []
+    ycord1 = []
+    xcord2 = []
+    ycord2 = []
+    for i in range(n):
+        if int(labelMat[i]) == 1:
+            xcord1.append(dataArr[i, 1])
+            ycord1.append(dataArr[i, 2])
+        else:
+            xcord2.append(dataArr[i, 1])
+            ycord2.append(dataArr[i, 2])
+    fig = plt.figure()
+    ax = fig.add_subplot(111) # 一行一列一个格子
+    ax.scatter(xcord1, ycord1, s=30, c='red', marker='s')
+    ax.scatter(xcord2, ycord2, s=30, c='green') # 画出散点图
+    x = arange(-3.0, 3.0, 0.1) # 生成数组
+    y = (-weights[0] - weights[1] * x) / weights[2] # 决策边界
+    ax.plot(x, y)
+    plt.xlabel('X1')
+    plt.ylabel('X2')
+    plt.show()
+

定义主函数

1
2
3
4
def main():
+    dataMat, labelMat = loadDataSet()
+    weights = gradAscent(dataMat, labelMat).getA()
+    plotBestFit(weights)
+

调用主函数

1
2
if __name__ == '__main__':
+    main()
+

运行结果

    +
  • 普通梯度上升
  • +
  • 随机梯度上升
  • +
  • 改进版随机梯度上升
  • +
+
+

本文源码参考自 逻辑回归原理(python 代码实现)

+
+
文章作者: 希亚的西红柿
文章链接: http://blog.refrain.site/posts/355699d5/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
+ + + + \ No newline at end of file diff --git a/posts/35a88b44/index.html b/posts/35a88b44/index.html new file mode 100644 index 000000000..8f75a838b --- /dev/null +++ b/posts/35a88b44/index.html @@ -0,0 +1,785 @@ +JS事件循环 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + + + +

JS事件循环

本文主要介绍了 JS 中的事件循环概念,详细讲解了 Browser 中的事件循环以及 Node.js 中的事件循环。

+
+
查看参考资料 +
+
参考方向教程原帖
面试必问之 JS 事件循环(Event Loop),看这一篇足够面试必问之 JS 事件循环(Event Loop)
Node.js 事件循环Event Loop
通俗移动的 Node.js 事件循环node.js 事件循环
+
+
+

前言

JS 是一门单线程的语言,执行过程中每一步代码都有严格的先后顺序,但 JS 也能够实现异步编程,JS 本身并不具备异步编程的能力,其异步编程是依赖其宿主实现的,而实现异步编程的核心机制就是事件循环

+
+

浏览器中 JS 异步执行的原理

例如发送ajax请求以及设定setTimeout定时器,实现这种异步,主要依赖于浏览器的定时触发线程以及 HTTP 请求线程,即浏览器才是执行发送请求以及定时功能的角色,JS 引擎只负责执行这些事件回调的代码。

+

+

浏览器的进程与线程

以 Chrome 为例,浏览器不仅有多个线程,还有多个进程,如渲染进程、GPU 进程和插件进程等。而每个 tab 标签页都是一个独立的渲染进程,所以一个 tab 异常崩溃后,其他 tab 基本不会被影响。作为前端开发者,主要重点关注其渲染进程,渲染进程下包含了 JS 引擎线程、HTTP 请求线程和定时器线程等,这些线程为 JS 在浏览器中完成异步任务提供了基础。

+

+
深入探讨事件循环 +
+

有关js同步与异步问题的深入探讨。

  • 进程是一个程序的执行实例,它拥有自己的内存空间和资源。一个程序可以同时运行多个进程,互不干扰。
  • 线程是进程内部的一个执行单元,它共享进程的内存空间和资源。一个进程可以同时运行多个线程,提高效率和并发性。

浏览器是一个多进程的应用程序,它由以下几种进程组成:

  • 浏览器主进程:负责浏览器的界面显示、用户交互、子进程管理等。
  • 渲染进程:负责渲染网页,每个标签页对应一个渲染进程。
  • 网络进程:负责网络请求的处理,如 HTTP、WebSocket 等。
  • GPU 进程:负责 GPU 的使用,如 3D 绘制、视频解码等。
  • 插件进程:负责插件的运行,如 Flash、PDF 等。

渲染进程是我们关注的重点,因为它涉及到前端开发中的同步异步以及线程相关的概念。渲染进程内部有以下几种线程:

  • GUI 线程:负责渲染网页的界面,如 HTML、CSS、图片等。
  • JS 引擎线程:负责执行 JS 代码,如 V8 引擎。
  • 事件触发线程:负责监听和处理事件,如点击、滚动、定时器等。
  • 定时器触发线程:负责处理定时器的回调函数,如 setTimeout、setInterval 等。
  • 异步 HTTP 请求线程:负责处理异步的网络请求,如 XMLHttpRequest、fetch 等。
  • Web Worker 线程:负责执行后台任务,不影响主线程。

JS 引擎线程是一个单线程的执行环境,也就是说,在同一时间只能执行一段 JS 代码。这是因为 JS 是一门设计用来与用户交互的脚本语言,如果允许多个 JS 代码同时运行,可能会导致 DOM 的操作冲突和数据不一致。因此,JS 的执行机制是基于事件循环(Event Loop)的。

事件循环是 JS 实现异步编程的核心机制,它可以简单地理解为以下几个步骤:

  1. JS 引擎线程从执行栈中取出一个同步任务并执行,直到执行栈为空。
  2. JS 引擎线程从任务队列中取出一个异步任务(也叫微任务)并执行,直到任务队列为空。
  3. JS 引擎线程从事件队列中取出一个异步任务(也叫宏任务)并执行,然后回到第二步。

同步任务是指那些不需要等待任何其他条件就可以立即执行的任务,如普通的赋值、计算、循环等。异步任务是指那些需要等待一定条件才能执行的任务,如网络请求、定时器、事件监听等。异步任务又分为微任务和宏任务,微任务是指那些在当前同步任务结束后立即执行的任务,如 Promise 的回调函数、MutationObserver 的回调函数等。宏任务是指那些在下一轮事件循环开始时才执行的任务,如 setTimeout 的回调函数、setInterval 的回调函数、DOM 事件的回调函数等。

JS 单线程存在的问题是,在执行一些耗时长或者阻塞的同步任务时,会导致后续的任务无法及时执行,影响用户体验和程序性能。解决方案是,尽量将这些任务转化为异步任务,或者使用 Web Worker 线程来执行这些任务,从而避免阻塞主线程。

JS 线程和 GUI 线程是互斥的,也就是说,在 JS 线程执行时,GUI 线程会暂停,反之亦然。这是为了保证 DOM 的渲染和操作的一致性。因此,如果 JS 代码执行时间过长,会导致页面的渲染和更新被延迟,影响用户体验。解决方案是,尽量优化 JS 代码的性能,或者使用 requestAnimationFrame 等 API 来实现动画效果,从而避免卡顿和闪烁。

+
+
+

JS 执行上下文、作用域以及作用域链

查看 JS 中执行上下文 +
+

在 JS 中,执行上下文是一个抽象的概念,它表示代码在运行时的环境。执行上下文可以分为三种类型:

  • 全局执行上下文:它是为运行代码主体而创建的执行上下文,也就是说它是为那些存在于函数之外的任何代码而创建的。全局执行上下文只有一个,它在浏览器中对应于 window 对象,在 Node.js 中对应于 global 对象。
  • 函数执行上下文:它是为每个函数调用而创建的执行上下文,也就是说它是为那些存在于函数内部的代码而创建的。函数执行上下文可以有多个,每次调用函数时都会创建一个新的执行上下文,并且在函数返回后销毁。
  • eval 执行上下文:它是为 eval 函数内部的代码而创建的执行上下文,也就是说它是为那些通过 eval 函数执行的代码而创建的。eval 执行上下文很少使用,因为 eval 函数通常被认为是不安全和低效的。

每个执行上下文都有以下三个重要的组成部分:

  • 变量对象(Variable Object):它是一个用于存储变量和函数声明的对象,它包含了该执行上下文中定义的所有变量和函数。
  • 作用域链(Scope Chain):它是一个用于确定变量访问权限的链表,它包含了该执行上下文及其所有父级执行上下文的变量对象。
  • this 值(This Value):它是一个用于指代当前对象的值,它根据函数调用方式的不同而有所不同。

当 JS 引擎解析到可执行代码片段(通常是函数调用阶段)的时候,就会先做一些执行前的准备工作,这个 “准备工作”,就叫做 “创建执行上下文”。创建执行上下文分为以下两个阶段:

  • 创建阶段(Creation Phase):在这个阶段,JS 引擎会做以下三件事:
    • 创建变量对象,并初始化其中的变量和函数声明。
    • 创建作用域链,并将当前变量对象添加到作用域链的最前端。
    • 确定 this 值,并将其赋值给当前执行上下文。
  • 执行阶段(Execution Phase):在这个阶段,JS 引擎会逐行执行代码,并根据变量对象、作用域链和 this 值来访问和操作变量和函数。
+
+
+
查看 JS 中的作用域以及作用域链 +
+

JS 中的作用域是指代码中变量和函数的可访问范围,它决定了变量和函数的生命周期和可见性。JS 中有三种类型的作用域:

  • 全局作用域:在代码中任何地方都能访问到的对象拥有全局作用域,例如最外层函数、最外层变量、未定义直接赋值的变量、window 对象的属性等。全局作用域有一个缺点,就是容易造成命名冲突和污染全局命名空间。
  • 函数作用域:在函数内部定义的变量和函数拥有函数作用域,它们只能在函数内部被访问,而不能在函数外部被访问。函数作用域可以隔离变量,避免与外部发生冲突。
  • 块级作用域:在 ES6 中,使用 let 和 const 关键字声明的变量拥有块级作用域,它们只能在当前代码块(由一对花括号包裹)内部被访问,而不能在代码块外部被访问。块级作用域可以更细粒度地控制变量的生命周期和可见性。

JS 中的作用域链是指在查找变量时沿着作用域层级所形成的链条,它由当前执行上下文及其所有父级执行上下文的变量对象组成。当一个变量在当前执行上下文中没有找到时,就会沿着作用域链向上一级查找,直到找到为止或者到达全局作用域。

作用域链的创建过程如下:

  • 当执行全局代码时,会创建一个全局执行上下文,并将其压入执行栈。
  • 当执行全局代码中的一个函数时,会创建一个函数执行上下文,并将其压入执行栈。
  • 在函数执行上下文中,会先创建一个变量对象,并初始化其中的变量和函数声明。
  • 然后会创建一个作用域链,并将当前变量对象添加到作用域链的最前端。
  • 接着会确定 this 值,并将其赋值给当前执行上下文。
  • 最后会逐行执行函数内部的代码,并根据作用域链来访问和操作变量和函数。
+
+
+

浏览器中的事件循环

执行栈与任务队列

    +
  1. 执行栈:JS 按照顺序执行同步代码的时候,代码的执行依赖于一个栈结构,我们把这个栈叫做执行栈。

    +
  2. +
  3. 任务队列:

    +
      +
    1. 宏任务:有明确的异步任务需要执行和回调;需要其他异步线程支持。如 script(整体代码)setTimeoutsetIntervalsetImmediate

      +

      注意 html 中的script标签,以及浏览器中的Dev Tools中控制台中的执行代码都是包装在宏任务中执行的。

      +
      +
    2. +
    3. 微任务:没有明确的异步任务需要执行,只有回调;不需要其他异步线程支持。如promise.then()MutationObserverprocess.nextTick

      +

      requestAnimationFrame在宏任务之后,在每次浏览器视图重绘前进行,所以其既不属于宏任务也不属于微任务。

      +
      +
      查看requestAnimationFrame的使用 +
      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      const element = document.getElementById(
      +  "some-element-you-want-to-animate"
      +);
      +let start, previousTimeStamp;
      +let done = false;
      +
      +function step(timeStamp) {
      +  if (start === undefined) {
      +    start = timeStamp;
      +  }
      +  const elapsed = timeStamp - start;
      +
      +  if (previousTimeStamp !== timeStamp) {
      +    // Math.min() is used here to make sure the element stops at exactly 200px
      +    const count = Math.min(0.1 * elapsed, 200);
      +    element.style.transform = `translateX(${count}px)`;
      +    if (count === 200) done = true;
      +  }
      +
      +  if (elapsed < 2000) {
      +    // Stop the animation after 2 seconds
      +    previousTimeStamp = timeStamp;
      +    if (!done) {
      +      window.requestAnimationFrame(step);
      +    }
      +  }
      +}
      +
      +window.requestAnimationFrame(step);
      +
      +
      +
    4. +
    +
  4. +
+

JS 执行过程

执行顺序:同步代码->微任务队列->宏任务队列,注意对嵌套层级深的代码一定需要结合执行栈进行分析。

+
1
2
3
4
5
6
7
8
9
10
11
12
console.log("同步代码1");
+setTimeout(() => {
+  console.log("setTimeout");,
+}, 0);
+new Promise((resolve) => {
+  console.log("同步代码2");
+  resolve();
+}).then(() => {
+  console.log("promise.then");
+});
+console.log("同步代码3");
+// 最终输出"同步代码1"、"同步代码2"、"同步代码3"、"promise.then"、"setTimeout"
+

NodeJS 中的事件循环

循环周期

NodeJSJS 的执行,我们主要需要关心的过程分为以下几个阶段,下面每个阶段都有自己单独的任务队列,当执行到对应阶段时,就判断当前阶段的任务队列是否有需要处理的任务。

+
    +
  • timers阶段:执行所有 setTimeout()setInterval() 的回调,事件循环首先进入此阶段
  • +
  • pending callbacks阶段:某些系统操作相关的回调函数,如 TCP 链接错误。
  • +
  • idle, prepare:系统内部使用,程序员无需关心。
  • +
  • poll 阶段:轮询等待新的链接和请求等事件,执行 I/O 回调等。优先执行poll阶段的任务队列。如果此阶段任务队列已经执行完了,则进入 check 阶段执行 setImmediate 回调(如果有 setImmediate),或等待新的任务进来(如果没有 setImmediate)。在等待新的任务时,如果有 timers 计时到期,则会直接进入 timers 阶段。此阶段可能会阻塞等待。
  • +
  • check 阶段:setImmediate 回调函数执行。
  • +
  • close callbacks阶段:关闭回调执行,如socket.on('close', ...)
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
   ┌───────────────────────────┐
+┌─>│           timers          │
+│  └─────────────┬─────────────┘
+│  ┌─────────────┴─────────────┐
+│  │     pending callbacks     │
+│  └─────────────┬─────────────┘
+│  ┌─────────────┴─────────────┐
+│  │       idle, prepare       │
+│  └─────────────┬─────────────┘      ┌───────────────┐
+│  ┌─────────────┴─────────────┐      │   incoming:   │
+│  │           poll            │<─────┤  connections, │
+│  └─────────────┬─────────────┘      │   data, etc.  │
+│  ┌─────────────┴─────────────┐      └───────────────┘
+│  │           check           │
+│  └─────────────┬─────────────┘
+│  ┌─────────────┴─────────────┐
+└──┤      close callbacks      │
+   └───────────────────────────┘
+

上面每个阶段都会去执行完当前阶段的任务队列,然后继续执行当前阶段的微任务队列,只有当前阶段所有微任务都执行完了,才会进入下个阶段。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const fs = require("fs");
+fs.readFile(__filename, (data) => {
+  // poll(I/O 回调) 阶段
+  console.log("readFile");
+  Promise.resolve().then(() => {
+    console.error("promise1");
+  });
+  Promise.resolve().then(() => {
+    console.error("promise2");
+  });
+});
+setTimeout(() => {
+  // timers 阶段
+  console.log("timeout");
+  Promise.resolve().then(() => {
+    console.error("promise3");
+  });
+  Promise.resolve().then(() => {
+    console.error("promise4");
+  });
+}, 0);
+// 下面代码只是为了同步阻塞1秒钟,确保上面的异步任务已经准备好了
+var startTime = new Date().getTime();
+var endTime = startTime;
+while (endTime - startTime < 1000) {
+  endTime = new Date().getTime();
+}
+// 最终输出 timeout promise3 promise4 readFile promise1 promise2
+

nextTick 和 setImmediate

NodeJS 中的 process.nextTick()setImmediate() 也有类似效果。其中 setImmediate() 我们前面已经讲了是在 check 阶段执行的,而 process.nextTick() 的执行时机不太一样,它比 promise.then() 的执行还早,在同步任务之后,其他所有异步任务之前,会优先执行 nextTick。可以想象是把 nextTick 的任务放到了当前循环的后面,与 promise.then() 类似,但比 promise.then() 更前面。意思就是在当前同步代码执行完成后,不管其他异步任务,先尽快执行 nextTick。如下面的代码,因此这里的 nextTick 其实应该更符合setImmediate这个命名才对。

+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 引入 fs 模块
+const fs = require("fs");
+
+// 定义一个异步读取文件的函数
+function readFile(callback) {
+  // 读取文件
+  fs.readFile("./test.txt", (err, data) => {
+    // 如果出错,抛出异常
+    if (err) throw err;
+    // 打印文件内容
+    console.log("test3");
+    // 调用回调函数
+    callback();
+  });
+}
+
+// 定义一个定时器函数
+function timer() {
+  // 设置一个 100 毫秒后执行的定时器
+  setTimeout(() => {
+    // 打印定时器信息
+    console.log("Timer is executed");
+  }, 100);
+}
+
+// 调用读取文件函数,并传入一个匿名回调函数
+readFile(() => {
+  // 在回调函数中,使用 process.nextTick() 调用另一个匿名回调函数
+  fs.readFile("./test.txt", (err, data) => {
+    // 如果出错,抛出异常
+    if (err) throw err;
+    // 打印文件内容
+    console.log(data.toString());
+  });
+  setImmediate(() => {
+    console.log("test5");
+  });
+  process.nextTick(() => {
+    // 打印 nextTick 信息
+    console.log("Next tick is executed");
+  });
+  Promise.resolve(1).then(() => {
+    console.log("promise");
+  });
+});
+console.log("test1");
+process.nextTick(() => {
+  console.log("test2");
+});
+// 调用定时器函数
+timer();
+
+// 执行结果
+// test1
+// test2
+// test3
+// Next tick is executed
+// promise
+// test5
+// test4
+// Timer is executed
+
文章作者: 希亚的西红柿
文章链接: http://blog.refrain.site/posts/35a88b44/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
+ + + + \ No newline at end of file diff --git a/posts/3c94855e/index.html b/posts/3c94855e/index.html new file mode 100644 index 000000000..8a52e28cb --- /dev/null +++ b/posts/3c94855e/index.html @@ -0,0 +1,3415 @@ +Vue3笔记 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + + + + +

Vue3笔记

本文主要记录了关于 Vue3 的相关笔记,主要记录 Vue3 及其生态的使用与技巧,不定时更新。

+
+

基础

创建一个 app 实例并挂载

1
2
3
4
5
6
7
8
9
10
11
import { createApp } from "vue";
+// 从一个单文件组件中导入根组件
+import App from "./App.vue";
+
+const app = createApp(App);
+//
+app.config.errorHandler = (err) => {
+  /* 处理错误 */
+};
+// 挂在到跟组件 #app 容器里面
+app.mount("#app");
+

应用根组件的内容将会被渲染在容器元素里面。容器元素自己将不会被视为应用的一部分。
.mount() 方法应该始终在整个应用配置和资源注册完成后被调用。同时请注意,不同于其他资源注册方法,它的返回值是根组件实例而非应用实例。
这几个方法的返回值都是应用实例本身,也就是通过 createApp() 方法创建的对象。这样可以方便地链式调用这些方法,而不需要每次都写 app. 前缀。

+
1
2
3
4
5
6
7
8
9
10
11
// 创建应用实例
+const app = createApp(App);
+
+// 链式调用注册方法
+app
+  .component("my-button", MyButton)
+  .directive("focus", focus)
+  .use(vuex)
+  .mixin(mixin)
+  .provide("foo", "bar")
+  .mount("#app"); // 最后调用挂载方法
+
+

模板语法

    +
  1. 动态绑定多个值

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <script setup lang="ts">
    +const objectOfAttrs = {
    +  id: "container",
    +  class: "wrapper",
    +};
    +</script>
    +
    +<template>
    +  <div v-bind="objectOfAttrs"></div>
    +</template>
    +<!-- 或者 -->
    +<template>
    +  <div :="objectOfAttrs"></div>
    +</template>
    +
  2. +
  3. v-html指令相当于将元素的innerHTML与之同步。

    +
    1
    2
    <p>Using text interpolation: {{ rawHtml }}</p>
    +<p>Using v-html directive: <span v-html="rawHtml"></span></p>
    +
  4. +
  5. 调用函数

    +
    1
    2
    3
    <time :title="toTitleDate(date)" :datetime="date">
    +  {{ formatDate(date) }}
    +</time>
    +

    绑定在表达式中的方法在组件每次更新时都会被重新调用,因此不应该产生任何副作用,比如改变数据或触发异步操作。

    +
    +

    模板中的表达式将被沙盒化,仅能够访问到有限的全局对象列表。该列表中会暴露常用的内置全局对象,比如 Math 和 Date。
    没有显式包含在列表中的全局对象将不能在模板内表达式中访问,例如用户附加在 window 上的属性。然而,你也可以自行在 app.config.globalProperties 上显式地添加它们,供所有的 Vue 表达式使用。

    +
    +
  6. +
  7. 动态参数

    +
    1
    2
    3
    4
    <a v-on:[eventName]="doSomething"> ... </a>
    +
    +<!-- 简写 -->
    +<a @[eventName]="doSomething"></a>
    +
  8. +
+

响应式基础

    +
  1. 为保证访问代理的一致性,对同一个原始对象调用 reactive() 会总是返回同样的代理对象,而对一个已存在的代理对象调用 reactive() 会返回其本身:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    const raw = {};
    +const proxy = reactive(raw);
    +
    +// 代理对象和原始对象不是全等的
    +console.log(proxy === raw); // false
    +
    +// 在同一个对象上调用 reactive() 会返回相同的代理
    +console.log(reactive(raw) === proxy); // true
    +
    +// 在一个代理上调用 reactive() 会返回它自己
    +console.log(reactive(proxy) === proxy); // true
    +// 这个规则对嵌套对象也适用。依靠深层响应性,响应式对象内的嵌套对象依然是代理:
    +const proxy = reactive({});
    +
    +const raw = {};
    +proxy.nested = raw;
    +
    +console.log(proxy.nested === raw); // false
    +
  2. +
  3. ref对象在reactive对象中的解包。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    const count = ref(0);
    +const state = reactive({
    +  count,
    +});
    +
    +console.log(state.count); // 0
    +
    +state.count = 1;
    +console.log(count.value); // 1
    +
    +const otherCount = ref(2);
    +
    +state.count = otherCount;
    +console.log(state.count); // 2
    +// 原始 ref 现在已经和 state.count 失去联系
    +console.log(count.value); // 1
    +

    reactive 对象不同的是,当 ref 作为响应式数组或原生集合类型(如 Map) 中的元素被访问时,它不会被解包:

    +
    1
    2
    3
    4
    5
    6
    7
    const books = reactive([ref("Vue 3 Guide")]);
    +// 这里需要 .value
    +console.log(books[0].value);
    +
    +const map = reactive(new Map([["count", ref(0)]]));
    +// 这里需要 .value
    +console.log(map.get("count").value);
    +
    +
  4. +
  5. Vue3中的内联事件处理器中的ref对象解包,与模板解包相同。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <script setup lang="ts">
    +// 注意 ref 对象在模板中的解包只有顶层属性才能解包,类似下面的 count,正如上面所说,内联事件处理器中的解包规则与模板解包完全相同
    +const count = ref(0);
    +const object = ref({ foo: ref(1) });
    +const object = { foo: ref(1) };
    +const object = ref({ foo: 1 });
    +</script>
    +
    +<template>
    +  <button ref="btn" @click="test">{{ a }}</button>
    +  {{ b }}
    +  <button @click="count++">Add 1</button>
    +  <p>Count is: {{ count }}</p>
    +  {{ object.foo }} {{ object.foo.value }}/ {{ object.foo }}
    +</template>
    +
  6. +
+
查看 ref 与 reactive 简单实现 +
+
  1. 基础的refreactive函数的定义,注意refvalue对象为gettersetter值用来适配基本的数据类型。Reflectreceiver绑定get或者setthis

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    // ref的函数
    +function ref(val) {
    +  // 此处源码中为了保持一致,在对象情况下也做了用value 访问的情况value->proxy对象
    +  // 我们在对象情况下就不在使用value 访问
    +  return new refObj(val);
    +}
    +//创建响应式对象
    +class refObj {
    +  constructor(val) {
    +    this._value = reactive(val);
    +  }
    +  get value() {
    +    // 在第一次执行之后触发get来收集依赖
    +    track(this, "value");
    +    return this._value;
    +  }
    +  set value(newVal) {
    +    console.log(newVal);
    +    this._value = newVal;
    +    trigger(this, "value");
    +  }
    +}
    +// 对象的响应式处理 在这里我们为了理解原理原理暂时不考虑对象里嵌套对象的情况
    +// 其实对象的响应式处理也就是重复执行reactive
    +function reactive(target) {
    +  if (!isObject(target)) {
    +    return target;
    +  }
    +  return new Proxy(target, {
    +    get(target, key, receiver) {
    +      // Reflect用于执行对象默认操作,更规范、函数式
    +      // Proxy和Object的方法Reflect都有对应
    +      const res = Reflect.get(target, key, receiver);
    +      track(target, key);
    +      return isObject(res) ? reactive(res) : res;
    +    },
    +    set(target, key, value, receiver) {
    +      console.log(target, key, value);
    +      const res = Reflect.set(target, key, value, receiver);
    +      trigger(target, key);
    +      return res;
    +    },
    +    deleteProperty(target, key) {
    +      const res = Reflect.deleteProperty(target, key);
    +      trigger(target, key);
    +      return res;
    +    },
    +  });
    +}
  2. 追踪依赖,这部分代码比较难理解,具体思路可这样理解
    判断当前target在整体WeakMap中是否出现,如果没出现,则设置为Map对象,有无key属性的值,则设置为Set对象,防止重复收集依赖,然后将依赖函数放到Set,对象中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    function track(target, key) {
    +  // 取出最后一个数据内容
    +  const effect = effectStack[effectStack.length - 1];
    +  // 如果当前变量有依赖
    +  if (effect) {
    +    //判断当前的map中是否有target
    +    let depsMap = targetMap.get(target);
    +    // 如果没有
    +    if (!depsMap) {
    +      // new map存储当前weakmap
    +      depsMap = new Map();
    +      targetMap.set(target, depsMap);
    +    }
    +    // 获取key对应的响应函数集
    +    let deps = depsMap.get(key);
    +    if (!deps) {
    +      // 建立当前key 和依赖的关系,因为一个key 会有多个依赖
    +      // 为了防止重复依赖,使用set
    +      deps = new Set();
    +      depsMap.set(key, deps);
    +    }
    +    // 存入当前依赖
    +    if (!deps.has(effect)) {
    +      deps.add(effect);
    +    }
    +  }
    +}
  3. 依赖收集部分

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 但是还是没有响应式的能力,那么他是怎样实现响应式的呢----依赖收集,触发更新=
    +// 用来做依赖收集
    +// 在源码中为为了方法的通用性,他还传入了很多参数用于兼容不同情况
    +// 我们意在理解原理,只需要包装fn 即可
    +function effect(fn) {
    +  // 包装当前依赖函数
    +  const effect = function reactiveEffect() {
    +    // 模拟源码中也加入错误处理,为了避免你瞎写出现错误的情况,这就是框架的高明之处
    +    if (!effectStack.includes(effect)) {
    +      try {
    +        // 给当前函数放入临时栈中,为在下面执行中,触发get,在依赖收集中能找到当前变量的依赖项来建立关系
    +        effectStack.push(fn);
    +        // 执行当前函数,开始依赖收集了
    +        return fn();
    +      } finally {
    +        // 执行成功了出栈
    +        effectStack.pop();
    +      }
    +    }
    +  };
    +
    +  effect();
    +}
  4. 触发更新,取对象取key执行。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 用于触发更新
    +function trigger(target, key) {
    +  // 获取所有依赖内容
    +  const depsMap = targetMap.get(target);
    +  // 如果有依赖的话全部拉出来执行
    +  if (depsMap) {
    +    // 获取响应函数集合
    +    const deps = depsMap.get(key);
    +    if (deps) {
    +      // 执行所有响应函数
    +      const run = (effect) => {
    +        // 源码中有异步调度任务,我们在这里省略
    +        effect();
    +      };
    +      deps.forEach(run);
    +    }
    +  }
    +}
  5. 完整代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    // 保存临时依赖函数用于包装
    +const effectStack = [];
    +// 依赖关系的map对象只能接受对象
    +let targetMap = new WeakMap();
    +// 判断是不是对象
    +const isObject = (val) => val !== null && typeof val === "object";
    +// ref的函数
    +function ref(val) {
    +  // 此处源码中为了保持一致,在对象情况下也做了用value 访问的情况value->proxy对象
    +  // 我们在对象情况下就不在使用value 访问
    +  return new refObj(val);
    +}
    +//创建响应式对象
    +class refObj {
    +  constructor(val) {
    +    this._value = reactive(val);
    +  }
    +  get value() {
    +    // 在第一次执行之后触发get来收集依赖
    +    track(this, "value");
    +    return this._value;
    +  }
    +  set value(newVal) {
    +    console.log(newVal);
    +    this._value = newVal;
    +    trigger(this, "value");
    +  }
    +}
    +// 对象的响应式处理 在这里我们为了理解原理原理暂时不考虑对象里嵌套对象的情况
    +// 其实对象的响应式处理也就是重复执行reactive
    +function reactive(target) {
    +  if (!isObject(target)) {
    +    return target;
    +  }
    +  return new Proxy(target, {
    +    get(target, key, receiver) {
    +      // Reflect用于执行对象默认操作,更规范、函数式
    +      // Proxy和Object的方法Reflect都有对应
    +      const res = Reflect.get(target, key, receiver);
    +      track(target, key);
    +      return isObject(res) ? reactive(res) : res;
    +    },
    +    set(target, key, value, receiver) {
    +      console.log(target, key, value);
    +      const res = Reflect.set(target, key, value, receiver);
    +      trigger(target, key);
    +      return res;
    +    },
    +    deleteProperty(target, key) {
    +      const res = Reflect.deleteProperty(target, key);
    +      trigger(target, key);
    +      return res;
    +    },
    +  });
    +}
    +
    +// 到此处,当前的ref 对象就已经实现了对数据改变的监听
    +const newRef = ref({ a: { b: 1 } });
    +const newRef1 = ref(0);
    +// 但是还是没有响应式的能力,那么他是怎样实现响应式的呢----依赖收集,触发更新=
    +// 用来做依赖收集
    +// 在源码中为为了方法的通用性,他还传入了很多参数用于兼容不同情况
    +// 我们意在理解原理,只需要包装fn 即可
    +function effect(fn) {
    +  // 包装当前依赖函数
    +  const effect = function reactiveEffect() {
    +    // 模拟源码中也加入错误处理,为了避免你瞎写出现错误的情况,这就是框架的高明之处
    +    if (!effectStack.includes(effect)) {
    +      try {
    +        // 给当前函数放入临时栈中,为在下面执行中,触发get,在依赖收集中能找到当前变量的依赖项来建立关系
    +        effectStack.push(fn);
    +        // 执行当前函数,开始依赖收集了
    +        return fn();
    +      } finally {
    +        // 执行成功了出栈
    +        effectStack.pop();
    +      }
    +    }
    +  };
    +
    +  effect();
    +}
    +//  在收集的依赖中建立关系
    +function track(target, key) {
    +  // 取出最后一个数据内容
    +  const effect = effectStack[effectStack.length - 1];
    +  // 如果当前变量有依赖
    +  if (effect) {
    +    //判断当前的map中是否有target
    +    let depsMap = targetMap.get(target);
    +    // 如果没有
    +    if (!depsMap) {
    +      // new map存储当前weakmap
    +      depsMap = new Map();
    +      targetMap.set(target, depsMap);
    +    }
    +    // 获取key对应的响应函数集
    +    let deps = depsMap.get(key);
    +    if (!deps) {
    +      // 建立当前key 和依赖的关系,因为一个key 会有多个依赖
    +      // 为了防止重复依赖,使用set
    +      deps = new Set();
    +      depsMap.set(key, deps);
    +    }
    +    // 存入当前依赖
    +    if (!deps.has(effect)) {
    +      deps.add(effect);
    +    }
    +  }
    +}
    +// 用于触发更新
    +function trigger(target, key) {
    +  // 获取所有依赖内容
    +  const depsMap = targetMap.get(target);
    +  // 如果有依赖的话全部拉出来执行
    +  if (depsMap) {
    +    // 获取响应函数集合
    +    const deps = depsMap.get(key);
    +    if (deps) {
    +      // 执行所有响应函数
    +      const run = (effect) => {
    +        // 源码中有异步调度任务,我们在这里省略
    +        effect();
    +      };
    +      deps.forEach(run);
    +    }
    +  }
    +}
    +effect(() => {
    +  console.log(11111);
    +  // 在自己实现的effect中,由于为了演示原理,没有做兼容,不能来触发set,否则会死循环
    +  // vue源码中触发对effect中的做了兼容处理只会执行一次
    +  newRef.value.a.b;
    +  newRef1.value;
    +});
    +newRef.value.a.b++;
    +newRef1.value++;
+
+
+

计算属性

计算属性是vue3中用于从现有数据派生衍生数据的一种API,它们基于vue实例中已有的数据,通过对这些数据进行计算得出新的值,因此我们只需要声明衍生数据的计算逻辑即可,同时计算属性会自动跟踪依赖的数据,并在相关数据发生变化时自动更新计算结果,这样,当依赖数据发生改变时,计算属性会自动重新计算,不需要手动控制何时进行计算或更新,而JavaScript表达式则需要我们在特定的时机通过代码执行来计算值。

+

计算属性的原理是基于vue3的响应式系统,它利用了Proxy对象和Ref对象来实现数据的拦截和追踪。具体来说,计算属性的实现过程可以分为以下几个步骤:

+
    +
  1. 创建一个计算属性ref,它是一个特殊的ref对象,它有一个.value属性,用于存储计算结果,以及一个.effect属性,用于存储计算逻辑。
  2. +
  3. 调用computed()函数,传入一个getter函数,作为计算逻辑,返回一个计算属性ref
  4. +
  5. getter函数中,访问响应式数据,如reactive对象或ref对象,这样就会触发响应式数据的get拦截器,从而收集当前计算属性ref作为依赖,建立依赖关系。
  6. +
  7. getter函数中,返回计算结果,将其赋值给计算属性ref.value属性,这样就会触发计算属性refset拦截器,从而触发计算属性refeffect属性,执行计算逻辑。
  8. +
  9. 在模板或者JavaScript中,访问计算属性ref.value属性,获取计算结果,同时检查计算属性refdirty属性,如果为true,说明计算属性ref需要重新计算,如果为false,说明计算属性ref可以直接返回缓存的结果。
  10. +
  11. 当响应式数据发生变化时,会触发响应式数据的set拦截器,从而触发响应式数据的依赖列表,通知所有依赖于该数据的计算属性ref更新,将其dirty属性设为true,表示需要重新计算。
  12. +
+

以下是一个简单的示例,演示了计算属性的原理和用法:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 创建一个响应式对象
+const state = reactive({
+  count: 0,
+});
+// 创建一个计算属性ref
+const double = computed(() => {
+  // 在getter函数中,访问响应式数据
+  // 这样就会收集当前计算属性ref作为依赖
+  console.log("double computed");
+  // 在getter函数中,返回计算结果
+  return state.count * 2;
+});
+// 在模板或者JavaScript中,访问计算属性ref的.value属性
+// 获取计算结果,同时检查计算属性ref的dirty属性
+console.log(double.value); // 0
+// 当响应式数据发生变化时,会触发响应式数据的依赖列表
+// 通知所有依赖于该数据的计算属性ref更新,将其dirty属性设为true
+state.count++;
+// 再次访问计算属性ref的.value属性时,会重新计算
+console.log(double.value); // 2
+

计算属性的优点是:

+
    +
  • 可以简化模板或者 JavaScript 中的复杂逻辑,提高代码的可读性和可维护性。
  • +
  • 可以自动缓存计算结果,提高性能和效率,避免重复计算。
  • +
  • 可以自动追踪和更新依赖数据,保证数据的一致性和正确性,避免数据的过时和错误。
  • +
+

计算属性的使用场景是:

+
    +
  • 当需要从现有数据派生衍生数据时,如对数据进行格式化、过滤、排序、统计等操作。
  • +
  • 当需要对数据进行复杂的计算时,如对数据进行数学、逻辑、字符串等运算。
  • +
  • 当需要对数据进行条件判断时,如根据数据的值返回不同的结果或状态。
  • +
+

计算属性默认是只读的。当你尝试修改一个计算属性时,你会收到一个运行时警告。只在某些特殊场景中你可能才需要用到“可写”的属性,你可以通过同时提供 gettersetter 来创建:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script setup>
+import { ref, computed } from "vue";
+
+const firstName = ref("John");
+const lastName = ref("Doe");
+
+const fullName = computed({
+  // getter
+  get() {
+    return firstName.value + " " + lastName.value;
+  },
+  // setter
+  set(newValue) {
+    // 注意:我们这里使用的是解构赋值语法
+    [firstName.value, lastName.value] = newValue.split(" ");
+  },
+});
+</script>
+

类与样式绑定

    +
  1. Vue3中多个class的绑定

    +
    +

    需要注意对象写法,必须以键值对的形式,不能少
    数组写法没定义字符串的话得用字符串形式
    组件样式和组件内模板合并

    +
    +
    1
    2
    const activeClass = ref("active");
    +const errorClass = ref("text-danger");
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <div
    +  class="static"
    +  :class="{ active: isActive, 'text-danger': hasError }"
    +></div>
    +<div :class="[{ active: isActive }, errorClass]"></div>
    +<div :class="[{ test1: true }, 'test2']"></div>
    +
    +<!-- 子组件模板 -->
    +<p class="foo bar">Hi!</p>
    +
    +<!-- 在使用组件时 -->
    +<MyComponent class="baz boo" />
    +
    +<!-- 结果为 -->
    +<p class="foo bar baz boo">Hi!</p>
    +
    +<!-- MyComponent 模板使用 $attrs 时 -->
    +<p :class="$attrs.class">Hi!</p>
    +<span>This is a child component</span>
    +
  2. +
  3. 绑定内联样式

    +
    1
    2
    3
    4
    5
    6
    const activeColor = ref("red");
    +const fontSize = ref(30);
    +const styleObject = reactive({
    +  color: "red",
    +  fontSize: "13px",
    +});
    +
    1
    2
    3
    4
    5
    6
    7
    <div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
    +<div :style="{ 'font-size': fontSize + 'px' }"></div>
    +<div :style="styleObject"></div>
    +<!-- 还可以给 :style 绑定一个包含多个样式对象的数组。这些对象会被合并后渲染到同一元素上 -->
    +<div :style="[baseStyles, overridingStyles]"></div>
    +<!-- 你可以对一个样式属性提供多个 (不同前缀的) 值,数组仅会渲染浏览器支持的最后一个值。在这个示例中,在支持不需要特别前缀的浏览器中都会渲染为 display: flex。 -->
    +<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
    +
  4. +
+

条件渲染

    +
  1. 一个 v-else 元素必须跟在一个 v-if 或者 v-else-if 元素后面,否则它将不会被识别。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <div v-if="type === 'A'">
    +  A
    +</div>
    +<!-- 和 v-else 类似,一个使用 v-else-if 的元素必须紧跟在一个 v-if 或一个 v-else-if 元素后面。 -->
    +<div v-else-if="type === 'B'">
    +  B
    +</div>
    +<div v-else-if="type === 'C'">
    +  C
    +</div>
    +<div v-else>
    +  Not A/B/C
    +</div>
    +
  2. +
  3. template上面的v-ifv-elsev-else-if 也可以在 <template> 上使用。

    +
    1
    2
    3
    4
    5
    6
    <!-- 一次性切换多个元素的显隐 -->
    +<template v-if="ok">
    +  <h1>Title</h1>
    +  <p>Paragraph 1</p>
    +  <p>Paragraph 2</p>
    +</template>
    +

    v-show 不支持在<template> 元素上使用,也不能和 v-else 搭配使用。

    +
    +
  4. +
  5. v-ifv-for不应该一起使用。原因为v-if的优先级比vue-for更高,推荐的用法如下。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <!-- 使用计算属性或者与子元素作判断 -->
    +<ul>
    +  <li
    +    v-for="user in activeUsers"
    +    :key="user.id"
    +  >
    +    {{ user.name }}
    +  </li>
    +</ul>
    +<ul>
    +  <template v-for="user in users" :key="user.id">
    +    <li v-if="user.isActive">
    +      {{ user.name }}
    +    </li>
    +  </template>
    +</ul>
    +
    1
    2
    3
    4
    5
    6
    7
    <!--
    +这会抛出一个错误,因为属性 todo 此时
    +没有在该实例上定义
    +-->
    +<li v-for="todo in todos" v-if="!todo.isComplete">
    +  {{ todo.name }}
    +</li>
    +
  6. +
+

列表渲染

    +
  1. v-for与对象

    +
    1
    2
    3
    4
    5
    const myObject = reactive({
    +  title: "How to do lists in Vue",
    +  author: "Jane Doe",
    +  publishedAt: "2016-04-10",
    +});
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <!-- value:属性值,key:属性名,index:索引 -->
    +<li v-for="(value, key, index) in myObject">
    +{{ index }}. {{ key }}: {{ value }}
    +</li>
    +<!-- 渲染结果 -->
    +<!-- 0. title: How to do lists in Vue
    +     1. author: Jane Doe
    +     2. publishedAt: 2016-04-10 -->
    +
    +<!-- 你也可以使用 of 作为分隔符来替代 in,这更接近 JavaScript 的迭代器,二者行为基本一致 -->
    +<div v-for="item of items"></div>
    +<!-- v-for 可以直接接受一个整数值。在这种用例中,会将该模板基于 1...n 的取值范围重复多次。注意此处 n 的初值是从 1 开始而非 0。 -->
    +<span v-for="n in 10">{{ n }}</span>
    +
  2. +
+

事件处理

    +
  1. 关于vue中事件的传参,@click="showInfo1"类似这样的默认会传过来event参数,可以在函数中接受,showInfo1(event){},注意不能加括号,这样不会传event参数。传多个参数的时候需要注意占位问题。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <!-- 使用特殊的 $event 变量 -->
    +<button @click="warn('Form cannot be submitted yet.', $event)">
    +   Submit
    +</button>
    +
    +<!-- 使用内联箭头函数 -->
    +<button @click="(event) => warn('Form cannot be submitted yet.', event)">
    +   Submit
    +</button>
    +
  2. +
  3. 内联事件修饰符

    +
      +
    • .stop
    • +
    • .prevent
    • +
    • .self
    • +
    • .capture
    • +
    • .once
    • +
    • .passive
    • +
    +
  4. +
  5. 按键修饰符
    常用按键别名

    +
      +
    • .enter
    • +
    • .tab
    • +
    • .delete (捕获“Delete”和“Backspace”两个按键)
    • +
    • .esc
    • +
    • .space
    • +
    • .up
    • +
    • .down
    • +
    • .left
    • +
    • .right
    • +
    +

    你可以使用以下系统按键修饰符来触发鼠标或键盘事件监听器,只有当按键被按下时才会触发。

    +
      +
    • .ctrl
    • +
    • .alt
    • +
    • .shift
    • +
    • .meta
    • +
    +

    .exact 修饰符允许控制触发一个事件所需的确定组合的系统按键修饰符。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <!-- 仅在 `key` 为 `Enter` 时调用 `submit` -->
    +<input @keyup.enter="submit" />
    +<input @keyup.page-down="onPageDown" />
    +<!-- Alt + Enter -->
    +<input @keyup.alt.enter="clear" />
    +
    +<!-- Ctrl + 点击 -->
    +<div @click.ctrl="doSomething">Do something</div>
    +
    +<!-- 当按下 Ctrl 时,即使同时按下 Alt 或 Shift 也会触发 -->
    +<button @click.ctrl="onClick">A</button>
    +
    +<!-- 仅当按下 Ctrl 且未按任何其他键时才会触发 -->
    +<button @click.ctrl.exact="onCtrlClick">A</button>
    +
    +<!-- 仅当没有按下任何系统按键时触发 -->
    +<button @click.exact="onClick">A</button>
    +
  6. +
  7. 鼠标按键修饰符

    +
      +
    • .left
    • +
    • .right
    • +
    • .middle
    • +
    +
  8. +
+

表单输入绑定

    +
  1. 收集表单数据

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    <!--
    +我们可以使用 v-model 指令在状态和表单输入之间创建双向绑定。
    +-->
    +
    +<script setup>
    +import { ref } from 'vue'
    +
    +const text = ref('Edit me')
    +const checked = ref(true)
    +const checkedNames = ref(['Jack'])
    +const picked = ref('One')
    +const selected = ref('A')
    +const multiSelected = ref(['A'])
    +</script>
    +
    +<template>
    +  <h2>Text Input</h2>
    +  <input v-model="text"> {{ text }}
    +
    +  <h2>Checkbox</h2>
    +  <input type="checkbox" id="checkbox" v-model="checked">
    +  <label for="checkbox">Checked: {{ checked }}</label>
    +
    +  <!--
    +    多个复选框可以绑定到
    +    相同的 v-model 数组
    +  -->
    +  <h2>Multi Checkbox</h2>
    +  <input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
    +  <label for="jack">Jack</label>
    +  <input type="checkbox" id="john" value="John" v-model="checkedNames">
    +  <label for="john">John</label>
    +  <input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
    +  <label for="mike">Mike</label>
    +  <p>Checked names: <pre>{{ checkedNames }}</pre></p>
    +
    +  <h2>Radio</h2>
    +  <input type="radio" id="one" value="One" v-model="picked">
    +  <label for="one">One</label>
    +  <br>
    +  <input type="radio" id="two" value="Two" v-model="picked">
    +  <label for="two">Two</label>
    +  <br>
    +  <span>Picked: {{ picked }}</span>
    +
    +  <h2>Select</h2>
    +  <!-- 如果 v-model 表达式的初始值不匹配任何一个选择项,<select> 元素会渲染成一个“未选择”的状态。在 iOS 上,这将导致用户无法选择第一项,因为 iOS 在这种情况下不会触发一个 change 事件。因此,我们建议提供一个空值的禁用选项,如上面的例子所示。 -->
    +  <select v-model="selected">
    +    <option disabled value="">Please select one</option>
    +    <option>A</option>
    +    <option>B</option>
    +    <option>C</option>
    +  </select>
    +  <span>Selected: {{ selected }}</span>
    +
    +  <h2>Multi Select</h2>
    +  <select v-model="multiSelected" multiple style="width:100px">
    +    <option>A</option>
    +    <option>B</option>
    +    <option>C</option>
    +  </select>
    +  <span>Selected: {{ multiSelected }}</span>
    +</template>
    +
  2. +
  3. 复选框

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <input type="checkbox" v-model="toggle" true-value="yes" false-value="no" />
    +<input
    +  type="checkbox"
    +  v-model="toggle"
    +  true-value="yes"
    +  false-value="no" />
    +<!-- true-value 和 false-value 是 Vue 特有的 attributes,仅支持和 v-model 配套使用。这里 toggle 属性的值会在选中时被设为 'yes',取消选择时设为 'no'。你同样可以通过 v-bind 将其绑定为其他动态值: -->
    +<input
    +  type="checkbox"
    +  v-model="toggle"
    +  :true-value="dynamicTrueValue"
    +  :false-value="dynamicFalseValue"
    +/>
    +
  4. +
  5. 选择器对象

    +
    1
    2
    3
    4
    <select v-model="selected">
    +<!-- 内联对象字面量 -->
    + <option :value="{ number: 123 }">123</option>
    +</select>
    +
  6. +
+

生命周期

Vue3中的生命周期选项式和组合式有一些区别。

+
    +
  • 选项式:

    +

    可以看到,在选项式api中,setup()函数的执行时机最早。

    +
    +
  • +
  • 组合式

    +

    在组合式api中移除了createonBeforeCreate这两个钩子,因为,其功能与setup()函数重复。

    +
    +
    +

    其他的声明周期钩子,完整示例请参考声明周期钩子索引

    +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <script setup>
    +import { ref, onServerPrefetch, onMounted } from "vue";
    +
    +const data = ref(null);
    +
    +onServerPrefetch(async () => {
    +  // 组件作为初始请求的一部分被渲染
    +  // 在服务器上预抓取数据,因为它比在客户端上更快。
    +  data.value = await fetchOnServer(/* ... */);
    +});
    +
    +onMounted(async () => {
    +  if (!data.value) {
    +    // 如果数据在挂载时为空值,这意味着该组件
    +    // 是在客户端动态渲染的。将转而执行
    +    // 另一个客户端侧的抓取请求
    +    data.value = await fetchOnClient(/* ... */);
    +  }
    +});
    +</script>
    +
  • +
+

侦听器

对于vue3中的计算属性,我们知道需要确保其只做计算而没有副作用。然而在有些情况下,我们需要在状态变化时执行一些“副作用”:例如更改 DOM,或是根据异步操作的结果去修改另一处的状态。

+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<script setup>
+import { ref, watch } from "vue";
+
+const question = ref("");
+const answer = ref("Questions usually contain a question mark. ;-)");
+
+// 可以直接侦听一个 ref
+watch(question, async (newQuestion, oldQuestion) => {
+  if (newQuestion.indexOf("?") > -1) {
+    answer.value = "Thinking...";
+    try {
+      const res = await fetch("https://yesno.wtf/api");
+      answer.value = (await res.json()).answer;
+    } catch (error) {
+      answer.value = "Error! Could not reach the API. " + error;
+    }
+  }
+});
+</script>
+
+<template>
+  <p>
+    Ask a yes/no question:
+    <input v-model="question" />
+  </p>
+  <p>{{ answer }}</p>
+</template>
+
    +
  1. watch 的第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    const x = ref(0);
    +const y = ref(0);
    +
    +// 单个 ref
    +watch(x, (newX) => {
    +  console.log(`x is ${newX}`);
    +});
    +
    +// getter 函数
    +watch(
    +  () => x.value + y.value,
    +  (sum) => {
    +    console.log(`sum of x + y is: ${sum}`);
    +  }
    +);
    +
    +// 多个来源组成的数组
    +watch([x, () => y.value], ([newX, newY]) => {
    +  console.log(`x is ${newX} and y is ${newY}`);
    +});
    +
  2. +
  3. watch函数的疑难解析

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    //情况一:监视 ref 定义的响应式数据
    +watch(
    +  sum,
    +  (newValue, oldValue) => {
    +    console.log("sum变化了", newValue, oldValue);
    +  },
    +  { immediate: true }
    +);
    +
    +//情况二:监视多个 ref 定义的响应式数据 newValue: 新值的数组,按顺序排列, oldValue:  旧值的数组,按顺序排列
    +watch([sum, msg], (newValue, oldValue) => {
    +  console.log("sum或msg变化了", newValue, oldValue);
    +});
    +
    +/* 情况三:监视 reactive 定义的响应式数据
    +      若 watch 监视的是 reactive 定义的响应式数据,则无法正确获得 oldValue!!
    +      若watch监视的是 reactive 定义的响应式数据,则强制开启了深度监视 
    +*/
    +watch(
    +  person,
    +  (newValue, oldValue) => {
    +    console.log("person变化了", newValue, oldValue);
    +  },
    +  { immediate: true, deep: false }
    +); //此处的 deep 配置不再奏效
    +
    +//情况四:监视 reactive 定义的响应式数据中的某个属性
    +watch(
    +  () => person.job,
    +  (newValue, oldValue) => {
    +    console.log("person的job变化了", newValue, oldValue);
    +  },
    +  { immediate: true, deep: true }
    +);
    +
    +//情况五:监视 reactive 定义的响应式数据中的某些属性
    +watch(
    +  [() => person.job, () => person.name],
    +  (newValue, oldValue) => {
    +    console.log("person的job变化了", newValue, oldValue);
    +  },
    +  { immediate: true, deep: true }
    +);
    +
    +//特殊情况
    +watch(
    +  () => person.job,
    +  (newValue, oldValue) => {
    +    console.log("person的job变化了", newValue, oldValue);
    +  },
    +  { deep: true }
    +); // 此处由于监视的是 reactive 是定义的对象中的某个属性,所以 deep 配置有效
    +
  4. +
  5. watchEffectwatchEffect函数可以降低手动维护依赖列表的负担。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // 使用 watch
    +const todoId = ref(1);
    +const data = ref(null);
    +
    +watch(
    +  todoId,
    +  async () => {
    +    const response = await fetch(
    +      `https://jsonplaceholder.typicode.com/todos/${todoId.value}`
    +    );
    +    data.value = await response.json();
    +  },
    +  { immediate: true }
    +);
    +
    +// 使用 watchEffect
    +watchEffect(async () => {
    +  const response = await fetch(
    +    `https://jsonplaceholder.typicode.com/todos/${todoId.value}`
    +  );
    +  data.value = await response.json();
    +});
    +

    watchEffect 仅会在其同步执行期间,才追踪依赖。在使用异步回调时,只有在第一个 await 正常工作前访问到的属性才会被追踪。

    +
    +
  6. +
  7. 回调的触发时机
    默认情况下,用户创建的侦听器回调,都会在 Vue 组件更新之前被调用。这意味着你在侦听器回调中访问的 DOM 将是被 Vue 更新之前的状态。如果想在侦听器回调中能访问被 Vue 更新之后的 DOM,你需要指明 flush: 'post' 选项:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    watch(source, callback, {
    +  flush: "post",
    +});
    +
    +watchEffect(callback, {
    +  flush: "post",
    +});
    +// 后置刷新的 watchEffect() 有个更方便的别名 watchPostEffect():
    +import { watchPostEffect } from "vue";
    +
    +watchPostEffect(() => {
    +  /* 在 Vue 更新后执行 */
    +});
    +
  8. +
  9. 停止侦听器
    setup()<script setup> 中用同步语句创建的侦听器,会自动绑定到宿主组件实例上,并且会在宿主组件卸载时自动停止。因此,在大多数情况下,你无需关心怎么停止一个侦听器。
    一个关键点是,侦听器必须用同步语句创建:如果用异步回调创建一个侦听器,那么它不会绑定到当前组件上,你必须手动停止它,以防内存泄漏。如下方这个例子:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <script setup>
    +import { watchEffect } from "vue";
    +
    +// 它会自动停止
    +watchEffect(() => {});
    +
    +// ...这个则不会!
    +setTimeout(() => {
    +  watchEffect(() => {});
    +}, 100);
    +</script>
    +

    要手动停止一个侦听器,请调用 watchwatchEffect 返回的函数:

    +
    1
    2
    3
    4
    const unwatch = watchEffect(() => {});
    +
    +// ...当该侦听器不再需要时
    +unwatch();
    +

    注意,需要异步创建侦听器的情况很少,请尽可能选择同步创建。如果需要等待一些异步数据,你可以使用条件式的侦听逻辑:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 需要异步请求得到的数据
    +const data = ref(null);
    +// 一个异步的函数,用来从 API 获取数据
    +async function fetchData() {
    +  const response = await fetch("https://some-api.com/data");
    +  data.value = await response.json();
    +}
    +
    +// 在 setup 函数中调用 fetchData 函数
    +fetchData();
    +watchEffect(() => {
    +  if (data.value) {
    +    // 数据加载后执行某些操作...
    +  }
    +});
    +
  10. +
+

模板引用

    +
  1. 基本示例

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <script setup>
    +import { ref, onMounted } from "vue";
    +
    +// 声明一个 ref 来存放该元素的引用
    +// 必须和模板里的 ref 同名
    +const input = ref(null);
    +
    +onMounted(() => {
    +  input.value.focus();
    +});
    +</script>
    +
    +<template>
    +  <input ref="input" />
    +</template>
    +

    注意,你只可以在组件挂载后才能访问模板引用。如果你想在模板中的表达式上访问 input,在初次渲染时会是 null。这是因为在初次渲染前这个元素还不存在呢!
    果你需要侦听一个模板引用 ref 的变化,确保考虑到其值为 null 的情况:

    +
    1
    2
    3
    4
    5
    6
    7
    watchEffect(() => {
    +  if (input.value) {
    +    input.value.focus();
    +  } else {
    +    // 此时还未挂载,或此元素已经被卸载(例如通过 v-if 控制)
    +  }
    +});
    +
    +
  2. +
  3. v-for 中的模板引用:当在 v-for 中使用模板引用时,对应的 ref 中包含的值是一个数组,它将在元素被挂载后包含对应整个列表的所有元素(DOM 对象):

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <script setup>
    +import { ref, onMounted } from "vue";
    +
    +const list = ref([
    +  /* ... */
    +]);
    +
    +const itemRefs = ref([]);
    +
    +onMounted(() => console.log(itemRefs.value));
    +</script>
    +
    +<template>
    +  <ul>
    +    <li v-for="item in list" ref="itemRefs">
    +      {{ item }}
    +    </li>
    +  </ul>
    +</template>
    +

    应该注意的是,ref 数组并不保证与源数组相同的顺序。

    +
    +
  4. +
  5. 函数模板引用
    除了使用字符串值作名字,ref attribute 还可以绑定为一个函数,会在每次组件更新时都被调用。该函数会收到元素引用DOM对象或者组件实例作为其第一个参数:

    +
    1
    2
    <!-- el 为绑定的 DOM 元素 -->
    +<input :ref="(el) => { /* 将 el 赋值给一个数据属性或 ref 变量 */ }">
    +

    注意我们这里需要使用动态的 :ref绑定才能够传入一个函数。当绑定的元素被卸载时,函数也会被调用一次,此时的 el 参数会是 null。你当然也可以绑定一个组件方法而不是内联函数。

    +
    +
  6. +
  7. 组件上的ref
    模板引用也可以被用在一个子组件上。这种情况下引用中获得的值是组件实例:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <script setup>
    +import { ref, onMounted } from "vue";
    +import Child from "./Child.vue";
    +
    +const child = ref(null);
    +
    +onMounted(() => {
    +  // child.value 是 <Child /> 组件的实例
    +});
    +</script>
    +
    +<template>
    +  <Child ref="child" />
    +</template>
    +

    如果一个子组件使用的是选项式 API 或没有使用 <script setup>,被引用的组件实例和该子组件的 this 完全一致,这意味着父组件对子组件的每一个属性和方法都有完全的访问权。这使得在父组件和子组件之间创建紧密耦合的实现细节变得很容易,当然也因此,应该只在绝对需要时才使用组件引用。大多数情况下,你应该首先使用标准的 propsemit 接口来实现父子组件交互。

    +

    有一个例外的情况,使用了 <script setup> 的组件是默认私有的:一个父组件无法访问到一个使用了 <script setup> 的子组件中的任何东西,除非子组件在其中通过 defineExpose 宏显式暴露:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <script setup>
    +import { ref } from "vue";
    +
    +const a = 1;
    +const b = ref(2);
    +
    +// 像 defineExpose 这样的编译器宏不需要导入
    +defineExpose({
    +  a,
    +  b,
    +});
    +</script>
    +

    当父组件通过模板引用获取到了该组件的实例时,得到的实例类型为 { a: number, b: number } (ref 都会自动解包,和一般的实例一样,因为拿到的this是一个proxy对象,类似reactive对象的解包)。

    +
    +
  8. +
+

深入组件

组件注册

一个 Vue 组件在使用前需要先被“注册”,这样 Vue 才能在渲染模板时找到其对应的实现。组件注册有两种方式:全局注册和局部注册。

+
+
    +
  1. 全局注册
    基本示例:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    import { createApp } from "vue";
    +
    +const app = createApp({});
    +
    +app.component(
    +  // 注册的名字
    +  "MyComponent",
    +  // 组件的实现
    +  {
    +    /* ... */
    +  }
    +);
    +// 插件形式
    +import ImgView from "./ImgView/index.vue";
    +import Sku from "./XtxSku/index.vue";
    +
    +export const componentPlugin = {
    +  install(app) {
    +    // app.component('组件名字',组件配置对象)
    +    app.component("ImgView", ImgView);
    +    app.component("XtxSku", Sku);
    +  },
    +};
    +

    .component()方法可以被链式调用

    +
    1
    2
    3
    4
    app
    +  .component("ComponentA", ComponentA)
    +  .component("ComponentB", ComponentB)
    +  .component("ComponentC", ComponentC);
    +
  2. +
  3. 局部注册

    +
      +
    • 组合式:

      +
      1
      2
      3
      4
      5
      6
      7
      <script setup>
      +import ComponentA from "./ComponentA.vue";
      +</script>
      +
      +<template>
      +  <ComponentA />
      +</template>
      +
    • +
    • 选项式

      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      <script>
      +import ComponentA from "./ComponentA.js";
      +
      +export default {
      +  components: {
      +    ComponentA,
      +  },
      +  setup() {
      +    // ...
      +  },
      +};
      +</script>
      +

      请注意:局部注册的组件在后代组件中并不可用。在这个例子中,ComponentA 注册后仅在当前组件可用,而在任何的子组件或更深层的子组件中都不可用。

      +
      +
    • +
    +
  4. +
+

Props

一个组件需要显式声明它所接受的 props,这样 Vue 才能知道外部传入的哪些是 props,哪些是透传 attribute(后续详细讨论)。

+
    +
  1. 采用数组的形式声明

    +
    1
    2
    3
    4
    5
    <script setup>
    +const props = defineProps(["foo"]);
    +
    +console.log(props.foo);
    +</script>
    +
  2. +
  3. 采用对象的形式添加校验

    +
    1
    2
    3
    4
    5
    // 使用 <script setup>
    +defineProps({
    +  title: String,
    +  likes: Number,
    +});
  4. +
  5. 详细的校验规则

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    defineProps({
    +  // 基础类型检查
    +  // (给出 `null` 和 `undefined` 值则会跳过任何类型检查)
    +  propA: Number,
    +  // 多种可能的类型
    +  propB: [String, Number],
    +  // 必传,且为 String 类型
    +  propC: {
    +    type: String,
    +    required: true,
    +  },
    +  // Number 类型的默认值
    +  propD: {
    +    type: Number,
    +    default: 100,
    +  },
    +  // 对象类型的默认值
    +  propE: {
    +    type: Object,
    +    // 对象或数组的默认值
    +    // 必须从一个工厂函数返回。
    +    // 该函数接收组件所接收到的原始 prop 作为参数。
    +    default(rawProps) {
    +      return { message: "hello" };
    +    },
    +  },
    +  // 自定义类型校验函数
    +  propF: {
    +    validator(value) {
    +      // The value must match one of these strings
    +      return ["success", "warning", "danger"].includes(value);
    +    },
    +  },
    +  // 函数类型的默认值
    +  propG: {
    +    type: Function,
    +    // 不像对象或数组的默认,这不是一个
    +    // 工厂函数。这会是一个用来作为默认值的函数
    +    default() {
    +      return "Default function";
    +    },
    +  },
    +});
    +

    defineProps() 宏中的参数不可以访问 <script setup> 中定义的其他变量,因为在编译时整个表达式都会被移到外部的函数中。

    +
    +

    一些补充细节:

    +
      +
    • 所有 prop 默认都是可选的,除非声明了required: true

      +
    • +
    • Boolean 外的未传递的可选 prop 将会有一个默认值 undefined

      +
    • +
    • Boolean 类型的未传递 prop 将被转换为 false。这可以通过为它设置 default 来更改——例如:设置为 default: undefined 将与非布尔类型的 prop 的行为保持一致。

      +
    • +
    • 如果声明了 default 值,那么在 prop 的值被解析为 undefined 时,无论 prop 是未被传递还是显式指明的 undefined,都会改为 default 值。

      +
    • +
    • prop 的校验失败后,Vue 会抛出一个控制台警告 (在开发模式下)。

      +
    • +
    • 如果使用了基于类型的 prop 声明 ,Vue 会尽最大努力在运行时按照 prop 的类型标注进行编译。举例来说,defineProps<{ msg: string }> 会被编译为 { msg: { type: String, required: true }}

      +
    • +
    +

    另外,type 也可以是自定义的类或构造函数,Vue 将会通过 instanceof 来检查类型是否匹配。例如下面这个类:

    +
    1
    2
    3
    4
    5
    6
    class Person {
    +  constructor(firstName, lastName) {
    +    this.firstName = firstName;
    +    this.lastName = lastName;
    +  }
    +}
    +

    你可以将其作为一个 prop 的类型:

    +
    1
    2
    3
    defineProps({
    +  author: Person,
    +});
    +
  6. +
  7. Boolean类型转换
    为了更贴近原生 boolean attributes 的行为,声明为 Boolean 类型的 props 有特别的类型转换规则。以带有如下声明的 <MyComponent> 组件为例:

    +
    1
    2
    3
    4
    5
    <!-- 等同于传入 :disabled="true" -->
    +<MyComponent disabled />
    +
    +<!-- 等同于传入 :disabled="false" -->
    +<MyComponent />
    +

    当一个 prop 被声明为允许多种类型时,Boolean 的转换规则也将被应用。然而,当同时允许 StringBoolean 时,有一种边缘情况——只有当 Boolean 出现在 String 之前时,Boolean 转换规则才适用:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // disabled 将被转换为 true
    +defineProps({
    +  disabled: [Boolean, Number],
    +});
    +
    +// disabled 将被转换为 true
    +defineProps({
    +  disabled: [Boolean, String],
    +});
    +
    +// disabled 将被转换为 true
    +defineProps({
    +  disabled: [Number, Boolean],
    +});
    +
    +// disabled 将被解析为空字符串 (disabled="")
    +defineProps({
    +  disabled: [String, Boolean],
    +});
    +
  8. +
  9. 单向数据流

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const props = defineProps(["initialCounter"]);
    +
    +// 计数器只是将 props.initialCounter 作为初始值
    +// 像下面这样做就使 prop 和后续更新无关了
    +const counter = ref(props.initialCounter);
    +
    +const props = defineProps(["size"]);
    +
    +// 该 prop 变更时计算属性也会自动更新
    +const normalizedSize = computed(() => props.size.trim().toLowerCase());
    +
  10. +
+

组件事件

    +
  1. 触发与监听事件

    +
    1
    2
    3
    4
    5
    6
    <!-- MyComponent -->
    +<button @click="$emit('someEvent')">click me</button>
    +
    +<MyComponent @some-event="callback" />
    +<!-- 同样,组件的事件监听器也支持 .once 修饰符: -->
    +<MyComponent @some-event.once="callback" />
    +

    像组件与 prop 一样,事件的名字也提供了自动的格式转换。注意这里我们触发了一个以 camelCase 形式命名的事件,但在父组件中可以使用 kebab-case 形式来监听。与 prop 大小写格式一样,在模板中我们也推荐使用 kebab-case 形式来编写监听器。

    +
    +
  2. +
  3. 事件参数

    +
    1
    2
    3
    function increaseCount(n) {
    +  count.value += n;
    +}
    +
    1
    2
    3
    4
    5
    <button @click="$emit('increaseBy', 1)">
    + Increase by 1
    +</button>
    +<MyButton @increase-by="(n) => (count += n)" />
    +<MyButton @increase-by="increaseCount" />
    +
  4. +
  5. 声明触发的事件

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <script setup>
    +const emit = defineEmits(["inFocus", "submit"]);
    +
    +function buttonClick() {
    +  emit("submit");
    +}
    +</script>
    +<!-- 这个 emits 选项和 defineEmits() 宏还支持对象语法,它允许我们对触发事件的参数进行验证: -->
    +<script setup lang="ts">
    +const emit = defineEmits<{
    +  (e: "change", id: number): void;
    +  (e: "update", value: string): void;
    +}>();
    +</script>
    +

    如果一个原生事件的名字 (例如 click) 被定义在 emits 选项中,则监听器只会监听组件触发的 click 事件而不会再响应原生的 click 事件。

    +
    +
  6. +
  7. 事件校验
    和对 props 添加类型校验的方式类似,所有触发的事件也可以使用对象形式来描述。
    要为事件添加校验,那么事件可以被赋值为一个函数,接受的参数就是抛出事件时传入 emit 的内容,返回一个布尔值来表明事件是否合法。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <script setup>
    +const emit = defineEmits({
    +  // 没有校验
    +  click: null,
    +
    +  // 校验 submit 事件
    +  submit: ({ email, password }) => {
    +    if (email && password) {
    +      return true;
    +    } else {
    +      console.warn("Invalid submit event payload!");
    +      return false;
    +    }
    +  },
    +});
    +
    +function submitForm(email, password) {
    +  emit("submit", { email, password });
    +}
    +</script>
    +
  8. +
+

组件 v-model

    +
  1. 当使用在一个组件上时,v-model 会被展开为如下的形式:

    +
    1
    2
    3
    4
    <CustomInput
    +  :model-value="searchText"
    +  @update:model-value="(newValue) => (searchText = newValue)"
    +/>
    +

    要让这个例子实际工作起来,<CustomInput> 组件内部需要做两件事:

    +

    将内部原生 <input> 元素的 value attribute 绑定到 modelValue prop
    当原生的 input 事件触发时,触发一个携带了新值的 update:modelValue 自定义事件

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <!-- CustomInput.vue -->
    +<script setup>
    +defineProps(["modelValue"]);
    +defineEmits(["update:modelValue"]);
    +</script>
    +
    +<template>
    +  <input
    +    :value="modelValue"
    +    @input="$emit('update:modelValue', $event.target.value)"
    +  />
    +</template>
    +

    另一种在组件内实现 v-model 的方式是使用一个可写的,同时具有 gettersettercomputed 属性。get 方法需返回 modelValue prop,而 set 方法需触发相应的事件:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <!-- CustomInput.vue -->
    +<script setup>
    +import { computed } from "vue";
    +
    +const props = defineProps(["modelValue"]);
    +const emit = defineEmits(["update:modelValue"]);
    +
    +const value = computed({
    +  get() {
    +    return props.modelValue;
    +  },
    +  set(value) {
    +    emit("update:modelValue", value);
    +  },
    +});
    +</script>
    +
    +<template>
    +  <input v-model="value" />
    +</template>
    +

    HTML 中的原生表单元素的v-model Vue对其的实现与组件不同,涉及到输入开始与结束的事件,所以在IME的输入法中与input事件的行为有所不同。

    +
    +
  2. +
  3. v-model的参数,给propsemit事件添加名字
    默认情况下,v-model 在组件上都是使用 modelValue 作为 prop,并以 update:modelValue 作为对应的事件。我们可以通过给 v-model 指定一个参数来更改这些名字:

    +
    1
    <UserName v-model:first-name="first" v-model:last-name="last" />
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <script setup>
    +defineProps({
    +  firstName: String,
    +  lastName: String,
    +});
    +
    +defineEmits(["update:firstName", "update:lastName"]);
    +</script>
    +
    +<template>
    +  <input
    +    type="text"
    +    :value="firstName"
    +    @input="$emit('update:firstName', $event.target.value)"
    +  />
    +  <input
    +    type="text"
    +    :value="lastName"
    +    @input="$emit('update:lastName', $event.target.value)"
    +  />
    +</template>
    +
  4. +
  5. 处理v-model修饰符
    在某些场景下,你可能想要一个自定义组件的 v-model 支持自定义的修饰符。我们来创建一个自定义的修饰符 capitalize,它会自动将 v-model 绑定输入的字符串值第一个字母转为大写:

    +
    1
    <MyComponent v-model.capitalize="myText" />
    +

    组件的 v-model 上所添加的修饰符,可以通过 modelModifiers prop 在组件内访问到。在下面的组件中,我们声明了 modelModifiers 这个 prop,它的默认值是一个空对象:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <script setup>
    +const props = defineProps({
    +  modelValue: String,
    +  modelModifiers: { default: () => ({}) },
    +});
    +defineEmits(["update:modelValue"]);
    +
    +console.log(props.modelModifiers); // { capitalize: true }
    +</script>
    +
    +<template>
    +  <input
    +    type="text"
    +    :value="modelValue"
    +    @input="$emit('update:modelValue', $event.target.value)"
    +  />
    +</template>
    +

    注意这里组件的 modelModifiers prop 包含了 capitalize 且其值为 true,因为它在模板中的 v-model 绑定 v-model.capitalize="myText" 上被使用了。

    +

    有了这个 prop,我们就可以检查 modelModifiers 对象的键,并编写一个处理函数来改变抛出的值。在下面的代码里,我们就是在每次 <input /> 元素触发 input 事件时将值的首字母大写:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <script setup>
    +const props = defineProps({
    +  modelValue: String,
    +  modelModifiers: { default: () => ({}) },
    +});
    +
    +const emit = defineEmits(["update:modelValue"]);
    +
    +function emitValue(e) {
    +  let value = e.target.value;
    +  if (props.modelModifiers.capitalize) {
    +    value = value.charAt(0).toUpperCase() + value.slice(1);
    +  }
    +  emit("update:modelValue", value);
    +}
    +</script>
    +
    +<template>
    +  <input type="text" :value="modelValue" @input="emitValue" />
    +</template>
    +
  6. +
  7. 带参数的 v-model 修饰符
    对于又有参数又有修饰符的 v-model 绑定,生成的 prop 名将是 arg + "Modifiers"。举例来说:

    +
    1
    <MyComponent v-model:title.capitalize="myText">
    +

    相应的声明应该是:

    +
    1
    2
    3
    4
    const props = defineProps(["title", "titleModifiers"]);
    +defineEmits(["update:title"]);
    +
    +console.log(props.titleModifiers); // { capitalize: true }
    +

    这里是另一个例子,展示了如何在使用多个不同参数的 v-model 时使用修饰符:

    +
    1
    2
    3
    4
    <UserName
    +  v-model:first-name.capitalize="first"
    +  v-model:last-name.uppercase="last"
    +/>
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <script setup>
    +const props = defineProps({
    +  firstName: String,
    +  lastName: String,
    +  firstNameModifiers: { default: () => ({}) },
    +  lastNameModifiers: { default: () => ({}) },
    +});
    +defineEmits(["update:firstName", "update:lastName"]);
    +
    +console.log(props.firstNameModifiers); // { capitalize: true }
    +console.log(props.lastNameModifiers); // { uppercase: true}
    +</script>
    +
  8. +
+

透传 Attributes

    +
  1. Attributes 继承
    “透传 attribute”指的是传递给一个组件,却没有被该组件声明为 propsemitsattribute 或者 v-on 事件监听器。最常见的例子就是 classstyleid

    +
    1
    2
    3
    4
    5
    6
    <!-- <MyButton> 的模板 -->
    +<button>click me</button>
    +<!-- 一个父组件使用了这个组件,并且传入了 class: -->
    +<MyButton class="large" />
    +<!-- 最后渲染出的 DOM 结果是: -->
    +<button class="large">click me</button>
    +

    这里,<MyButton> 并没有将 class 声明为一个它所接受的 prop,所以 class 被视作透传 attribute,自动透传到了 <MyButton> 的根元素上。

    +
    +
  2. +
  3. classstyle 的合并

    +
    1
    2
    3
    4
    <!-- <MyButton> 的模板 -->
    +<button class="btn">click me</button>
    +<!-- 则最后渲染出的 DOM 结果会变成 -->
    +<button class="btn large">click me</button>
    +
  4. +
  5. v-on 监听器继承
    同样的规则也适用于 v-on 事件监听器:

    +
    1
    <MyButton @click="onClick" />
    +

    click 监听器会被添加到 <MyButton> 的根元素,即那个原生的 <button> 元素之上。当原生的 <button> 被点击,会触发父组件的 onClick 方法。同样的,如果原生 button 元素自身也通过 v-on 绑定了一个事件监听器,则这个监听器和从父组件继承的监听器都会被触发。

    +
  6. +
  7. 深层组件继承
    有些情况下一个组件会在根节点上渲染另一个组件。例如,我们重构一下 <MyButton>,让它在根节点上渲染 <BaseButton>

    +
    1
    2
    <!-- <MyButton/> 的模板,只是渲染另一个组件 -->
    +<BaseButton />
    +

    此时 <MyButton> 接收的透传 attribute 会直接继续传给 <BaseButton>

    +

    请注意:

    +
      +
    1. 透传的 attribute 不会包含 <MyButton> 上声明过的 props 或是针对 emits 声明事件的 v-on 侦听函数,换句话说,声明过的 props 和侦听函数被 <MyButton>“消费”了。

      +
    2. +
    3. 透传的 attribute 若符合声明,也可以作为 props 传入 <BaseButton>

      +
    4. +
    +
  8. +
  9. 禁用 Attributes 继承

    +
    1
    2
    3
    4
    5
    6
    <script setup>
    +defineOptions({
    +  inheritAttrs: false,
    +});
    +// ...setup 逻辑
    +</script>
    +

    最常见的需要禁用 attribute 继承的场景就是 attribute 需要应用在根节点以外的其他元素上。通过设置 inheritAttrs 选项为 false,你可以完全控制透传进来的 attribute 被如何使用。

    +

    这些透传进来的 attribute 可以在模板的表达式中直接用 $attrs 访问到。

    +
    1
    <span>Fallthrough attribute: {{ $attrs }}</span>
    +

    这个 $attrs 对象包含了除组件所声明的 propsemits 之外的所有其他 attribute,例如 classstylev-on 监听器等等。

    +

    有几点需要注意:

    +

    props 有所不同,透传 attributesJavaScript 中保留了它们原始的大小写,所以像 foo-bar 这样的一个 attribute 需要通过 $attrs['foo-bar'] 来访问。

    +

    @click 这样的一个 v-on 事件监听器将在此对象下被暴露为一个函数 $attrs.onClick

    +

    现在我们要再次使用一下之前小节中的 <MyButton> 组件例子。有时候我们可能为了样式,需要在 <button> 元素外包装一层 <div>

    +
    1
    2
    3
    <div class="btn-wrapper">
    +  <button class="btn">click me</button>
    +</div>
    +

    我们想要所有像 classv-on 监听器这样的透传 attribute 都应用在内部的 <button> 上而不是外层的 <div> 上。我们可以通过设定 inheritAttrs: false 和使用 v-bind="$attrs" 来实现:

    +
    1
    2
    3
    <div class="btn-wrapper">
    +  <button class="btn" v-bind="$attrs">click me</button>
    +</div>
    +
  10. +
  11. 多根节点的 Attributes 继承
    和单根节点组件有所不同,有着多个根节点的组件没有自动 attribute 透传行为。如果 $attrs 没有被显式绑定,将会抛出一个运行时警告。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <CustomLayout id="custom-layout" @click="changeValue" />
    +<!-- 如果 <CustomLayout> 有下面这样的多根节点模板,由于 Vue 不知道要将 attribute 透传到哪里,所以会抛出一个警告。 -->
    +<header>...</header>
    +<main>...</main>
    +<footer>...</footer>
    +<!-- 如果 $attrs 被显式绑定,则不会有警告: -->
    +<header>...</header>
    +<main v-bind="$attrs">...</main>
    +<footer>...</footer>
    +
  12. +
  13. setup中访问Attributes

    +
    1
    2
    3
    4
    5
    <script setup>
    +import { useAttrs } from "vue";
    +
    +const attrs = useAttrs();
    +</script>
    +

    需要注意的是,虽然这里的 attrs 对象总是反映为最新的透传 attribute,但它并不是响应式的 (考虑到性能因素),即不像props一样随着父元素的更新而更新。你不能通过侦听器去监听它的变化。如果你需要响应性,可以使用 prop。或者你也可以使用 onUpdated() 使得在每次更新时结合最新的 attrs 执行副作用。

    +
    +
  14. +
+

插槽

    +
  1. 插槽内容可以是元素,文本甚至组件
  2. +
  3. 插槽的默认内容,默认显示,但提供了插槽的内容则会取代默认内容

    +
    1
    2
    3
    4
    5
    6
    <button type="submit">
    +  <slot>
    +    <!-- 默认内容 -->
    +    Submit
    +  </slot>
    +</button>
    +
  4. +
  5. 具名插槽

    +
    +

    没有提供name<slot> 出口会隐式地命名为“default”。

    +

    v-slot 有对应的简写 #,因此 <template v-slot:header> 可以简写为 <template #header>。其意思就是“将这部分模板片段传入子组件的 header 插槽中”

    +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    <div class="container">
    +  <header>
    +    <slot name="header"></slot>
    +  </header>
    +  <main>
    +    <slot></slot>
    +  </main>
    +  <footer>
    +    <slot name="footer"></slot>
    +  </footer>
    +</div>
    +
    +<BaseLayout>
    +  <template v-slot:header>
    +    <!-- header 插槽的内容放这里 -->
    +  </template>
    +</BaseLayout>
    +
    +<BaseLayout>
    +  <template #header>
    +    <h1>Here might be a page title</h1>
    +  </template>
    +
    +  <!-- 隐式的默认插槽 -->
    +  <p>A paragraph for the main content.</p>
    +  <p>And another one.</p>
    +
    +  <template #footer>
    +    <p>Here's some contact info</p>
    +  </template>
    +</BaseLayout>
    +
  6. +
  7. 动态插槽

    +
    1
    2
    3
    4
    5
    6
    <base-layout>
    +  <template v-slot:[dynamicSlotName]> ... </template>
    +
    +  <!-- 缩写为 -->
    +  <template #[dynamicSlotName]> ... </template>
    +</base-layout>
    +
  8. +
  9. 作用域插槽

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    <MyComponent>
    +  <template #header="headerProps"> {{ headerProps }} </template>
    +
    +  <template #default="defaultProps"> {{ defaultProps }} </template>
    +
    +  <template #footer="footerProps"> {{ footerProps }} </template>
    +</MyComponent>
    +
    +<!-- 该模板无法编译,message仅默认插槽可见 -->
    +<template>
    +  <MyComponent v-slot="{ message }">
    +    <p>{{ message }}</p>
    +    <template #footer>
    +      <!-- message 属于默认插槽,此处不可用 -->
    +      <p>{{ message }}</p>
    +    </template>
    +  </MyComponent>
    +</template>
    +
    +<slot name="header" message="hello"></slot>
    +
    +<template>
    +<!-- 显示声明默认插槽作用域 -->
    +  <MyComponent>
    +    <!-- 使用显式的默认插槽 -->
    +    <template #default="{ message }">
    +      <p>{{ message }}</p>
    +    </template>
    +    <template #footer>
    +      <p>Here's some contact info</p>
    +    </template>
    +  </MyComponent>
    +</template>
    +
  10. +
+

依赖注入

    +
  1. 示例

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <script setup>
    +import { provide } from "vue";
    +
    +provide(/* 注入名 */ "message", /* 值 */ "hello!");
    +
    +// 全局注入
    +import { createApp } from "vue";
    +
    +const app = createApp({});
    +
    +app.provide(/* 注入名 */ "message", /* 值 */ "hello!");
    +</script>
    +
    +<!-- 接收 -->
    +<script setup>
    +import { inject } from "vue";
    +
    +const message = inject("message");
    +</script>
    +
  2. +
  3. 默认值

    +
    1
    2
    3
    // 如果没有祖先组件提供 "message"
    +// `value` 会是 "这是默认值"
    +const value = inject("message", "这是默认值");
    +

    在一些场景中,默认值可能需要通过调用一个函数或初始化一个类来取得。为了避免在用不到默认值的情况下进行不必要的计算或产生副作用,我们可以使用工厂函数来创建默认值:

    +
    1
    const value = inject("key", () => new ExpensiveClass(), true);
    +

    第三个参数表示默认值应该被当作一个工厂函数,即返回一个函数,按需调用。

    +
  4. +
  5. 如果你想确保提供的数据不能被注入方的组件更改,你可以使用 readonly() 来包装提供的值。

    +
    1
    2
    3
    4
    5
    6
    <script setup>
    +import { ref, provide, readonly } from "vue";
    +
    +const count = ref(0);
    +provide("read-only-count", readonly(count));
    +</script>
    +

    provide传递ref对象不会发生解包。

    +
    +
  6. +
+

异步组件

    +
  1. 基本用法

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import { defineAsyncComponent } from "vue";
    +
    +const AsyncComp = defineAsyncComponent(() => {
    +  return new Promise((resolve, reject) => {
    +    // ...从服务器获取组件
    +    resolve(/* 获取到的组件 */);
    +  });
    +});
    +// ... 像使用其他一般组件一样使用 `AsyncComp`
    +
    +import { defineAsyncComponent } from "vue";
    +
    +const AsyncComp = defineAsyncComponent(() =>
    +  import("./components/MyComponent.vue")
    +);
    +

    最后得到的 AsyncComp 是一个外层包装过的组件,仅在页面需要它渲染时才会调用加载内部实际组件的函数。它会将接收到的 props 和插槽传给内部组件,所以你可以使用这个异步的包装组件无缝地替换原始组件,同时实现延迟加载。

    +
  2. +
  3. 全局注册和父组件定义

    +
    1
    2
    3
    4
    app.component(
    +  "MyComponent",
    +  defineAsyncComponent(() => import("./components/MyComponent.vue"))
    +);
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <script setup>
    +import { defineAsyncComponent } from "vue";
    +
    +const AdminPage = defineAsyncComponent(() =>
    +  import("./components/AdminPageComponent.vue")
    +);
    +</script>
    +
    +<template>
    +  <AdminPage />
    +</template>
    +
  4. +
  5. 加载与错误状态
    异步操作不可避免地会涉及到加载和错误状态,因此 defineAsyncComponent() 也支持在高级选项中处理这些状态:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    const AsyncComp = defineAsyncComponent({
    +  // 加载函数
    +  loader: () => import("./Foo.vue"),
    +
    +  // 加载异步组件时使用的组件
    +  loadingComponent: LoadingComponent,
    +  // 展示加载组件前的延迟时间,默认为 200ms
    +  delay: 200,
    +
    +  // 加载失败后展示的组件
    +  errorComponent: ErrorComponent,
    +  // 如果提供了一个 timeout 时间限制,并超时了
    +  // 也会显示这里配置的报错组件,默认值是:Infinity
    +  timeout: 3000,
    +});
    +

    如果提供了一个加载组件,它将在内部组件加载时先行显示。在加载组件显示之前有一个默认的 200ms 延迟——这是因为在网络状况较好时,加载完成得很快,加载组件和最终组件之间的替换太快可能产生闪烁,反而影响用户感受。

    +

    如果提供了一个报错组件,则它会在加载器函数返回的 Promise 抛错时被渲染。你还可以指定一个超时时间,在请求耗时超过指定时间时也会渲染报错组件。

    +
  6. +
+

逻辑复用

组合式函数

Vue 的组合式 API 逻辑服用很大程度上借鉴了React Hooks的使用,核心目的为将一个函数或者逻辑抽离到一个文件中形成复用。

+
+
    +
  1. 异步状态示例

    +
      +
    • App.vue

      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      <script setup>
      +import { ref, computed } from "vue";
      +import { useFetch } from "./useFetch.js";
      +
      +const baseUrl = "https://jsonplaceholder.typicode.com/todos/";
      +const id = ref("1");
      +const url = computed(() => baseUrl + id.value);
      +
      +const { data, error, retry } = useFetch(url);
      +</script>
      +
      +<template>
      +  Load post id:
      +  <button v-for="i in 5" @click="id = i">{{ i }}</button>
      +
      +  <div v-if="error">
      +    <p>Oops! Error encountered: {{ error.message }}</p>
      +    <button @click="retry">Retry</button>
      +  </div>
      +  <div v-else-if="data">
      +    Data loaded:
      +    <pre>{{ data }}</pre>
      +  </div>
      +  <div v-else>Loading...</div>
      +</template>
      +
    • +
    • useFetch.js

      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      import { ref, watchEffect, toValue } from "vue";
      +
      +export function useFetch(url) {
      +  const data = ref(null);
      +  const error = ref(null);
      +
      +  watchEffect(async () => {
      +    // reset state before fetching..
      +    data.value = null;
      +    error.value = null;
      +
      +    // resolve the url value synchronously so it's tracked as a
      +    // dependency by watchEffect()
      +    const urlValue = toValue(url);
      +
      +    try {
      +      // artificial delay / random error
      +      await timeout();
      +      // unref() will return the ref value if it's a ref
      +      // otherwise the value will be returned as-is
      +      const res = await fetch(urlValue);
      +      data.value = await res.json();
      +    } catch (e) {
      +      error.value = e;
      +    }
      +  });
      +
      +  return { data, error };
      +}
      +
      +// artificial delay
      +function timeout() {
      +  return new Promise((resolve, reject) => {
      +    setTimeout(() => {
      +      if (Math.random() > 0.3) {
      +        resolve();
      +      } else {
      +        reject(new Error("Random Error"));
      +      }
      +    }, 300);
      +  });
      +}
      +

      toValue() 是一个在 3.3 版本中新增的 API。它的设计目的是将 refgetter 规范化为值。如果参数是 ref,它会返回 ref 的值;如果参数是函数,它会调用函数并返回其返回值。否则,它会原样返回参数。它的工作方式类似于 unref(),但对函数有特殊处理。

      +
      +
    • +
    +
  2. +
  3. 约定与最佳实践

    +
      +
    • 命名:组合式函数约定用驼峰命名法命名,并以 use 作为开头。
    • +
    • 输入参数:即便不依赖于 refgetter 的响应性,组合式函数也可以接收它们作为参数。如果你正在编写一个可能被其他开发者使用的组合式函数,最好处理一下输入参数是 refgetter 而非原始值的情况。可以利用 toValue() 工具函数来实现:

      +
      1
      2
      3
      4
      5
      6
      7
      8
      import { toValue } from "vue";
      +
      +function useFeature(maybeRefOrGetter) {
      +  // 如果 maybeRefOrGetter 是一个 ref 或 getter,
      +  // 将返回它的规范化值。
      +  // 否则原样返回。
      +  const value = toValue(maybeRefOrGetter);
      +}
      +

      如果你的组合式函数在输入参数是 refgetter 的情况下创建了响应式 effect,为了让它能够被正确追踪,请确保要么使用 watch() 显式地监视 refgetter,要么在 watchEffect() 中调用 toValue(),以确保能在await之前正确追踪依赖。

      +
      +
    • +
    • 返回值:你可能已经注意到了,我们一直在组合式函数中使用 ref() 而不是 reactive()。我们推荐的约定是组合式函数始终返回一个包含多个 ref 的普通的非响应式对象,这样该对象在组件中被解构为 ref 之后仍可以保持响应性:

      +
      1
      2
      // x 和 y 是两个 ref
      +const { x, y } = useMouse();
      +

      从组合式函数返回一个响应式对象会导致在对象解构过程中丢失与组合式函数内状态的响应性连接。与之相反,ref 则可以维持这一响应性连接。
      如果你更希望以对象属性的形式来使用组合式函数中返回的状态,你可以将返回的对象用 reactive() 包装一次,这样其中的 ref 会被自动解包,例如:

      +
      1
      2
      3
      const mouse = reactive(useMouse());
      +// mouse.x 链接到了原来的 x ref
      +console.log(mouse.x);
      +
      1
      Mouse position is at: {{ mouse.x }}, {{ mouse.y }}
      +
    • +
    • 副作用

      +

      在组合式函数中的确可以执行副作用 (例如:添加 DOM 事件监听器或者请求数据),但请注意以下规则:
      如果你的应用用到了服务端渲染 (SSR),请确保在组件挂载后才调用的生命周期钩子中执行 DOM 相关的副作用,例如:onMounted()。这些钩子仅会在浏览器中被调用,因此可以确保能访问到 DOM
      确保在 onUnmounted() 时清理副作用。举例来说,如果一个组合式函数设置了一个事件监听器,它就应该在 onUnmounted() 中被移除 (就像我们在 useMouse() 示例中看到的一样)。当然也可以像之前的 useEventListener() 示例那样,使用一个组合式函数来自动帮你做这些事。

      +
    • +
    • 使用限制

      +

      组合式函数只能在 <script setup>setup() 钩子中被调用。在这些上下文中,它们也只能被同步调用。在某些情况下,你也可以在像 onMounted() 这样的生命周期钩子中调用它们。

      +

      这些限制很重要,因为这些是 Vue 用于确定当前活跃的组件实例的上下文。访问活跃的组件实例很有必要,这样才能:

      +
        +
      1. 将生命周期钩子注册到该组件实例上。

        +
      2. +
      3. 将计算属性和监听器注册到该组件实例上,以便在该组件被卸载时停止监听,避免内存泄漏。

        +
      4. +
      +
    • +
    +
  4. +
  5. 和无渲染组件的对比
    我们推荐在纯逻辑复用时使用组合式函数,在需要同时复用逻辑和视图布局时使用无渲染组件。

    +
    插件无渲染组件的示例 +
    +

    如果我们将这个概念拓展一下,可以想象的是,一些组件可能只包括了逻辑而不需要自己渲染内容,视图输出通过作用域插槽全权交给了消费者组件。我们将这种类型的组件称为无渲染组件

    1. App.vue

      1
      2
      3
      4
      5
      6
      7
      8
      9
      <script setup>
      +import MouseTracker from "./MouseTracker.vue";
      +</script>
      +
      +<template>
      +  <MouseTracker v-slot="{ x, y }">
      +    Mouse is at: {{ x }}, {{ y }}
      +  </MouseTracker>
      +</template>
    2. MouseTracker.vue

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      <script setup>
      +import { ref, onMounted, onUnmounted } from "vue";
      +
      +const x = ref(0);
      +const y = ref(0);
      +
      +const update = (e) => {
      +  x.value = e.pageX;
      +  y.value = e.pageY;
      +};
      +
      +onMounted(() => window.addEventListener("mousemove", update));
      +onUnmounted(() => window.removeEventListener("mousemove", update));
      +</script>
      +
      +<template>
      +  <slot :x="x" :y="y" />
      +</template>
    +
    +
    +
  6. +
+

自定义指令

基本使用

    +
  1. <script setup>中:在<script setup> 中,任何以 v 开头的驼峰式命名的变量都可以被用作一个自定义指令。在上面的例子中,vFocus 即可以在模板中以 v-focus 的形式使用。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <script setup>
    +// 在模板中启用 v-focus
    +// 假设你还未点击页面中的其他地方,那么上面这个 input 元素应该会被自动聚焦。该指令比 autofocus attribute 更有用,因为它不仅仅可以在页面加载完成后生效,还可以在 Vue 动态插入元素后生效。
    +const vFocus = {
    +  mounted: (el) => el.focus(),
    +};
    +</script>
    +
    +<template>
    +  <input v-focus />
    +</template>
    +
  2. +
  3. 选项式api:在没有使用 <script setup> 的情况下,自定义指令需要通过 directives 选项注册:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    export default {
    +  setup() {
    +    /*...*/
    +  },
    +  directives: {
    +    // 在模板中启用 v-focus
    +    focus: {
    +      /* ... */
    +    },
    +  },
    +};
    +
  4. +
  5. 全局注册

    +
    1
    2
    3
    4
    5
    6
    const app = createApp({});
    +
    +// 使 v-focus 在所有组件中都可用
    +app.directive("focus", {
    +  /* ... */
    +});
    +

    只有当所需功能只能通过直接的 DOM 操作来实现时,才应该使用自定义指令。其他情况下应该尽可能地使用 v-bind 这样的内置指令来声明式地使用模板,这样更高效,也对服务端渲染更友好。

    +
    +
  6. +
+

指令钩子

    +
  1. 基本使用

    +

    一个指令的定义对象可以提供几种钩子函数 (都是可选的):

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    const myDirective = {
    +  // 在绑定元素的 attribute 前
    +  // 或事件监听器应用前调用
    +  created(el, binding, vnode, prevVnode) {
    +    // 下面会介绍各个参数的细节
    +  },
    +  // 在元素被插入到 DOM 前调用
    +  beforeMount(el, binding, vnode, prevVnode) {},
    +  // 在绑定元素的父组件
    +  // 及他自己的所有子节点都挂载完成后调用
    +  mounted(el, binding, vnode, prevVnode) {},
    +  // 绑定元素的父组件更新前调用
    +  beforeUpdate(el, binding, vnode, prevVnode) {},
    +  // 在绑定元素的父组件
    +  // 及他自己的所有子节点都更新后调用
    +  updated(el, binding, vnode, prevVnode) {},
    +  // 绑定元素的父组件卸载前调用
    +  beforeUnmount(el, binding, vnode, prevVnode) {},
    +  // 绑定元素的父组件卸载后调用
    +  unmounted(el, binding, vnode, prevVnode) {},
    +};
    +
  2. +
  3. 钩子参数
    指令的钩子会传递以下几种参数:

    +
      +
    • el:指令绑定到的元素。这可以用于直接操作 DOM

      +
    • +
    • binding:一个对象,包含以下属性。

      +
        +
      • value:传递给指令的值。例如在 v-my-directive="1 + 1" 中,值是 2。
      • +
      • oldValue:之前的值,仅在 beforeUpdateupdated 中可用。无论值是否更改,它都可用。
      • +
      • arg:传递给指令的参数 (如果有的话)。例如在 v-my-directive:foo 中,参数是 "foo"
      • +
      • modifiers:一个包含修饰符的对象 (如果有的话)。例如在 v-my-directive.foo.bar 中,修饰符对象是 { foo: true, bar: true }
      • +
      • instance:使用该指令的组件实例。
      • +
      • dir:指令的定义对象。
      • +
      +
    • +
    • vnode:代表绑定元素的底层 VNode

      +
    • +
    • prevNode:代表之前的渲染中指令所绑定元素的 VNode。仅在 beforeUpdateupdated 钩子中可用。

      +
    • +
    +

    举例来说,像下面这样使用指令:

    +
    1
    <div v-example:foo.bar="baz">
    +

    binding参数是一个这样的对象

    +
    1
    2
    3
    4
    5
    6
    {
    +  arg: 'foo',
    +  modifiers: { bar: true },
    +  value: /* `baz` 的值 */,
    +  oldValue: /* 上一次更新时 `baz` 的值 */
    +}
    +

    和内置指令类似,自定义指令的参数也可以是动态的。举例来说:

    +
    1
    2
    <div v-example:[arg]="value"></div>
    +<!-- 这里指令的参数会基于组件的 arg 数据属性响应式地更新。 -->
    +

    除了 el 外,其他参数都是只读的,不要更改它们。若你需要在不同的钩子间共享信息,推荐通过元素的 dataset attribute 实现。

    +
    +
  4. +
+

简化形式

对于自定义指令来说,一个很常见的情况是仅仅需要在 mountedupdated 上实现相同的行为,除此之外并不需要其他钩子。这种情况下我们可以直接用一个函数来定义指令,如下所示:

+
1
2
3
<div v-color="color"></div>
+<!-- 如果你的指令需要多个值,你可以向它传递一个 JavaScript 对象字面量。别忘了,指令也可以接收任何合法的 JavaScript 表达式。 -->
+<div v-demo="{ color: 'white', text: 'hello!' }"></div>
+
1
2
3
4
5
6
7
8
9
app.directive("color", (el, binding) => {
+  // 这会在 `mounted` 和 `updated` 时都调用
+  el.style.color = binding.value;
+});
+
+app.directive("demo", (el, binding) => {
+  console.log(binding.value.color); // => "white"
+  console.log(binding.value.text); // => "hello!"
+});
+

在组件上使用

当在组件上使用自定义指令时,它会始终应用于组件的根节点,和透传 attributes 类似。

+
1
2
3
4
5
6
7
8
<MyComponent v-demo="test" />
+
+<!-- MyComponent 的模板 -->
+
+<div>
+<!-- v-demo 指令会被应用在此处 -->
+  <span>My component content</span>
+</div>
+

需要注意的是组件可能含有多个根节点。当应用到一个多根组件时,指令将会被忽略且抛出一个警告。和 attribute 不同,指令不能通过 v-bind="$attrs" 来传递给一个不同的元素。总的来说,不推荐在组件上使用自定义指令。

+
+
查看使用自定义指令实现图片懒加载的部分 +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { useIntersectionObserver } from "@vueuse/core";
+
+export const lazyPlugin = {
+  install(app) {
+    app.directive("img-lazy", {
+      mounted(el, binding) {
+        const { stop } = useIntersectionObserver(el, ([{ isIntersecting }]) => {
+          if (isIntersecting) {
+            el.src = binding.value;
+            stop();
+          }
+        });
+      },
+    });
+  },
+};
+
+
+

插件

    +
  1. 基本使用

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import { createApp } from "vue";
    +
    +const app = createApp({});
    +
    +app.use(myPlugin, {
    +  /* 可选的选项 */
    +});
    +
    +const myPlugin = {
    +  install(app, options) {
    +    // 配置此应用
    +  },
    +};
    +

    插件没有严格定义的使用范围,但是插件发挥作用的常见场景主要包括以下几种:

    +
      +
    1. 通过 app.component()app.directive() 注册一到多个全局组件或自定义指令。

      +
    2. +
    3. 通过 app.provide() 使一个资源可被注入进整个应用。

      +
    4. +
    5. app.config.globalProperties 中添加一些全局实例属性或方法

      +
    6. +
    7. 一个可能上述三种都包含了的功能库 (例如 vue-router)。

      +
    8. +
    +
  2. +
  3. 插件示例

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // plugins/i18n.js
    +export default {
    +  install: (app, options) => {
    +    // 注入一个全局可用的 $translate() 方法
    +    app.config.globalProperties.$translate = (key) => {
    +      // 获取 `options` 对象的深层属性,即选择对应的语言读取对象,层层读入
    +      // 使用 `key` 作为索引
    +      return key.split(".").reduce((o, i) => {
    +        if (o) return o[i];
    +      }, options);
    +    };
    +  },
    +};
    +
    +// main.js
    +import i18nPlugin from "./plugins/i18n";
    +
    +app.use(i18nPlugin, {
    +  greetings: {
    +    hello: "Bonjour!",
    +  },
    +});
    +
  4. +
+

内置组件

Transition

    +
  1. 基本使用

    +
    1
    2
    3
    4
    <button @click="show = !show">Toggle</button>
    +<Transition>
    +   <p v-if="show">hello</p>
    +</Transition>
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /* 下面我们会解释这些 class 是做什么的 */
    +.v-enter-active,
    +.v-leave-active {
    +  transition: opacity 0.5s ease;
    +}
    +
    +.v-enter-from,
    +.v-leave-to {
    +  opacity: 0;
    +}
    +
    1
    2
    3
    4
    <button @click="show = !show">Toggle</button>
    +<Transition name="slide-fade">
    +  <p v-if="show">hello</p>
    +</Transition>
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /*
    +   进入和离开动画可以使用不同
    +   持续时间和速度曲线。
    + */
    +.slide-fade-enter-active {
    +  transition: all 0.3s ease-out;
    +}
    +
    +.slide-fade-leave-active {
    +  transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
    +}
    +
    +.slide-fade-enter-from,
    +.slide-fade-leave-to {
    +  transform: translateX(20px);
    +  opacity: 0;
    +}
    +

    <Transition> 仅支持单个元素或组件作为其插槽内容。如果内容是一个组件,这个组件必须仅有一个根元素。

    +
    +
  2. +
  3. 触发时机

    +
      +
    • v-if 所触发的切换
    • +
    • v-show 所触发的切换
    • +
    • 由特殊元素 <component> 切换的动态组件
    • +
    • 改变特殊的 key 属性
    • +
    +
  4. +
  5. CSS过渡class

    +
  6. +
  7. 为过渡效果命名

    +
    1
    2
    3
    <Transition name="fade">
    +  ...
    +</Transition>
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    .fade-enter-active,
    +.fade-leave-active {
    +  transition: opacity 0.5s ease;
    +}
    +
    +.fade-enter-from,
    +.fade-leave-to {
    +  opacity: 0;
    +}
    +
  8. +
  9. CSSanimation

    +
    1
    2
    3
    4
    5
    <Transition name="bounce">
    +  <p v-if="show" style="text-align: center;">
    +    Hello here is some bouncy text!
    +  </p>
    +</Transition>
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    .bounce-enter-active {
    +  animation: bounce-in 0.5s;
    +}
    +.bounce-leave-active {
    +  animation: bounce-in 0.5s reverse;
    +}
    +@keyframes bounce-in {
    +  0% {
    +    transform: scale(0);
    +  }
    +  50% {
    +    transform: scale(1.25);
    +  }
    +  100% {
    +    transform: scale(1);
    +  }
    +}
    +
  10. +
  11. 自定义过渡class
    你也可以向 <Transition> 传递以下的 props 来指定自定义的过渡 class

    +
      +
    • enter-from-class
    • +
    • enter-active-class
    • +
    • enter-to-class
    • +
    • leave-from-class
    • +
    • leave-active-class
    • +
    • leave-to-class
    • +
    +

    你传入的这些 class 会覆盖相应阶段的默认 class 名。这个功能在你想要在 Vue 的动画机制下集成其他的第三方 CSS 动画库时非常有用,比如 Animate.css

    +
    +
  12. +
  13. 同时使用transitionanimation
    然而在某些场景中,你或许想要在同一个元素上同时使用它们两个。举例来说,Vue 触发了一个 CSS 动画,同时鼠标悬停触发另一个 CSS 过渡。此时你需要显式地传入 type prop 来声明,告诉 Vue 需要关心哪种类型,传入的值是 animationtransition:即只关心一个。

    +
    1
    <Transition type="animation">...</Transition>
    +
  14. +
  15. 深层级过渡与显式过渡时长
    默认情况下,<Transition> 组件会通过监听过渡根元素上的第一个 transitionend 或者 animationend 事件来尝试自动判断过渡何时结束。而在嵌套的过渡中,期望的行为应该是等待所有内部元素的过渡完成。

    +

    在这种情况下,你可以通过向 <Transition> 组件传入 duration prop 来显式指定过渡的持续时间 (以毫秒为单位)。总持续时间应该匹配延迟加上内部元素的过渡持续时间:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <Transition :duration="550">
    +
    +  <div v-if="show" class="outer">
    +    <div class="inner">
    +      Hello
    +    </div>
    +  </div>
    +</Transition>
    +<!-- 如果有必要的话,你也可以用对象的形式传入,分开指定进入和离开所需的时间: -->
    +<Transition :duration="{ enter: 500, leave: 800 }">...</Transition>
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /* 应用于嵌套元素的规则 */
    +.nested-enter-active .inner,
    +.nested-leave-active .inner {
    +  transition-delay: 0.25s;
    +  transition: all 0.3s ease-in-out;
    +}
    +
    +.nested-enter-from .inner,
    +.nested-leave-to .inner {
    +  transform: translateX(30px);
    +  opacity: 0;
    +}
    +
    +/* ... 省略了其他必要的 CSS */
    +
  16. +
  17. JS 钩子
    你可以通过监听 <Transition> 组件事件的方式在过渡过程中挂上钩子函数:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <Transition
    +  @before-enter="onBeforeEnter"
    +  @enter="onEnter"
    +  @after-enter="onAfterEnter"
    +  @enter-cancelled="onEnterCancelled"
    +  @before-leave="onBeforeLeave"
    +  @leave="onLeave"
    +  @after-leave="onAfterLeave"
    +  @leave-cancelled="onLeaveCancelled"
    +>
    +  <!-- ... -->
    +</Transition>
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    // 在元素被插入到 DOM 之前被调用
    +// 用这个来设置元素的 "enter-from" 状态
    +function onBeforeEnter(el) {}
    +
    +// 在元素被插入到 DOM 之后的下一帧被调用
    +// 用这个来开始进入动画
    +function onEnter(el, done) {
    +  // 调用回调函数 done 表示过渡结束
    +  // 如果与 CSS 结合使用,则这个回调是可选参数
    +  done();
    +}
    +
    +// 当进入过渡完成时调用。
    +function onAfterEnter(el) {}
    +
    +// 当进入过渡在完成之前被取消时调用
    +function onEnterCancelled(el) {}
    +
    +// 在 leave 钩子之前调用
    +// 大多数时候,你应该只会用到 leave 钩子
    +function onBeforeLeave(el) {}
    +
    +// 在离开过渡开始时调用
    +// 用这个来开始离开动画
    +function onLeave(el, done) {
    +  // 调用回调函数 done 表示过渡结束
    +  // 如果与 CSS 结合使用,则这个回调是可选参数
    +  done();
    +}
    +
    +// 在离开过渡完成、
    +// 且元素已从 DOM 中移除时调用
    +function onAfterLeave(el) {}
    +
    +// 仅在 v-show 过渡中可用
    +function onLeaveCancelled(el) {}
    +

    这些钩子可以与 CSS 过渡或动画结合使用,也可以单独使用。

    +

    在使用仅由 JavaScript 执行的动画时,最好是添加一个 :css="false" prop。这显式地向 Vue 表明可以跳过对 CSS 过渡的自动探测。除了性能稍好一些之外,还可以防止 CSS 规则意外地干扰过渡效果。
    在有了 :css="false" 后,我们就自己全权负责控制什么时候过渡结束了。这种情况下对于 @enter@leave 钩子来说,回调函数 done 就是必须的。否则,钩子将被同步调用,过渡将立即完成。

    +
  18. +
  19. 可复用过渡效果
    有点类似无渲染组件

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <!-- MyTransition.vue -->
    +<script>
    +// JavaScript 钩子逻辑...
    +</script>
    +
    +<template>
    +  <!-- 包装内置的 Transition 组件 -->
    +  <Transition name="my-transition" @enter="onEnter" @leave="onLeave">
    +    <slot></slot>
    +    <!-- 向内传递插槽内容 -->
    +  </Transition>
    +</template>
    +
    +<style>
    +/*
    +   必要的 CSS...
    +   注意:避免在这里使用 <style scoped>
    +   因为那不会应用到插槽内容上
    + */
    +</style>
    +
    1
    2
    3
    <MyTransition>
    +  <div v-if="show">Hello</div>
    +</MyTransition>
    +
  20. +
  21. 元素间过渡

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    <script setup>
    +import { ref } from "vue";
    +
    +const docState = ref("saved");
    +</script>
    +
    +<template>
    +  <span style="margin-right: 20px">Click to cycle through states:</span>
    +  <div class="btn-container">
    +    <Transition name="slide-up">
    +      <button v-if="docState === 'saved'" @click="docState = 'edited'">
    +        Edit
    +      </button>
    +      <button
    +        v-else-if="docState === 'edited'"
    +        @click="docState = 'editing'"
    +      >
    +        Save
    +      </button>
    +      <button
    +        v-else-if="docState === 'editing'"
    +        @click="docState = 'saved'"
    +      >
    +        Cancel
    +      </button>
    +    </Transition>
    +  </div>
    +</template>
    +
    +<style>
    +.btn-container {
    +  display: inline-block;
    +  position: relative;
    +  height: 1em;
    +}
    +
    +button {
    +  position: absolute;
    +}
    +
    +.slide-up-enter-active,
    +.slide-up-leave-active {
    +  transition: all 0.25s ease-out;
    +}
    +
    +.slide-up-enter-from {
    +  opacity: 0;
    +  transform: translateY(30px);
    +}
    +
    +.slide-up-leave-to {
    +  opacity: 0;
    +  transform: translateY(-30px);
    +}
    +</style>
    +
  22. +
  23. 过渡模式
    在之前的例子中,进入和离开的元素都是在同时开始动画的,因此我们不得不将它们设为 position: absolute 以避免二者同时存在时出现的布局问题。(利用定位元素可以互相重叠覆盖)

    +

    然而,很多情况下这可能并不符合需求。我们可能想要先执行离开动画,然后在其完成之后再执行元素的进入动画。手动编排这样的动画是非常复杂的,好在我们可以通过向 <Transition> 传入一个 mode prop 来实现这个行为:

    +
    1
    2
    3
    <Transition mode="out-in">
    +  ...
    +</Transition>
    +
  24. +
  25. 组件间过渡

    +
    1
    2
    3
    <Transition name="fade" mode="out-in">
    +  <component :is="activeComponent"></component>
    +</Transition>
    +
  26. +
  27. 动态过渡

    +
    1
    2
    3
    <Transition :name="transitionName">
    +  <!-- ... -->
    +</Transition>
    +
  28. +
+

TransitionGroup

    +
  1. Transition的区别
    <TransitionGroup> 支持和 <Transition> 基本相同的 propsCSS 过渡 classJavaScript 钩子监听器,但有以下几点区别:

    +
      +
    • 默认情况下,它不会渲染一个容器元素。但你可以通过传入 tag prop 来指定一个元素作为容器元素来渲染。

      +
    • +
    • 过渡模式在这里不可用,因为我们不再是在互斥的元素之间进行切换。

      +
    • +
    • 列表中的每个元素都必须有一个独一无二的 key attribute

      +
    • +
    • CSS 过渡 class 会被应用在列表内的元素上,而不是容器元素上。

      +
    • +
    +
    1
    2
    3
    4
    5
    <TransitionGroup name="list" tag="ul">
    +  <li v-for="item in items" :key="item">
    +    {{ item }}
    +  </li>
    +</TransitionGroup>
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    /* 对移动中的元素应用的过渡 */
    +.list-move,
    +.list-enter-active,
    +.list-leave-active {
    +  transition: all 0.5s ease;
    +}
    +
    +.list-enter-from,
    +.list-leave-to {
    +  opacity: 0;
    +  transform: translateX(30px);
    +}
    +
    +/* 确保将离开的元素从布局流中删除
    +   以便能够正确地计算移动的动画。 */
    +.list-leave-active {
    +  position: absolute;
    +}
    +
    +

    经典示例

    +
    +
  2. +
+

KeepAlive

    +
  1. 基本使用

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <!-- 非活跃的组件将会被缓存! -->
    +<KeepAlive>
    + <component :is="activeComponent" />
    +</KeepAlive>
    +<!-- 路由懒加载 -->
    +<router-view v-slot="{ component }">
    + <keep-alive>
    +   <component :is="component" />
    + </keep-alive>
    +</router-view>
    +
  2. +
  3. 包含与排除
    <KeepAlive> 默认会缓存内部的所有组件实例,但我们可以通过 includeexclude prop 来定制该行为。这两个 prop 的值都可以是一个以英文逗号分隔的字符串、一个正则表达式,或是包含这两种类型的一个数组:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <!-- 以英文逗号分隔的字符串 -->
    +<KeepAlive include="a,b">
    + <component :is="view" />
    +</KeepAlive>
    +
    +<!-- 正则表达式 (需使用 `v-bind`) -->
    +<KeepAlive :include="/a|b/">
    +  <component :is="view" />
    +</KeepAlive>
    +
    +<!-- 数组 (需使用 `v-bind`) -->
    +<KeepAlive :include="['a', 'b']">
    +  <component :is="view" />
    +</KeepAlive>
    +

    它会根据组件的 name 选项进行匹配,所以组件如果想要条件性地被 KeepAlive 缓存,就必须显式声明一个 name 选项。

    +

    在 3.2.34 或以上的版本中,使用 <script setup> 的单文件组件会自动根据文件名生成对应的 name 选项,无需再手动声明。

    +
    +
  4. +
  5. 最大缓存实例数

    +
    1
    2
    3
    <KeepAlive :max="10">
    +  <component :is="activeComponent" />
    +</KeepAlive>
    +
  6. +
  7. 缓存实例的生命周期

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <script setup>
    +import { onActivated, onDeactivated } from "vue";
    +
    +onActivated(() => {
    +  // 调用时机为首次挂载
    +  // 以及每次从缓存中被重新插入时
    +});
    +
    +onDeactivated(() => {
    +  // 在从 DOM 上移除、进入缓存
    +  // 以及组件卸载时调用
    +});
    +</script>
    +
      +
    • onActivated 在组件挂载时也会调用,并且 onDeactivated 在组件卸载时也会调用。

      +
    • +
    • 这两个钩子不仅适用于 <KeepAlive> 缓存的根组件,也适用于缓存树中的后代组件。

      +
    • +
    +
    +
  8. +
+

Teleport

+

<Teleport> 是一个内置组件,它可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去。

+
+
    +
  1. 模态框示例

    +
      +
    • App.vue

      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      <!--
      +   可定制插槽和 CSS 过渡效果的模态框组件。
      +   -->
      +
      +<script setup>
      +import Modal from "./Modal.vue";
      +import { ref } from "vue";
      +
      +const showModal = ref(false);
      +</script>
      +
      +<template>
      +  <button id="show-modal" @click="showModal = true">Show Modal</button>
      +
      +  <Teleport to="body">
      +    <!-- 使用这个 modal 组件,传入 prop -->
      +    <modal :show="showModal" @close="showModal = false">
      +      <template #header>
      +        <h3>custom header</h3>
      +      </template>
      +    </modal>
      +  </Teleport>
      +</template>
      +
    • +
    • Modal.vue

      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      <script setup>
      +const props = defineProps({
      +  show: Boolean,
      +});
      +</script>
      +
      +<template>
      +  <Transition name="modal">
      +    <div v-if="show" class="modal-mask">
      +      <div class="modal-container">
      +        <div class="modal-header">
      +          <slot name="header">default header</slot>
      +        </div>
      +
      +        <div class="modal-body">
      +          <slot name="body">default body</slot>
      +        </div>
      +
      +        <div class="modal-footer">
      +          <slot name="footer">
      +            default footer
      +            <button class="modal-default-button" @click="$emit('close')">
      +              OK
      +            </button>
      +          </slot>
      +        </div>
      +      </div>
      +    </div>
      +  </Transition>
      +</template>
      +
      +<style>
      +.modal-mask {
      +  position: fixed;
      +  z-index: 9998;
      +  top: 0;
      +  left: 0;
      +  width: 100%;
      +  height: 100%;
      +  background-color: rgba(0, 0, 0, 0.5);
      +  display: flex;
      +  transition: opacity 0.3s ease;
      +}
      +
      +.modal-container {
      +  width: 300px;
      +  margin: auto;
      +  padding: 20px 30px;
      +  background-color: #fff;
      +  border-radius: 2px;
      +  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
      +  transition: all 0.3s ease;
      +}
      +
      +.modal-header h3 {
      +  margin-top: 0;
      +  color: #42b983;
      +}
      +
      +.modal-body {
      +  margin: 20px 0;
      +}
      +
      +.modal-default-button {
      +  float: right;
      +}
      +
      +/*
      + * 对于 transition="modal" 的元素来说
      + * 当通过 Vue.js 切换它们的可见性时
      + * 以下样式会被自动应用。
      + *
      + * 你可以简单地通过编辑这些样式
      + * 来体验该模态框的过渡效果。
      + */
      +
      +.modal-enter-from {
      +  opacity: 0;
      +}
      +
      +.modal-leave-to {
      +  opacity: 0;
      +}
      +
      +.modal-enter-from .modal-container,
      +.modal-leave-to .modal-container {
      +  -webkit-transform: scale(1.1);
      +  transform: scale(1.1);
      +}
      +</style>
      +
    • +
    +
  2. +
  3. 搭配组件使用
    <Teleport> 只改变了渲染的 DOM 结构,它不会影响组件间的逻辑关系。也就是说,如果 <Teleport> 包含了一个组件,那么该组件始终和这个使用了 <teleport> 的组件保持逻辑上的父子关系。传入的 props 和触发的事件也会照常工作。

    +
  4. +
  5. 禁用Teleport

    +
    1
    2
    3
    <Teleport :disabled="isMobile">
    + ...
    +</Teleport>
    +
  6. +
  7. 多个 Teleport 共享目标

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <Teleport to="#modals">
    +  <div>A</div>
    +</Teleport>
    +<Teleport to="#modals">
    +  <div>B</div>
    +</Teleport>
    +
    +<!-- 渲染结果 -->
    +<div id="modals">
    +  <div>A</div>
    +  <div>B</div>
    +</div>
    +
  8. +
+

Suspense

+

<Suspense> 是一个内置组件,用来在组件树中协调对异步依赖的处理。它让我们可以在组件树上层等待下层的多个嵌套异步依赖项解析完成,并可以在等待时渲染一个加载状态。

+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<RouterView v-slot="{ Component }">
+  <template v-if="Component">
+    <Transition mode="out-in">
+      <KeepAlive>
+        <Suspense>
+          <!-- 主要内容 -->
+          <component :is="Component"></component>
+
+          <!-- 加载中状态 -->
+          <template #fallback>
+            正在加载...
+          </template>
+        </Suspense>
+      </KeepAlive>
+    </Transition>
+  </template>
+</RouterView>
+

Feature

包体积与 Tree-shaking 优化

    +
  1. 尽量使用构建版本(Production)

    +
      +
    • Tree-shaking减少无用代码。
    • +
    • Vue编译器无需载入减少体积,模板会被预编译。
    • +
    +
  2. +
  3. 在引入新的依赖项时要小心包体积膨胀!尽量使用ES版本的包,其对tree-shaking更加友好。权衡依赖体积与功能的性价比。

    +
  4. +
  5. 代码分割
    代码分割是指构建工具将构建后的 JavaScript 包拆分为多个较小的,可以按需或并行加载的文件。通过适当的代码分割,页面加载时需要的功能可以立即下载,而额外的块只在需要时才加载,从而提高性能。像 Rollup (Vite 就是基于它之上开发的) 或者 webpack 这样的打包工具可以通过分析 ESM 动态导入的语法来自动进行代码分割:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // lazy.js 及其依赖会被拆分到一个单独的文件中
    +// 并只在 `loadLazy()` 调用时才加载
    +function loadLazy() {
    +  return import("./lazy.js");
    +}
    +import { defineAsyncComponent } from "vue";
    +
    +// 会为 Foo.vue 及其依赖创建单独的一个块
    +// 它只会按需加载
    +//(即该异步组件在页面中被渲染时)
    +const Foo = defineAsyncComponent(() => import("./Foo.vue"));
    +

    对于使用了 Vue Router 的应用,强烈建议使用异步组件作为路由组件。Vue Router 已经显性地支持了独立于 defineAsyncComponent 的懒加载。查看懒加载路由了解更多细节。

    +
  6. +
  7. 保持props的稳定新,移除无用的更新步骤

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <ListItem
    + v-for="item in list"
    + :id="item.id"
    + :active-id="activeId" />
    +
    +<ListItem
    + v-for="item in list"
    + :id="item.id"
    + :active="item.id === activeId" />
    +
  8. +
  9. 内置指令

    +
      +
    • v-oncev-once 是一个内置的指令,可以用来渲染依赖运行时数据但无需再更新的内容。它的整个子树都会在未来的更新中被跳过。
    • +
    • v-memov-memo 是一个内置指令,可以用来有条件地跳过某些大型子树或者 v-for 列表的更新。
    • +
    +
  10. +
  11. 虚拟列表

    +
  12. +
  13. 浅层响应式对象
  14. +
  15. 大型列表中避免不必要的组件抽象。
  16. +
+

组合式 API 的优势

    +
  1. 更好的逻辑复用。
  2. +
  3. 更加灵活的代码组织。
  4. +
  5. 更好的类型推导。
    近几年来,越来越多的开发者开始使用 TypeScript 书写更健壮可靠的代码,TypeScript 还提供了非常好的 IDE 开发支持。然而选项式 API 是在 2013 年被设计出来的,那时并没有把类型推导考虑进去,因此我们不得不做了一些复杂到夸张的类型体操才实现了对选项式 API 的类型推导。但尽管做了这么多的努力,选项式 API 的类型推导在处理 mixins 和依赖注入类型时依然不甚理想。
  6. +
  7. 更小的生产包体积
    搭配 <script setup> 使用组合式 API 比等价情况下的选项式 API 更高效,对代码压缩也更友好。这是由于 <script setup> 形式书写的组件模板被编译为了一个内联函数,和 <script setup> 中的代码位于同一作用域。不像选项式 API 需要依赖 this 上下文对象访问属性,被编译的模板可以直接访问 <script setup> 中定义的变量,无需从实例中代理。这对代码压缩更友好,因为本地变量的名字可以被压缩,但对象的属性名则不能。
  8. +
+

虚拟 DOM 的算法改进

    +
  • Vue3 使用了片段(fragments)的概念,可以让一个组件返回多个根节点,而不需要使用额外的包裹元素。这样可以减少不必要的 DOM 节点,提高渲染效率。
  • +
  • Vue3 使用了静态标记(static hoisting)的技术,可以在编译阶段识别出不会改变的静态内容,比如文本、属性、样式等,并将它们提升到渲染函数之外,避免每次重新渲染时都要重新创建。这样可以减少不必要的内存分配和垃圾回收,提高性能。
  • +
  • Vue3 使用了事件侦听器缓存(event listener caching)的技术,可以在编译阶段识别出不会改变的事件侦听器,并将它们缓存起来,避免每次重新渲染时都要重新绑定。这样可以减少不必要的事件侦听器的创建和销毁,提高性能。
  • +
  • Vue3 使用了块跟踪(block tracking)的技术,可以在编译阶段识别出动态内容的边界,并将它们分割成不同的块,每个块都有一个唯一的标识符。这样可以在运行时只对发生变化的块进行 diff,而不需要遍历整个模板。这样可以减少不必要的比较和更新,提高性能。
  • +
  • Vue3 使用了优化的 diff 算法,可以在处理完首尾节点后,对剩余节点的进行更高效的匹配和移动。Vue3 使用了LCS(最长公共子序列)的算法,可以找出新旧节点列表中的最长公共子序列,然后将其保持不变,只对非公共子序列的节点进行移动。这样可以减少不必要的节点移动,提高性能。
  • +
+

最佳实践

TypeScript 与组合式 API

    +
  1. 为组件的 props 标注类型

    +
      +
    • 运行时声明:

      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      <script setup lang="ts">
      +const props = defineProps({
      +  foo: { type: String, required: true },
      +  bar: Number,
      +});
      +
      +props.foo; // string
      +props.bar; // number | undefined
      +</script>
      +
    • +
    • 基于类型的声明

      +
      1
      2
      3
      4
      5
      6
      <script setup lang="ts">
      +const props = defineProps<{
      +  foo: string;
      +  bar?: number;
      +}>();
      +</script>
      +
    • +
    +
  2. +
  3. props解构默认值
    当使用基于类型的声明时,我们失去了为 props 声明默认值的能力。这可以通过 withDefaults 编译器宏解决:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    export interface Props {
    +  msg?: string;
    +  labels?: string[];
    +}
    +
    +const props = withDefaults(defineProps<Props>(), {
    +  msg: "hello",
    +  labels: () => ["one", "two"],
    +});
    +
  4. +
  5. 复杂的prop类型

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <script setup lang="ts">
    +interface Book {
    +  title: string;
    +  author: string;
    +  year: number;
    +}
    +
    +const props = defineProps<{
    +  book: Book;
    +}>();
    +</script>
    +

    对于运行时声明,我们可以使用 PropType 工具类型:

    +
    1
    2
    3
    4
    5
    import type { PropType } from "vue";
    +
    +const props = defineProps({
    +  book: Object as PropType<Book>,
    +});
    +
  6. +
  7. 为组件的 emits 标注类型

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <script setup lang="ts">
    +// 运行时
    +const emit = defineEmits(["change", "update"]);
    +
    +// 基于类型
    +const emit = defineEmits<{
    +  (e: "change", id: number): void;
    +  (e: "update", value: string): void;
    +}>();
    +
    +// 3.3+: 可选的、更简洁的语法
    +const emit = defineEmits<{
    +  change: [id: number];
    +  update: [value: string];
    +}>();
    +</script>
    +
  8. +
  9. ref标注类型

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    import { ref } from "vue";
    +
    +// 推导出的类型:Ref<number>
    +const year = ref(2020);
    +
    +// => TS Error: Type 'string' is not assignable to type 'number'.
    +year.value = "2020";
    +
    +import { ref } from "vue";
    +import type { Ref } from "vue";
    +
    +const year: Ref<string | number> = ref("2020");
    +
    +year.value = 2020; // 成功!
    +
    +// 得到的类型:Ref<string | number>
    +const year = ref<string | number>("2020");
    +
    +year.value = 2020; // 成功!
    +
    +// 推导得到的类型:Ref<number | undefined>
    +const n = ref<number>();
    +
  10. +
  11. reactive标注类型

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import { reactive } from "vue";
    +
    +// 推导得到的类型:{ title: string }
    +const book = reactive({ title: "Vue 3 指引" });
    +
    +import { reactive } from "vue";
    +
    +interface Book {
    +  title: string;
    +  year?: number;
    +}
    +
    +const book: Book = reactive({ title: "Vue 3 指引" });
    +

    不推荐使用 reactive() 的泛型参数,因为处理了深层次 ref 解包的返回值与泛型参数的类型不同。

    +
    +
  12. +
  13. computed() 标注类型

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import { ref, computed } from "vue";
    +
    +const count = ref(0);
    +
    +// 推导得到的类型:ComputedRef<number>
    +const double = computed(() => count.value * 2);
    +
    +// => TS Error: Property 'split' does not exist on type 'number'
    +const result = double.value.split("");
    +
    +const double = computed<number>(() => {
    +  // 若返回值不是 number 类型则会报错
    +});
    +
  14. +
  15. 为事件处理函数标注类型

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <script setup lang="ts">
    +function handleChange(event) {
    +  // `event` 隐式地标注为 `any` 类型
    +  console.log(event.target.value);
    +}
    +</script>
    +
    +<template>
    +  <input type="text" @change="handleChange" />
    +</template>
    +
    1
    2
    3
    function handleChange(event: Event) {
    +  console.log((event.target as HTMLInputElement).value);
    +}
    +
  16. +
  17. provide / inject 标注类型
    provideinject 通常会在不同的组件中运行。要正确地为注入的值标记类型,Vue 提供了一个 InjectionKey 接口,它是一个继承自 Symbol 的泛型类型,可以用来在提供者和消费者之间同步注入值的类型:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    import { provide, inject } from "vue";
    +import type { InjectionKey } from "vue";
    +
    +const key = Symbol() as InjectionKey<string>;
    +
    +provide(key, "foo"); // 若提供的是非字符串值会导致错误
    +
    +const foo = inject(key); // foo 的类型:string | undefined
    +

    建议将注入 key 的类型放在一个单独的文件中,这样它就可以被多个组件导入。

    +

    当使用字符串注入 key 时,注入值的类型是 unknown,需要通过泛型参数显式声明:

    +
    1
    const foo = inject<string>("foo"); // 类型:string | undefined
    +

    注意注入的值仍然可以是 undefined,因为无法保证提供者一定会在运行时 provide 这个值。

    +

    当提供了一个默认值后,这个 undefined 类型就可以被移除:

    +
    1
    const foo = inject<string>("foo", "bar"); // 类型:string
    +

    如果你确定该值将始终被提供,则还可以强制转换该值:

    +
    1
    const foo = inject("foo") as string;
    +
  18. +
  19. 为模板引用标注类型

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <script setup lang="ts">
    +import { ref, onMounted } from "vue";
    +
    +const el = ref<HTMLInputElement | null>(null);
    +
    +onMounted(() => {
    +  el.value?.focus();
    +});
    +</script>
    +
    +<template>
    +  <input ref="el" />
    +</template>
    +
  20. +
  21. 为组件模板引用标注类型
    有时,你可能需要为一个子组件添加一个模板引用,以便调用它公开的方法。举例来说,我们有一个 MyModal 子组件,它有一个打开模态框的方法:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <!-- MyModal.vue -->
    +<script setup lang="ts">
    +import { ref } from "vue";
    +
    +const isContentShown = ref(false);
    +const open = () => (isContentShown.value = true);
    +
    +defineExpose({
    +  open,
    +});
    +</script>
    +

    为了获取 MyModal 的类型,我们首先需要通过 typeof 得到其类型,再使用 TypeScript 内置的 InstanceType 工具类型来获取其实例类型:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <!-- App.vue -->
    +<script setup lang="ts">
    +import MyModal from "./MyModal.vue";
    +
    +const modal = ref<InstanceType<typeof MyModal> | null>(null);
    +
    +const openModal = () => {
    +  modal.value?.open();
    +};
    +</script>
    +
  22. +
  23. 如果组件的具体类型无法获得,或者你并不关心组件的具体类型,那么可以使用 ComponentPublicInstance。这只会包含所有组件都共享的属性,比如 $el

    +
    1
    2
    3
    4
    5
    import { ref } from "vue";
    +
    +import type { ComponentPublicInstance } from "vue";
    +
    +const child = ref<ComponentPublicInstance | null>(null);
    +
  24. +
+
文章作者: 希亚的西红柿
文章链接: http://blog.refrain.site/posts/3c94855e/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
+ + + + \ No newline at end of file diff --git a/posts/40f24371/index.html b/posts/40f24371/index.html new file mode 100644 index 000000000..0dd53fec0 --- /dev/null +++ b/posts/40f24371/index.html @@ -0,0 +1,1592 @@ +线性表 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + + +

线性表

本篇文章将详细讲述数据结构 线性表 章节的内容,没想到距离上次更新已经快两个礼拜了

+
+

本教程编程语言采用 JAVA ,文章框架参考自 HUST数据结构PPT ,源码内容参考自 尚硅谷 JAVA 数据结构教程

+
+

线性表章节部分将简单讲述概念方面的问题,重点将放在具体的代码实现过程中。

+
+

线性表的定义

线性表的逻辑结构

    +
  1. 线性表:由$n(n≥0)$个数据元素$(a_1,a_2,…, a_n)$构成的有限序列,记作: $L=(a_1,a_2,…,a_n)$,$a_1$称为首元素,$a_n$称为尾元素。
  2. +
  3. 表长:线性表中数据元素的数目。
  4. +
  5. 空表:不含数据元素的线性表。
  6. +
+

在线性表$L=(a_1,a_2,…,a_n)$中:

+
    +
  1. $a_{i-1}$是$a_i$的直接前驱(1<i<=n)
  2. +
  3. $a_{i+1}$是$a_i$的直接后继(1<=i<n)
  4. +
  5. $a_1$没有前驱,$a_n$没有后继,$a_i$有且仅有一个前驱和后继(1<i<n)
  6. +
+
+

抽象数据类型线性表的定义

抽象类型的线性表主要定义几个属性和方法

+
+
    +
  1. InitList(&L) //构造空表 L。
  2. +
  3. LengthList(L) //求表 L 的长度
  4. +
  5. GetElem(L,i,&e) //取元素 ai,由 e 返回 ai
  6. +
  7. PriorElem(L,ce,&pre_e) //求 ce 的前驱,由 pre_e 返回
  8. +
  9. InsertElem(&L,i,e) //在元素 ai 之前插入新元素 e
  10. +
  11. DeleteElem(&L,i) //删除第 i 个元素
  12. +
  13. EmptyList(L) //判断 L 是否为空表
  14. +
+

线性表的顺序表示(顺序存储结构)

顺序分配

    +
  1. 定义:将线性表中的数据元素依次存放到计算机存储器中一组地址连续的存储单元中, 这种分配方式称为顺序分配顺序映像。由此得到的存储结构称为顺序存储结构或向量(一维数组)
  2. +
  3. 线性表的几个参数
      +
    • a:首元素的地址。
    • +
    • sizeof(a[0]):线性表元素所占的储存空间的大小。
    • +
    • sizeof(a[n]):线性表的长度。
    • +
    +
  4. +
+

寻址公式

顺序储存方式的地址查询与数组类似,故不再赘述,感兴趣的读者可以自己查询相关资料。

+
+

顺序储存结构的评价

    +
  1. 是一种随机存取结构,存取任何元素的时间是一个常数,速度快。
  2. +
  3. 结构简单,逻辑上相邻的元素在物理上也是相邻的。
  4. +
  5. 不使用指针,节省存储空间。
  6. +
    +
  1. 插入和删除元素要移动大量元素,消耗大量时间。
  2. +
  3. 需要一个连续的存储空间。
  4. +
  5. 插入元素可能发生“溢出”。
  6. +
  7. 自由区中的存储空间不能被其它数据占用(共享)。
  8. +
+

稀疏数组的实现

    +
  1. 书写思路:
    +

    二维数组转稀疏数组:

    +
    +
      +
    1. 遍历原始二维数组得到,有效数字的个数sum
    2. +
    3. 创建稀疏数组int[sum+1][3]
    4. +
    5. 记录chessRowchessCol以及有效数字count
    6. +
    7. 遍历数组,为稀疏数组sparseArr赋值。
    8. +
    9. 返回稀疏数组。
    10. +
    +
  2. +
+
+

稀疏数组转二维数组:

+
1. 创建原有大小的二维数组。 2. 将稀疏数组中的有效数字返回到原来的二维数组中。 3. 返回原来的二维矩阵。 + +2. 代码实现: +
查看代码实现 +
+
  1. 编写函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package DataStructure;
+
+public class SparseArray {
+    // 主函数:负责打印原始数组,转换后的稀疏数组,以及在转换回来的二维数组
+    public static void main(String[] args) {
+        int[][] chessArr = new int[11][11];
+        chessArr[1][2] = 1;
+        chessArr[2][3] = 2;
+        chessArr[5][5] = 5;
+        System.out.println("Raw array preview:");
+        printChessArray(chessArr);
+
+        int[][] sparseArr = chessToSparse(chessArr);
+        System.out.println("chessArray to sparseArray:");
+        printChessArray(sparseArr);
+
+        int[][] chessArr2 = sparseToChess(sparseArr);
+        System.out.println("sparseArray to chessArray:");
+        printChessArray(chessArr2);
+    }
+
+    // 二维数组转稀疏数组:
+    // 1. 记录有效数字的个数sum
+    // 2. 创建稀疏数组int[sum+1][3]
+    // 3. 记录chessRow和chessCol以及有效数字count
+    // 4. 遍历数组,为稀疏数组sparseArr赋值
+    // 5. 返回稀疏数组
+    private static int[][] chessToSparse(int[][] chessArr) {
+        int sum = 0;
+        for (int[] row : chessArr) {
+            for (int chess : row) {
+                if (chess != 0) {
+                    sum++;
+                }
+            }
+        }
+        int[][] sparseArr = new int[sum + 1][3];
+        int chessRow = chessArr.length;
+        int chessCol = 0;
+        int count = 0;
+        for (int i = 0; i < chessArr.length; i++) {
+            int[] rows = chessArr[i];
+            if (chessCol == 0) {
+                chessCol = rows.length;
+            }
+            for (int j = 0; j < rows.length; j++) {
+                int chess = rows[j];
+                if (chess == 0) {
+                    continue;
+                }
+                count++;
+                sparseArr[count][0] = i;
+                sparseArr[count][1] = j;
+                sparseArr[count][2] = chess;
+            }
+        }
+        sparseArr[0][0] = chessRow;
+        sparseArr[0][1] = chessCol;
+        sparseArr[0][2] = sum;
+        return sparseArr;
+    }
+
+    /*
+     * 1. 创建原有大小的二维数组
+     * 2. 将稀疏数组中的有效数字返回到原来的二维数组中
+     * 3. 返回原来的二维矩阵
+     */
+    private static int[][] sparseToChess(int[][] sparseArr) {
+        int[][] chessArr = new int[sparseArr[0][0]][sparseArr[0][1]];
+        for (int i = 1; i < sparseArr.length; i++) {
+            int[] rows = sparseArr[i];
+            chessArr[rows[0]][rows[1]] = rows[2];
+        }
+        return chessArr;
+    }
+
+    /*
+     * 通过两个for循环每行循环打印输出
+     */
+    public static void printChessArray(int[][] chessArr) {
+        for (int[] row : chessArr) {
+            for (int data : row) {
+                System.out.printf("%-2d\t", data);
+            }
+            System.out.println("");
+        }
+    }
+}
  1. 输出结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Raw array preview:
+0       0       0       0       0       0       0       0       0       0       0
+0       0       1       0       0       0       0       0       0       0       0
+0       0       0       2       0       0       0       0       0       0       0
+0       0       0       0       0       0       0       0       0       0       0
+0       0       0       0       0       0       0       0       0       0       0
+0       0       0       0       0       5       0       0       0       0       0
+0       0       0       0       0       0       0       0       0       0       0
+0       0       0       0       0       0       0       0       0       0       0
+0       0       0       0       0       0       0       0       0       0       0
+0       0       0       0       0       0       0       0       0       0       0
+0       0       0       0       0       0       0       0       0       0       0
+chessArray to sparseArray:
+11      11      3
+1       2       1
+2       3       2
+5       5       5
+sparseArray to chessArray:
+0       0       0       0       0       0       0       0       0       0       0
+0       0       1       0       0       0       0       0       0       0       0
+0       0       0       2       0       0       0       0       0       0       0
+0       0       0       0       0       0       0       0       0       0       0
+0       0       0       0       0       0       0       0       0       0       0
+0       0       0       0       0       5       0       0       0       0       0
+0       0       0       0       0       0       0       0       0       0       0
+0       0       0       0       0       0       0       0       0       0       0
+0       0       0       0       0       0       0       0       0       0       0
+0       0       0       0       0       0       0       0       0       0       0
+0       0       0       0       0       0       0       0       0       0       0
+
+
+

线性表的链式存储结构

基本概念

    +
  1. 链表是以 节点 的方式来存储,是 链式存储
  2. +
  3. 每个节点包含 data 域next 域,next 域指向下一个节点。
  4. +
  5. 链表还分:带头节点、不带头节点,根据实际需求来确定。
  6. +
  7. 链表的各个节点 不一定是连续存储。
  8. +
+

单链表

    +
  1. 单链表的一般形式

    +

    +
  2. +
  3. 单链表的代码实现(考虑一个这样的场景使用带头单链表实现P5R Phantom Thieves原教程是用的水浒传英雄的管理为啥我换成P5R呢?这是因为vscode Junit单元测试的时候输出中文乱码,我上网搜索了一下也没什么好的解决办法,索性全部用英文算了,正好最近我在打P5R就干脆拿来用了。的成员管理)

    +
    查看代码实现 +
    +
    1. 完成对怪盗团成员的增删查改操作。
    2. 第一种方法:在添加成员时,直接添加到链表的尾部。
    3. 第二种方法:在添加成员时,根据序号将成员添加到指定的位置,如果有这个序号,则添加失败并给出提示。
    1. 构造链表节点的类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // 构造链表中的一个节点
    +class HeroNode {
    +    // 定义4个基本属性
    +    public int no; // 序号
    +    public String name; // 名字
    +    public String nickName; // 代号
    +    public HeroNode next; // 指针域
    +
    +    // 书写构造函数
    +    public HeroNode(int no, String name, String nickName) {
    +        this.no = no;
    +        this.name = name;
    +        this.nickName = nickName;
    +    }
    +
    +    // 为方便重新打印重写toString函数
    +    @Override
    +    public String toString() {
    +        return "HeroNode{" +
    +                "no=" + no +
    +                ", name='" + name + '\'' +
    +                ", nickName='" + nickName + '\'' +
    +                '}';
    +    }
    +}

    1. 单链表反转思路

      1. 定义一个新的reverseHead节点。
      2. 从原链表中依次取出节点,并始终添加到reverseHead的第一个节点。
        next = cur.next;
        cur.next = reverseHead.next;
        reverseHead.next = cur;
        cur = next;
      3. head节点的next指向reverseHead节点的next

      4. </ol>
        </li>
        </ol>
        </div>

        1. 构造链表类
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        68
        69
        70
        71
        72
        73
        74
        75
        76
        77
        78
        79
        80
        81
        82
        83
        84
        85
        86
        87
        88
        89
        90
        91
        92
        93
        94
        95
        96
        97
        98
        99
        100
        101
        102
        103
        104
        105
        106
        107
        108
        109
        110
        111
        112
        113
        114
        115
        116
        117
        118
        119
        120
        121
        122
        123
        124
        125
        126
        127
        128
        129
        130
        131
        132
        133
        134
        135
        136
        137
        138
        139
        140
        141
        142
        143
        144
        145
        146
        147
        148
        149
        150
        151
        152
        153
        154
        155
        156
        157
        158
        159
        160
        161
        162
        163
        164
        165
        166
        167
        168
        169
        170
        171
        172
        173
        174
        175
        176
        177
        178
        179
        180
        181
        182
        183
        184
        // 单向链表节点的书写
        +class SingleLinkedList {
        +    // 创建一个头节点
        +    private HeroNode head = new HeroNode(0, "", "");
        +
        +    // 添加节点,将新加入的元素添加至链表的最后
        +
        +    public void add(HeroNode node) {
        +        HeroNode temp = head;
        +        while (true) {
        +            if (temp.next == null) {
        +                break;
        +            }
        +            temp = temp.next;
        +        }
        +        temp.next = node;
        +    }
        +
        +    // 打印链表中的数据
        +    public void list() {
        +        if (head.next == null) {
        +            System.out.println("The linked list is empty!");
        +            return;
        +        }
        +        HeroNode temp = head.next;
        +        while (true) {
        +            if (temp == null) {
        +                break;
        +            }
        +            System.out.println(temp);
        +            temp = temp.next;
        +        }
        +    }
        +
        +    // 有序插入元素
        +    public void addByOrder(HeroNode node) {
        +        // 分为节点为空 大于 相等三种情况来书写函数
        +        HeroNode temp = head; // 此处temp为辅助变量存放在栈中,指向堆空间的head
        +        boolean exist = false;
        +        while (true) {
        +            if (temp.next == null) {
        +                break;
        +            }
        +            if (temp.next.no > node.no) {
        +                break;
        +            }
        +            if (temp.next.no == node.no) {
        +                exist = true;
        +                break;
        +            }
        +            temp = temp.next;
        +        }
        +        if (exist) {
        +            System.out.printf("The phantom thief number %d to be inserted already exists and can't be added! \n",
        +                    node.no);
        +            return;
        +        }
        +        node.next = temp.next;
        +        temp.next = node;
        +    }
        +
        +    // 书写更新链表数据函数
        +    public void update(HeroNode newNode) {
        +        // 分为空和相等来区分计算
        +        if (head.next == null) {
        +            System.out.println("The linked list is empty!");
        +            return;
        +        }
        +        HeroNode temp = head.next;
        +        boolean exist = false;
        +        while (true) {
        +            if (temp == null) {
        +                System.out.println("The linked list is empty!");
        +                return;
        +            }
        +            if (temp.no == newNode.no) {
        +                exist = true;
        +                break;
        +            }
        +            temp = temp.next;
        +        }
        +        if (exist) {
        +            temp.name = newNode.name;
        +            temp.nickName = newNode.nickName;
        +        } else {
        +            System.out.printf("Phantom thief with number %d not found", newNode.no);
        +        }
        +    }
        +
        +    // 删除节点操作
        +    public void delete(int no) {
        +        if (head.next == null) {
        +            System.out.println("The linked list is empty!");
        +            return;
        +        }
        +        HeroNode temp = head;
        +        boolean exist = false; // 是否找到要删除的节点
        +        while (true) {
        +            if (temp.next == null) {
        +                break;
        +            }
        +            if (temp.next.no == no) {
        +                exist = true;
        +                break;
        +            }
        +            temp = temp.next;
        +        }
        +        if (!exist) {
        +            System.out.printf("Phantom thief with number %d not found! \n", no);
        +            return;
        +        }
        +        // 删除操作
        +        temp.next = temp.next.next;
        +    }
        +
        +    // 统计有效节点的个数,即遍历链表
        +    public int length() {
        +        HeroNode temp = head.next;
        +        int num = 0;
        +        if (head.next == null) {
        +            return 0;
        +        }
        +        while (temp != null) {
        +            num++;
        +            temp = temp.next;
        +        }
        +        return num;
        +    }
        +
        +    // 查找单链表中的倒数第k个节点
        +    public HeroNode findLastIndexNode(int index) {
        +        // 通过size与index的大小来判断
        +        int size = length();
        +        if (size == 0) {
        +            return null;
        +        }
        +        if (index <= 0 || index > size) {
        +            return null;
        +        }
        +        HeroNode cur = head.next;
        +        for (int i = 0; i < size - index; i++) {
        +            cur = cur.next;
        +        }
        +        return cur;
        +    }
        +
        +    // 单链表的反转
        +    public void reverse() {
        +        // 始终添加到第一个节点
        +        if (head.next == null) {
        +            return;
        +        }
        +        HeroNode cur = head.next;
        +        HeroNode next = null;
        +        HeroNode reverseHead = new HeroNode(0, "", "");
        +
        +        while (cur != null) {
        +            next = cur.next; // 储存下一个元素的指针
        +            cur.next = reverseHead.next;
        +            reverseHead.next = cur;
        +            cur = next;
        +        }
        +        head.next = reverseHead.next;
        +    }
        +
        +    public void reversePrint() {
        +        if (head.next == null) {
        +            System.out.println("The Linked List is Empty!");
        +            return;
        +        }
        +
        +        Stack<HeroNode> stack = new Stack<>();
        +        HeroNode cur = head.next;
        +        // 遍历原链表,入栈
        +        while (cur != null) {
        +            stack.push(cur);
        +            cur = cur.next;
        +        }
        +        // 打印栈
        +        while (!stack.empty()) {
        +            System.out.println(stack.pop());
        +        }
        +    }
        +}
        1. 测试函数
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        68
        69
        70
        71
        72
        73
        74
        75
        76
        77
        78
        79
        80
        81
        82
        83
        84
        85
        86
        87
        88
        89
        90
        91
        92
        93
        94
        95
        96
        97
        98
        99
        100
        101
        102
        103
        104
        105
        106
        107
        108
        109
        110
        111
        112
        113
        public static void main(String[] args) {
        +    SingleLinkedListDemo singleLinkedListDemo = new SingleLinkedListDemo();
        +    singleLinkedListDemo.test1();
        +    singleLinkedListDemo.test2();
        +}
        +
        +public void test1() {
        +    HeroNode hero1 = new HeroNode(1, "Amamiya Ren", "Joker");
        +    HeroNode hero2 = new HeroNode(2, "Ryuji Sakamoto", "Skull");
        +    HeroNode hero3 = new HeroNode(3, "Anne Takamaki", "Panther");
        +    HeroNode hero4 = new HeroNode(4, "Yusuke Kitagawa", "Fox");
        +    SingleLinkedList singleLinkedList = new SingleLinkedList();
        +    singleLinkedList.add(hero1);
        +    singleLinkedList.add(hero2);
        +    singleLinkedList.add(hero3);
        +    singleLinkedList.add(hero4);
        +    singleLinkedList.list();
        +}
        +
        +public void test2() {
        +    HeroNode hero1 = new HeroNode(1, "Amamiya Ren", "Joker");
        +    HeroNode hero2 = new HeroNode(2, "Ryuji Sakamoto", "Skull");
        +    HeroNode hero3 = new HeroNode(3, "Anne Takamaki", "Panther");
        +    HeroNode hero4 = new HeroNode(4, "Yusuke Kitagawa", "Fox");
        +
        +    SingleLinkedList singleLinkedList = new SingleLinkedList();
        +
        +    singleLinkedList.addByOrder(hero4); // 添加顺序提前
        +    singleLinkedList.addByOrder(hero2);
        +    singleLinkedList.addByOrder(hero1);
        +    singleLinkedList.addByOrder(hero3);
        +    singleLinkedList.addByOrder(hero3);
        +    singleLinkedList.list();
        +}
        +
        +private SingleLinkedList singleLinkedList;
        +
        +@Before
        +public void before() {
        +    HeroNode hero1 = new HeroNode(1, "Amamiya Ren", "Joker");
        +    HeroNode hero2 = new HeroNode(2, "Ryuji Sakamoto", "Skull");
        +    HeroNode hero3 = new HeroNode(3, "Anne Takamaki", "Panther");
        +    HeroNode hero4 = new HeroNode(4, "Yusuke Kitagawa", "Fox");
        +
        +    singleLinkedList = new SingleLinkedList();
        +
        +    singleLinkedList.addByOrder(hero4); // 添加顺序提前
        +    singleLinkedList.addByOrder(hero2);
        +    singleLinkedList.addByOrder(hero1);
        +    singleLinkedList.addByOrder(hero3);
        +}
        +
        +@Test
        +public void updateTest() {
        +    // 测试修改
        +    System.out.println("Before update:");
        +    singleLinkedList.list();
        +    HeroNode hero4New = new HeroNode(4, "Yusuke Kitagawa-Test", "Fox-Test");
        +    singleLinkedList.update(hero4New);
        +
        +    System.out.println("After update:");
        +    singleLinkedList.list();
        +}
        +
        +@Test
        +public void deleteTest() {
        +    System.out.println("Before deletion:");
        +    singleLinkedList.list();
        +    singleLinkedList.delete(1);
        +    singleLinkedList.delete(4);
        +    System.out.println("After deletion:");
        +    singleLinkedList.list();
        +}
        +
        +@Test
        +public void lengthTest() {
        +    System.out.println(singleLinkedList.length());
        +    singleLinkedList.delete(1);
        +    System.out.println(singleLinkedList.length());
        +}
        +
        +@Test
        +public void findLastIndexNodeTest() {
        +    singleLinkedList.list();
        +    System.out.println("Search Test");
        +    HeroNode lastIndexNode = singleLinkedList.findLastIndexNode(1);
        +    System.out.println("Find the 1st last " + lastIndexNode);
        +    lastIndexNode = singleLinkedList.findLastIndexNode(4);
        +    System.out.println("Find the 4th last " + lastIndexNode);
        +    lastIndexNode = singleLinkedList.findLastIndexNode(2);
        +    System.out.println("Find the 2nd last " + lastIndexNode);
        +    lastIndexNode = singleLinkedList.findLastIndexNode(5);
        +    System.out.println("Find the 5th last " + lastIndexNode);
        +}
        +
        +@Test
        +public void reverseTest() {
        +    System.out.println("Before reversal:");
        +    singleLinkedList.list();
        +
        +    singleLinkedList.reverse();
        +
        +    System.out.println("After reversal:");
        +    singleLinkedList.list();
        +}
        +
        +@Test
        +public void reversePrintTest() {
        +    System.out.println("The data of the linked list:");
        +    singleLinkedList.list();
        +    System.out.println("Print in reverse order:");
        +    singleLinkedList.reversePrint();
        +}
        1. 输出结果
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        68
        69
        70
        71
        72
        73
        Test1:
        +HeroNode{no=1, name='Amamiya Ren', nickName='Joker'}
        +HeroNode{no=2, name='Ryuji Sakamoto', nickName='Skull'}
        +HeroNode{no=3, name='Anne Takamaki', nickName='Panther'}
        +HeroNode{no=4, name='Yusuke Kitagawa', nickName='Fox'}
        +
        +Test2:
        +The phantom thief number 3 to be inserted already exists and can't be added!
        +HeroNode{no=1, name='Amamiya Ren', nickName='Joker'}
        +HeroNode{no=2, name='Ryuji Sakamoto', nickName='Skull'}
        +HeroNode{no=3, name='Anne Takamaki', nickName='Panther'}
        +HeroNode{no=4, name='Yusuke Kitagawa', nickName='Fox'}
        +
        +updateTest:
        +Before update:
        +HeroNode{no=1, name='Amamiya Ren', nickName='Joker'}
        +HeroNode{no=2, name='Ryuji Sakamoto', nickName='Skull'}
        +HeroNode{no=3, name='Anne Takamaki', nickName='Panther'}
        +HeroNode{no=4, name='Yusuke Kitagawa', nickName='Fox'}
        +After update:
        +HeroNode{no=1, name='Amamiya Ren', nickName='Joker'}
        +HeroNode{no=2, name='Ryuji Sakamoto', nickName='Skull'}
        +HeroNode{no=3, name='Anne Takamaki', nickName='Panther'}
        +HeroNode{no=4, name='Yusuke Kitagawa-Test', nickName='Fox-Test'}
        +
        +deleteTest:
        +Before deletion:
        +HeroNode{no=1, name='Amamiya Ren', nickName='Joker'}
        +HeroNode{no=2, name='Ryuji Sakamoto', nickName='Skull'}
        +HeroNode{no=3, name='Anne Takamaki', nickName='Panther'}
        +HeroNode{no=4, name='Yusuke Kitagawa', nickName='Fox'}
        +After deletion:
        +HeroNode{no=2, name='Ryuji Sakamoto', nickName='Skull'}
        +HeroNode{no=3, name='Anne Takamaki', nickName='Panther'}
        +
        +lengthTest:
        +4
        +3
        +
        +findLastIndexNodeTest:
        +HeroNode{no=1, name='Amamiya Ren', nickName='Joker'}
        +HeroNode{no=2, name='Ryuji Sakamoto', nickName='Skull'}
        +HeroNode{no=3, name='Anne Takamaki', nickName='Panther'}
        +HeroNode{no=4, name='Yusuke Kitagawa', nickName='Fox'}
        +Search Test
        +Find the 1st last HeroNode{no=4, name='Yusuke Kitagawa', nickName='Fox'}
        +Find the 4th last HeroNode{no=1, name='Amamiya Ren', nickName='Joker'}
        +Find the 2nd last HeroNode{no=3, name='Anne Takamaki', nickName='Panther'}
        +Find the 5th last null
        +
        +reverseTest:
        +Before reversal:
        +HeroNode{no=1, name='Amamiya Ren', nickName='Joker'}
        +HeroNode{no=2, name='Ryuji Sakamoto', nickName='Skull'}
        +HeroNode{no=3, name='Anne Takamaki', nickName='Panther'}
        +HeroNode{no=4, name='Yusuke Kitagawa', nickName='Fox'}
        +After reversal:
        +HeroNode{no=4, name='Yusuke Kitagawa', nickName='Fox'}
        +HeroNode{no=3, name='Anne Takamaki', nickName='Panther'}
        +HeroNode{no=2, name='Ryuji Sakamoto', nickName='Skull'}
        +HeroNode{no=1, name='Amamiya Ren', nickName='Joker'}
        +
        +reversePrintTest:
        +The data of the linked list:
        +HeroNode{no=1, name='Amamiya Ren', nickName='Joker'}
        +HeroNode{no=2, name='Ryuji Sakamoto', nickName='Skull'}
        +HeroNode{no=3, name='Anne Takamaki', nickName='Panther'}
        +HeroNode{no=4, name='Yusuke Kitagawa', nickName='Fox'}
        +Print in reverse order:
        +HeroNode{no=4, name='Yusuke Kitagawa', nickName='Fox'}
        +HeroNode{no=3, name='Anne Takamaki', nickName='Panther'}
        +HeroNode{no=2, name='Ryuji Sakamoto', nickName='Skull'}
        +HeroNode{no=1, name='Amamiya Ren', nickName='Joker'}
        +
    +
    +
  4. +
+

双向链表

双向链表节点定义与单向链表大致相同,只不过多了一个指向前一个节点的指针域。

+
+
    +
  1. 双向链表相关函数的实现思路
    +

    基本实现思路与单向链表相同,可在前者的基础上作出修改

    +
  2. +
  3. 双向链表的代码实现
    查看代码实现 +
    +
    1. 函数编写
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    package DataStructure.DoubleLinkedListPackage;
    +
    +class HeroNode {
    +    // 基本的元素与构造元素与单链表基本相同,主要多了一个pre域
    +    public int no;
    +    public String name;
    +    public String nickName;
    +    public HeroNode next;
    +    public HeroNode pre;
    +
    +    public HeroNode(int no, String name, String nickName) {
    +        this.no = no;
    +        this.name = name;
    +        this.nickName = nickName;
    +    }
    +
    +    // 重写函数也基本一致
    +    @Override
    +    public String toString() {
    +        return "HeroNode{" +
    +                "no=" + no +
    +                ", name='" + name + '\'' +
    +                ", nickName='" + nickName + '\'' +
    +                '}';
    +    }
    +}
    +
    +// 对双向链表类进行编写
    +public class DoubleLinkedList {
    +    // 同样的,初始化一个最初的节点
    +    private HeroNode head = new HeroNode(0, "", "");
    +
    +    // 将节点添加到链表的尾部
    +    public void add(HeroNode node) {
    +        HeroNode temp = head;
    +        while (temp.next != null) {
    +            temp = temp.next;
    +        }
    +        temp.next = node;
    +        node.pre = temp;
    +    }
    +
    +    // 修改值的函数,与单向链表基本相同
    +    public void update(HeroNode newNode) {
    +        if (head.next == null) {
    +            System.out.println("The linked list is empty!");
    +            return;
    +        }
    +
    +        HeroNode temp = head;
    +        boolean exist = false;
    +        while (true) {
    +            if (temp.next == null) {
    +                break;
    +            }
    +            if (temp.no == newNode.no) {
    +                exist = true;
    +                break;
    +            }
    +            temp = temp.next;
    +        }
    +        if (exist) {
    +            temp.name = newNode.name;
    +            temp.nickName = newNode.nickName;
    +        } else {
    +            System.out.printf("no element found with number: %d !", newNode.no);
    +        }
    +    }
    +
    +    // 删除函数与单向链表的思路基本相同,需要注意删除的操作以及最后一位元素的删除处理
    +    public void delete(int no) {
    +        if (head.next == null) {
    +            System.out.println("The linked list is empty!");
    +            return;
    +        }
    +
    +        HeroNode temp = head.next;
    +        boolean exist = false;
    +        while (true) {
    +            if (temp.next == null) {
    +                break;
    +            }
    +            if (temp.no == no) {
    +                exist = true;
    +                break;
    +            }
    +            temp = temp.next;
    +        }
    +        if (!exist) {
    +            System.out.printf("no element found with number: %d !", no);
    +            return;
    +        }
    +        if (temp.next != null) {
    +            temp.next.pre = temp.pre;
    +        }
    +        temp.pre.next = temp.next;
    +    }
    +
    +    // 打印链表
    +    public void print() {
    +        if (head.next == null) {
    +            System.out.println("The linked list is empty!");
    +            return;
    +        }
    +        HeroNode cur = head.next;
    +        while (cur != null) {
    +            System.out.println(cur);
    +            cur = cur.next;
    +        }
    +    }
    +
    +    // 按序添加
    +    public void addByOrder(HeroNode node) {
    +        HeroNode temp = head;
    +        boolean exist = false; // 添加的节点是否已经在链表中存在
    +
    +        while (true) {
    +            // 已到列表尾部
    +            if (temp.next == null) {
    +                break;
    +            }
    +            // 已找到
    +            if (temp.next.no > node.no) {
    +                break;
    +            }
    +
    +            // 已存在该编号
    +            if (temp.next.no == node.no) {
    +                exist = true;
    +                break;
    +            }
    +            temp = temp.next;
    +        }
    +        if (exist) {
    +            System.out.printf("The phantom thief number %d to be inserted already exists and can't be added! \n", node.no);
    +            return;
    +        }
    +
    +        // 把节点插入到 temp 和 temp.next 之间
    +        // temp -> node -> temp.next
    +        node.next = temp.next;
    +        temp.next = node;
    +        node.pre = temp.next.pre;
    +        temp.next.pre = node;
    +    }
    +}
    1. 测试用例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    package DataStructure.DoubleLinkedListPackage;
    +
    +import org.junit.Before;
    +import org.junit.Test;
    +
    +/**
    + * 双向链表测试
    + */
    +public class DoubleLinkedListTest {
    +    DoubleLinkedList doubleLinkedList;
    +
    +    @Before
    +    public void before() {
    +        HeroNode hero1 = new HeroNode(1, "Amamiya Ren", "Joker");
    +        HeroNode hero2 = new HeroNode(2, "Ryuji Sakamoto", "Skull");
    +        HeroNode hero3 = new HeroNode(3, "Anne Takamaki", "Panther");
    +        HeroNode hero4 = new HeroNode(4, "Yusuke Kitagawa", "Fox");
    +
    +        // 测试新增
    +        doubleLinkedList = new DoubleLinkedList();
    +        doubleLinkedList.add(hero1);
    +        doubleLinkedList.add(hero4);
    +        doubleLinkedList.add(hero2);
    +        doubleLinkedList.add(hero3);
    +    }
    +
    +    @Test
    +    public void addTest() {
    +        // before 中已测试
    +    }
    +
    +    /**
    +     * 更新测试
    +     */
    +    @Test
    +    public void updateTest() {
    +        System.out.println("Before update:");
    +        doubleLinkedList.print();
    +        HeroNode hero4New = new HeroNode(4, "Yusuke Kitagawa-Test", "Fox-Test");
    +        doubleLinkedList.update(hero4New);
    +        System.out.println("After update:");
    +        doubleLinkedList.print();
    +    }
    +
    +    /**
    +     * 删除测试
    +     */
    +    @Test
    +    public void deleteTest() {
    +        System.out.println("Before deletion:");
    +        doubleLinkedList.print();
    +        doubleLinkedList.delete(1);
    +        doubleLinkedList.delete(4);
    +        doubleLinkedList.delete(3);
    +        System.out.println("After deletion:");
    +        doubleLinkedList.print();
    +    }
    +    @Test
    +    public void addByOrderTest() {
    +        HeroNode hero1 = new HeroNode(1, "Amamiya Ren", "Joker");
    +        HeroNode hero2 = new HeroNode(2, "Ryuji Sakamoto", "Skull");
    +        HeroNode hero3 = new HeroNode(3, "Anne Takamaki", "Panther");
    +        HeroNode hero4 = new HeroNode(4, "Yusuke Kitagawa", "Fox");
    +
    +        // 测试新增
    +        doubleLinkedList = new DoubleLinkedList();
    +        doubleLinkedList.addByOrder(hero1);
    +        doubleLinkedList.addByOrder(hero4);
    +        doubleLinkedList.addByOrder(hero2);
    +        doubleLinkedList.addByOrder(hero3);
    +        doubleLinkedList.addByOrder(hero3);
    +        doubleLinkedList.print();
    +
    +    }
    +}
    1. 输出结果
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    Before update:
    +HeroNode{no=1, name='Amamiya Ren', nickName='Joker'}
    +HeroNode{no=4, name='Yusuke Kitagawa', nickName='Fox'}
    +HeroNode{no=2, name='Ryuji Sakamoto', nickName='Skull'}
    +HeroNode{no=3, name='Anne Takamaki', nickName='Panther'}
    +After update:
    +HeroNode{no=1, name='Amamiya Ren', nickName='Joker'}
    +HeroNode{no=4, name='Yusuke Kitagawa-Test', nickName='Fox-Test'}
    +HeroNode{no=2, name='Ryuji Sakamoto', nickName='Skull'}
    +HeroNode{no=3, name='Anne Takamaki', nickName='Panther'}
    +
    +
    +Before deletion:
    +HeroNode{no=1, name='Amamiya Ren', nickName='Joker'}
    +HeroNode{no=4, name='Yusuke Kitagawa', nickName='Fox'}
    +HeroNode{no=2, name='Ryuji Sakamoto', nickName='Skull'}
    +HeroNode{no=3, name='Anne Takamaki', nickName='Panther'}
    +no element found with number: 3 !After deletion:
    +HeroNode{no=2, name='Ryuji Sakamoto', nickName='Skull'}
    +HeroNode{no=3, name='Anne Takamaki', nickName='Panther'}
    +
    +
    +The phantom thief number 3 to be inserted already exists and can't be added!
    +HeroNode{no=1, name='Amamiya Ren', nickName='Joker'}
    +HeroNode{no=2, name='Ryuji Sakamoto', nickName='Skull'}
    +HeroNode{no=3, name='Anne Takamaki', nickName='Panther'}
    +HeroNode{no=4, name='Yusuke Kitagawa', nickName='Fox'}
    +
    +
    +
  4. +
+

循环链表

循环链表的讲解将与约瑟夫问题结合实现

+
+
    +
  1. 实现基本思路
      +
    1. 先构成一个有n个节点的单循环链表。
    2. +
    3. 然后由k节点起从1开始计数。
    4. +
    5. 计数到m时,对应节点从链表中删除,然后从下一个节点又从1开始计数
    6. +
    +
  2. +
  3. 代码实现
    查看代码实现 +
    +
    1. 函数编写
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    package DataStructure;
    +
    +// 构造孩子节点
    +class Boy {
    +    int no;
    +    Boy next;
    +
    +    public Boy(int no) {
    +        this.no = no;
    +    }
    +}
    +
    +public class CircleSingleLinkedList {
    +    Boy first = null;
    +
    +    /*
    +     * 添加节点
    +     * 1. 检查num的输入是否在1及以上
    +     * 2. 通过cur指针循环前移,创建环形单链表
    +     * 3. 第一个节点为空的时候需要特殊考虑
    +     * 4. 添加节点的顺序为:cur指向boy,boy指向first,cur指针指向boy
    +     */
    +    public void add(int num) {
    +        if (num < 1) {
    +            System.out.println("Please input a number greater than 1! ");
    +            return;
    +        }
    +        Boy cur = null;
    +        for (int i = 1; i <= num; i++) {
    +            Boy boy = new Boy(i);
    +            if (first == null) {
    +                first = boy;
    +                boy.next = first;
    +                cur = first;
    +                continue;
    +            }
    +            cur.next = boy;
    +            boy.next = first;
    +            cur = boy;
    +        }
    +    }
    +
    +    /**
    +     * 打印队列
    +     */
    +    public void print() {
    +        if (first == null) {
    +            System.out.println("Queue empty!");
    +            return;
    +        }
    +        Boy cur = first;
    +        while (true) {
    +            System.out.printf("Child number %d \n", cur.no);
    +            cur = cur.next;
    +            // 如果和 first 一致,则标识已经走了一圈了
    +            if (cur == first) {
    +                return;
    +            }
    +        }
    +    }
    +
    +    public void countBoy(int startNo, int countNum, int nums) {
    +        // 进行一个数据校验
    +        if (first == null || // 环形队列没有构建
    +                countNum < 1 || // 每次至少数 1 下
    +                startNo > nums // 开始小孩不能大于参与游戏的人数
    +        ) {
    +            System.out.println("The parameter is incorrect.Please re-enter it!");
    +        }
    +        // 1. 初始化辅助变量到 first 的后面
    +        Boy helper = first;
    +        // 当 helper.next = first 时,就说明已经定位了
    +        while (helper.next != first) {
    +            helper = helper.next;
    +        }
    +
    +        // 2. 定位 first 和 helper 在 startNo 位置
    +        // first 初始在最开始,移动到 startNo 位置
    +        for (int i = 0; i < startNo - 1; i++) {
    +            helper = first;
    +            first = first.next;
    +        }
    +
    +        // 为了测试方便,这里添加一个日志输出
    +        System.out.printf("Localize: %d \n", startNo);
    +        print();
    +
    +        // 3. 开始报数 和 出圈
    +        while (true) {
    +            // 当队列中只剩下一个人的时候,跳出循环,因为最后一个必然是他自己出队列
    +            if (helper == first) {
    +                break;
    +            }
    +            // 报数:每次报数 m-1
    +            for (int i = 0; i < countNum - 1; i++) {
    +                // 因为 helper 永远在 first 后面,只要在 first 移动时,指向 first 原来所在的位置即可
    +                helper = first;
    +                first = first.next;
    +            }
    +            // 出圈
    +            System.out.printf("The number of the child out of the circle %d \n", first.no);
    +            first = first.next; // first 重置为下一次报数的小孩节点上
    +            helper.next = first; // helper 重置为下一次报数的小孩节点上
    +        }
    +        System.out.printf("The number of the last child left %d \n", first.no);
    +    }
    +}
    1. 测试用例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    package DataStructure;
    +
    +import org.junit.Test;
    +
    +public class JosepfuTest {
    +    @Test
    +    public void addTest() {
    +        CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
    +        circleSingleLinkedList.add(5);
    +        circleSingleLinkedList.print();
    +    }
    +
    +    @Test
    +    public void countBoy() {
    +        CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
    +        circleSingleLinkedList.add(5);
    +        System.out.println("Build Circle Queue:");
    +        circleSingleLinkedList.print();
    +
    +        // 开始玩游戏
    +        // 正确的输出顺序为:2、4、1、5、3
    +        circleSingleLinkedList.countBoy(1, 2, 5);
    +    }
    +}
    1. 输出结果
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    Child number 1
    +Child number 2
    +Child number 3
    +Child number 4
    +Child number 5
    +
    +Build Circle Queue:
    +Child number 1
    +Child number 2
    +Child number 3
    +Child number 4
    +Child number 5
    +Localize: 1
    +Child number 1
    +Child number 2
    +Child number 3
    +Child number 4
    +Child number 5
    +
    +
    +
  4. +
+
文章作者: 希亚的西红柿
文章链接: http://blog.refrain.site/posts/40f24371/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
+ + + + \ No newline at end of file diff --git a/posts/47341/index.html b/posts/47341/index.html new file mode 100644 index 000000000..5cd0b575c --- /dev/null +++ b/posts/47341/index.html @@ -0,0 +1,595 @@ +关于cloudflare对网站搭建的使用 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + + +

关于cloudflare对网站搭建的使用

为 hexo 博客添加自定义域名

+

域名注册方面不做赘述,下面详细讲述利用 cloudflare 中 DNS 解析域名至 hexo 博客站的过程

+
+ +

cloudflare 中域名的解析

    +
  1. 注册 cloudflare 账号(过程比较简单就不放图了,推荐使用 gmail 或者 outlook 邮箱)
  2. +
  3. cloudflare 中添加站点
  4. +
+
    +
  • 注意此处添加的站点为你所购买的域名
  • +
  • 添加站点
  • +
  • 注意你需要在域名管理网站中设置好对应的 nameserver, 每个人分配的服务器都不一样,记得到注册域名的网站修改为对应的 nameserver!!!
  • +
  • 填写 DNS 解析域名的地址
  • +
+

将域名添加到 github 中

    +
  1. 进入到 github 项目中的 settings 中的 pages 中
  2. +
  3. 此时项目 main 分支里会出现 CNAME 文件,内容为你的域名(如 xxx.com)

  4. +
  5. 最后在本地的博客文件夹中hexo/source文件夹下新建 CNAME 文件,内容也为你所拥有的域名
  6. +
  7. 最后输入域名博客站就可以正常访问啦!
  8. +
+
文章作者: 希亚的西红柿
文章链接: http://blog.refrain.site/posts/47341/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/posts/54c08517/index.html b/posts/54c08517/index.html new file mode 100644 index 000000000..f1ff6b309 --- /dev/null +++ b/posts/54c08517/index.html @@ -0,0 +1,859 @@ +Python实现决策树(Decision Tree)算法 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + + + +

Python实现决策树(Decision Tree)算法

决策树算法基础原理分析

信息的不纯度(决定决策树分支的指标)

+

不纯度是在决策树中衡量特征分裂优异性的最主要的指标,用于衡量样本在根据某一特征的分类标准分裂后,样本是否被正确分类的准确程度。

+
+

主要有 3 种计算方式,分别对应了 3 类决策树:

+
+ + + + + + + + + + + + + + + + + + + + + +
决策树类别不纯度计算方式
ID3信息增益(Information Gain)
C4.5信息增益率(Information Gain Ratio)
CART基尼系数(Gini index)
+
+
    +
  • 信息熵的计算
    信息熵的计算公式为:

    $ H(D)=-\displaystyle\sum_{i=1}^n p_i log_2 p_i $

    其中$ n $为样本D的类别数,$ p_i $为该样本所占样本总数的比例

    +
  • +
  • 信息增益的计算
    信息增益的计算公式:

    $ g(D,A)=H(D)-H(D|A) $

    其中,$ H(D|A) $代表在以$ A $为标准分类后的所有样本的信息熵之和

    +
  • +
  • 信息增益率
    信息增益率的计算公式:

    $$ IGR=\frac{g(D,A)}{H(A)} $$

    其中 $ H(A) $代表 $ A $

    +
  • +
  • 基尼系数
    基尼系数的计算公式:

    $ Gini(p)=\displaystyle\sum_{i=1}^n p_i (1-p_i) = 1 - \displaystyle\sum_{i=1}^n p_i ^2 $

    其中$ n $为样本D的类别数,$ p_i $为该样本所占样本总数的比例
    基尼系数值越小,表示该分类方法贡献的信息度越高,即不纯度越小

    +
  • +
+

特征的最佳切分点

特征的类型分为两种情况:

+
+
    +
  • 离散型变量
  • +
  • 连续性变量
  • +
+
+
    +
  1. 对于离散型变量:
    可分为二叉树与多叉树
  2. +
  3. 对于连续性变量
      +
    1. 首先将样本的某一连续变量的值去重后按照升序进行排列
    2. +
    3. 计算两两相邻的平均值
    4. +
    5. 遍历,将的每一个点都作为该连续变量的切分点,并计算其分裂的不纯度,获得长度为 n − 1 的不纯度集
    6. +
    7. 其中最大的不纯度,其切分点即为最佳切分点
    8. +
    +
  4. +
  5. 特征在决策树中决策的先后顺序
    决策的先后顺序,即为根据不同变量进行分裂的顺序。在找出每个变量的最佳分裂点后,可以计算出以该点分裂所能获得的信息度(信息增益/信息增益率/基尼系数…等),以最大信息度的变量放在最前面进行分裂,最小的放在最后面分裂。这样就确定了在对样本进行区分的时候,越早分裂的样本能以最佳的区分方法进行划分。
  6. +
+

ID3, C4.5, CART 对比说明

+ + + + + + + + + + + + + + + + + + + + + + + + + +
类型特点劣势
ID3多叉树;特征只用一次;健壮性较好,能训练属性值有缺失的情况1.当特征取值类型较多时,信息增益会越大,容易造成过拟合;2. 只能用于分类;3. 只能处理离散变量;4. 对缺失值敏感
C4.5多叉树;特征只用一次;使用信息增益比对特征值多的特征进行惩罚,减少过拟合;可以处理连续变量;可以处理缺失值处理连续值只是将其离散化,形成多个离散值
CART二叉树;特征使用多次;可以用于回归任务-
+
+

决策树算法 python 代码实现

创建数据集

1
2
3
4
5
6
7
8
def createDataSet():
+  dataSet = [[1, 1, 'yes'],
+             [1, 1, 'yes'],
+             [1, 0, 'no'],
+             [0, 1, 'no'],
+             [0, 1, 'no']]
+  labels = ['no surfaceing', 'flippers']
+  return dataSet, labels
+

计算香农熵

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 计算香农熵,分两步,第一步计算频率,第二部根据公式计算香农熵
+def calcShannonEnt(dataSet):
+  numEntries = len(dataSet)
+  labelCounts = {}
+  for feaVec in dataSet:
+      currentLabel = feaVec[-1]
+      if currentLabel not in labelCounts:
+          labelCounts[currentLabel] = 0
+      labelCounts[currentLabel] += 1
+  shannonEnt = 0.0
+  for key in labelCounts:
+      prob = float(labelCounts[key]) / numEntries
+      shannonEnt -= prob * log(prob, 2)
+  return shannonEnt
+

划分数据集

1
2
3
4
5
6
7
8
9
# 划分数据集,将满足X[aixs]==value的值都划分到一起,返回一个划分好的集合(不包括用来划分的aixs属性,因为不需要)
+def splitDataSet(dataSet, axis, value):
+  retDataSet = []
+  for featVec in dataSet:
+      if featVec[axis] == value:
+          reducedFeatVec = featVec[:axis]
+          reducedFeatVec.extend(featVec[axis + 1:])
+          retDataSet.append(reducedFeatVec)
+  return retDataSet
+

选择划分的最佳属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 选择最好的属性进行划分,思路很简单就是对每个属性都划分下,看哪个好。这里使用到了一个set来选取列表中唯一的元素,这是一中很快的方法
+def chooseBestFeatureToSplit(dataSet):
+  numFeatures = len(dataSet[0]) - 1  # 因为数据集的最后一项是标签
+  baseEntropy = calcShannonEnt(dataSet)
+  bestInfoGain = 0.0
+  bestFeature = -1
+  for i in range(numFeatures):
+      featList = [example[i] for example in dataSet]
+      uniqueVals = set(featList)
+      newEntropy = 0.0
+      for value in uniqueVals:
+          subDataSet = splitDataSet(dataSet, i, value)
+          prob = len(subDataSet) / float(len(dataSet))
+          newEntropy += prob * calcShannonEnt(subDataSet)
+      infoGain = baseEntropy - newEntropy
+      if infoGain > bestInfoGain:
+          bestInfoGain = infoGain
+          bestFeature = i
+  return bestFeature
+

采用多数表决的方式计算结点分类

1
2
3
4
5
6
7
8
9
# 因为我们递归构建决策树是根据属性的消耗进行计算的,所以可能会存在最后属性用完了,但是分类
+# 还是没有算完,这时候就会采用多数表决的方式计算节点分类
+def majorityCnt(classList):
+  classCount = {}
+  for vote in classList:
+      if vote not in classCount.keys():
+          classCount[vote] = 0
+      classCount[vote] += 1
+  return max(classCount)
+

基于递归构造决策树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 基于递归构建决策树。这里的label更多是对于分类特征的名字,为了更好看和后面的理解。
+def createTree(dataSet, labels):
+  classList = [example[-1] for example in dataSet]
+  if classList.count(classList[0]) == len(classList):  # 类别相同则停止划分
+      return classList[0]
+  if len(dataSet[0]) == 1:  # 所有特征已经用完
+      return majorityCnt(classList)
+  bestFeat = chooseBestFeatureToSplit(dataSet)
+  bestFeatLabel = labels[bestFeat]
+  myTree = {bestFeatLabel: {}}
+  del (labels[bestFeat])
+  featValues = [example[bestFeat] for example in dataSet]
+  uniqueVals = set(featValues)
+  for value in uniqueVals:
+      subLabels = labels[:]  # 为了不改变原始列表的内容复制了一下
+      myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels)
+  return myTree
+

调用函数 构造主函数

1
2
3
4
5
6
7
8
9
10
11
def main():
+  data, label = createDataSet()
+  # t1 = time.clock()
+  myTree = createTree(data, label)
+  # t2 = time.clock()
+  print(myTree)
+  # print('execute for ',t2-t1)
+
+
+if __name__ == '__main__':
+  main()
+

完整源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#!/user/bin/env python3
+# -*- coding: utf-8 -*-
+
+
+import operator
+from math import log
+import time
+
+
+def createDataSet():
+  dataSet = [[1, 1, 'yes'],
+             [1, 1, 'yes'],
+             [1, 0, 'no'],
+             [0, 1, 'no'],
+             [0, 1, 'no']]
+  labels = ['no surfaceing', 'flippers']
+  return dataSet, labels
+
+
+# 计算香农熵,分两步,第一步计算频率,第二部根据公式计算香农熵
+def calcShannonEnt(dataSet):
+  numEntries = len(dataSet)
+  labelCounts = {}
+  for feaVec in dataSet:
+      currentLabel = feaVec[-1]
+      if currentLabel not in labelCounts:
+          labelCounts[currentLabel] = 0
+      labelCounts[currentLabel] += 1
+  shannonEnt = 0.0
+  for key in labelCounts:
+      prob = float(labelCounts[key]) / numEntries
+      shannonEnt -= prob * log(prob, 2)
+  return shannonEnt
+
+
+# 划分数据集,将满足X[aixs]==value的值都划分到一起,返回一个划分好的集合(不包括用来划分的aixs属性,因为不需要)
+def splitDataSet(dataSet, axis, value):
+  retDataSet = []
+  for featVec in dataSet:
+      if featVec[axis] == value:
+          reducedFeatVec = featVec[:axis]
+          reducedFeatVec.extend(featVec[axis + 1:])
+          retDataSet.append(reducedFeatVec)
+  return retDataSet
+
+
+# 选择最好的属性进行划分,思路很简单就是对每个属性都划分下,看哪个好。这里使用到了一个set来选取列表中唯一的元素,这是一中很快的方法
+def chooseBestFeatureToSplit(dataSet):
+  numFeatures = len(dataSet[0]) - 1  # 因为数据集的最后一项是标签
+  baseEntropy = calcShannonEnt(dataSet)
+  bestInfoGain = 0.0
+  bestFeature = -1
+  for i in range(numFeatures):
+      featList = [example[i] for example in dataSet]
+      uniqueVals = set(featList)
+      newEntropy = 0.0
+      for value in uniqueVals:
+          subDataSet = splitDataSet(dataSet, i, value)
+          prob = len(subDataSet) / float(len(dataSet))
+          newEntropy += prob * calcShannonEnt(subDataSet)
+      infoGain = baseEntropy - newEntropy
+      if infoGain > bestInfoGain:
+          bestInfoGain = infoGain
+          bestFeature = i
+  return bestFeature
+
+
+# 因为我们递归构建决策树是根据属性的消耗进行计算的,所以可能会存在最后属性用完了,但是分类
+# 还是没有算完,这时候就会采用多数表决的方式计算节点分类
+def majorityCnt(classList):
+  classCount = {}
+  for vote in classList:
+      if vote not in classCount.keys():
+          classCount[vote] = 0
+      classCount[vote] += 1
+  return max(classCount)
+
+
+# 基于递归构建决策树。这里的label更多是对于分类特征的名字,为了更好看和后面的理解。
+def createTree(dataSet, labels):
+  classList = [example[-1] for example in dataSet]
+  if classList.count(classList[0]) == len(classList):  # 类别相同则停止划分
+      return classList[0]
+  if len(dataSet[0]) == 1:  # 所有特征已经用完
+      return majorityCnt(classList)
+  bestFeat = chooseBestFeatureToSplit(dataSet)
+  bestFeatLabel = labels[bestFeat]
+  myTree = {bestFeatLabel: {}}
+  del (labels[bestFeat])
+  featValues = [example[bestFeat] for example in dataSet]
+  uniqueVals = set(featValues)
+  for value in uniqueVals:
+      subLabels = labels[:]  # 为了不改变原始列表的内容复制了一下
+      myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet,
+                                                             bestFeat, value), subLabels)
+  return myTree
+
+
+def main():
+  data, label = createDataSet()
+  # t1 = time.clock()
+  myTree = createTree(data, label)
+  # t2 = time.clock()
+  print(myTree)
+  # print('execute for ',t2-t1)
+
+
+if __name__ == '__main__':
+  main()
+
+

本文源代码参考自 机器学习——决策树 (Decision Tree)算法原理及 python 实现python 决策树算法原理及基于 numpy 的代码实现

+
+
文章作者: 希亚的西红柿
文章链接: http://blog.refrain.site/posts/54c08517/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
+ + + + \ No newline at end of file diff --git a/posts/582b690b/index.html b/posts/582b690b/index.html new file mode 100644 index 000000000..4843cb6d2 --- /dev/null +++ b/posts/582b690b/index.html @@ -0,0 +1,895 @@ +Git 笔记 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + + + +

Git 笔记

本文为个人自用 Git 笔记,记录了 Git 的常用命令,不定时更新(●’◡’●)。

+
+

Git 常用命令

clone

+

基本格式为:git clone [url] [directory],其中[directory]表示需要创建目录的本地名称,省略则默认为版本库的名称。
有一些附加参数如下

+
    +
  • -l | --local:只有当指定的版本库是一个本地路径才会生效,能跳过正常的传输机制,直接复制.git目录下的部分文件,即使不指定这个选项默认也会采用本地优化,但注意由于硬链接的特性,本地优化的克隆方式存在一些风险。
  • +
  • -s | --shared:当远程版本库在本地机器上时,使用这个选项可以让本地版本库与远程版本库共享对象文件,而不是复制或链接。
  • +
  • --depth <depth>:使用这个选项可以创建一个浅克隆,即只获取最近的<depth>个提交,而不是整个历史记录。这样可以节省时间和空间。
  • +
  • --single-branch:使用这个选项可以只克隆远程版本库的一个分支,而不是所有分支,来节省克隆时间。默认情况下,这个分支是当前活动分支。
  • +
+
+
1
2
3
4
5
6
# 克隆本地机器上的一个项目到本地,目录名为another-runoob-name
+git clone /path/to/repo another-runoob-name
+# 克隆一个项目到本地,只获取最近的10个提交,目录名为shallow-clone
+git clone --depth 10 https://github.com/tianqixin/runoob-git-test shallow-clone
+# 克隆一个项目到本地,只获取远程的dev分支,目录名为dev-clone
+git clone --single-branch -b dev https://github.com/tianqixin/runoob-git-test dev-clone
+

add

+

基本格式为:git add [file1] [file2] …,其中file为文件名称。
有一些附加参数如下

+
    +
  • -u:只会添加已经被跟踪的文件,也就是说,它会将修改过或删除过的文件添加到暂存区,但是不会添加新文件。
  • +
  • -A:会添加所有的文件,包括修改过、删除过和新建的文件。
  • +
+
+
1
2
3
4
5
6
7
8
# 一个项目,里面有三个文件:a.txt, b.txt, c.txt。其中 a.txt 和 b.txt 是已经被跟踪的文件,c.txt 是新建的文件。
+
+# 添加所有文件
+git add .
+# 添加 a.txt 和 b.txt
+git add -u
+# 添加所有的文件
+git add -A
+

commit

+

基本格式为:git commit [file1] [file2] … -m 'msg',没有file文件默认为全部提交。

+
+
1
git commit test.txt -m 'test'
+

branch

+

基本格式为:git branch <branchname>,创建一个新的分支。

+

有一些附加参数如下

+
    +
  • -a:显示本地分支和远程分支。
  • +
  • -m | -M:重命名当前分支,-M为强制重命名,即使名称已存在会强制覆盖掉分支名称。
  • +
  • -d | -D:删除分支,-D强制删除。
  • +
  • -v | -vv:显示本地分支最后一次提交记录,-vv显示本地分支与远程分支的关联。
  • +
+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 创建一个 test 分支
+git branch test
+# 显示本地分支和远程分支
+git branch -a
+# 删除本地的 test 分支
+git branch -d test
+# 显示本地分支与远程分支的关联
+git branch -vv
+# 将本地的 dev 分支重命名为 develop 分支
+git branch -m dev develop
+# 将已存在的本地分支与已存在的远程分支相关联
+git branch -u origin/dev
+# 取消本地分支与远程分支的关联
+git branch --unset-upstream
+

status

+

基本格式为:git status

+
+
1
git status
+

merge

+

基本格式为:git merge <branchname>
快速合并的时候是不会生成提交记录的

+
+
1
2
3
4
# 合并 dev 分支
+git merge dev
+# 合并
+git merge --abort
+
+

TIP:
最新提交是合并提交,那么它有两个父提交,分别是合并前的两个分支的最新提交。当你用git reset --hard HEAD^回退时,你其实是回退到合并提交的第一个父提交,也就是当前分支合并前的最新提交。这样你就会丢失合并提交和另一个分支的所有修改。如果你想回退到合并提交的第二个父提交,也就是另一个分支合并前的最新提交,你可以用git reset --hard HEAD^2,其中2表示第二个父提交。

+
+

rebase

Git rebase 是一种变基操作,它可以将一个分支的提交重新应用到另一个分支上,从而使两个分支的历史线性化。Git rebase 的好处是可以保持提交历史的清晰和简洁,避免不必要的合并提交。但是,Git rebase 也有一些缺点,其中之一就是可能会产生冲突。

+

Git rebase 的基本用法是:

+
1
git rebase <base-branch> <target-branch>
+

这个命令的意思是,将 <target-branch> 分支变基到 <base-branch> 分支上。也就是说,将 <target-branch> 分支上的提交,重新应用到 <base-branch> 分支的最新提交之后,从而使 <target-branch> 分支的历史成为 <base-branch> 分支的直接后继。

+

例如,假设有两个分支 masterdev,它们的提交历史如下:

+
1
2
master: A - B - C - D
+dev: A - B - E - F
+

其中,A、B、C、D、E、F 都是提交 ID 的简写,A 是最早的提交,F 是最新的提交。如果我们想要将 dev 分支变基到 master 分支上,也就是说,我们想要将 EF 这两个提交重新应用到 master 分支的最新提交 D 上,从而使 dev 分支的历史线性化,我们可以在 dev 分支上执行以下命令:

+
1
git rebase master dev
+

或者,我们可以先切换到 dev 分支,然后执行:

+
1
git rebase master
+

这样,dev 分支的提交历史就变成了:

+
1
2
master: A - B - C - D
+dev: A - B - C - D - E' - F'
+

这里,E'F'EF 的副本,它们的内容和 EF 相同,但是它们的提交 ID 不同,因为它们是在 D 的基础上重新生成的。这样,dev 分支就变成了 master 分支的直接后继,它们的历史线性化了。

+

但是,如果在 master 分支上的 CD 这两个提交,和 dev 分支上的 EF 这两个提交,都对同一个文件的同一行内容做了不同的修改,那么在执行 git rebase master 时,就会发生冲突。例如,假设在 master 分支上的 C 提交,修改了文件 file.txt 的第 10 行,将原来的内容 "Hello World" 改成了 “Hello Git",而在 dev 分支上的 E 提交,也修改了文件 file.txt 的第 10 行,将原来的内容 "Hello World" 改成了 "Hello Bing"。那么当 Git 尝试将 E 提交应用到 D 上时,就会遇到冲突,因为 Git 不知道要保留哪个版本的内容。这时候,Git 会在 file.txt 的第 10 行,用特殊的符号标记出冲突的部分,如下所示:

+
1
2
3
4
5
6
<<<<<<< HEAD
+Hello Git
+=======
+Hello Bing
+
+> > > > > > > E
+

这里,HEAD 表示目标分支的最新提交,也就是 DE 表示正在应用的提交,也就是 EGit 会中断变基的过程,让你手动解决冲突,你可以选择保留其中一个版本的内容,或者合并两个版本的内容,或者删除整个内容,然后保存文件,执行 git add file.txt 命令,将文件标记为已解决冲突,再执行 git rebase --continue 命令,继续变基的过程。如果你没有冲突需要解决,或者你想放弃变基的操作,你可以执行 git rebase --abort 命令,回到变基之前的状态。如果你想跳过某个提交,不将它应用到目标分支上,你可以执行 git rebase --skip 命令,继续变基的过程。

+

当你解决完所有的冲突后,Git 会将 dev 分支的指针移动到最新的提交上,也就是 F',这样,dev 分支就变成了 master 分支的直接后继,它们的历史线性化了。但是,你要注意,dev 分支的提交记录已经被重写了,它的提交 ID 和提交顺序都可能发生了变化,这可能会影响你和其他人的工作,所以在使用 Git rebase 时,要谨慎操作,并且与团队成员保持沟通。

+

Git rebase 还有一些高级用法,例如交互式变基,它可以让你修改、重排、合并、拆分、删除或者重新编写你的提交记录。你可以使用 git rebase -i <base-branch> 命令来启动交互式变基,它会打开一个文本编辑器,让你选择你想要执行的操作。

+

switch

+

git switch相比与git checkout而言更注重于分支操作,而不会用于恢复文件
基本格式为:git switch <branchname
常用命令:

+
    +
  • 切换到已存在的分支:git switch <branch>
  • +
  • 创建并切换到新的分支:git switch -c <new-branch> [<start-point>]
  • +
  • 从任意分支分离工作树:git switch --detach [<start-point>]
  • +
  • 创建并切换到孤儿分支:git switch --orphan <new-branch>
  • +
  • 切换到上一次切换的分支:git switch -
    其中,<branch>是要切换到的分支名,<new-branch>是要创建的新分支名,<start-point>是新分支的起始点,可以是一个提交或者另一个分支。如果省略<start-point>,则默认为当前 HEAD 所指向的位置。
  • +
+
+
1
2
3
4
5
6
7
8
9
10
# 切换到 dev 分支
+git switch dev
+# 创建一个新的分支 feature 并从 dev 分支进行开发
+git switch -c feature dev
+# 检查某个提交的状态,可以尝试在这个状态下新建一个分支解决问题
+git switch --detach HEAD~2
+# 切换到孤儿分支 docs
+git switch --orphan docs
+# 切换到上一次分支
+git switch -
+

restore

+

将文件恢复到最近一次提交的状态
基本格式为:git restore <file>

+
+
1
2
3
4
5
6
7
8
9
10
# 恢复工作树的文件
+git restore file.txt
+# 恢复暂存区的文件
+git restore --staged file.txt
+# 恢复到特定提交或者分支的状态
+git restore --source=dev file.txt
+# 交互式模式
+git restore --patch
+# 同时恢复暂存区和工作树的文件
+git restore --staged --worktree file.txt
+

log

+

用于显示日志提交信息
基本格式为git log
进入git log界面后的操作

+
    +
  • 空格键:向下滚动一屏幕的内容
  • +
  • 回车键:向下滚动一行的内容
  • +
  • b 键:向上滚动一屏幕的内容
  • +
  • k 键:向上滚动一行的内容
  • +
  • q 键:退出 git log 界面
  • +
+
+
1
git log
+

fetch

+

从远程仓库获取数据但不会自动合并
基本用法为:git fetch <remote>

+
    +
  • 从默认的远程仓库获取数据:git fetch
  • +
  • 从指定的远程仓库获取数据:git fetch <remote>
  • +
  • 从指定的远程仓库和分支获取数据:git fetch <remote> <branch>
  • +
  • 从指定的远程仓库和分支获取数据并更新本地分支:git fetch <remote> <branch>:<branch>
  • +
  • 从所有配置的远程仓库获取数据:git fetch --all
  • +
+
+
1
2
3
4
5
6
# fetch 远程库
+git fetch origin
+# 切换到 main 分支
+git switch main
+# 合并分支远程 main 分支
+git merge origin/main
+

push

+

将本地的提交向远程库推送
基本格式为:git push <remote> <branch>

+
+
1
2
3
4
5
6
7
8
9
10
# 推送所有分支,将当前分支推送远程仓库的同名分支
+git push -a origin
+# 推送标签
+git push --tags origin
+# 强制推送,将本地的 main 分支推送到远程仓库的 main 分支上
+git push -f origin main
+# 删除远程仓库的 feature 分支
+git push origin --delete feature
+# 将本地仓库的 master 分支推送到远程仓库的 dev 分支上
+git push origin master:dev
+

pull

+

从远程库获取数据并合并
基本格式为:git pull <remote> <branch>

+
+
1
2
3
4
5
6
# 拉去远程库 main 分支的数据并合并到当前分支
+git pull origin main
+# 要从 origin 远程仓库的 dev 分支获取最新的提交并合并到当前分支的 my-dev 分支
+git pull origin dev:my-dev
+# 使用 rebase 选项来拉取代码,在远程库的基础上应用本地更改,不会额外生成合并提交
+git pull --rebase origin main
+

checkout

+

用于切换分支、恢复文件或检出提交。
基本格式为:git checkout <branch>

+
+
1
2
3
4
5
6
7
8
# 切换分支
+git checkout dev
+# 创建新的分支并切换
+git checkout -b dev
+# 恢复文件状态,效果与 git restore <file>
+git checkout -- file.txt
+# 分离工作树,相当于 git switch --detach <commit>
+git checkout abc123
+

reset

+

用于将当前的HEAD复位到指定状态
基本格式为:git reset --[option] <commit>
有三个选项

+
    +
  • mixed:将文件回退到工作区,此时会保留工作区中的文件,但会丢弃暂存区中的文件。
  • +
  • soft:将文件回退到暂存区,此时会保留工作区和暂存区中的文件。
  • +
  • hard:将文件回退到修改前,此时会丢弃工作区和暂存区中的文件。
    当版本不同的时候,更改会存放在工作区或者暂存区。
  • +
+
+
1
2
# 将 HEAD 复位到上一个状态
+git reset --hard HEAD^
+

revert

+

用于创建一次新的提交来回滚版本
基本格式为:git revert <commit>

+
+
1
git revert HEAD
+

stash

+

该命令用于将当前工作目录中的临时更改保存到一个栈中,以便你可以切换到其他分支或提交上继续工作,注意新建的文件不会被stash暂存,需要全部add到工作区或者加上-u参数,另外当所有文件都在暂存区的话,pop之后新文件会放到暂存区,而修改的文件放在工作区。
基本格式为:git stash

+
+
1
2
3
4
5
6
7
8
9
10
11
# 保存特定的信息
+git stash save "Work in progress"
+# 保存特定的新文件
+git stash push -u .\source\_posts\JS\ES6.md
+git stash list
+# 恢复并删除stash中存储的最新修改
+git stash pop
+# 恢复但不删除stash中存储的最新修改
+git stash apply
+# 恢复但不删除stash中存储的特定提交
+git stash apply stash@{0}
+

remote

+

用于对远程库进行操作

+
+
1
2
3
4
5
6
7
8
9
10
11
12
# 添加远程库
+git remote add upstream https://github.com/runoob/runoob-git-test.git
+# 更改远程库的连接
+git remote set-url origin git@github.com:tianqixin/runoob-git-test.git
+# 将远程库 upstream 重命名为 source
+git remote rename upstream source
+# 删除远程库
+git remote rm source
+# 列出当前仓库中已配置的所有远程仓库的名称和 URL
+git remote -v
+# 本地仓库中删除过时的远程分支引用的
+git remote prune origin
+

cherry-pick

+

基本格式为git cherry-pick <commitHash>

+
    +
  • git cherry-pick <commitHash>:将某一提交应用到当前分支。
  • +
  • git cherry-pick <branchname>:将某一分支的最新提交应用到当前分支
  • +
  • git cherry-pick <HashA> <HashB>:将多个提交应用到当前分支
  • +
+

选项:

+
    +
  • -x:在提交信息的末尾追加一行(cherry picked from commit ...),方便以后查到这个提交是如何产生的。
  • +
  • -s,--signoff:在提交信息的末尾追加一行操作者的签名,表示是谁进行了这个操作。
  • +
+
+
1
2
3
4
# 转移 A 到 B 之间的提交,不包括 A
+git cherry-pick A..B
+# 转移 A 到 B 之间的提交,包括 A
+git cherry-pick A^..B
+

tag

+

Gittag 有两种类型:轻量标签(lightweight)和含附注的标签(annotated)。轻量标签就是一个指向特定提交的引用,它不会存储任何额外的信息。含附注标签是存储在 Git 数据库中的一个完整对象,它有一个标签名,标签信息,标签签名等信息。一般我们都建议使用含附注型的标签,以便保留相关信息;当然,如果只是临时性加注标签,或者不需要旁注额外信息,用轻量标签也没问题。

+

Gittag 的使用方法如下:

+
    +
  • 创建轻量标签的命令如下:git tag <tag_name> <commit_id>,其中 <tag_name> 是标签的名称, <commit_id> 是要标记的提交的 ID,如果省略 <commit_id>,默认>会使用当前所在分支的最新提交作为标签指向的提交。
  • +
  • 创建含附注标签的命令如下:git tag -a <tag_name> -m "<tag_message>" <commit_id>,其中 -a 选项表示创建一个带注解的标签, -m 选项表示添加标签的信息, <tag_message> 是标签的描述, <commit_id> 是要标记的提交的 ID,如果省略 <commit_id>,默认会使用当前所在分支的最新提交作为标签指向的提交。
  • +
  • 查看当前项目中的所有标签,可以使用以下命令:git tag,如果想查看某个具体标签的信息,可以使用以下命令:git show <tag_name>
  • +
  • 推送标签到远程服务器,可以使用以下命令:git push origin <tag_name>,如果要一次性推送所有本地标签,可以使用以下命令:git push origin --tags
  • +
  • 删除本地标签的命令如下:git tag -d <tag_name>,删除远程标签的命令如下:git push origin :refs/tags/<tag_name>
  • +
+
+
1
2
# 推送 tag 标签
+git tag -a v1.0 -m “Release version 1.0
+

Git 使用技巧

查看本地分支与远程分支的关联

1
2
git branch -vv
+git remote show origin
+

将本地分支与远程分支关联

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 本地创建了分支而远程没有
+git push -u origin main
+git push --set-upstream origin main
+# 远程创建了分支而本地没有
+git checkout -b dev origin/dev
+git checkout --track origin/dev
+git checkout -t origin/dev
+git switch -c fix/time-picker upstream/dev
+git fetch upstream dev:test1
+# 远程分支与本地分支都有
+git branch --set-upstream-to=origin/dev
+# 缩写
+git branch -u origin/dev
+# 取消本地分支与远程分支的关联
+git branch --unset-upstream
+

如何回退版本

    +
  • git reset --hard HEAD^
  • +
  • git revert HEAD
  • +
  • git checkout commit_id
  • +
+

查看 Git 日志

1
git log
+

如何修改 git commit 信息

+

vim编辑器的使用

+
    +
  • 按下i键,进入插入模式,可以修改提交信息。
  • +
  • 按下Esc键,退出插入模式,回到命令模式。
  • +
  • 在命令模式下,输入:wq,保存并退出编辑界面。
  • +
  • 在命令模式下,输入:q!,放弃修改并退出编辑界面。
  • +
+
+
1
git commit --amend
+

暂存工作区

1
2
git stash
+git stash pop
+

多个上游的管理

+

需要多种命令的组合

+
+
1
2
3
4
5
6
7
8
# 添加远程库
+git remote add upstream https://github.com/runoob/runoob-git-test.git
+# fetch 远程库
+git fetch origin
+# 合并分支
+git merge upstream1/main
+# 删除远程仓库的 feature 分支
+git push origin --delete feature
+

设置代理

1
2
3
4
# 设置代理
+git config --global http.proxy http://127.0.0.1:7890
+# 取消代理
+git config --global --unset http.proxy
+

将文件从 Git 中移除

1
2
3
4
# 从 git 中删除,但仍然保留索引
+git rm --cached .\ui\bs\auto-imports.d.ts .\ui\bs\components.d.ts
+# 索引和本地都删除
+git rm .\ui\bs\auto-imports.d.ts .\ui\bs\components.d.ts
+
文章作者: 希亚的西红柿
文章链接: http://blog.refrain.site/posts/582b690b/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
+ + + + \ No newline at end of file diff --git a/posts/6534ce06/index.html b/posts/6534ce06/index.html new file mode 100644 index 000000000..16f1275df --- /dev/null +++ b/posts/6534ce06/index.html @@ -0,0 +1,1684 @@ +栈与队列 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + + + +

栈与队列

本篇文章将详细讲述数据结构 栈与队列 章节的内容。

+
+

本教程编程语言采用 JAVA ,文章框架参考自 HUST数据结构PPT ,源码内容参考自 尚硅谷 JAVA 数据结构教程

+
+

栈与队列章节部分概念方面比较简单,将放在具体的代码实现过程中。

+
+

栈的定义与操作

定义与术语

+

栈的基本操作

    +
  1. Initstack(s) // 置 s 为空栈
  2. +
  3. Push(s,e) // 元素 e 进栈 s
  4. +
  5. Pop(s,e // 元素 e 出栈 s
  6. +
  7. Gettop(s,e) // 顶元素拷贝到 e
  8. +
  9. Empty(s) // 判断是否为空栈
  10. +
+

栈的应用场景:

+
    +
  • 子程序的调用:
    在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中
    如方法中调用方法。
  • +
  • 处理递归调用:
    和子程序调用类似,只是除了存储下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。
  • +
  • 表达式的转换(中缀表达式转后缀表达式)与求值(实际解决)
  • +
  • 二叉树的遍历
  • +
  • 图形的深度优先(depth-first)搜索法
  • +
+
+

栈的存储表示和操作实现

顺序栈(用数组来模拟栈) +
    +
  1. 实现思路

    +
      +
    1. 定义一个数组,来模拟栈。
    2. +
    3. 定义一个top变量表示栈顶,初始化为-1
    4. +
    5. 入栈:stack[++top] = data
    6. +
    7. 出栈:return stack[top--]
    8. +
    +
  2. +
  3. 代码实现

    +
    查看代码实现 +
    +
    1. 编写ArrayStack
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    package DataStructure.Stack;
    +
    +public class ArrayStack {
    +    // 定义三个基本属性,堆栈,堆栈的大小,栈顶元素
    +    int[] stack;
    +    int maxSize;
    +    int top = -1;
    +
    +    // 构造函数书写
    +    public ArrayStack(int maxSize) {
    +        this.maxSize = maxSize;
    +        stack = new int[maxSize];
    +    }
    +
    +    // 判断堆栈是否已满,判断top是否到了最后一个元素
    +    public boolean isFull() {
    +        return top == maxSize - 1;
    +    }
    +
    +    // 判断堆栈是否为空,即判断top的值是否等于-1
    +    public boolean isEmpty() {
    +        return top == -1;
    +    }
    +
    +    // 入栈函数书写,先判断是否为满再入栈
    +    public void push(int element) {
    +        if (isFull()) {
    +            System.out.println("Stack full!");
    +            return;
    +        }
    +        stack[++top] = element;
    +    }
    +
    +    // 出栈函数书写,先判断是否为空再出栈
    +    public int pop() {
    +        if (isEmpty()) {
    +            throw new RuntimeException("No data in stack!");
    +        }
    +        return stack[top--];
    +    }
    +
    +    // 打印栈中元素
    +    public void print() {
    +        if (isEmpty()) {
    +            System.out.println("No data in stack!");
    +            return;
    +        }
    +        for (int i = top; i >= 0; i--) {
    +            System.out.printf("index=%d value=%d \n", i, stack[i]);
    +        }
    +    }
    +}
    1. 编写测试函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    package DataStructure.Stack;
    +
    +import org.junit.Test;
    +
    +public class ArrayStackTest {
    +    @Test
    +    public void pushTest() {
    +        ArrayStack stack = new ArrayStack(4);
    +        stack.push(1);
    +        stack.push(2);
    +        stack.push(3);
    +        stack.push(4);
    +        stack.print();
    +        stack.push(5);
    +    }
    +
    +    @Test
    +    public void popTest() {
    +        ArrayStack stack = new ArrayStack(4);
    +        stack.push(1);
    +        stack.push(2);
    +        stack.print();
    +        System.out.println("pop data:" + stack.pop());
    +        stack.print();
    +        System.out.println("pop data:" + stack.pop());
    +        stack.print();
    +    }
    +}
    1. 测试结果
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    pushTest:
    +index=3 value=4
    +index=2 value=3
    +index=1 value=2
    +index=0 value=1
    +Stack full!
    +
    +popTest:
    +index=1 value=2
    +index=0 value=1
    +pop data:2
    +index=0 value=1
    +pop data:1
    +No data in stack!
    +
    +
    +
  4. +
+链式栈(用链表来模拟栈) +
    +
  1. 实现思路
  2. +
  3. 定义一个链表来模拟栈,成员变量有:
      +
    • 最大存储量: maxSize
    • +
    • 栈中元素个数: size
    • +
    • 栈顶元素(同时作为表头): top
    • +
    +
  4. +
  5. 入栈:
    Node temp = top;
    top = new Node(value);
    top.next = temp;
    size++;
  6. +
  7. 出栈
    Node temp = top;
    top = top.next
    size--;
    return temp.value;
  8. +
  9. 代码实现
    查看代码实现 +
    +
    1. 编写LinkedListStack
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    package DataStructure.Stack;
    +
    +public class LinkedListStack {
    +    // 构造三个基本属性
    +    int maxSize; // 栈中最多的元素
    +    int size; // 栈中现在具有的元素
    +    Node top; // 栈顶的元素
    +
    +    // 构造函数
    +    public LinkedListStack(int maxSize) {
    +        this.maxSize = maxSize;
    +    }
    +
    +    // 判断栈是否为满的元素
    +    public boolean isFull() {
    +        return size == maxSize;
    +    }
    +
    +    // 判断栈是否为空的元素
    +    public boolean isEmpty() {
    +        return size == 0;
    +    }
    +
    +    // 入栈函数,先判断是否为满,再将元素添加到链表的表头
    +    public void push(int value) {
    +        if (isFull()) {
    +            System.out.println("Stack full!");
    +            return;
    +        }
    +        // 储存top元素
    +        Node temp = top;
    +        // 更新top节点
    +        top = new Node(value);
    +        // 指向前一top节点
    +        top.next = temp;
    +        // 储存元素个数自增
    +        size++;
    +    }
    +
    +    // 出栈函数书写
    +    public int pop() {
    +        if (isEmpty()) {
    +            throw new RuntimeException("Stack empty!");
    +        }
    +        Node temp = top;
    +        top = top.next; // 注意已经存在的变量加上int会新创建变量
    +        size--;
    +        return temp.value;
    +    }
    +
    +    // 显示栈中的元素
    +    public void print() {
    +        if (isEmpty()) {
    +            System.out.println("Stack empty!");
    +            return;
    +        }
    +        Node cur = top;
    +        while (cur != null) {
    +            System.out.println(cur);
    +            cur = cur.next;
    +        }
    +    }
    +}
    +
    +class Node {
    +    // 定义节点的两个基本属性
    +    int value;
    +    Node next;
    +
    +    // 书写构造函数
    +    public Node(int value) {
    +        this.value = value;
    +    }
    +
    +    // 重写toString函数
    +    @Override
    +    public String toString() {
    +        return "Node{" +
    +                "value=" + value +
    +                '}';
    +
    +    }
    +}
    1. 编写测试函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    package DataStructure.Stack;
    +
    +import org.junit.Test;
    +
    +public class LinkedListStackTest {
    +    @Test
    +    public void pushTest() {
    +        LinkedListStack stack = new LinkedListStack(4);
    +        stack.push(1);
    +        stack.push(2);
    +        stack.push(3);
    +        stack.push(4);
    +        stack.print();
    +        stack.push(5);
    +    }
    +
    +    @Test
    +    public void popTest() {
    +        LinkedListStack stack = new LinkedListStack(4);
    +        stack.push(1);
    +        stack.push(2);
    +        stack.print();
    +        System.out.println("pop data:" + stack.pop());
    +        stack.print();
    +        System.out.println("pop data:" + stack.pop());
    +        stack.print();
    +    }
    +}
    1. 测试结果
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    pushTest;
    +Node{value=4}
    +Node{value=3}
    +Node{value=2}
    +Node{value=1}
    +Stack full!
    +
    +popTest;
    +Node{value=2}
    +Node{value=1}
    +pop data:2
    +Node{value=1}
    +pop data:1
    +Stack empty!
    +
    +
    +
  10. +
+

栈的应用

栈的一个十分经典的应用就是计算机如何计算表达式。

+
+

中缀表达式

    +
  1. 基本概念
    中缀表达式是一个通用的 算术逻辑公式表示方法操作符 是以 中缀形式 处于操作数的 中间 (例: 3 + 4 ),中缀表达式是人们常用的算数表达方式。

    +

    中缀表达式就是 常见的运算表达式,如 (3+4)x5-6。
    中缀表达式的求值是人类最熟悉的,但是对于计算机来说却不好操作:

    +
      +
    • 需要计算运算符的优先级
    • +
    • 对于中括号来说,笔者想不出实现办法
      因此,在计算结果时,往往会将 中缀表达式 转成其他表达式,一般转成后缀表达式。
    • +
    +
    +
  2. +
  3. 思路分析

    +
  4. +
  5. 代码实现

    如上图,代码实现的基本思路为:

    +
      +
    1. 先扫描字符串,可以通过一个 index 变量来辅助扫描。
    2. +
    3. 如果发现是一个 数字 ,直接入栈。
    4. +
    5. 如果发现是一个 操作符 ,分一下情况:
        +
      1. 当前操作符 的优先级 大于栈顶符号 :将 当前操作符 入符号栈。
      2. +
      3. 当前操作符 的优先级 小于或等于栈顶符号
          +
        1. 弹出数栈中的 2 个数值。
        2. +
        3. 弹出符号栈顶的元素。
        4. +
        5. 2 个数字与符号进行运算。
        6. +
        7. 将计算结果压入数栈。
        8. +
        9. 将当前操作符压入符号栈。
        10. +
        +
      4. +
      +
    6. +
    7. 当扫描完毕时:
        +
      1. 顺序地从数栈中弹出 2 个数。
      2. +
      3. 从符号栈中弹出 1 个操作符。
      4. +
      5. 将他们进行计算,然后把计算结果压入数栈中。
      6. +
      +
    8. +
    9. 最后数栈中只会存在一个数值,那就是计算结果。
    10. +
    +
    +
    查看代码实现 +
    +
    1. 使用前面实现的数组栈来做我们的容器,只增加了一个查看栈顶元素的peek()方法。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    package DataStructure.Stack;
    +
    +public class ArrayStack {
    +    // 定义三个基本属性,堆栈,堆栈的大小,栈顶元素
    +    int[] stack;
    +    int maxSize;
    +    int top = -1;
    +
    +    // 构造函数书写
    +    public ArrayStack(int maxSize) {
    +        this.maxSize = maxSize;
    +        stack = new int[maxSize];
    +    }
    +
    +    // 判断堆栈是否已满,判断top是否到了最后一个元素
    +    public boolean isFull() {
    +        return top == maxSize - 1;
    +    }
    +
    +    // 判断堆栈是否为空,即判断top的值是否等于-1
    +    public boolean isEmpty() {
    +        return top == -1;
    +    }
    +
    +    // 入栈函数书写,先判断是否为满再入栈
    +    public void push(int element) {
    +        if (isFull()) {
    +            System.out.println("Stack full!");
    +            return;
    +        }
    +        stack[++top] = element;
    +    }
    +
    +    // 出栈函数书写,先判断是否为空再出栈
    +    public int pop() {
    +        if (isEmpty()) {
    +            throw new RuntimeException("No data in stack!");
    +        }
    +        return stack[top--];
    +    }
    +
    +    // 打印栈中元素
    +    public void print() {
    +        if (isEmpty()) {
    +            System.out.println("No data in stack!");
    +            return;
    +        }
    +        for (int i = top; i >= 0; i--) {
    +            System.out.printf("index=%d value=%d \n", i, stack[i]);
    +        }
    +    }
    +
    +    // 添加显示栈顶元素的方法
    +    public int peek() {
    +        if (isEmpty()) {
    +            throw new RuntimeException("Stack empty!");
    +        }
    +        return stack[top];
    +    }
    +}
    1. 计算器代码实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    package DataStructure.Stack;
    +
    +/**
    + * 计算器代码实现
    + */
    +public class Calculator {
    +    // 使用前面章节实现过的 数组模拟栈,来当我们 计算器中用来存储数据和符号的 容器
    +    private ArrayStack numStack = new ArrayStack(10); // 数组栈
    +    private ArrayStack operStack = new ArrayStack(10); // 符号栈
    +
    +    public static void main(String[] args) {
    +        String expression = "30-2*4+2";
    +
    +        Calculator calculator = new Calculator();
    +        // 扫描表达式
    +        calculator.scan(expression);
    +        // 剩余数据进行计算
    +        int res = calculator.nextCal();
    +        System.out.printf("%s = %d \n", expression, res);
    +    }
    +
    +    public void scan(String expression) {
    +        int index = 0;
    +        String keepNum = ""; // 用来保存数字,有可能是 = "1" 或则 "123" 的多位数
    +        while (true) {
    +            if (index == expression.length()) {
    +                break;
    +            }
    +            // 每次只截取一个数字
    +            // 要注意这里的 ch,使用 ch 做运算的时候要小心
    +            char ch = expression.substring(index, ++index).charAt(0);
    +            if (isOper(ch)) {
    +                subScan(ch);
    +            } else {
    +                // 是数字,直接入数栈
    +                // ch 被当成 int 的使用的话,需要特别注意
    +                // ASCII 码表中数字是从 48 开始的,这里的 ch 对应的数字是 ASCII 码表,所以要减掉 48
    +                // 当然也可以使用字符串解析的方式 Integer.valueOf(字符串) 来得到数字
    +                // numStack.push(ch - 48);
    +                keepNum += ch;
    +                // 已经是末尾了,则直接入栈
    +                if (index == expression.length()) {
    +                    numStack.push(Integer.parseInt(keepNum));
    +                    continue;
    +                }
    +                // 需要往后多看一位,如果是符号,才能将当前的数入栈
    +                char tempCh = expression.substring(index, index + 1).charAt(0);
    +                if (isOper(tempCh)) {
    +                    numStack.push(Integer.parseInt(keepNum));
    +                    keepNum = "";
    +                }
    +            }
    +        }
    +    }
    +
    +    public void subScan(char ch) {
    +        // 当 当前操作符 的优先级 大于 栈顶符号:将 当前操作符入符号栈
    +        // 一定要大于,如果是同级的话,有可能前面一个也是 * 号,需要先在第一步进行计算
    +        if (operStack.isEmpty() || priority(ch) > priority((char) operStack.peek())) {
    +            operStack.push(ch);
    +            return;
    +        }
    +        // 小于栈顶操作符,则将栈顶符号取出,进行计算
    +        int num1 = numStack.pop();
    +        int num2 = numStack.pop();
    +        int oper = operStack.pop();
    +        int res = cal(num1, num2, oper);
    +        // 将结果入数栈
    +        numStack.push(res);
    +        // 递归调用
    +        subScan(ch);
    +    }
    +
    +    /**
    +     * 第 2 步:从栈中取出来数据和符号,然后计算
    +     *
    +     * @return
    +     */
    +    private int nextCal() {
    +        System.out.println("符号栈中符号情况:");
    +        operStack.print();
    +        while (true) {
    +            // 当符号栈为空时,表示已经计算完了
    +            if (operStack.isEmpty()) {
    +                break;
    +            }
    +            int num1 = numStack.pop();
    +            int num2 = numStack.pop();
    +            int oper = operStack.pop();
    +            int res = cal(num1, num2, oper);
    +            // 将结果入数栈
    +            numStack.push(res);
    +        }
    +        // 计算完成之后,数栈中只有一个数据了,就是结果
    +        System.out.println("栈中数据是否只有一个结果数字:");
    +        numStack.print();
    +        return numStack.pop();
    +    }
    +
    +    /**
    +     * 计算
    +     *
    +     * @param num1 依次从栈顶弹出来的数据
    +     * @param num2
    +     * @param oper 操作符
    +     * @return
    +     */
    +    private int cal(int num1, int num2, int oper) {
    +        switch (oper) {
    +            case '+':
    +                return num1 + num2;
    +            case '-':
    +                // 注意顺序,在栈底的数据,是先进去的,如果是减法,则是前面的数字减后面的数字
    +                return num2 - num1;
    +            case '*':
    +                return num1 * num2;
    +            case '/':
    +                return num2 / num1;
    +        }
    +        // 由于前面校验过操作符,不会走到这里来的
    +        return 0;
    +    }
    +
    +    /**
    +     * 计算操作符号优先级,暂时只支持 + - * /
    +     *
    +     * @param ch
    +     * @return 优先级越高,数值越大
    +     */
    +    private int priority(char ch) {
    +        switch (ch) {
    +            case '+':
    +            case '-':
    +                return 0;
    +            case '*':
    +            case '/':
    +                return 1;
    +            default:
    +                return -1;
    +        }
    +    }
    +
    +    /**
    +     * 是否是操作符
    +     *
    +     * @param ch
    +     * @return
    +     */
    +    private boolean isOper(char ch) {
    +        switch (ch) {
    +            case '+':
    +            case '-':
    +            case '*':
    +            case '/':
    +                return true;
    +        }
    +        return false;
    +    }
    +}
    +
    +
    +
  6. +
+

后缀表达式

后缀表达式 又称为 逆波兰表达式 ,与前缀表达式类似,只是 运算符 位于 操作数 之后。

+
+
    +
  1. 后缀表达式求值过程:

    +
      +
    1. 左到右 扫描表达式。
    2. +
    3. 遇到 数字 时,将数字压入堆栈。
    4. +
    5. 遇到 符号 时,将数字栈栈顶和次顶元素弹出,计算顺序为 次顶元素(符号)栈顶元素。
    6. +
    7. 重复上述过程直至剩余最后一个结果。
    8. +
    +
  2. +
  3. 中缀表达式转后缀表达式

    +
      +
    1. 初始化两个栈
    2. +
    +
      +
    • 符号栈 s1
    • +
    • 中间结果栈 s2
    • +
    +
      +
    1. 从左到右扫描中缀表达式
    2. +
    3. 遇到操作数直接压入 s2
    4. +
    5. 遇到运算符时
        +
      1. s1 为空,或者栈顶元素符号位 ( ,则将其压入符号栈 s1
      2. +
      3. 若优先级比栈顶运算符 ,也将其压入符号栈 s1
      4. +
      5. 若优先级比栈顶运算符 低或者相等 ,则弹出 s1栈顶元素,将其压入 s2 中,然后继续循环步骤 4,直至当前符号压入 s1
      6. +
      +
    6. +
    7. 遇到括号时
        +
      1. 如果是左括号 (:直接压入 s1
      2. +
      3. 如果是右括号 )
        则依次弹出 s1栈顶的运算符,并压入 s2 直到遇到 左括号 为止,此时将这一对括号 丢弃
      4. +
      +
    8. +
    9. 重复步骤 2 到 5 ,直到表达式的最右端
    10. +
    11. 将 s1 的运算符依次弹出并压入 s2 ,结果的 逆序 即为:中缀表达式转后缀表达式的结果
    12. +
    +
  4. +
  5. 中缀表达式转后缀表达式的举例说明

    +
    查看举例 +
    +

    将中缀表达式:1+((2+3)*4)-5转化为后缀表达式

    扫描到的元素s2(栈底->栈顶)s1(栈底->栈顶)说明
    11null遇到操作数直接压入 s2
    +1+s1为空,直接压入
    (1+ (运算符为(,直接压入
    (1+ ( (运算符为(,直接压入
    21 2+ ( (遇到操作数直接压入 s2
    +1 2+ ( ( +栈顶元素为(,直接压入 s1
    31 2 3+ ( ( +遇到操作数直接压入 s2
    )1 2 3 ++ (遇到)时,依次弹出 s1栈顶的运算符,并压入 s2 直到遇到 左括号 为止,此时将这一对括号 丢弃
    *1 2 3 ++ ( *栈顶元素为(,直接压入s1
    41 2 3 + 4+ ( *遇到操作数直接压入 s2
    )1 2 3 + 4 *+遇到)时,依次弹出 s1栈顶的运算符,并压入 s2 直到遇到 左括号 为止,此时将这一对括号 丢弃
    -1 2 3 + 4 * +-优先级比栈顶运算符 低或者相等 ,则弹出 s1栈顶元素,将其压入 s2 中,然后继续循环步骤 4,直至当前符号压入 s1
    51 2 3 + 4 * + 5-遇到操作数直接压入 s2
    null1 2 3 + 4 * + 5 -nulls1 的运算符依次弹出并压入 s2 ,结果的 逆序 即为:中缀表达式转后缀表达式的结果
    +
    +
    +
  6. +
  7. 代码实现

    +
    查看代码实现 +
    +
    1. 逆波兰表达式的计算器

      实现思路:

      1. 先将后缀表达式转化为一个 List
      2. 对这个 List 进行遍历然后进行计算
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    package DataStructure.Stack;
    +
    +import java.util.Arrays;
    +import java.util.List;
    +import java.util.Stack;
    +
    +public class ReversePolishCalculator {
    +    public static void main(String[] args) {
    +        ReversePolishCalculator calculator = new ReversePolishCalculator();
    +
    +        // (3+4)*5-6 => 对应的后缀表达式 `3 4 + 5 * 6 -`
    +        String postfixExpression = "3 4 + 5 * 6 -";
    +        System.out.println("(3+4)*5-6 = " + calculator.cal(postfixExpression));
    +
    +        // (30+4)*5-6 => 对应的后缀表达式 `30 4 + 5 * 6 -`
    +        postfixExpression = "30 4 + 5 * 6 -";
    +        System.out.println("(30+4)*5-6 = " + calculator.cal(postfixExpression));
    +
    +        // 4*5-8+60+8/2 => 对应的后缀表达式 `4 5 * 8 - 60 + 8 2 / +`
    +        postfixExpression = "4 5 * 8 - 60 + 8 2 / +";
    +        System.out.println("4*5-8+60+8/2 = " + calculator.cal(postfixExpression));
    +
    +        // (3+4)-(3-4)*10,对应后缀表达式为:3 4 + 10 3 4 - * -
    +        postfixExpression = "3 4 + 10 3 4 - * -";
    +        System.out.println("(3+4)-(3-4)*10 = " + calculator.cal(postfixExpression));
    +    }
    +
    +    /**
    +     * 计算一个后缀表达式的值
    +     *
    +     * @param postfixExpression
    +     * @return
    +     */
    +    public int cal(String postfixExpression) {
    +        return start(convert(postfixExpression));
    +    }
    +
    +    /**
    +     * 将后缀表达式转换成 list
    +     *
    +     * @param postfixExpression 表达式中的每个元素都用空格隔开,是为了方便;这里重点不在于怎么去解析出每一个元素了
    +     * @return
    +     */
    +    private List<String> convert(String postfixExpression) {
    +        return Arrays.asList(postfixExpression.split(" "));
    +    }
    +
    +    /**
    +     * 计算
    +     *
    +     * @param postfixElements
    +     * @return
    +     */
    +    private int start(List<String> postfixElements) {
    +        /*
    +         * 比如:`(3+4)x5-6` 对应的后缀表达式 `3 4 + 5 x 6 -`
    +         * 1. 从左到右扫描,将 3、4 压入堆栈
    +         * 2. 扫描到 `+` 运算符时
    +         * 将弹出 4 和 3,计算 `3 + 4 = 7`,将 7 压入栈
    +         * 3. 将 5 入栈
    +         * 4. 扫描到 `x` 运算符时
    +         * 将弹出 5 和 7 ,计算 `7 x 5 = 35`,将 35 入栈
    +         * 5. 将 6 入栈
    +         * 6. 扫描到 `-` 运算符时
    +         * 将弹出 6 和 35,计算 `35 - 6 = 29`,将 29 压入栈
    +         * 7. 扫描表达式结束,29 是表达式的值
    +         */
    +        Stack<Integer> stack = new Stack<>();
    +        for (String el : postfixElements) {
    +            // 如果是数字则入栈
    +            if (el.matches("\\d+")) {
    +                stack.push(Integer.parseInt(el));
    +                continue;
    +            }
    +            // 是运算符,则弹出两个数
    +            Integer num2 = stack.pop();
    +            Integer num1 = stack.pop();
    +            int res = cal(num1, num2, el.charAt(0));
    +            stack.push(res);
    +        }
    +        return stack.pop();
    +    }
    +
    +    /**
    +     * 计算
    +     *
    +     * @param num1
    +     * @param num2
    +     * @param oper 操作符
    +     * @return
    +     */
    +    private int cal(int num1, int num2, char oper) {
    +        switch (oper) {
    +            case '+':
    +                return num1 + num2;
    +            case '-':
    +                return num1 - num2;
    +            case '*':
    +                return num1 * num2;
    +            case '/':
    +                return num1 / num2;
    +        }
    +        throw new IllegalArgumentException("不支持的运算符:" + oper);
    +    }
    +
    +}
    1. 中缀表达式转后缀表达式代码实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    package DataStructure.Stack;
    +
    +import java.util.ArrayList;
    +import java.util.List;
    +import java.util.Stack;
    +
    +/**
    + * 中缀表达式转后缀表达式
    + */
    +public class InfixToSuffix {
    +    public static void main(String[] args) {
    +        InfixToSuffix infixToSuffix = new InfixToSuffix();
    +        // 目标:1+((2+3)*4)-5 转为 1 2 3 + 4 * + 5 -
    +        // 1. 将中缀表达式转成 List,方便在后续操作中获取数据
    +        String infixExpression = "1+((2+3)*4)-5";
    +        List<String> infixList = infixToSuffix.infix2List(infixExpression);
    +        System.out.println(infixList); // [1, +, (, (, 2, +, 3, ), *, 4, ), -, 5]
    +        // 2. 将中缀表达式转成后缀表达式
    +        ArrayList<String> suffixList = infixToSuffix.infixList2SuffixList(infixList);
    +        System.out.println(suffixList); // [1, 2, 3, +, 4, *, +, 5, -]
    +    }
    +
    +    /**
    +     * 将中缀表达式解析成单个元素的 List,
    +     *
    +     * @param infixExpression
    +     * @return 1+((2+3)*4)-5 -> [1,+,(,(,2,+,3,),*,4,),5]
    +     */
    +    public List<String> infix2List(String infixExpression) {
    +        ArrayList<String> res = new ArrayList<>();
    +        // 扫描并解析
    +        int index = 0;
    +        char ch = 0;
    +        String tempNum = ""; // 支持多位数
    +        while (index < infixExpression.length()) {
    +            ch = infixExpression.charAt(index++);
    +            // 如果不是数字,就是符号,直接添加到容器中
    +            // 0 = 48, 9 = 57
    +            if (!(ch >= 48 && ch <= 57)) {
    +                // 如果拼接的多位数还有值,则添加到容器中
    +                if (!tempNum.isEmpty()) {
    +                    res.add(tempNum);
    +                    tempNum = "";
    +                }
    +                res.add(ch + "");
    +                continue;
    +            }
    +            // 如果是数字,则考虑处理多位数
    +            tempNum += ch;
    +            // 如果已经是最后一个字符了,则将这个多位数添加到容器中
    +            if (index == infixExpression.length()) {
    +                res.add(tempNum);
    +                tempNum = "";
    +            }
    +        }
    +        return res;
    +    }
    +
    +    /**
    +     * 中缀表达式 List 转为后缀表达式 List
    +     *
    +     * @param infixList
    +     * @return
    +     */
    +    private ArrayList<String> infixList2SuffixList(List<String> infixList) {
    +        // 符号栈
    +        Stack<String> s1 = new Stack<>();
    +        // 思路是使用栈来存储表达式元素
    +        // 仔细观察他的解析步骤,会发现:只是在入栈,并未出现出栈操作
    +        // 而且,最后的结果还要逆序,所以这里使用 list,直接顺序读取出来就是最后的结果了
    +        ArrayList<String> s2 = new ArrayList<>();
    +
    +        for (String item : infixList) {
    +            // 如果是数字,则加入 s2
    +            if (item.matches("\\d+")) {
    +                s2.add(item);
    +            }
    +            // 如果是左括号,直接压入 s1
    +            else if (item.equals("(")) {
    +                s1.push(item);
    +            }
    +            // 如果是右括号
    +            // 则依次弹出 s1 栈顶的运算符,并压入 s2,直到遇到 左括号 为止,此时将这一对括号 丢弃
    +            else if (item.equals(")")) {
    +                // 如果不是左括号,则取出 s1 中的符号,添加到 s2 中
    +                while (!s1.peek().equals("(")) {
    +                    s2.add(s1.pop());
    +                }
    +                // 上面循环完之后,那么就是遇到了左括号
    +                // 则直接弹出这个左括号丢弃
    +                s1.pop();
    +            }
    +            // 剩下的则是运算符
    +            else {
    +                // 如果 s1 为空,或则栈顶运算符为 (,则压入符号栈 s1
    +                // 如果优先级比栈顶运算符 高,则压入符号栈 s1,否则,否则将 s1 栈顶的运算符弹出,压入 s2 中
    +                // 上面两句话,转换成下面的描述
    +                // 上面如果 s1 栈顶符号优先级比 当前符号高,则弹出加入到 s2 中。
    +                // 因为:如果栈顶符号是 ( 返回优先级为 -1.比当前符号低,则不会走该方法
    +                while (!s1.isEmpty() && priority(s1.peek().charAt(0)) >= priority(item.charAt(0))) {
    +                    s2.add(s1.pop());
    +                }
    +                s1.push(item);
    +            }
    +        }
    +        // 将 s1 中的运算符依次弹出并加入 s2 中
    +        while (!s1.isEmpty()) {
    +            s2.add(s1.pop());
    +        }
    +        return s2;
    +    }
    +
    +    /**
    +     * 计算操作符号优先级,暂时只支持 + - * /
    +     *
    +     * @param ch
    +     * @return 优先级越高,数值越大
    +     */
    +    private int priority(char ch) {
    +        switch (ch) {
    +            case '+':
    +            case '-':
    +                return 0;
    +            case '*':
    +            case '/':
    +                return 1;
    +            default:
    +                return -1;
    +        }
    +    }
    +}
    +
    +
    +
  8. +
+

前缀表达式

    +
  1. 前缀表达式又称 波兰表达式 ,前缀表达式的 运算符位于操作数之前

    +
  2. +
  3. 前缀表达式求值过程:

    +
      +
    1. 右到左 扫描表达式。
    2. +
    3. 遇到 数字 将数字压入栈。
    4. +
    5. 遇到 符号 弹出数字栈的两个元素,与运算符计算,顺序为: 弹出的 (符号 弹出的。
    6. +
    7. 重复上述过程直至剩余最后一个结果。
    8. +
    +
  4. +
  5. 中缀表达式转前缀表达式

    +
    查看思路 +
    +

    (1) 初始化两个栈:运算符栈 S1 和储存中间结果的栈 S2;
    (2) 从右至左扫描中缀表达式;
    (3) 遇到操作数时,将其压入 S2;
    (4) 遇到运算符时,比较其与 S1 栈顶运算符的优先级:
    (4-1) 如果 S1 为空,或栈顶运算符为右括号“)”,则直接将此运算符入栈;
    (4-2) 否则,若优先级比栈顶运算符的较高或相等,也将运算符压入 S1;
    (4-3) 否则,将 S1 栈顶的运算符弹出并压入到 S2 中,再次转到(4-1)与 S1 中新的栈顶运算符相比较;
    (5) 遇到括号时:
    (5-1) 如果是右括号“)”,则直接压入 S1;
    (5-2) 如果是左括号“(”,则依次弹出 S1 栈顶的运算符,并压入 S2,直到遇到右括号为止,此时将这一对括号丢弃;
    (6) 重复步骤(2)至(5),直到表达式的最左边;
    (7) 将 S1 中剩余的运算符依次弹出并压入 S2;
    (8) 依次弹出 S2 中的元素并输出,结果即为中缀表达式对应的前缀表达式。

    值得注意的是 s2 输出结果 不需要 逆序

    +
    +
    +

    代码实现思路与后缀表达式类似,且使用情况较少,故不再详细讲述。

    +
    +
  6. +
+

队列

基本概念

    +
  1. 队列:是一个 有序列表 ,可以用 数组链表 来实现。
  2. +
  3. 特点:遵循 先入先出 的原则。
    示意图
  4. +
+

数组模拟队列

声明 4 个变量:

+
    +
  • arr:用来储存数据的数组
  • +
  • maxSize:该队列的最大容量
  • +
  • front:队首下标,随着数据输出而改变
  • +
  • rear:队尾下标,随着数据输入二改变
  • +
+

队列中常用操作分析,以 add ,把数据存入队列为例,思路分析:

+
    +
  1. 存入数据后将尾指针往后移:rear + 1,前提是当 front == rear时,队列是空的
  2. +
  3. 若尾指针rear < maxSize - 1
      +
    • 则将数据存入rear所指的数组元素中
    • +
    • 否则无法存入数据,rear = maxSize - 1表示队列满了
    • +
    +
  4. +
+
+
查看代码实现 +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
package DataStructure;
+
+public class Queue {
+    /*
+     * 主函数:
+     * 添加队列元素与展示队列元素
+     */
+    public static void main(String[] args) {
+        ArrayQueue queue = new ArrayQueue(3);
+        queue.add(1);
+        queue.add(2);
+        queue.add(3);
+        System.out.println("查看队列中的数据:");
+        queue.show();
+        System.out.println("查看队列头数据:" + queue.head());
+        System.out.println("查看队列尾数据:" + queue.tail());
+        System.out.println("获取队列的数据:" + queue.get());
+        System.out.println("查看队列中的数据:");
+        queue.show();
+    }
+}
+
+class ArrayQueue {
+    // 确定队列的4个基本属性,使用private修饰符修饰成员变量
+    private int maxSize;
+    private int front;
+    private int rear;
+    private int arr[];
+
+    // 创建队列,初始化成员变量,使用构造函数创建
+    public ArrayQueue(int arrMaxSize) {
+        maxSize = arrMaxSize;
+        arr = new int[maxSize];
+        front = -1;
+        rear = -1;
+    }
+
+    // 取出队列的数据,同时使用isEmpty来判断队列是否为空
+    public int get() {
+        if (isEmpty()) {
+            throw new RuntimeException("队列空");
+        }
+        return arr[++front];
+    }
+
+    // 往队列中放入数据,用isFull来判断队列是否已满
+    public void add(int n) {
+        if (isFull()) {
+            System.out.println("队列已满!");
+            return;
+        }
+        arr[++rear] = n;
+    }
+
+    // 显示队列中的数据,判断不为空之后采用循环遍历的方式显示数据
+    public void show() {
+        if (isEmpty()) {
+            throw new RuntimeException("队列为空");
+        }
+        for (int i = 0; i < arr.length; i++) {
+            System.out.printf("arr[%d]: %d\n", i, arr[i]);
+        }
+    }
+
+    // 取出队头元素
+    public int head() {
+        if (isEmpty()) {
+            throw new RuntimeException("队列为空");
+        }
+        return arr[front + 1];
+    }
+
+    // 取出队尾元素
+    public int tail() {
+        if (isEmpty()) {
+            throw new RuntimeException("队列为空");
+        }
+        return arr[rear];
+    }
+
+    private boolean isFull() {
+        return rear == maxSize - 1;
+    }
+
+    private boolean isEmpty() {
+        return rear == front;
+    }
+
+}
+
+
+

循环队列

基本思路:

+
    +
  1. front:队列的第一个元素
  2. +
  3. rear:队列最后一个元素的下一个位置
  4. +
  5. 队列 计算公式:(rear + 1) % maxSize == front
  6. +
  7. 队列 计算公式:rear == front
  8. +
  9. 队列中 有效元素个数 计算公式:(rear + maxSize -front) % maxSize
  10. +
+
+
查看代码实现 +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package DataStructure;
+
+import java.util.Scanner;
+
+public class CircleQueueDemo {
+    // 写一个主函数内部写一个控制台输入的小程序
+    public static void main(String[] args) {
+        CircleQueue queue = new CircleQueue(3);
+
+        Scanner scanner = new Scanner(System.in);
+        boolean loop = true;
+        char key = ' ';
+        System.out.println("s(show): 显示队列");
+        System.out.println("e(exit): 退出程序");
+        System.out.println("a(add): 添加数据到队列");
+        System.out.println("g(get): 从队列取出数据");
+        System.out.println("h(head): 查看队列头的数据");
+        System.out.println("t(tail): 查看队列尾的数据");
+        System.out.println("p(isEmpty): 队列是否为空");
+        while (loop) {
+            key = scanner.next().charAt(0);
+            switch (key) {
+                case 's':
+                    queue.show();
+                    break;
+                case 'e':
+                    loop = false;
+                    break;
+                case 'a':
+                    System.out.println("请输入要添加到队列中的整数:");
+                    int value = scanner.nextInt();
+                    queue.add(value);
+                    break;
+                case 'g':
+                    try {
+                        int res = queue.get();
+                        System.out.printf("取出的数据是:%d\n", res);
+                    } catch (Exception e) {
+                        System.out.println(e.getMessage());
+                    }
+                    break;
+                case 'h':
+                    try {
+                        int res = queue.head();
+                        System.out.println("队头数据为:" + res);
+                    } catch (Exception e) {
+                        System.out.println(e.getMessage());
+                    }
+                    break;
+                case 't':
+                    try {
+                        int res = queue.tail();
+                        System.out.println("队尾数据为:" + res);
+                    } catch (Exception e) {
+                        System.out.println(e.getMessage());
+                    }
+                    break;
+                case 'p':
+                    System.out.printf("队列是否为空:%s", queue.isEmpty());
+                    break;
+            }
+        }
+    }
+}
+
+// 开始编写环形队列这个类
+class CircleQueue {
+    // 使用私有变量储存四个基本属性
+    private int maxSize;
+    private int front;
+    private int rear;
+    private int[] arr;
+
+    // 初始化环形队列
+    public CircleQueue(int arrMaxSize) {
+        maxSize = arrMaxSize + 1; // 此处+1是因为需要给rear留出空的储存空间
+        front = 0;
+        rear = 0;
+        arr = new int[maxSize];
+    }
+
+    // 取出队列的数据
+    public int get() {
+        if (isEmpty()) {
+            throw new RuntimeException("队列空");
+        }
+        int value = arr[front];
+        front = (front + 1) % maxSize;
+        return value;
+    }
+
+    // 往队列中储存数据
+    public void add(int n) {
+        if (isFull()) {
+            System.out.println("队列已满");
+            return;
+        }
+        arr[rear] = n;
+        rear = (rear + 1) % maxSize;
+    }
+
+    // 显示队列中的数据
+    public void show() {
+        if (isEmpty()) {
+            System.out.println("队列为空");
+            return;
+        }
+        for (int i = front; i < front + size(); i++) {
+            int index = i % maxSize;
+            System.out.printf("arr[%d] = %d\n", index, arr[index]);
+        }
+    }
+
+    public int head() {
+        if (isEmpty()) {
+            throw new RuntimeException("队列空");
+        }
+        return arr[front];
+    }
+
+    public int tail() {
+        if (isEmpty()) {
+            throw new RuntimeException("队列空");
+        }
+        return rear - 1 < 0 ? arr[maxSize - 1] : arr[rear - 1];
+    }
+
+    private boolean isFull() {
+        return (rear + 1) % maxSize == front;
+    }
+
+    public boolean isEmpty() {
+        return rear == front;
+    }
+
+    public int size() {
+        return (rear + maxSize - front) % maxSize;
+    }
+}
+
+
+
文章作者: 希亚的西红柿
文章链接: http://blog.refrain.site/posts/6534ce06/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
+ + + + \ No newline at end of file diff --git a/posts/75378e04/index.html b/posts/75378e04/index.html new file mode 100644 index 000000000..ce9763e7a --- /dev/null +++ b/posts/75378e04/index.html @@ -0,0 +1,725 @@ +机器学习基本概念与知识 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + +

机器学习基本概念与知识

+

本文主要介绍机器学习的基本概念与知识,文字比较简洁内容并不长,主要用于记录学习内容,方便之后复习

+
+

主要任务

    +
  • 分类(classification): 将实例数据划分到合适的类别中。
      +
    • 应用实例: 判断网站是否被黑客入侵(二分类 ),手写数字的自动识别(多分类)
    • +
    +
  • +
  • 回归(regression): 主要用于预测数值型数据。
      +
    • 应用实例: 股票价格波动的预测,房屋价格的预测等。
    • +
    +
  • +
+

监督学习(supervised learning)

    +
  • 特点:训练样本同时具有特征与目标变量的机器学习算法。
      +
    • 特征通常是训练样本集的列,它们是独立测量得到的。
    • +
    • 目标变量: 目标变量是机器学习预测算法的测试结果。
    • +
    +
  • +
  • 监督学习需要注意的问题:
      +
    • 偏置方差权衡
    • +
    • 功能的复杂性和数量的训练数据
    • +
    • 输入空间的维数
    • +
    • 噪声中的输出值
    • +
    +
  • +
+

非监督学习(unsupervised learning)

    +
  • 特点:训练样本没有目标变量,即是指在没有类别信息情况下,通过对所研究对象的大量样本的数据分析实现对样本分类的一种数据处理方法。
  • +
  • 非监督学习包括的类型:
      +
    • 聚类: 在无监督学习中,将数据集分成由类似的对象组成多个类的过程称为聚类。
    • +
    • 密度估计: 通过样本分布的紧密程度,来估计与分组的相似性。
    • +
    • 此外,无监督学习还可以减少数据特征的维度,以便我们可以使用二维或三维图形更加直观地展示数据信息。
    • +
    +
  • +
+

强化学习

这个算法可以训练程序做出某一决定。程序在某一情况下尝试所有的可能行动,记录不同行动的结果并试着找出最好的一次尝试来做决定。 属于这一类算法的有马尔可夫决策过程。
训练过程

+

算法汇总

+

机器学习的使用

+

选择算法需要考虑的两个问题

+
+
    +
  1. 算法场景
      +
    • 预测明天是否下雨,因为可以用历史的天气情况做预测,所以选择监督学习算法
    • +
    • 给一群陌生的人进行分组,但是我们并没有这些人的类别信息,所以选择无监督学习算法、通过他们身高、体重等特征进行处理。
    • +
    +
  2. +
  3. 需要收集或分析的数据是什么
  4. +
+
+

举例

+
+

+

机器学习的开发流程

+
    +
  1. 收集数据: 收集样本数据
  2. +
  3. 准备数据: 注意数据的格式
  4. +
  5. 分析数据: 为了确保数据集中没有垃圾数据;
      +
    • 如果是算法可以处理的数据格式或可信任的数据源,则可以跳过该步骤;
    • +
    • 另外该步骤需要人工干预,会降低自动化系统的价值。
    • +
    +
  6. +
  7. 训练算法: [机器学习算法核心]如果使用无监督学习算法,由于不存在目标变量值,则可以跳过该步骤
  8. +
  9. 测试算法: [机器学习算法核心]评估算法效果
  10. +
  11. 使用算法: 将机器学习算法转为应用程序
  12. +
+

机器学习基础概念补充

+

一个总结知识点很棒的链接 https://zhuanlan.zhihu.com/p/25197792

+
+

机器学习基本术语

    +
  • 模型(model): 计算机层面的认知
  • +
  • 学习算法(learning algorithm),从数据中产生模型的方法
  • +
  • 数据集(data set): 一组记录的合集
  • +
  • 示例(instance): 对于某个对象的描述
  • +
  • 样本(sample): 也叫示例
  • +
  • 属性(attribute): 对象的某方面表现或特征
  • +
  • 特征(feature): 同属性
  • +
  • 属性值(attribute value): 属性上的取值
  • +
  • 属性空间(attribute space): 属性张成的空间
  • +
  • 样本空间/输入空间(samplespace): 同属性空间
  • +
  • 特征向量(feature vector): 在属性空间里每个点对应一个- 坐标向量,把一个示例称作特征向量
  • +
  • 维数(dimensionality): 描述样本参数的个数(也就是空间- 是几维的)
  • +
  • 学习(learning)/训练(training): 从数据中学得模型
  • +
  • 训练数据(training data): 训练过程中用到的数据
  • +
  • 训练样本(training sample):训练用到的每个样本
  • +
  • 训练集(training set): 训练样本组成的集合
  • +
  • 假设(hypothesis): 学习模型对应了关于数据的某种潜在规则
  • +
  • 真相(ground-truth):真正存在的潜在规律
  • +
  • 学习器(learner): 模型的另一种叫法,把学习算法在给定数据和参数空间的实例化
  • +
  • 预测(prediction): 判断一个东西的属性
  • +
  • 标记(label): 关于示例的结果信息,比如我是一个“好人”。
  • +
  • 样例(example): 拥有标记的示例
  • +
  • 标记空间/输出空间(label space): 所有标记的集合
  • +
  • 分类(classification): 预测是离散值,比如把人分为好人和坏人之类的学习任务
  • +
  • 回归(regression): 预测值是连续值,比如你的好人程度达到了 0.9,0.6 之类的
  • +
  • 二分类(binary classification): 只涉及两个类别的分类任务
  • +
  • 正类(positive class): 二分类里的一个
  • +
  • 反类(negative class): 二分类里的另外一个
  • +
  • 多分类(multi-class classification): 涉及多个类别的分类
  • +
  • 测试(testing): 学习到模型之后对样本进行预测的过程
  • +
  • 测试样本(testing sample): 被预测的样本
  • +
  • 聚类(clustering): 把训练集中的对象分为若干组
  • +
  • 簇(cluster): 每一个组叫簇
  • +
  • 监督学习(supervised learning): 典范—分类和回归
  • +
  • 无监督学习(unsupervised learning): 典范—聚类
  • +
  • 未见示例(unseen instance): “新样本“,没训练过的样本
  • +
  • 泛化(generalization)能力: 学得的模型适用于新样本的能力
  • +
  • 分布(distribution): 样本空间的全体样本服从的一种规律
  • +
  • 独立同分布(independent and identically distributed,简称 i,i,d.):获得的每个样本都是独立地从这个分布上采样获得的。
  • +
+

数据集的划分

    +
  • 训练集(Training set) —— 学习样本数据集,通过匹配一些参数来建立一个模型,主要用来训练模型。类比考研前做的解题大全。
  • +
  • 验证集(validation set) —— 对学习出来的模型,调整模型的参数,如在神经网络中选择隐藏单元数。验证集还用来确定网络结构或者控制模型复杂程度的参数。类比 考研之前做的模拟考试。
  • +
  • 测试集(Test set) —— 测试训练好的模型的分辨能力。类比 考研。这次真的是一考定终身。
  • +
+

模型拟合程度

    +
  • 欠拟合(Underfitting): 模型没有很好地捕捉到数据特征,不能够很好地拟合数据,对训练样本的一般性质尚未学好。类比,光看书不做题觉得自己什么都会了,上了考场才知道自己啥都不会。
  • +
  • 过拟合(Overfitting): 模型把训练样本学习“太好了”,可能把一些训练样本自身的特性当做了所有潜在样本都有的一般性质,导致泛化能力下降。类比,做课后题全都做对了,超纲题也都认为是考试必考题目,上了考场还是啥都不会。
  • +
+
+

通俗来说,欠拟合和过拟合都可以用一句话来说,欠拟合就是: “你太天真了!”,过拟合就是: “你想太多了!”。

+
+

常见的模型指标

    +
  • 正确率 —— 提取出的正确信息条数 / 提取出的信息条数
  • +
  • 召回率 —— 提取出的正确信息条数 / 样本中的信息条数
  • +
  • F 值 —— 正确率 召回率 2 / (正确率 + 召回率)(F 值即为正确率和召回率的调和平均值)
  • +
+
+

举个例子如下:
某池塘有 1400 条鲤鱼,300 只虾,300 只乌龟。现在以捕鲤鱼为目的。撒了一张网,逮住了 700 条鲤鱼,200 只 虾, 100 只乌龟。那么这些指标分别如下: 正确率 = 700 / (700 + 200 + 100) = 70% 召回率 = 700 / 1400 = 50% F 值 = 70% 50% 2 / (70% + 50%) = 58.3%

+
+

模型

    +
  • 分类问题 —— 说白了就是将一些未知类别的数据分到现在已知的类别中去。比如,根据你的一些信息,判断你是高富帅,还是穷屌丝。评判分类效果好坏的三个指标就是上面介绍的三个指标: 正确率,召回率,F 值。
  • +
  • 回归问题 —— 对数值型连续随机变量进行预测和建模的监督学习算法。回归往往会通过计算 误差(Error)来确定模型的精确性。
  • +
  • 聚类问题 —— 聚类是一种无监督学习任务,该算法基于数据的内部结构寻找观察样本的自然族群(即集群)。聚类问题的标准一般基于距离: 簇内距离(Intra-cluster Distance) 和 簇间距离(Inter-cluster Distance) 。簇内距离是越小越好,也就是簇内的元素越相似越好;而簇间距离越大越好,也就是说簇间(不同簇)元素越不相同越好。一般的,衡量聚类问题会给出一个结合簇内距离和簇间距离的公式。
  • +
+

下面这个图可以比较直观地展示出来:

+

特征工程的一些细节

    +
  • 特征选择 —— 也叫特征子集选择(FSS,Feature Subset Selection)。是指从已有的 M 个特征(Feature)中选择 N 个特征使得系统的特定指标最优化,是从原始特征中选择出一些最有效特征以降低数据集维度的过程,是提高算法性能的一个重要手段,也是模式识别中关键的数据预处理步骤。
    +

    模式识别(pattern recognition): 模式识别是最古老的(作为一个术语而言,可以说是很过时的)。

    +
      +
    • 我们把环境与客体统称为“模式”,识别是对模式的一种认知,是如何让一个计算机程序去做一些看起来很“智能”的事情。
    • +
    • 通过融于智慧和直觉后,通过构建程序,识别一些事物,而不是人,例如: 识别数字。
    • +
    +
    +
  • +
  • 特征提取 —— 特征提取是计算机视觉和图像处理中的一个概念。它指的是使用计算机提取图像信息,决定每个图像的点是否属于一个图像特征。特征提取的结果是把图像上的点分为不同的子集,这些子集往往属于孤立的点,连续的曲线或者连续的区域。
  • +
+

下面给出特征工程的图:

+
+

本文内容参考自AIlearning,想了解更多细节推荐阅读原文,项目地址为Github

+
+
文章作者: 希亚的西红柿
文章链接: http://blog.refrain.site/posts/75378e04/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
+ + + + \ No newline at end of file diff --git a/posts/775eb342/index.html b/posts/775eb342/index.html new file mode 100644 index 000000000..b08515777 --- /dev/null +++ b/posts/775eb342/index.html @@ -0,0 +1,710 @@ +使用Numpy实现k-Nearest-Neighbor算法 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + + + + +

使用Numpy实现k-Nearest-Neighbor算法

KNN 算法基本原理

kNN 算法的核心思想是用距离最近的 k 个样本数据的分类来代表目标数据的分类。 用俗话来说就是 “近朱者赤,近墨者黑”,采用距离最近的 k 个样本中占比最高的类别来代表测试数据的类别。

+
+

注意 KNN 算法仍然是有监督学习算法的一种

+
+

KNN 算法的特点

    +
  • 优点:
      +
    1. 监督学习:可以看到,kNN 算法首先需要一个训练样本集,这个集合中含有分类信息,因此它属于监督学习
    2. +
    3. 通过计算距离来衡量样本之间相似度,算法简单,易于理解和实现。
    4. +
    5. 对异常值不敏感
    6. +
    +
  • +
  • 缺点:
      +
    1. 需要设定 k 值,结果会受到 k 值的影响,不同的 k 值,最后得到的分类结果不尽相同。k 一般不超过 20
    2. +
    3. 计算量大,需要计算样本集中每个样本的距离,才能得到 k 个最近的数据样本。
    4. +
    5. 训练样本集不平衡导致结果不准确问题。当样本集中主要是某个分类,该分类数量太大,导致近邻的 k 个样本总是该类,而不接近目标分类。
    6. +
    +
  • +
+

KNN 算法的流程

+

一般情况下,kNN 有如下流程:

+
    +
  1. 收集数据:确定训练样本集合测试数据;
  2. +
  3. 计算测试数据和训练样本集中每个样本数据的距离;
  4. +
+
    +
  • 常用的距离计算公式:
  • +
+
+

欧式距离计算公式:
曼哈顿距离公式:

+
+
    +
  1. 按照距离递增的顺序排序;
  2. +
  3. 选取距离最近的 k 个点(这个 k 一般小于等于 20);
  4. +
  5. 确定这 k 个点中分类信息的频率;
  6. +
  7. 返回前 k 个点中出现频率最高的分类,作为当前测试数据的分类。
  8. +
+
+

python 算法实现

+

开发的基本流程如下:

+
    +
  • 收集数据: 提供文本文件
  • +
  • 准备数据: 使用 Python 解析文本文件
  • +
  • 分析数据: 使用 Matplotlib 画二维散点图
  • +
  • 训练算法: 此步骤不适用于 k-近邻算法
  • +
  • 测试算法: 使用海伦提供的部分数据作为测试样本。
  • +
+
+

测试样本和非测试样本的区别在于:
测试样本是已经完成分类的数据,如果预测分类与实际类别不同,则标记为一个错误。

+
+
    +
  • 使用算法: 产生简单的命令行程序,然后海伦可以输入一些特征数据以判断对方是否为自己喜欢的类型。
  • +
+
+
    +
  1. KNN 算法分类器函数
  2. +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 定义KNN算法分类器函数
+# 函数参数包括: (测试数据, 训练数据, 分类, k值)
+def classify(inX, dataSet, labels, k):
+    dataSetSize = dataSet.shape[0]  # 读取矩阵有几层数据
+    diffMat = tile(inX, (dataSetSize, 1)) - dataSet  # 将测试数据转化为数值相同的行向量,再与训练集作差
+    sqDiffMat = diffMat ** 2
+    sqDistances = sqDiffMat.sum(axis=1)  # axis=1 按行相加;axis=0 按列相加
+    distances = sqDistances ** 0.5  # 计算欧式距离
+    sortedDistIndicies = distances.argsort()  # 排序并返回index,注意此处返回的是索引而不是具体值!
+    # 选择距离最近的k个值
+    classCount = {}
+    for i in range(k):
+        voteIlabel = labels[sortedDistIndicies[i]]
+        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1  # get()相当于将其赴初值0然后加1
+    # 排序
+    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True) # items()返回一个元组,operator.itemgetter()按照value排序
+    return sortedClassCount[0][0] #返回标签
+
    +
  1. 定义生成训练样本集的函数
  2. +
+
1
2
3
4
def createDataSet():
+    group = array([[1, 1.1], [1, 1], [0, 0], [0, 0.1]])
+    labels = ['A', 'A', 'B', 'B']
+    return group, labels
+
    +
  1. 定义主函数运行代码
  2. +
+
1
2
3
4
5
6
7
def main():
+    group, labels = createDataSet()
+    print(classify([0, 0], group, labels, 3))
+
+
+if __name__ == '__main__':
+    main()
+

完整源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#!/user/bin/env python3
+# -*- coding: utf-8 -*-
+
+from numpy import *
+import operator
+
+
+# 定义KNN算法分类器函数
+# 函数参数包括: (测试数据, 训练数据, 分类, k值)
+def classify(inX, dataSet, labels, k):
+    dataSetSize = dataSet.shape[0]  # 读取矩阵有几层数据
+    diffMat = tile(inX, (dataSetSize, 1)) - dataSet  # 将测试数据转化为数值相同的行向量,再与训练集作差
+    sqDiffMat = diffMat ** 2
+    sqDistances = sqDiffMat.sum(axis=1)  # axis=1 按行相加;axis=0 按列相加
+    distances = sqDistances ** 0.5  # 计算欧式距离
+    sortedDistIndicies = distances.argsort()  # 排序并返回index,注意此处返回的是索引而不是具体值!
+    # 选择距离最近的k个值
+    classCount = {}
+    for i in range(k):
+        voteIlabel = labels[sortedDistIndicies[i]]
+        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1  # get()相当于将其赴初值0然后加1
+    # 排序
+    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True) # items()返回一个元组,operator.itemgetter()按照value排序
+    return sortedClassCount[0][0] #返回标签
+
+
+# 定义一个生成"训练样本集"的函数,包含特征和分类信息
+def createDataSet():
+    group = array([[1, 1.1], [1, 1], [0, 0], [0, 0.1]])
+    labels = ['A', 'A', 'B', 'B']
+    return group, labels
+
+
+def main():
+    group, labels = createDataSet()
+    print(classify([0, 0], group, labels, 3))
+
+
+if __name__ == '__main__':
+    main()
+
+

本文参考自 机器学习之 k-近邻(kNN)算法与 Python 实现

+
+
文章作者: 希亚的西红柿
文章链接: http://blog.refrain.site/posts/775eb342/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/posts/8d55d49a/index.html b/posts/8d55d49a/index.html new file mode 100644 index 000000000..b416843ba --- /dev/null +++ b/posts/8d55d49a/index.html @@ -0,0 +1,1073 @@ +JAVA学习笔记 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + + +

JAVA学习笔记

本篇文章将作为编程语言 JAVA 的学习笔记,供以后查阅与复习使用。

+
+

JAVA 学习笔记参考自 菜鸟 JAVA 教程

+
+

本学习笔记以 查询和复习 为主要功能,文章内容应 短小精悍

+
+

Java 主要特性

查看Java 主要特性 +
+

关于 Java 特性的介绍的文字可能比较多 emm。。。主要因为我是照搬的 ,不过只需要简单了解,但需要特别注意的是 Java 与 C++的一个重要区别为 Java 不使用指针而是引用,这对后面理解 Java 程序有很大的帮助。

  • Java 语言是简单的:
    Java 语言的语法与 C 语言和 C++ 语言很接近,使得大多数程序员很容易学习和使用。另一方面,Java 丢弃了 C++ 中很少使用的、很难理解的、令人迷惑的那些特性,如操作符重载、多继承、自动的强制类型转换。特别地,Java 语言不使用指针,而是引用。并提供了自动分配和回收内存空间,使得程序员不必为内存管理而担忧。
  • Java 语言是面向对象的:
    Java 语言提供类、接口和继承等面向对象的特性,为了简单起见,只支持类之间的单继承,但支持接口之间的多继承,并支持类与接口之间的实现机制(关键字为 implements)。Java 语言全面支持动态绑定,而 C++语言只对虚函数使用动态绑定。总之,Java 语言是一个纯的面向对象程序设计语言。
  • Java 语言是分布式的:
    Java 语言支持 Internet 应用的开发,在基本的 Java 应用编程接口中有一个网络应用编程接口(java net),它提供了用于网络应用编程的类库,包括 URL、URLConnection、Socket、ServerSocket 等。Java 的 RMI(远程方法激活)机制也是开发分布式应用的重要手段。
  • Java 语言是健壮的:
    Java 的强类型机制、异常处理、垃圾的自动收集等是 Java 程序健壮性的重要保证。对指针的丢弃是 Java 的明智选择。Java 的安全检查机制使得 Java 更具健壮性。
  • Java 语言是安全的:
    Java 通常被用在网络环境中,为此,Java 提供了一个安全机制以防恶意代码的攻击。除了 Java 语言具有的许多安全特性以外,Java 对通过网络下载的类具有一个安全防范机制(类 ClassLoader),如分配不同的名字空间以防替代本地的同名类、字节代码检查,并提供安全管理机制(类 SecurityManager)让 Java 应用设置安全哨兵。
  • Java 语言是体系结构中立的:
    Java 程序(后缀为 java 的文件)在 Java 平台上被编译为体系结构中立的字节码格式(后缀为 class 的文件),然后可以在实现这个 Java 平台的任何系统中运行。这种途径适合于异构的网络环境和软件的分发。
  • Java 语言是可移植的:
    这种可移植性来源于体系结构中立性,另外,Java 还严格规定了各个基本数据类型的长度。Java 系统本身也具有很强的可移植性,Java 编译器是用 Java 实现的,Java 的运行环境是用 ANSI C 实现的。
  • Java 语言是解释型的:
    如前所述,Java 程序在 Java 平台上被编译为字节码格式,然后可以在实现这个 Java 平台的任何系统中运行。在运行时,Java 平台中的 Java 解释器对这些字节码进行解释执行,执行过程中需要的类在联接阶段被载入到运行环境中。
  • Java 是高性能的:
    与那些解释型的高级脚本语言相比,Java 的确是高性能的。事实上,Java 的运行速度随着 JIT(Just-In-Time)编译器技术的发展越来越接近于 C++。
  • Java 语言是多线程的:
    在 Java 语言中,线程是一种特殊的对象,它必须由 Thread 类或其子(孙)类来创建。通常有两种方法来创建线程:其一,使用型构为 Thread(Runnable) 的构造子类将一个实现了 Runnable 接口的对象包装成一个线程,其二,从 Thread 类派生出子类并重写 run 方法,使用该子类创建的对象即为线程。值得注意的是 Thread 类已经实现了 Runnable 接口,因此,任何一个线程均有它的 run 方法,而 run 方法中包含了线程所要运行的代码。线程的活动由一组方法来控制。Java 语言支持多个线程的同时执行,并提供多线程之间的同步机制(关键字为 synchronized)。
  • Java 语言是动态的:
    Java 语言的设计目标之一是适应于动态变化的环境。Java 程序需要的类能够动态地被载入到运行环境,也可以通过网络来载入所需要的类。这也有利于软件的升级。另外,Java 中的类有一个运行时刻的表示,能进行运行时刻的类型检查。
+
+
+

Java 基础语法

查看Java 基础语法 +
+

此处对 Java 的基础语法只作简单的介绍,以便对 Java 语法有一个整体的印象,Java 语法的详细讲解在后面相应部分。

一个 Java 程序可以认为是一系列对象的集合,而这些对象通过调用彼此的方法来协同工作。下面简要介绍下类、对象、方法和实例变量的概念。

  • 对象:对象是类的一个实例,有状态和行为。例如狗是一个类,而小狗包弟是类的一个实例。
  • 类:类是一个模板,它描述一类对象的行为和状态。
  • 方法:方法就是行为,一个类可以有很多方法。逻辑运算、数据修改以及所有动作都是在方法中完成的。
  • 实例变量:每个对象都有独特的实例变量,对象的状态由这些实例变量的值决定。

第一个 Java 程序,分析主函数的构成

1
2
3
4
5
6
7
8
public class HelloWorld {
+    /* 第一个Java程序
+     * 它将输出字符串 Hello World
+     */
+    public static void main(String[] args) {
+        System.out.println("Hello World"); // 输出 Hello World
+    }
+}

主函数语句的构成

编写 Java 程序时应注意一下几点:

  • 大小写敏感:Java 是大小写敏感的,这就意味着标识符 Hellohello 是不同的。
  • 类名:对于所有的类来说,类名的首字母应该大写。如果类名由若干单词组成,那么每个单词的首字母应该大写,例如 MyFirstJavaClass
  • 方法名:所有的方法名都应该以小写字母开头。如果方法名含有若干单词,则后面的每个单词首字母大写。
  • 源文件名:源文件名必须和类名相同。当保存文件的时候,你应该使用类名作为文件名保存(切记 Java 是大小写敏感的),文件名的后缀为 .java 。(如果文件名和类名不相同则会导致编译错误)。
  • 主方法入口:所有的 Java 程序由 public static void main(String[] args) 方法开始执行。

Java 所有的组成部分都需要名字。类名、变量名以及方法名都被称为标识符。

关于 Java 标识符,有以下几点需要注意:

  • 所有的标识符都应该以字母(A-Z 或者 a-z),美元符($)、或者下划线(_)开始
  • 首字符之后可以是字母(A-Z 或者 a-z),美元符($)、下划线(_)或数字的任何字符组合
  • 关键字不能用作标识符
  • 标识符是大小写敏感的
  • 合法标识符举例:age、$salary、_value、__1_value
  • 非法标识符举例:123abc、-salary

一言蔽之:即标识符可由字母,美元符,下划线和数字组成,其中数字不能放在标识符的开头。


像其他语言一样,Java 可以使用修饰符来修饰类中方法和属性。主要有两类修饰符:

  • 访问控制修饰符 : default, public, protected, private
  • 非访问控制修饰符 :final, abstract, static, synchronized


Java 中主要有如下几种类型的变量

  • 局部变量
  • 类变量(静态变量)
  • 成员变量(非静态变量)


数组是储存在堆上的对象,可以保存多个同类型变量。在后面的章节中,我们将会学到如何声明、构造以及初始化一个数组。

Java 5.0 引入了枚举,枚举限制变量只能是预先设定好的值。使用枚举可以减少代码中的 bug。


例如,我们为果汁店设计一个程序,它将限制果汁为小杯、中杯、大杯。这就意味着它不允许顾客点除了这三种尺寸外的果汁。

1
2
3
4
5
6
7
8
9
10
11
class FreshJuice {
+   enum FreshJuiceSize{ SMALL, MEDIUM , LARGE }
+   FreshJuiceSize size;
+}
+
+public class FreshJuiceTest {
+   public static void main(String[] args){
+      FreshJuice juice = new FreshJuice();
+      juice.size = FreshJuice.FreshJuiceSize.MEDIUM  ;
+   }
+}

注意:枚举可以单独声明或者声明在类里面。方法、变量、构造函数也可以在枚举中定义。



下面列出了 Java 关键字。这些保留字不能用于常量、变量、和任何标识符的名称。

类别关键字说明
访问控制private私有的
protected受保护的
public公共的
default 默认
类、方法和变量修饰符abstract声明抽象
class
extends扩充,继承
final最终值,不可改变的
implements实现(接口)
interface接口
native本地,原生方法(非 Java 实现)
new新,创建
static静态
strictfp严格,精准
synchronized线程,同步
transient短暂
volatile易失
程序控制语句break跳出循环
case定义一个值以供 switch 选择
continue继续
do运行
else否则
for循环
if如果
instanceof实例
return返回
switch根据值选择执行
while循环
错误处理assert断言表达式是否为真
catch捕捉异常
finally有没有异常都执行
throw抛出一个异常对象
throws声明一个异常可能被抛出
try捕获异常
包相关import引入
package
基本类型boolean布尔型
byte字节型
char字符型
double双精度浮点
float单精度浮点
int整型
long长整型
short短整型
变量引用super父类,超类
this本类
void无返回值
保留关键字goto是关键字,但不能使用
const是关键字,但不能使用

注意:Java 的 null 不是关键字,类似于 true 和 false,它是一个字面常量,不允许作为标识符使用。



类似于 C/C++、Java 也支持单行以及多行注释。注释中的字符将被 Java 编译器忽略。

1
2
3
4
5
6
7
8
9
10
11
public class HelloWorld {
+   /* 这是第一个Java程序
+    * 它将输出 Hello World
+    * 这是一个多行注释的示例
+    */
+    public static void main(String[] args){
+       // 这是单行注释的示例
+       /* 这个也是单行注释的示例 */
+       System.out.println("Hello World");
+    }
+}


空白行或者有注释的行,Java 编译器都会忽略掉。

在 Java 中,一个类可以由其他类派生。如果你要创建一个类,而且已经存在一个类具有你所需要的属性或方法,那么你可以将新创建的类继承该类。

利用继承的方法,可以重用已存在类的方法和属性,而不用重写这些代码。被继承的类称为超类(super class),派生类称为子类(sub class)。

在 Java 中,接口可理解为对象间相互通信的协议。接口在继承中扮演着很重要的角色。
接口只定义派生要用到的方法,但是方法的具体实现完全取决于派生类。

区别如下图所示:
JAVA源程序与编译型运行区别

+
+
+

Java 对象和类

查看Java 对象和类 +
+

  • 类(class):类是一个模板,它描述一类对象的行为和状态。
  • 对象(object):对象是类的一个实例,有状态和行为。

一个类可以包含以下类型的变量:

  • 局部变量:在方法、构造方法或者语句块中定义的变量被称为局部变量。变量声明和初始化都是在方法中,方法结束后,变量就会自动销毁。
  • 成员变量:成员变量是定义在类中,方法体之外的变量。这种变量在创建对象的时候实例化。成员变量可以被类中方法、构造方法和特定类的语句块访问。
  • 类变量:类变量也声明在类中,方法体之外,但必须声明为 static 类型。

语句块:用一对{}包括起来的有着相同的变量作用域的相关一组语句的集合。


每个类都有构造方法。如果没有显式地为类定义构造方法,Java 编译器将会为该类提供一个默认构造方法。
在创建一个对象的时候,至少要调用一个构造方法。构造方法的名称必须与类同名,一个类可以有多个构造方法。


下面是一个构造方法示例:

1
2
3
4
5
6
7
8
public class Puppy {
+    public Puppy() {
+    }
+
+    public Puppy(String name) {
+        // 这个构造器仅有一个参数:name
+    }
+}

对象是根据类创建的。在 Java 中,使用关键字 new 来创建一个新的对象。创建对象需要以下三步:

  • 声明:声明一个对象,包括对象名称和对象类型。
  • 实例化:使用关键字 new 来创建一个对象。
  • 初始化:使用 new 创建对象时,会调用构造方法初始化对象。

下面是一个创建对象的例子:

1
2
3
4
5
6
7
8
9
10
11
public class Puppy {
+    public Puppy(String name) {
+        // 这个构造器仅有一个参数:name
+        System.out.println("小狗的名字是 : " + name);
+    }
+
+    public static void main(String[] args) {
+        // 下面的语句将创建一个Puppy对象
+        Puppy myPuppy = new Puppy("tommy");
+    }
+}

输出结果为:

1
小狗的名字是 : tommy


通过已创建的对象来访问成员变量和成员方法,如下所示:

1
2
3
4
5
6
/* 实例化对象 */
+Object referenceVariable = new Constructor();
+/* 访问类中的变量 */
+referenceVariable.variableName;
+/* 访问类中的方法 */
+referenceVariable.methodName();


在本节的最后部分,我们将学习源文件的声明规则。当在一个源文件中定义多个类,并且还有 import 语句和 package 语句时,要特别注意这些规则。

  • 一个源文件中只能有一个 public 类
  • 一个源文件可以有多个非 public 类
  • 源文件的名称应该和 public 类的类名保持一致。例如:源文件中 public 类的类名是 Employee,那么源文件应该命名为 Employee.java。
  • 如果一个类定义在某个包中,那么 package 语句应该在源文件的首行。
  • 如果源文件包含 import 语句,那么应该放在 package 语句和类定义之间。如果没有 package 语句,那么 import 语句应该在源文件中最前面。
  • import 语句和 package 语句对源文件中定义的所有类都有效。在同一源文件中,不能给不同的类不同的包声明。

类有若干种访问级别,并且类也分不同的类型:抽象类和 final 类等。这些将在访问控制章节介绍。
除了上面提到的几种类型,Java 还有一些特殊的类,如:内部类匿名类


包主要用来对类和接口进行分类。当开发 Java 程序时,可能编写成百上千的类,因此很有必要对类和接口进行分类。


在 Java 中,如果给出一个完整的限定名,包括包名、类名,那么 Java 编译器就可以很容易地定位到源代码或者类。import 语句就是用来提供一个合理的路径,使得编译器可以找到某个类。
例如,下面的命令行将会命令编译器载入 java_installation/java/io 路径下的所有类

1
import java.io.*;
+
+
+

Java 基本数据类型

查看Java 基本数据类型 +
+

变量就是申请内存来存储值。也就是说,当创建变量的时候,需要在内存中申请空间。
内存管理系统根据变量的类型为变量分配存储空间,分配的空间只能用来储存该类型数据。
Java 的两大数据类型:

  • 内置数据类型
  • 引用数据类型


Java 语言提供了八种基本类型。六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型。

1byte(字节)=8bit(比特)


byte:

  • byte 数据类型是 8 位(1byte)、有符号的,以二进制补码表示的整数;
  • 取值范围:-128(-2^7)~127(2^7-1)
  • 默认值是 0

short:

  • short 数据类型是 16 位(2byte)、有符号的以二进制补码表示的整数;
  • 取值范围:-32768(-2^15)~32767(2^15 - 1)
  • 默认值是 0

int:

  • int 数据类型是 32 位(4byte)、有符号的以二进制补码表示的整数;
  • 取值范围:-2,147,483,648(-2^31)~2,147,483,647(2^31 - 1)
  • 默认值是 0

long:

  • long 数据类型是 64 位(8byte)、有符号的以二进制补码表示的整数;
  • 取值范围:-9,223,372,036,854,775,808(-2^63)~9,223,372,036,854,775,807(2^63 -1)
  • 默认值是 0L

float:

  • float 数据类型是单精度、32 位、符合 IEEE 754 标准的浮点数;
  • 默认值是 0.0f
  • 浮点数不能用来表示精确的值,如货币;

double:

  • double 数据类型是双精度、64 位、符合 IEEE 754 标准的浮点数;
  • 浮点数的默认类型为 double 类型;
  • double 类型同样不能表示精确的值,如货币;
  • 默认值是 0.0d

boolean:

  • boolean 数据类型表示一位的信息;
  • 只有两个取值:true 和 false;
  • 这种类型只作为一种标志来记录 true/false 情况;
  • 默认值是 false

char:

  • char 类型是一个单一的 16 位 Unicode 字符;
  • 最小值是 \u0000(十进制等效值为 0)
  • 最大值是 \uffff(即为 65535)
  • char 数据类型可以储存任何字符;

在Java中,引用类型的变量非常类似于C/C++的指针。引用类型指向一个对象,指向对象的变量是引用变量。这些变量在声明时被指定为一个特定的类型,比如 Employee、Puppy 等。变量一旦声明后,类型就不能被改变了。

对象、数组都是引用数据类型。

所有引用类型的默认值都是null。

一个引用变量可以用来引用任何与之兼容的类型。


例子:Site site = new Site("Runoob")。


常量在程序运行时是不能被修改的。
在 Java 中使用 final 关键字来修饰常量,声明方式和变量类似:

1
2
//虽然常量名也可以用小写,但为了便于识别,通常使用大写字母表示常量。
+final double PI = 3.1415927;

Java 语言支持一些特殊的转义字符序列。

符号 字符含义
\n 换行 (0x0a)
\r 回车 (0x0d)
\f 换页符(0x0c)
\b 退格 (0x08)
\0 空字符 (0x0)
\s 空格 (0x20)
\t 制表符
\" 双引号
\' 单引号
\\ 反斜杠
\ddd 八进制字符 (ddd)
\uxxxx 16进制Unicode字符 (xxxx)

整型、实型(常量)、字符型数据可以混合运算。运算中,不同类型的数据先转化为同一类型,然后进行运算。
转换从低级到高级。

1
2
3
低  ------------------------------------>  高
+
+byte,short,char —> int —> long—> float —> double

数据类型转换必须满足如下规则:

  • 不能对 boolean 类型进行类型转换。
  • 不能把对象类型转换成不相关类的对象。
  • 在把容量大的类型转换为容量小的类型时必须使用强制类型转换。
  • 转换过程中可能导致溢出或损失精度,例如:
1
2
3
int i = 128;
+byte b = (byte)i;
+// 因为 byte 类型是 8 位,最大值为127,所以当 int 强制转换为 byte 类型时,值 128 时候就会导致溢出。
  • 浮点数到整数的转换是通过舍弃小数得到,而不是四舍五入,例如:
1
2
(int)23.7 == 23;
+(int)-45.89f == -45

自动类型转换
必须满足转换前的数据类型的位数要低于转换后的数据类型,例如: short 数据类型的位数为 16 位,就可以自动转换位数为 32 的 int 类型,同样 float 数据类型的位数为 32,可以自动转换为 64 位的 double 类型。

1
2
3
4
5
6
7
8
9
10
public class ZiDongLeiZhuan {
+        public static void main(String[] args) {
+            char c1='a';//定义一个char类型
+            int i1 = c1;//char自动类型转换为int
+            System.out.println("char自动类型转换为int后的值等于"+i1);
+            char c2 = 'A';//定义一个char类型
+            int i2 = c2+1;//char 类型和 int 类型计算
+            System.out.println("char类型和int计算后的值等于"+i2);
+        }
+}

运行结果为:

1
2
char自动类型转换为int后的值等于97
+char类型和int计算后的值等于66

强制类型转换

  • 条件是转换的数据类型必须是兼容的。
  • 格式:(type)value type 是要强制类型转换后的数据类型 实例:
1
2
3
4
5
6
7
public class QiangZhiZhuanHuan{
+    public static void main(String[] args){
+        int i1 = 123;
+        byte b = (byte)i1;//强制类型转换为byte
+        System.out.println("int强制类型转换为byte后的值等于"+b);
+    }
+}

运行结果:

1
int强制类型转换为byte后的值等于123

隐含强制类型转换

  • 整数的默认类型是 int。
  • 小数默认是 double 类型浮点型,在定义 float 类型时必须在数字后面跟上 F 或者 f。
+
+
+

Java 变量类型

查看Java 变量类型 +
+

在 Java 语言中,所有的变量在使用前必须声明。声明变量的基本格式如下:

1
type identifier [ = value][, identifier [= value] ...] ;

以下列出了一些变量的声明实例。注意有些包含了初始化过程。

1
2
int a, b, c;         // 声明三个int型整数:a、 b、c
+int d = 3, e = 4, f = 5; // 声明三个整数并赋予初值

Java 语言支持的变量类型有:

  • 类变量:独立于方法之外的变量,用 static 修饰。
  • 实例变量(成员变量):独立于方法之外的变量,不过没有 static 修饰。
  • 局部变量:类的方法中的变量。
1
2
3
4
5
6
7
8
9
10
11
public class Variable{
+    static int allClicks=0;    // 类变量
+
+    String str="hello world";  // 实例变量
+
+    public void method(){
+
+        int i =0;  // 局部变量
+
+    }
+}

  1. 局部变量声明在方法、构造方法或者语句块中;
  2. 局部变量在方法、构造方法、或者语句块被执行的时候创建,当它们执行完成后,变量将会被销毁;
  3. 访问修饰符不能用于局部变量;
  4. 局部变量只在声明它的方法、构造方法或者语句块中可见;
  5. 局部变量是在栈上分配的;
  6. 局部变量没有默认值,所以局部变量被声明后,必须经过初始化,才可以使用。

  1. 实例变量声明在一个类中,但在方法、构造方法和语句块之外;
  2. 当一个对象被实例化之后,每个实例变量的值就跟着确定;
  3. 实例变量在对象创建的时候创建,在对象被销毁的时候销毁;
  4. 实例变量的值应该至少被一个方法、构造方法或者语句块引用,使得外部能够通过这些方式获取实例变量信息;
  5. 实例变量可以声明在使用前或者使用后;
  6. 访问修饰符可以修饰实例变量;
  7. 实例变量对于类中的方法、构造方法或者语句块是可见的。一般情况下应该把实例变量设为私有。通过使用访问修饰符可以使实例变量对子类可见;
  8. 实例变量具有默认值。数值型变量的默认值是 0,布尔型变量的默认值是 false,引用类型变量的默认值是 null。变量的值可以在声明时指定,也可以在构造方法中指定;
  9. 实例变量可以直接通过变量名访问。但在静态方法以及其他类中,就应该使用完全限定名: ObjectReference.VariableName

  1. 类变量也称为静态变量,在类中以 static 关键字声明,但必须在方法之外。
  2. 无论一个类创建了多少个对象,类只拥有类变量的一份拷贝。
  3. 静态变量除了被声明为常量外很少使用,静态变量是指声明为 public/private,final 和 static 类型的变量。静态变量初始化后不可改变。
  4. 静态变量储存在静态存储区。经常被声明为常量,很少单独使用 static 声明变量。
  5. 静态变量在第一次被访问时创建,在程序结束时销毁。
  6. 与实例变量具有相似的可见性。但为了对类的使用者可见,大多数静态变量声明为 public 类型。
  7. 默认值和实例变量相似。数值型变量默认值是 0,布尔型默认值是 false,引用类型默认值是 null。变量的值可以在声明的时候指定,也可以在构造方法中指定。此外,静态变量还可以在静态语句块中初始化。
  8. 静态变量可以通过:ClassName.VariableName 的方式访问。
  9. 类变量被声明为 public static final 类型时,类变量名称一般建议使用大写字母。如果静态变量不是 public 和 final 类型,其命名方式与实例变量以及局部变量的命名方式一致。
    实例:
1
2
3
4
5
6
7
8
9
10
11
12
import java.io.*;
+
+public class Employee {
+    //salary是静态的私有变量
+    private static double salary;
+    // DEPARTMENT是一个常量
+    public static final String DEPARTMENT = "开发人员";
+    public static void main(String[] args){
+    salary = 10000;
+        System.out.println(DEPARTMENT+"平均工资:"+salary);
+    }
+}

运行结果:

1
开发人员平均工资:10000.0
+
+
+

Java 修饰符

查看Java 修饰符 +
+

Java 语言提供了很多修饰符,主要分为以下两类:

  • 访问修饰符
  • 非访问修饰符

修饰符用来定义类、方法或者变量,通常放在语句的最前端。我们通过下面的例子来说明:

1
2
3
4
5
6
7
8
9
public class ClassName {
+   // ...
+}
+private boolean myFlag;
+static final double weeks = 9.5;
+protected static final int BOXWIDTH = 42;
+public static void main(String[] arguments) {
+   // 方法体
+}

Java 支持 4 种不同的访问权限:
default & public:使用对象:类、接口、变量、方法。
private & protected:使用对象:变量、方法。(PS:不过二者都能修饰内部类)

修饰符当前类同一包内子孙类(同一包)子孙类(不同包)其它包
publicYYYYY
protectedYYYY/NN
defaultYYYNN
privateYNNNN

default:接口里的变量隐式声明为:public static final,接口里的方法默认访问权限为:public
protected:被 protected 修饰的成员对于本包和其子类可见。

  • 子类与基类在同一包中:被声明为 protected 的变量、方法和构造器能被同一个包中的任何其他类访问。
  • 子类与基类不在同一包中:那么在子类中,子类实例可以访问其从基类继承而来的 protected 方法,而不能访问基类实例的 protected 方法。
    (PS:在判断java.lang包中的clone()等方法是需要注意方法的来源,到底来自于当前包还是java.lang包。)

请注意以下方法继承的规则:

  • 父类中声明为public的方法在子类中也必须为public
  • 父类中声明为protected的方法在子类中要么声明为protected,要么声明为public,不能声明为private
  • 父类中声明为private的方法,不能够被子类继承。

  • 静态变量:static 关键字用来声明独立于对象的静态变量,无论一个类实例化多少对象,它的静态变量只有一份拷贝。 静态变量也被称为类变量。局部变量不能被声明为 static 变量。
  • 静态方法:static 关键字用来声明独立于对象的静态方法。静态方法不能使用类的非静态变量。静态方法从参数列表得到数据,然后计算这些数据。

对类变量和方法的访问可以直接使用classname.variablenameclassname.methodname的方式访问。

final 变量:final 表示”最后的、最终的”含义,变量一旦赋值后,不能被重新赋值。被 final 修饰的实例变量必须显式指定初始值。final 修饰符通常和 static 修饰符一起使用来创建类常量。
final 方法:父类中的 final 方法可以被子类继承,但是不能被子类重写。声明 final 方法的主要目的是防止该方法的内容被修改。
final 类:final 类不能被继承,没有类能够继承 final 类的任何特性。

抽象类:抽象类不能用来实例化对象,声明抽象类的唯一目的是为了将来对该类进行扩充。
一个类不能同时被 abstract 和 final 修饰。如果一个类包含抽象方法,那么该类一定要声明为抽象类,否则将出现编译错误。
抽象类可以包含抽象方法和非抽象方法。
抽象方法:抽象方法是一种没有任何实现的方法,该方法的具体实现由子类提供。

抽象方法不能被声明成 final 和 static。

任何继承抽象类的子类必须实现父类的所有抽象方法,除非该子类也是抽象类。

如果一个类包含若干个抽象方法,那么该类必须声明为抽象类。抽象类可以不包含抽象方法。

抽象方法的声明以分号结尾,例如:public abstract sample();

1
2
3
4
5
6
7
8
9
10
public abstract class SuperClass{
+    abstract void m(); //抽象方法
+}
+
+class SubClass extends SuperClass{
+     //实现抽象方法
+      void m(){
+          .........
+      }
+}

synchronized 关键字声明的方法同一时间只能被一个线程访问。synchronized 修饰符可以应用于四个访问修饰符。

1
2
3
public synchronized void showDetails(){
+.......
+}

序列化的对象包含被 transient 修饰的实例变量时,java 虚拟机(JVM)跳过该特定的变量。
该修饰符包含在定义变量的语句中,用来预处理类和变量的数据类型。

1
2
public transient int limit = 55;   // 不会持久化
+public int b; // 持久化

volatile 修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。而且,当成员变量发生变化时,会强制线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

一个 volatile 对象引用可能是 null。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MyRunnable implements Runnable
+{
+    private volatile boolean active;
+    public void run()
+    {
+        active = true;
+        while (active) // 第一行
+        {
+            // 代码
+        }
+    }
+    public void stop()
+    {
+        active = false; // 第二行
+    }
+}
+/*
+通常情况下,在一个线程调用 run() 方法(在 Runnable 开启的线程),在另一个线程调用 stop() 方法。 如果 第一行 中缓冲区的 active 值被使用,那么在 第二行 的 active 值为 false 时循环不会停止。
+但是以上代码中我们使用了 volatile 修饰 active,所以该循环会停止。
+ */
+
+
+

Java 运算符

查看Java 运算符 +
+

Java 运算符方面与其他编程语言基本一致,故简单讲述。


Java 中基本的算数运算符为+,-,*,/,%,++,--


Java 中基本的关系运算符为==,!=,>,<,>=,<=

操作符 描述 例子
如果相对应位都是1,则结果为1,否则为0 (A&B),得到12,即0000 1100
| 如果相对应位都是 0,则结果为 0,否则为 1 (A | B)得到61,即 0011 1101
^ 如果相对应位值相同,则结果为0,否则为1 (A ^ B)得到49,即 0011 0001
按位取反运算符翻转操作数的每一位,即0变成1,1变成0。 (〜A)得到-61,即1100 0011
<<  按位左移运算符。左操作数按位左移右操作数指定的位数。 A << 2得到240,即 1111 0000
>>  按位右移运算符。左操作数按位右移右操作数指定的位数。 A >> 2得到15即 1111
>>>  按位右移补零操作符。左操作数的值按右操作数指定的位数右移,移动得到的空位以零填充。 A>>>2得到15即0000 1111


Java 支持的逻辑运算符有&&,||,!


Java 支持的赋值运算符有=,+=,-=,*=,/=,%=,<<=,>>=,&=,^=,|=

1
2
3
4
// 基本格式
+variable x = (expression) ? value if true : value if false
+// 实例
+b = (a == 1) ? 20 : 30;

1
2
3
4
5
// 基本格式
+( Object reference variable ) instanceof  (class/interface type)
+// 实例
+String name = "James";
+boolean result = name instanceof String; // 由于 name 是 String 类型,所以返回真
+
+
+

Java 循环结构

查看Java 循环结构 +
+

1
2
3
4
5
6
7
8
9
10
11
// 基本结构为
+while( 布尔表达式 ) {
+  //循环内容
+}
+
+// 实例
+while( x < 20 ) {
+         System.out.print("value of x : " + x );
+         x++;
+         System.out.print("\n");
+      }


do...while循环与while循环的区别在于do..while循环至少会执行一次。

1
2
3
4
// 基本格式
+do {
+       //代码语句
+}while(布尔表达式);

注意do...while结尾有;


for 循环语句基本格式与 C 语言完全一致。

1
2
3
4
5
// 实例
+for(int x = 10; x < 20; x = x+1) {
+         System.out.print("value of x : " + x );
+         System.out.print("\n");
+      }


通过 Java 增强 for 循环可快速便利数组元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 基本格式
+for(声明语句 : 表达式)
+{
+   //代码句子
+}
+/*
+声明语句:声明新的局部变量,该变量的类型必须和数组元素的类型匹配。其作用域限定在循环语句块,其值与此时数组元素的值相等。
+表达式:表达式是要访问的数组名,或者是返回值为数组的方法。
+*/
+
+for(int x : numbers ){
+         System.out.print( x );
+         System.out.print(",");
+      }


break关键字用于跳出当前的循环语句或者switch语句。


continue关键字用于直接进入下一次循环。

+
+
+

Java 条件语句

查看Java 条件语句 +
+

Java 中的if-else条件语句与 C 语言中完全相同,故只做简单示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void main(String args[]){
+      int x = 30;
+      int y = 10;
+
+      if( x == 30 ){
+         if( y == 10 ){
+             System.out.print("X = 30 and Y = 10");
+          }
+       }
+    }
+
+if( x == 10 ){
+         System.out.print("Value of X is 10");
+      }else if( x == 20 ){
+         System.out.print("Value of X is 20");
+      }else if( x == 30 ){
+         System.out.print("Value of X is 30");
+      }else{
+         System.out.print("这是 else 语句");
+      }
+
+
+

Java switch case 语句

查看Java switch case 语句 +
+

Java 中switch case语句与 C 语言完全相同,故只做简单示例。

switch case的语法格式如下

1
2
3
4
5
6
7
8
9
10
11
switch(expression){
+    case value :
+       //语句
+       break; //可选
+    case value :
+       //语句
+       break; //可选
+    //你可以有任意数量的case语句
+    default : //可选
+       //语句
+}

switch 语句中的变量类型可以是byte,short,int,char。从 Java SE 7 开始,支持字符串String类型,同时case标签必须是字符串常量或字面量。

+
+
+

Java Number&Math 类

查看Java Number&Math类 +
+

Java 为每个内置数据类型提供了对应的包装类,与Number类和Object类的关系如下图。

当内置数据类型被当作对象来使用时,编译器会把内置数据类型装箱为包装类,反之,编译器也可以把一个对象拆箱为内置类型。
举例如下

1
2
3
4
5
6
7
8
9
10
public class Test {
+
+   public static void main(String[] args) {
+      Integer x = 5;
+      x =  x + 10;
+      System.out.println(x);
+   }
+}
+// 运行结果为15
+// 当 x 被赋为整型值时,由于x是一个对象,所以编译器要对x进行装箱。然后,为了使x能进行加运算,所以要对x进行拆箱。


Java 的 Math 包含了用于执行基本数学运算的属性和方法,如初等指数、对数、平方根和三角函数。
Math 的方法都被定义为 static 形式,通过 Math 类可以在主函数中直接调用。

1
2
3
4
5
6
7
8
9
10
public class App {
+    public static void main(String[] args) {
+        System.out.println("90 度的正弦值:" + Math.sin(Math.PI / 2));
+        System.out.println("0度的余弦值:" + Math.cos(0));
+        System.out.println("60度的正切值:" + Math.tan(Math.PI / 3));
+        System.out.println("1的反正切值: " + Math.atan(1));
+        System.out.println("π/2的角度值:" + Math.toDegrees(Math.PI / 2));
+        System.out.println(Math.PI);
+    }
+}

  • Number 类的xxxValue()方法:将 Number 对象转换为 xxx 数据类型的值并返回。例:x.byteValue()
  • Number 类的compareTo()方法:该方法用于将 Number 对象与方法的参数进行比较,只能用于比较相同的两个数据类型。例:x.compareTo(3)。大于返回1,小于-1,等于0
  • Number 类的equals()方法:判断 Number 对象与参数是否相等。例:x.equals(y),相等返回true,否则返回false
  • Number 类的valueOf方法:返回一个 Number 对象指定的内置数据类型。例:Integer b = Integer.valueOf("444",16);444的十六进制表示。
  • parseInt():将字符串解析为 int 类型。
  • abs():返回参数的绝对值。
  • ceil():返回大于等于给定参数的最小整数,类型为双精度浮点型。
  • floor():返回小于等于给定参数的最大整数,类型为双精度浮点型。
  • rint():返回与参数最接近的整数。返回类型为 double。
  • round():表示四舍五入,数字+0.5后再向下取整。
  • min():返回两个参数中的最小值。
  • max():返回两个参数中的最大值。
  • exp():返回自然数底数 e 的参数次方。
  • log():返回参数的自然数底数的对数值。
  • pow():返回第一个参数的第二个参数次方。
  • sqrt():求参数的算数平方根。
  • sin():求指定 double 类型参数的正弦值。
  • cos():求指定 double 类型参数的余弦值。
  • tan():求指定 double 类型参数的正切值。
  • asin():求指定 double 类型参数的反正弦值。
  • acos():求指定 double 类型参数的反余弦值。
  • atan():求指定 double 类型参数的反正切值。
  • atan2():将笛卡尔坐标转变为极坐标,并返回极坐标的角度值。
  • toDegrees():将参数转化为角度。
  • toRadians():将角度转化为弧度。
  • random():返回一个随机数。
+
+
+

Java Character 类

查看 Java Character类 +
+

同样的,内置数据类型char同样有它的包装类Character,同样也有装箱和拆箱操作。

前面有反斜杠(\)的字符代表转义字符,它对编译器来说是有特殊含义的。
下面列表展示了 Java 的转义序列:

转义序列 描述
\t 在文中该处插入一个tab键
\b 在文中该处插入一个后退键
\n 在文中该处换行
\r 在文中该处插入回车
\f 在文中该处插入换页符
\' 在文中该处插入单引号
\" 在文中该处插入双引号
\\ 在文中该处插入反斜杠

  • isLetter():是否是一个字母。
  • isDigit():是否是一个数字字符。
  • isWhitespace():是否是一个空白字符。
  • isUpperCase():是否是大写字母。
  • isLowerCase():是否是小写字母。
  • toUpperCase():指定字母的大写形式。
  • toLowerCase():指定字母的小写形式。
  • toString():返回字符的字符串形式,字符串的长度为 1。
+
+
+

Java String 类

查看Java String 类 +
+

字符串广泛应用 在 Java 编程中,在 Java 中字符串属于对象,Java 提供了 String 类来创建和操作字符串。


String直接创建的字符串对象在公共池中,而对象创建的字符串对象在堆上:

1
2
3
4
5
String s1 = "Runoob";              // String 直接创建
+String s2 = "Runoob";              // String 直接创建
+String s3 = s1;                    // 相同引用
+String s4 = new String("Runoob");   // String 对象创建
+String s5 = new String("Runoob");   // String 对象创建

String类有 11 种构造方法,这些方法提供不同的参数来初始化字符串。
String类是不可更改的,一旦创建就不可修改。


用于获取有关对象的信息的方法称为访问器方法。
String 类的一个访问器方法是length()方法,它返回字符串对象包含的字符数。

1
2
3
4
5
6
7
public class StringDemo {
+    public static void main(String args[]) {
+        String site = "www.runoob.com";
+        int len = site.length();
+        System.out.println( "菜鸟教程网址长度 : " + len );
+   }
+}


String 类提供了连接两个字符串的方法:

1
string1.concat(string2);

更常用的是使用+操作符来连接字符串,如:

1
"Hello," + " runoob" + "!";


String类的format()方法能用来创建可复用的格式化字符串,而不是仅仅用来一次打印输出。

1
2
3
4
5
String fs;
+fs = String.format("浮点型变量的值为 " +
+                   "%f, 整型变量的值为 " +
+                   " %d, 字符串变量的值为 " +
+                   " %s", floatVar, intVar, stringVar);

String类方法较多,请查看api 文档

+
+
+

Java StringBuffer

查看Java StringBuffer +
+

当对字符串进行修改的时候,需要使用StringBufferStringBuilder类,二者的对象能被多次修改,并且不产生新的未使用对象。

由于StringBuilder相较于StringBuffer有速度优势,所以多数情况下建议使用StringBuilder类,然而StringBuilder的方法不是线程安全的(不能同步访问)。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class RunoobTest {
+    public static void main(String args[]) {
+        StringBuilder sb = new StringBuilder(10);
+        sb.append("Runoob..");
+        System.out.println(sb);
+        sb.append("!");
+        System.out.println(sb);
+        sb.insert(8, "Java");
+        System.out.println(sb);
+        sb.delete(5, 8);
+        System.out.println(sb);
+    }
+}

StringBuffer 类的方法:

+
+
+

Java 数组

查看Java 数组 +
+


Java 有两种声明数组变量的语法

1
2
3
4
// 首选
+dataType[] arrayRefVar;
+// 次选,只是符合C/C++的格式方便理解
+dataType arrayRefVar[];


Java 使用new操作符来创建数组,数组是通过索引来访问的,索引值从0~arrayRefVar.length-1

1
2
3
4
5
// 模板
+dataType[] arrayRefVar = new dataType[arraySize];
+
+// 或者直接赋值
+dataType[] arrayRefVar = {value0, value1, ..., valuek};


java.util.Arrays 类能方便地操作数组,它提供的所有方法都是静态的。
具有以下功能:

  • 给数组赋值:通过fill方法。
  • 对数组排序:通过sort方法,按升序。
  • 比较数组:通过equals方法比较数组中元素值是否相等。
  • 查找数组元素:通过binarySearch方法能对排序好的数组进行二分查找法操作。

Arrays 类还有很多方法,详情查看Java Api 文档

+
+
+

Java 日期时间

Java 日期时间 +
+

Date类提供了两个构造函数来实例化Date对象。

1
2
3
4
// 使用当前日期和时间来实例化对象
+Date date = new Date();
+// 接受第一个参数,该参数是从 1970 年 1 月 1 日起的毫秒数
+Date(long millisec);
序号 方法和描述
1 boolean after(Date date)
若当调用此方法的Date对象在指定日期之后返回true,否则返回false。
2 boolean before(Date date)
若当调用此方法的Date对象在指定日期之前返回true,否则返回false。
3 Object clone( )
返回此对象的副本。
4 int compareTo(Date date)
比较当调用此方法的Date对象和指定日期。两者相等时候返回0。调用对象在指定日期之前则返回负数。调用对象在指定日期之后则返回正数。
5 int compareTo(Object obj)
若obj是Date类型则操作等同于compareTo(Date) 。否则它抛出ClassCastException。
6 boolean equals(Object date)
当调用此方法的Date对象和指定日期相等时候返回true,否则返回false。
7 long getTime( )
返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。
8 int hashCode( )
 返回此对象的哈希码值。
9 void setTime(long time)
 
用自1970年1月1日00:00:00 GMT以后time毫秒数设置时间和日期。
10 String toString( )
把此 Date 对象转换为以下形式的 String: dow mon dd hh:mm:ss zzz yyyy 其中: dow 是一周中的某一天 (Sun, Mon, Tue, Wed, Thu, Fri, Sat)。

1
2
Date date = new Date();
+System.out.println(date.toString());


Java 使用以下三种方法来比较两个日期:

  • 使用getTime()方法获取两个日期(自 1970 年 1 月 1 日经历的毫秒数值),然后比较这两个值。
  • 使用方法before()after()equals()。例如,一个月的 12 号比 18 号早,则new Date(99, 2, 12).before(new Date (99, 2,18))返回 true。
  • 使用compareTo()方法,它是由Comparable接口定义的,Date类实现了这个接口。


SimpleDateFormat 是一个以语言环境敏感的方式来格式化和分析日期的类。SimpleDateFormat 允许你选择任何用户自定义日期时间格式来运行。例如:

1
2
3
4
Date dNow = new Date( );
+SimpleDateFormat ft = new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
+System.out.println("当前时间为: " + ft.format(dNow));
+// 运行结果为:当前时间为: 2018-09-06 10:16:34

有的格式大写,有的格式小写,例如MM是月份,mm是分;HH是 24 小时制,而hh是 12 小时制。


时间模式字符串用来指定时间格式。在此模式中,所有的 ASCII 字母被保留为模式字母,定义如下:


Calendar类是一个抽象类,在实际使用时实现特定的子类的对象,创建对象的过程对程序员来说是透明的,只需要使用getInstance方法创建即可。

1
2
3
4
Calendar c = Calendar.getInstance();//默认是当前日期
+//创建一个代表2009年6月12日的Calendar对象
+Calendar c1 = Calendar.getInstance();
+c1.set(2009, 6 - 1, 12);


Calendar 类实现了公历日历,GregorianCalendar 是 Calendar 类的一个具体实现。
Calendar 的 getInstance()方法返回一个默认用当前的语言环境和时区初始化的 GregorianCalendar 对象。GregorianCalendar 定义了两个字段:AD 和 BC。这是代表公历定义的两个时代。

+
+
+

Java 正则表达式

Java 正则表达式 +
+

正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个”规则字符串”,这个”规则字符串”用来表达对字符串的一种过滤逻辑。
给定一个正则表达式和另一个字符串,我们可以达到如下的目的:

  • 给定的字符串是否符合正则表达式的过滤逻辑(称作”匹配”)。
  • 可以通过正则表达式,从字符串中获取我们想要的特定部分。

正则表达式的特点是:

  • 灵活性、逻辑性和功能性非常的强。
  • 可以迅速地用极简单的方式达到字符串的复杂控制。

Java 正则表达式的知识点较为繁琐,详情见Java 正则表达式

+
+
+

Java 方法

查看Java 方法 +
+



可变参数,即参数的个数可变。方法的可变参数的声明如下所示:

1
typeName... parameterName

在方法声明中,在指定参数类型后加一个省略号(…) 。
一个方法中只能指定一个可变参数,它必须是方法的最后一个参数。任何普通的参数必须在它之前声明。

+
+
+

Java Stream, File, IO

查看Java Stream +
+

Java.io 包几乎包含了所有操作输入、输出需要的类。所有这些流类代表了输入源和输出目标。
Java.io 包中的流支持很多种格式,比如:基本类型、对象、本地化字符集等等。
一个流可以理解为一个数据的序列。输入流表示从一个源读取数据,输出流表示向一个目标写数据。
Java 为 I/O 提供了强大的而灵活的支持,使其更广泛地应用到文件传输和网络编程中。


Java 的控制台输入由System.in完成。
为了获得一个绑定到控制台的字符流,你可以把System.in包装在一个BufferedReader对象中来创建一个字符流。
下面是创建BufferedReader的基本语法:

1
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

BufferedReader 对象创建后,我们便可以使用 read() 方法从控制台读取一个字符,或者用 readLine() 方法读取一个字符串。

从 BufferedReader 对象读取一个字符要使用 read() 方法,它的语法如下:

1
int read( ) throws IOException

每次调用 read() 方法,它从输入流读取一个字符并把该字符作为整数值返回。 当流结束的时候返回 -1。该方法抛出 IOException。


从标准输入读取一个字符串需要使用BufferedReaderreadLine()方法。
基本格式:

1
String readLine( ) throws IOException

基本示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//使用 BufferedReader 在控制台读取字符
+import java.io.*;
+
+public class BRReadLines {
+    public static void main(String[] args) throws IOException {
+        // 使用 System.in 创建 BufferedReader
+        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
+        String str;
+        System.out.println("Enter lines of text.");
+        System.out.println("Enter 'end' to quit.");
+        do {
+            str = br.readLine();
+            System.out.println(str);
+        } while (!str.equals("end"));
+    }
+}


在此前已经介绍过,控制台的输出由print()println()完成。这些方法都由类PrintStream定义,System.out是该类对象的一个引用。
PrintStream继承了OutputStream类,并且实现了方法write()。这样,write()也可以用来往控制台写操作。
PrintStream定义write()的最简单格式如下所示:

1
void write(int byteval)

该方法将 byteval 的低八位字节写到流中。


如前所述,一个流被定义为一个数据序列。输入流用于从源读取数据,输出流用于向目标写数据。
下图是一个描述输入流和输出流的类层次图。


两种创建方法:

1
2
3
4
5
6
7
8
9
InputStream f = new FileInputStream("C:/java/hello");
+
+File f = new File("C:/java/hello");
+InputStream in = new FileInputStream(f);
+
+OutputStream f = new FileOutputStream("C:/java/hello")
+
+File f = new File("C:/java/hello");
+OutputStream fOut = new FileOutputStream(f);

使用实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//文件名 :fileStreamTest2.java
+import java.io.*;
+
+public class fileStreamTest2 {
+    public static void main(String[] args) throws IOException {
+
+        File f = new File("a.txt");
+        FileOutputStream fop = new FileOutputStream(f);
+        // 构建FileOutputStream对象,文件不存在会自动新建
+
+        OutputStreamWriter writer = new OutputStreamWriter(fop, "UTF-8");
+        // 构建OutputStreamWriter对象,参数可以指定编码,默认为操作系统默认编码,windows上是gbk
+
+        writer.append("中文输入");
+        // 写入到缓冲区
+
+        writer.append("\r\n");
+        // 换行
+
+        writer.append("English");
+        // 刷新缓存冲,写入到文件,如果下面已经没有写入的内容了,直接close也会写入
+
+        writer.close();
+        // 关闭写入流,同时会把缓冲区内容写入文件,所以上面的注释掉
+
+        fop.close();
+        // 关闭输出流,释放系统资源
+
+        FileInputStream fip = new FileInputStream(f);
+        // 构建FileInputStream对象
+
+        InputStreamReader reader = new InputStreamReader(fip, "UTF-8");
+        // 构建InputStreamReader对象,编码与写入相同
+
+        StringBuffer sb = new StringBuffer();
+        while (reader.ready()) {
+            sb.append((char) reader.read());
+            // 转成char加到StringBuffer对象中
+        }
+        System.out.println(sb.toString());
+        reader.close();
+        // 关闭读取流
+
+        fip.close();
+        // 关闭输入流,释放系统资源
+
+    }
+}
+
+
+

Java Scanner 类

查看Java Scanner 类 +
+

使用next方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.Scanner;
+
+public class ScannerDemo {
+    public static void main(String[] args) {
+        Scanner scan = new Scanner(System.in);
+        // 从键盘接收数据
+
+        // next方式接收字符串
+        System.out.println("next方式接收:");
+        // 判断是否还有输入
+        if (scan.hasNext()) {
+            String str1 = scan.next();
+            System.out.println("输入的数据为:" + str1);
+        }
+        scan.close();
+    }
+}

输出结果:

1
2
3
next方式接收:
+runoob com
+输入的数据为:runoob

使用nextLine方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.Scanner;
+
+public class ScannerDemo {
+    public static void main(String[] args) {
+        Scanner scan = new Scanner(System.in);
+        // 从键盘接收数据
+
+        // nextLine方式接收字符串
+        System.out.println("nextLine方式接收:");
+        // 判断是否还有输入
+        if (scan.hasNextLine()) {
+            String str2 = scan.nextLine();
+            System.out.println("输入的数据为:" + str2);
+        }
+        scan.close();
+    }
+}

输出结果为:

1
2
3
nextLine方式接收:
+runoob com
+输入的数据为:runoob com
+
+
+

Java 异常处理

查看Java 异常处理 +
+



实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 文件名 : ExcepTest.java
+import java.io.*;
+public class ExcepTest{
+
+   public static void main(String args[]){
+      try{
+         int a[] = new int[2];
+         System.out.println("Access element three :" + a[3]);
+      }catch(ArrayIndexOutOfBoundsException e){
+         System.out.println("Exception thrown  :" + e);
+      }
+      System.out.println("Out of the block");
+   }
+}

1
2
3
4
5
6
7
8
9
10
import java.io.*;
+public class className
+{
+  public void deposit(double amount) throws RemoteException
+  {
+    // Method implementation
+    throw new RemoteException();
+  }
+  //Remainder of class definition
+}
+
+
+
文章作者: 希亚的西红柿
文章链接: http://blog.refrain.site/posts/8d55d49a/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
+ + + + \ No newline at end of file diff --git a/posts/9614c7d1/index.html b/posts/9614c7d1/index.html new file mode 100644 index 000000000..ff4a1f252 --- /dev/null +++ b/posts/9614c7d1/index.html @@ -0,0 +1,908 @@ +Vite基础知识总结 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + + + + +

Vite基础知识总结

本文介绍了 Vite 的一些常用功能笔记,方便后续记忆与复习。总有种写这个不如直接看文档的感觉QAQ

+
+

Vite 工作基本原理

    +
  1. 传统基于jsbundler based构建工具的慢启动以及热更新缓慢的问题。

    +
      +
    • 原因:
        +
      • 开发服务器需要将所有模块全部打包后才能在浏览器中呈现。
      • +
      • 原生js的性能问题。
      • +
      +
    • +
    • 总结:bundler-based在大型项目下性能低下的原因在于,其开发服务器原理必须先打包才能使用,即便是HMR也是离不开打包这一环节,随着项目体积或者模块体积的增大,打包这一环节会耗费大量的时间。
    • +
    +
  2. +
  3. Vite 的解决方案

    +
      +
    • 将项目的代码分为两类:dependenciessource code
        +
      • 依赖:即node_modules文件夹下的文件,不常修改的,项目的依赖文件,vite采用使用go编写的esbuild来打包这些文件,提升开发体验。
      • +
      • 源码:通常包括tstsxscss等文件,对这些源码的提供是通过原生的ESM来实现的,vite只需要转换并按需提供代码,让浏览器接管了bundler的工作,实现了源码文件的按需加载。
      • +
      +
    • +
    • Vite 中 HMR 的更新方式以及浏览器缓存解决方案

      +
        +
      • 基于 ESM 的 HMR 解决方案:Vite 只需要将被修改的模块与其最近的 HMR 边界之间的链路失活,再次请求相应的模块文件(PS:通过fetch实现)。
      • +
      • 使用 HTTP 请求头来实现重新加载页面的相关文件的缓存设置,使用响应头:Etag与 HTTP 状态码304 Not Modified来判断源码文件是否更新,对依赖文件设置响应头Cache-Control: max-age=31536000,immutable进行强制缓存。
      • +
      +
    • +
    +

    尽管原生 ESM 现在得到了广泛支持,但由于嵌套导入会导致额外的网络往返,在生产环境中发布未打包的 ESM 仍然效率低下(即使使用 HTTP/2)。为了在生产环境中获得最佳的加载性能,最好还是将代码进行 tree-shaking、懒加载和 chunk 分割(以获得更好的缓存)。

    +
    +
    了解Vite HMR基本原理 +
    +

    Vite HMR 是通过 动态importWebSocket 实现的。Vite HMR 的原理是这样的:

    • Vitenode 端使用 chokidar 监听文件的变化,当文件发生变化时,会触发 HMR 事件,并将变化的文件名和模块 ID 发送给客户端。
    • Vite 在浏览器端使用 WebSocket 与服务端建立连接,接收 HMR 事件。当收到 HMR 事件时,会根据文件名和模块 ID 找到对应的模块,并使用动态import重新请求该模块的代码,返回更新回调。
    • Vite 在浏览器端使用原生的 ES Module 功能加载模块,当模块的代码更新时,会触发模块的更新函数(执行回调),实现热替换。
    +
    +
    +
    详细了解协商缓存 +
    +

    举个例子,假设客户端第一次请求一个图片文件,服务器返回 200 OK 的状态码,以及图片的内容,同时在响应头中设置了 Last-Modified: Wed, 10 Nov 2021 07:00:51 GMTEtag: "1234567890",表示该图片的最后修改时间和唯一标识。客户端会将这些信息和图片一起缓存到本地。当客户端再次请求该图片时,会在请求头中添加 If-Modified-Since: Wed, 10 Nov 2021 07:00:51 GMTIf-None-Match: "1234567890",表示只有当图片在这个时间之后被修改过,或者图片的标识发生变化时,才需要重新获取图片。如果服务器检查发现图片没有变化,就会返回 304 Not Modified 的状态码,不会返回图片的内容,客户端就可以直接使用本地缓存的图片。如果服务器检查发现图片有变化,就会返回 200 OK 的状态码,以及新的图片内容,客户端就会更新本地缓存,并显示新的图片。

    +
    +
    +
  4. +
+

Vite 快速上手

基本命令

1
2
pnpm create vite
+pnpm create vite my-vue-app --template vue
+

社区维护的模板awesome-vite

+
+

Vite 的特性

NPM 依赖解析和预构建

浏览器不支持类似下面的bare module imports

+
1
import { someMethod } from "my-dep";
+

为了解决这个问题Vite会完成两个工作

+
+
    +
  • pre-bundleVite会使用esbuild对这种类似的依赖进行打包。
  • +
  • 重写url:将url重写为/node_modules/.vite/deps/my-dep.js?v=f3sf2ebd这种格式方便浏览器识别引用(?v=f3sf2ebd主要用来标识模块的版本,防止应依赖缓存导致的模块无法更新)。
  • +
+

客户端类型

由于Vite的默认的类型定义为Node.js环境下的API,要补充到客户端的应用代码环境需要添加一个d.ts声明文件

+
1
/// <reference types="vite/client" />
+
+
    +
  • 资源导入 (例如:导入一个 .svg 文件)
  • +
  • import.meta.envVite 注入的环境变量的类型定义
  • +
  • import.meta.hot 上的 HMR API 类型定义
  • +
+

需要覆盖默认的类型定义需要像下面这样做

+
    +
  1. vite-env-override.d.ts

    +
    1
    2
    3
    4
    declare module "*.svg" {
    +  const content: React.FC<React.SVGProps<SVGElement>>;
    +  export default content;
    +}
    +
  2. +
  3. env.d.ts

    +
    1
    2
    /// <reference types="./vite-env-override.d.ts" />
    +/// <reference types="vite/client" />
    +
  4. +
+

JSON 导入

json文件使用解构赋值能有效帮助tree-shaking

+
1
2
3
4
// 导入整个对象
+import json from "./example.json";
+// 对一个根字段使用具名导入 —— 有效帮助 treeshaking!
+import { field } from "./example.json";
+

Glob 导入

    +
  1. Vite 支持使用特殊的 import.meta.glob 函数从文件系统导入多个模块:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    const modules = import.meta.glob("./dir/*.js");
    +
    +// 转译后
    +// vite 生成的代码
    +const modules = {
    +  "./dir/foo.js": () => import("./dir/foo.js"),
    +  "./dir/bar.js": () => import("./dir/bar.js"),
    +};
    +
    +// 遍历导入后的模块
    +for (const path in modules) {
    +  modules[path]().then((mod) => {
    +    console.log(path, mod);
    +  });
    +}
    +

    匹配到的文件默认是懒加载的,通过动态导入实现,并会在构建时分离为独立的 chunk。如果你倾向于直接引入所有的模块(例如依赖于这些模块中的副作用首先被应用),你可以传入 { eager: true } 作为第二个参数

    +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    const modules = import.meta.glob("./dir/*.js", { eager: true });
    +// 转译后
    +// vite 生成的代码
    +import * as __glob__0_0 from "./dir/foo.js";
    +import * as __glob__0_1 from "./dir/bar.js";
    +const modules = {
    +  "./dir/foo.js": __glob__0_0,
    +  "./dir/bar.js": __glob__0_1,
    +};
    +
  2. +
  3. Glob 导入形式

    +

    import.meta.glob 都支持以字符串形式导入文件,类似于 以字符串形式导入资源。

    +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const modules = import.meta.glob("./dir/*.js", {
    +  as: "raw",
    +  eager: true,
    +});
    +
    +// 转换后
    +// code produced by vite(代码由 vite 输出)
    +const modules = {
    +  "./dir/foo.js": 'export default "foo"\n',
    +  "./dir/bar.js": 'export default "bar"\n',
    +};
    +
  4. +
  5. 多个匹配模式

    +
    1
    const modules = import.meta.glob(["./dir/*.js", "./another/*.js"]);
    +
  6. +
  7. 反面匹配模式

    +

    同样也支持反面 glob 匹配模式(以 ! 作为前缀)。若要忽略结果中的一些文件,你可以添加“排除匹配模式”作为第一个参数:

    +
    +
    1
    2
    3
    4
    5
    6
    7
    const modules = import.meta.glob(["./dir/*.js", "!**/bar.js"]);
    +
    +// 转译后
    +// vite 生成的代码
    +const modules = {
    +  "./dir/foo.js": () => import("./dir/foo.js"),
    +};
    +
  8. +
  9. 具名导入

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    const modules = import.meta.glob("./dir/*.js", { import: "setup" });
    +// 转译后的代码
    +const modules = {
    +  "./dir/foo.js": () => import("./dir/foo.js").then((m) => m.setup),
    +  "./dir/bar.js": () => import("./dir/bar.js").then((m) => m.setup),
    +};
    +// eager 同时存在的时候进行 tree-shaking
    +const modules = import.meta.glob("./dir/*.js", {
    +  import: "setup",
    +  eager: true,
    +});
    +// 转译后
    +// vite 生成的代码
    +import { setup as __glob__0_0 } from "./dir/foo.js";
    +import { setup as __glob__0_1 } from "./dir/bar.js";
    +const modules = {
    +  "./dir/foo.js": __glob__0_0,
    +  "./dir/bar.js": __glob__0_1,
    +};
    +// 设置 import 为 default 可以加载默认导出
    +const modules = import.meta.glob("./dir/*.js", {
    +  import: "default",
    +  eager: true,
    +});
    +
    +// vite 生成的代码
    +import __glob__0_0 from "./dir/foo.js";
    +import __glob__0_1 from "./dir/bar.js";
    +const modules = {
    +  "./dir/foo.js": __glob__0_0,
    +  "./dir/bar.js": __glob__0_1,
    +};
    +
  10. +
  11. 自定义查询

    +

    你也可以使用 query 选项来提供对导入的自定义查询,以供其他插件使用。

    +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    const modules = import.meta.glob("./dir/*.js", {
    +  query: { foo: "bar", bar: true },
    +});
    +// vite 生成的代码
    +const modules = {
    +  "./dir/foo.js": () => import("./dir/foo.js?foo=bar&bar=true"),
    +  "./dir/bar.js": () => import("./dir/bar.js?foo=bar&bar=true"),
    +};
    +

    你还需注意,所有 import.meta.glob 的参数都必须以字面量传入。你 不 可以在其中使用变量或表达式。

    +
    +
  12. +
+

动态导入

1
const module = await import(`./dir/${file}.js`);
+

注意变量仅代表一层深的文件名。如果 filefoo/bar,导入将会失败。对于更进阶的使用详情,你可以使用 glob 导入 功能。

+

静态资源处理

显式 URL 引入

1
2
import workletURL from "extra-scalloped-border/worklet.js?url";
+CSS.paintWorklet.addModule(workletURL);
+

将资源引入为字符串

1
import shaderString from "./shader.glsl?raw";
+

公共基础路径

+

公共基础路径是指你的项目在部署时的根路径,它会影响到你的静态资源的引用和加载。例如,如果你的项目是部署在 https://example.com/my-app/ 下,那么你的公共基础路径就是 /my-app/

+
+

配置 base 项或者配置启动参数 vite build --base=/my/public/path/

+

环境变量与模式

环境变量

Vite 在一个特殊的 import.meta.env 对象上暴露环境变量。这里有一些在所有情况下都可以使用的内建变量:

+
    +
  • import.meta.env.MODE: {string} 应用运行的模式。

    +
  • +
  • import.meta.env.BASE_URL: {string} 部署应用时的基本 URL。他由 base 配置项决定。

    +
  • +
  • import.meta.env.PROD: {boolean} 应用是否运行在生产环境。

    +
  • +
  • import.meta.env.DEV: {boolean} 应用是否运行在开发环境 (永远与 import.meta.env.PROD 相反)。

    +
  • +
  • import.meta.env.SSR: {boolean} 应用是否运行在 server 上。

    +
  • +
+

.env文件

Vite 使用 dotenv 从你的 环境目录 中的下列文件加载额外的环境变量:

+
1
2
3
4
.env                # 所有情况下都会加载
+.env.local          # 所有情况下都会加载,但会被 git 忽略
+.env.[mode]         # 只在指定模式下加载
+.env.[mode].local   # 只在指定模式下加载,但会被 git 忽略
+

为了防止意外地将一些环境变量泄漏到客户端,只有以VITE\_ 为前缀的变量才会暴露给经过 vite 处理的代码。例如下面这些环境变量:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
VITE_SOME_KEY = 123;
+DB_PASSWORD = foobar;
+
+console.log(import.meta.env.VITE_SOME_KEY); // 123
+console.log(import.meta.env.DB_PASSWORD); // undefined
+// 还可以自定义前缀
+// vite.config.js
+export default defineConfig({
+  envPrefix: ["VITE_", "DZ_"], // 只暴露以VITE_或DZ_为前缀的环境变量
+});
+// .env
+VITE_API_URL = "https://example.com/api";
+DZ_APP_TITLE = "My App";
+// 客户端源码
+console.log(import.meta.env.VITE_API_URL); // https://example.com/api
+console.log(import.meta.env.DZ_APP_TITLE); // My App
+

HTML 环境变量替换

Vite 还支持在 HTML 文件中替换环境变量。import.meta.env 中的任何属性都可以通过特殊的 %ENV_NAME% 语法在 HTML 文件中使用:

+
1
2
<h1>Vite is running in %MODE%</h1>
+<p>Using data from %VITE_API_URL%</p>
+

CSS 变量的导入

css.preprocessorOptions
类型: Record<string, object>
指定传递给 CSS 预处理器的选项。文件扩展名用作选项的键。每个预处理器支持的选项可以在它们各自的文档中找到:

+
    +
  • sass/scss - 选项。
  • +
  • less - 选项。
  • +
  • styl/stylus - 仅支持 define,可以作为对象传递。
  • +
+

所有预处理器选项还支持 additionalData 选项,可以用于为每个样式内容注入额外代码。请注意,如果注入的是实际的样式而不仅仅是变量时,那么这些样式将会在最终的打包产物中重复出现。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export default defineConfig({
+  css: {
+    preprocessorOptions: {
+      scss: {
+        additionalData: `$injectedColor: orange;`,
+      },
+      less: {
+        math: "parens-division",
+      },
+      styl: {
+        define: {
+          $specialColor: new stylus.nodes.RGBA(51, 197, 255, 1),
+        },
+      },
+    },
+  },
+});
+

给 Vite 设置 proxy 代理转发

vite proxy代理中的proxyoptions的对象的配置是用来设置代理服务器的规则和行为的。proxyoptions的对象可以包含以下的属性:

+
    +
  • target: 一个字符串,表示要转发请求的目标服务器的地址,必须以http://https://开头,例如'http://localhost:3000'
  • +
  • changeOrigin: 一个布尔值,表示是否修改请求头中的origin字段,使其与目标服务器的域名一致。这样可以避免一些基于域名的虚拟主机或者跨域检查的问题。默认为false
  • +
  • rewrite: 一个函数,表示是否重写请求的路径,去掉或添加一些前缀。这样可以让后端服务器正确地处理请求,而不会出现404或者其他错误。函数的参数是请求的路径,返回值是重写后的路径,例如path => path.replace(/^\/api/, '')
  • +
  • ws: 一个布尔值,表示是否支持WebSocket代理。默认为false
  • +
  • secure: 一个布尔值,表示是否验证目标服务器的SSL证书。默认为true
  • +
  • headers: 一个对象,表示要添加或修改的请求头。对象的键是请求头的名称,值是请求头的内容,例如{'User-Agent': 'Mozilla/5.0'}
  • +
  • followRedirects: 一个布尔值,表示是否跟随目标服务器的重定向。默认为false
  • +
  • timeout: 一个数字,表示代理请求的超时时间,单位是毫秒。默认为0,表示无限制。
  • +
  • logLevel: 一个字符串,表示代理服务器的日志级别,可以是'silent','error','warn','info','debug','verbose'之一。默认为'silent',表示不输出任何日志。
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
export default defineConfig({
+  server: {
+    proxy: {
+      // 字符串简写写法:http://localhost:5173/foo -> http://localhost:4567/foo
+      "/foo": "http://localhost:4567",
+      // 带选项写法:http://localhost:5173/api/bar -> http://jsonplaceholder.typicode.com/bar
+      "/api": {
+        target: "http://jsonplaceholder.typicode.com",
+        changeOrigin: true,
+        rewrite: (path) => path.replace(/^\/api/, ""),
+      },
+      // 正则表达式写法:http://localhost:5173/fallback/ -> http://jsonplaceholder.typicode.com/
+      "^/fallback/.*": {
+        target: "http://jsonplaceholder.typicode.com",
+        changeOrigin: true,
+        rewrite: (path) => path.replace(/^\/fallback/, ""),
+      },
+      // 使用 proxy 实例
+      "/api": {
+        target: "http://jsonplaceholder.typicode.com",
+        changeOrigin: true,
+        configure: (proxy, options) => {
+          // proxy 是 'http-proxy' 的实例
+        },
+      },
+      // 代理 websockets 或 socket.io 写法:ws://localhost:5173/socket.io -> ws://localhost:5174/socket.io
+      "/socket.io": {
+        target: "ws://localhost:5174",
+        ws: true,
+      },
+    },
+  },
+});
+

请注意,如果使用了非相对的 基础路径 base,则必须在每个 key 值前加上该 base

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// vite.config.js
+export default {
+  base: "/app/",
+  server: {
+    proxy: {
+      // 以 /app/api 开头的请求,会被代理到 http://localhost:3000/api
+      "/app/api": {
+        target: "http://localhost:3000",
+        changeOrigin: true,
+        rewrite: (path) => path.replace(/^\/app/, ""),
+      },
+      // 以 /app/ws 开头的请求,会被代理到 ws://localhost:8080/ws
+      "/app/ws": {
+        target: "ws://localhost:8080",
+        ws: true,
+      },
+      // 以 /app/foo 开头的请求,会被代理到 http://example.com/foo
+      "/app/foo": "http://example.com/foo",
+      // 使用正则表达式匹配请求路径,以 /app/bar 开头的请求,会被代理到 http://localhost:8000/bar
+      "^/app/bar": {
+        target: "http://localhost:8000",
+        changeOrigin: true,
+      },
+    },
+  },
+};
+

跨域请求是浏览器自己的行为,服务器与服务器之间的请求不存在跨域行为,所谓前端开发工具设置proxy代理来实现避免同源策略的干扰的原理就是,设置一个本地服务器,将请求发往本地服务器,本地服务器再转发给远程服务器,从而避开跨域请求。核心为:服务器与服务器之间的请求不受浏览器同源策略的限制

+
+
文章作者: 希亚的西红柿
文章链接: http://blog.refrain.site/posts/9614c7d1/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
+ + + + \ No newline at end of file diff --git a/posts/a1605017/index.html b/posts/a1605017/index.html new file mode 100644 index 000000000..c531a4e66 --- /dev/null +++ b/posts/a1605017/index.html @@ -0,0 +1,3030 @@ +React笔记 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + + + + +

React笔记

基础

语法基础与规范

    +
  1. JSX注意事项。

    +
      +
    • JSX必须有一个根节点,如果没有,可以用幽灵节点<></>代替。
    • +
    • 所有标签必须闭合,成对闭合或者自闭和都可以。
    • +
    • JSX采用小驼峰命名法,对于class要转化成classNamefor需要转化成htmlFor,以防止js关键字冲突。
    • +
    • JSX支持换行,如果需要换行需要加上(),防止bug出现。
    • +
    • 注释在模板中的书写方式:{/* 苏苏苏 */}
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      import "./app.css";
      +const showTitle = true;
      +function App() {
      +  return (
      +    <div className="App">
      +      {/* 苏苏苏 */}
      +      <div className={showTitle ? "title" : ""}>this is a div</div>
      +    </div>
      +  );
      +}
      +export default App;
      +
    • +
    +
  2. +
  3. jsx的列表渲染

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // 来个列表
    +const songs = [
    +  { id: 1, name: "痴心绝对" },
    +  { id: 2, name: "像我这样的人" },
    +  { id: 3, name: "南山南" },
    +];
    +
    +function App() {
    +  return (
    +    <div className="App">
    +      <ul>
    +        {songs.map((item, index) => (
    +          <li key={index}>{item.name}</li>
    +        ))}
    +      </ul>
    +    </div>
    +  );
    +}
    +
    +export default App;
    +
  4. +
  5. React函数组件

    +
      +
    • 函数组件首字母必须大写
    • +
    • 函数组件必须有返回值,若不需要渲染内容则返回null
    • +
    • 使用函数组件可以自闭和也可以成对闭合。
    • +
    +
  6. +
  7. 注意在React中的样式绑定只能使用驼峰规则,这点和Vue不一样,vue可以使用kebab-cased命名方式。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 这种写法才是正确的
    +export function TodoList() {
    +  return (
    +    <ul
    +      style={{
    +        backgroundColor: "black",
    +        color: "pink",
    +      }}
    +    >
    +      <li>Improve the videophone</li>
    +      <li>Prepare aeronautics lectures</li>
    +      <li>Work on the alcohol-fuelled engine</li>
    +    </ul>
    +  );
    +}
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <script setup>
    +import { ref, reactive } from "vue";
    +// react 不能使用这样的方式,这样的方式会发出警告
    +const styleObject = reactive({
    +  color: "red",
    +  "font-size": "20px",
    +});
    +</script>
    +
    +<template>
    +  <input v-model="msg" :style="styleObject" />
    +</template>
    +
  8. +
  9. React的纯函数概念。

    +
      +
    • 一个组件必须是纯粹的,就意味着:
        +
      • 只负责自己的任务。 它不会更改在该函数调用前就已存在的对象或变量。
      • +
      • 输入相同,则输出相同。 给定相同的输入,组件应该总是返回相同的 JSX
      • +
      +
    • +
    • 渲染随时可能发生,因此组件不应依赖于彼此的渲染顺序。
    • +
    • 你不应该改变任何用于组件渲染的输入。这包括 propsstatecontext。通过 “设置” state 来更新界面,而不要改变预先存在的对象。
    • +
    • 努力在你返回的 JSX 中表达你的组件逻辑。当你需要“改变事物”时,你通常希望在事件处理程序中进行。作为最后的手段,你可以使用 useEffect
    • +
    • 编写纯函数需要一些练习,但它充分释放了 React 范式的能力。
    • +
    +
  10. +
+

状态(state)

    +
  1. useState特征

    +
      +
    1. 每个渲染(以及其中的函数)始终“看到”的是 React 提供给这个 渲染的 state 快照。
    2. +
    3. 过去创建的事件处理函数拥有的是创建它们的那次渲染中的 state 值。
    4. +
    5. setState推入队列后遍历

      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      export default function Counter() {
      +  const [number, setNumber] = useState(0);
      +
      +  return (
      +    <>
      +      <h1>{number}</h1>
      +      <button
      +        onClick={() => {
      +          setNumber((n) => n + 1);
      +          setNumber((n) => n + 1);
      +          setNumber((n) => n + 1);
      +        }}
      +      >
      +        +3
      +      </button>
      +    </>
      +  );
      +}
      +
      +

      当你在下次渲染期间调用 useState 时,React 会遍历队列。之前的 number state 的值是 0,所以这就是 React 作为参数 n 传递给第一个更新函数的值。然后 React 会获取你上一个更新函数的返回值,并将其作为 n 传递给下一个更新函数,以此类推…

      +
      +

      注意永远不要直接修改state!!!

      +
      +
    6. +
    +
  2. +
  3. Immer简化react对象的修改

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    import { useImmer } from "use-immer";
    +
    +export default function Form() {
    +  const [person, updatePerson] = useImmer({
    +    name: "Niki de Saint Phalle",
    +    artwork: {
    +      title: "Blue Nana",
    +      city: "Hamburg",
    +      image: "https://i.imgur.com/Sd1AgUOm.jpg",
    +    },
    +  });
    +
    +  function handleNameChange(e) {
    +    updatePerson((draft) => {
    +      draft.name = e.target.value;
    +    });
    +  }
    +
    +  function handleTitleChange(e) {
    +    updatePerson((draft) => {
    +      draft.artwork.title = e.target.value;
    +    });
    +  }
    +
    +  function handleCityChange(e) {
    +    updatePerson((draft) => {
    +      draft.artwork.city = e.target.value;
    +    });
    +  }
    +
    +  function handleImageChange(e) {
    +    updatePerson((draft) => {
    +      draft.artwork.image = e.target.value;
    +    });
    +  }
    +
    +  return (
    +    <>
    +      <label>
    +        Name:
    +        <input value={person.name} onChange={handleNameChange} />
    +      </label>
    +      <label>
    +        Title:
    +        <input value={person.artwork.title} onChange={handleTitleChange} />
    +      </label>
    +      <label>
    +        City:
    +        <input value={person.artwork.city} onChange={handleCityChange} />
    +      </label>
    +      <label>
    +        Image:
    +        <input value={person.artwork.image} onChange={handleImageChange} />
    +      </label>
    +      <p>
    +        <i>{person.artwork.title}</i>
    +        {" by "}
    +        {person.name}
    +        <br />
    +        (located in {person.artwork.city})
    +      </p>
    +      <img src={person.artwork.image} alt={person.artwork.title} />
    +    </>
    +  );
    +}
    +
  4. +
  5. 使用Immer编写数组更简单的逻辑

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    import { useState } from "react";
    +import { useImmer } from "use-immer";
    +
    +let nextId = 3;
    +const initialList = [
    +  { id: 0, title: "Big Bellies", seen: false },
    +  { id: 1, title: "Lunar Landscape", seen: false },
    +  { id: 2, title: "Terracotta Army", seen: true },
    +];
    +
    +export default function BucketList() {
    +  const [myList, updateMyList] = useImmer(initialList);
    +  const [yourList, updateYourList] = useImmer(initialList);
    +
    +  function handleToggleMyList(id, nextSeen) {
    +    updateMyList((draft) => {
    +      const artwork = draft.find((a) => a.id === id);
    +      artwork.seen = nextSeen;
    +    });
    +  }
    +
    +  function handleToggleYourList(artworkId, nextSeen) {
    +    updateYourList((draft) => {
    +      const artwork = draft.find((a) => a.id === artworkId);
    +      artwork.seen = nextSeen;
    +    });
    +  }
    +
    +  return (
    +    <>
    +      <h1>艺术愿望清单</h1>
    +      <h2>我想看的艺术清单:</h2>
    +      <ItemList artworks={myList} onToggle={handleToggleMyList} />
    +      <h2>你想看的艺术清单:</h2>
    +      <ItemList artworks={yourList} onToggle={handleToggleYourList} />
    +    </>
    +  );
    +}
    +
    +function ItemList({ artworks, onToggle }) {
    +  return (
    +    <ul>
    +      {artworks.map((artwork) => (
    +        <li key={artwork.id}>
    +          <label>
    +            <input
    +              type="checkbox"
    +              checked={artwork.seen}
    +              onChange={(e) => {
    +                onToggle(artwork.id, e.target.checked);
    +              }}
    +            />
    +            {artwork.title}
    +          </label>
    +        </li>
    +      ))}
    +    </ul>
    +  );
    +}
    +
  6. +
  7. State 不依赖于特定的函数调用或在代码中的位置,它的作用域“只限于”屏幕上的某块特定区域。你渲染了两个 <Gallery /> 组件,所以它们的 state 是分别存储的。
    还要注意 Page 组件“不知道”关于 Gallery state 的任何信息,甚至不知道它是否有任何 state。与 props 不同,state 完全私有于声明它的组件。父组件无法更改它。这使你可以向任何组件添加或删除 state,而不会影响其他组件。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import Gallery from "./Gallery.js";
    +
    +export default function Page() {
    +  return (
    +    <div className="Page">
    +      <Gallery />
    +      <Gallery />
    +    </div>
    +  );
    +}
    +
  8. +
  9. 不要在state中镜像props
    由于state只会在第一次渲染期间初始化,后续props发生更新后,这样的代码不会发生更新。

    +
    1
    2
    3
    function Message({ messageColor }) {
    +  const [color, setColor] = useState(messageColor);
    +}
    +

    只有当你 想要 忽略特定 props 属性的所有更新时,将 props “镜像”到 state 才有意义。按照惯例,prop 名称以 initialdefault 开头,以阐明该 prop 的新值将被忽略:

    +
    1
    2
    3
    4
    5
    function Message({ initialColor }) {
    +  // 这个 `color` state 变量用于保存 `initialColor` 的 **初始值**。
    +  // 对于 `initialColor` 属性的进一步更改将被忽略。
    +  const [color, setColor] = useState(initialColor);
    +}
  10. +
  11. React中的Hook只能在组件或者自己的Hook的顶层调用,而不能嵌套调用。原因为Hooks的执行需要严格按照其调用顺序,Hooks的调用顺序是一致的,从而避免了状态的混乱或者副作用的错误。
  12. +
  13. 注意React中的setState方法中的更改只会在下一次渲染时才会生效。
  14. +
  15. useState内部原理的简单实现。

    +
      +
    • index.js

      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      138
      139
      140
      141
      142
      143
      144
      145
      146
      147
      148
      149
      150
      151
      152
      153
      154
      155
      156
      157
      158
      159
      160
      161
      162
      163
      164
      165
      166
      167
      168
      169
      170
      171
      172
      173
      174
      175
      176
      177
      178
      179
      180
      181
      182
      183
      184
      185
      186
      187
      188
      let componentHooks = [];
      +let currentHookIndex = 0;
      +
      +// useState 在 React 中是如何工作的(简化版)
      +function useState(initialState) {
      +  let pair = componentHooks[currentHookIndex];
      +  if (pair) {
      +    // 这不是第一次渲染
      +    // 所以 state pair 已经存在
      +    // 将其返回并为下一次 hook 的调用做准备
      +    currentHookIndex++;
      +    return pair;
      +  }
      +
      +  // 这是我们第一次进行渲染
      +  // 所以新建一个 state pair 然后存储它
      +  pair = [initialState, setState];
      +
      +  function setState(nextState) {
      +    // 当用户发起 state 的变更,
      +    // 把新的值放入 pair 中
      +    pair[0] = nextState;
      +    updateDOM();
      +  }
      +
      +  // 存储这个 pair 用于将来的渲染
      +  // 并且为下一次 hook 的调用做准备
      +  componentHooks[currentHookIndex] = pair;
      +  currentHookIndex++;
      +  return pair;
      +}
      +
      +function Gallery() {
      +  // 每次调用 useState() 都会得到新的 pair
      +  const [index, setIndex] = useState(0);
      +  const [showMore, setShowMore] = useState(false);
      +
      +  function handleNextClick() {
      +    setIndex(index + 1);
      +  }
      +
      +  function handleMoreClick() {
      +    setShowMore(!showMore);
      +  }
      +
      +  let sculpture = sculptureList[index];
      +  // 这个例子没有使用 React,所以
      +  // 返回一个对象而不是 JSX
      +  return {
      +    onNextClick: handleNextClick,
      +    onMoreClick: handleMoreClick,
      +    header: `${sculpture.name} by ${sculpture.artist}`,
      +    counter: `${index + 1} of ${sculptureList.length}`,
      +    more: `${showMore ? "Hide" : "Show"} details`,
      +    description: showMore ? sculpture.description : null,
      +    imageSrc: sculpture.url,
      +    imageAlt: sculpture.alt,
      +  };
      +}
      +
      +function updateDOM() {
      +  // 在渲染组件之前
      +  // 重置当前 Hook 的下标
      +  currentHookIndex = 0;
      +  let output = Gallery();
      +
      +  // 更新 DOM 以匹配输出结果
      +  // 这部分工作由 React 为你完成
      +  nextButton.onclick = output.onNextClick;
      +  header.textContent = output.header;
      +  moreButton.onclick = output.onMoreClick;
      +  moreButton.textContent = output.more;
      +  image.src = output.imageSrc;
      +  image.alt = output.imageAlt;
      +  if (output.description !== null) {
      +    description.textContent = output.description;
      +    description.style.display = "";
      +  } else {
      +    description.style.display = "none";
      +  }
      +}
      +
      +let nextButton = document.getElementById("nextButton");
      +let header = document.getElementById("header");
      +let moreButton = document.getElementById("moreButton");
      +let description = document.getElementById("description");
      +let image = document.getElementById("image");
      +let sculptureList = [
      +  {
      +    name: "Homenaje a la Neurocirugía",
      +    artist: "Marta Colvin Andrade",
      +    description:
      +      "Although Colvin is predominantly known for abstract themes that allude to pre-Hispanic symbols, this gigantic sculpture, an homage to neurosurgery, is one of her most recognizable public art pieces.",
      +    url: "https://i.imgur.com/Mx7dA2Y.jpg",
      +    alt: "A bronze statue of two crossed hands delicately holding a human brain in their fingertips.",
      +  },
      +  {
      +    name: "Floralis Genérica",
      +    artist: "Eduardo Catalano",
      +    description:
      +      "This enormous (75 ft. or 23m) silver flower is located in Buenos Aires. It is designed to move, closing its petals in the evening or when strong winds blow and opening them in the morning.",
      +    url: "https://i.imgur.com/ZF6s192m.jpg",
      +    alt: "A gigantic metallic flower sculpture with reflective mirror-like petals and strong stamens.",
      +  },
      +  {
      +    name: "Eternal Presence",
      +    artist: "John Woodrow Wilson",
      +    description:
      +      'Wilson was known for his preoccupation with equality, social justice, as well as the essential and spiritual qualities of humankind. This massive (7ft. or 2,13m) bronze represents what he described as "a symbolic Black presence infused with a sense of universal humanity."',
      +    url: "https://i.imgur.com/aTtVpES.jpg",
      +    alt: "The sculpture depicting a human head seems ever-present and solemn. It radiates calm and serenity.",
      +  },
      +  {
      +    name: "Moai",
      +    artist: "Unknown Artist",
      +    description:
      +      "Located on the Easter Island, there are 1,000 moai, or extant monumental statues, created by the early Rapa Nui people, which some believe represented deified ancestors.",
      +    url: "https://i.imgur.com/RCwLEoQm.jpg",
      +    alt: "Three monumental stone busts with the heads that are disproportionately large with somber faces.",
      +  },
      +  {
      +    name: "Blue Nana",
      +    artist: "Niki de Saint Phalle",
      +    description:
      +      "The Nanas are triumphant creatures, symbols of femininity and maternity. Initially, Saint Phalle used fabric and found objects for the Nanas, and later on introduced polyester to achieve a more vibrant effect.",
      +    url: "https://i.imgur.com/Sd1AgUOm.jpg",
      +    alt: "A large mosaic sculpture of a whimsical dancing female figure in a colorful costume emanating joy.",
      +  },
      +  {
      +    name: "Ultimate Form",
      +    artist: "Barbara Hepworth",
      +    description:
      +      "This abstract bronze sculpture is a part of The Family of Man series located at Yorkshire Sculpture Park. Hepworth chose not to create literal representations of the world but developed abstract forms inspired by people and landscapes.",
      +    url: "https://i.imgur.com/2heNQDcm.jpg",
      +    alt: "A tall sculpture made of three elements stacked on each other reminding of a human figure.",
      +  },
      +  {
      +    name: "Cavaliere",
      +    artist: "Lamidi Olonade Fakeye",
      +    description:
      +      "Descended from four generations of woodcarvers, Fakeye's work blended traditional and contemporary Yoruba themes.",
      +    url: "https://i.imgur.com/wIdGuZwm.png",
      +    alt: "An intricate wood sculpture of a warrior with a focused face on a horse adorned with patterns.",
      +  },
      +  {
      +    name: "Big Bellies",
      +    artist: "Alina Szapocznikow",
      +    description:
      +      "Szapocznikow is known for her sculptures of the fragmented body as a metaphor for the fragility and impermanence of youth and beauty. This sculpture depicts two very realistic large bellies stacked on top of each other, each around five feet (1,5m) tall.",
      +    url: "https://i.imgur.com/AlHTAdDm.jpg",
      +    alt: "The sculpture reminds a cascade of folds, quite different from bellies in classical sculptures.",
      +  },
      +  {
      +    name: "Terracotta Army",
      +    artist: "Unknown Artist",
      +    description:
      +      "The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consisted of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.",
      +    url: "https://i.imgur.com/HMFmH6m.jpg",
      +    alt: "12 terracotta sculptures of solemn warriors, each with a unique facial expression and armor.",
      +  },
      +  {
      +    name: "Lunar Landscape",
      +    artist: "Louise Nevelson",
      +    description:
      +      "Nevelson was known for scavenging objects from New York City debris, which she would later assemble into monumental constructions. In this one, she used disparate parts like a bedpost, juggling pin, and seat fragment, nailing and gluing them into boxes that reflect the influence of Cubism’s geometric abstraction of space and form.",
      +    url: "https://i.imgur.com/rN7hY6om.jpg",
      +    alt: "A black matte sculpture where the individual elements are initially indistinguishable.",
      +  },
      +  {
      +    name: "Aureole",
      +    artist: "Ranjani Shettar",
      +    description:
      +      'Shettar merges the traditional and the modern, the natural and the industrial. Her art focuses on the relationship between man and nature. Her work was described as compelling both abstractly and figuratively, gravity defying, and a "fine synthesis of unlikely materials."',
      +    url: "https://i.imgur.com/okTpbHhm.jpg",
      +    alt: "A pale wire-like sculpture mounted on concrete wall and descending on the floor. It appears light.",
      +  },
      +  {
      +    name: "Hippos",
      +    artist: "Taipei Zoo",
      +    description:
      +      "The Taipei Zoo commissioned a Hippo Square featuring submerged hippos at play.",
      +    url: "https://i.imgur.com/6o5Vuyu.jpg",
      +    alt: "A group of bronze hippo sculptures emerging from the sett sidewalk as if they were swimming.",
      +  },
      +];
      +
      +// 使 UI 匹配当前 state
      +updateDOM();
      +
    • +
    • index.html

      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      <button id="nextButton">Next</button>
      +<h3 id="header"></h3>
      +<button id="moreButton"></button>
      +<p id="description"></p>
      +<img id="image" />
      +
      +<style>
      +  * {
      +    box-sizing: border-box;
      +  }
      +  body {
      +    font-family: sans-serif;
      +    margin: 20px;
      +    padding: 0;
      +  }
      +  button {
      +    display: block;
      +    margin-bottom: 10px;
      +  }
      +</style>
      +
    • +
    +

    相同位置的相同组件会使得 state 被保留下来:记住 对 React 来说重要的是组件在 UI 树中的位置,而不是在 JSX 中的位置!
    重置 state 有两种方法:

    +
      +
    • 在不同的位置渲染组件
    • +
    • 添加唯一的 key
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    import { useState } from "react";
    +
    +export default function App() {
    +  const [isFancy, setIsFancy] = useState(false);
    +  if (isFancy) {
    +    return (
    +      <div>
    +        <Counter isFancy={true} />
    +        <label>
    +          <input
    +            type="checkbox"
    +            checked={isFancy}
    +            onChange={(e) => {
    +              setIsFancy(e.target.checked);
    +            }}
    +          />
    +          使用好看的样式
    +        </label>
    +      </div>
    +    );
    +  }
    +  return (
    +    <div>
    +      <Counter isFancy={false} />
    +      <label>
    +        <input
    +          type="checkbox"
    +          checked={isFancy}
    +          onChange={(e) => {
    +            setIsFancy(e.target.checked);
    +          }}
    +        />
    +        使用好看的样式
    +      </label>
    +    </div>
    +  );
    +}
    +
    +function Counter({ isFancy }) {
    +  const [score, setScore] = useState(0);
    +  const [hover, setHover] = useState(false);
    +
    +  let className = "counter";
    +  if (hover) {
    +    className += " hover";
    +  }
    +  if (isFancy) {
    +    className += " fancy";
    +  }
    +
    +  return (
    +    <div
    +      className={className}
    +      onPointerEnter={() => setHover(true)}
    +      onPointerLeave={() => setHover(false)}
    +    >
    +      <h1>{score}</h1>
    +      <button onClick={() => setScore(score + 1)}>加一</button>
    +    </div>
    +  );
    +}
    +
    +
  16. +
+

Props

    +
  1. React中使用props传递参数

    +
    +

    对于React中的函数组件,props相当于一个整体对象,这里是将其解构赋值出来。

    +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    function Avatar({ person, size = 20 }) {
    +  return (
    +    <img
    +      className="avatar"
    +      src={getImageUrl(person)}
    +      alt={person.name}
    +      width={size}
    +      height={size}
    +    />
    +  );
    +}
    +
    +export default function Profile() {
    +  return (
    +    <div>
    +      <Avatar
    +        size={100}
    +        person={{
    +          name: "Katsuko Saruhashi",
    +          imageId: "YfeOqp2",
    +        }}
    +      />
    +      <Avatar
    +        size={80}
    +        person={{
    +          name: "Aklilu Lemma",
    +          imageId: "OKS67lh",
    +        }}
    +      />
    +      <Avatar
    +        size={50}
    +        person={{
    +          name: "Lin Lanying",
    +          imageId: "1bX5QH6",
    +        }}
    +      />
    +    </div>
    +  );
    +}
    +
    +

    React中的默认参数与js中函数的默认参数一致,但位置随意。

    +
    +
  2. +
  3. React中通过扩展运算符传递参数

    +
    1
    2
    3
    4
    5
    6
    7
    function Profile(props) {
    +  return (
    +    <div className="card">
    +      <Avatar {...props} />
    +    </div>
    +  );
    +}
    +

    你可以使用 <Avatar {...props} /> JSX 展开语法转发所有 props,但不要过度使用它!

    +
    +
  4. +
  5. React中的列表渲染

    +
    1
    const listItems = people.map((person, key) => <li key={key}>{person}</li>);
    +
  6. +
  7. 对于ReactVue中的组件中的keykey会作为组件中的一个特殊的属性而不会作为props传入。

    +
  8. +
  9. Reactpropschildren属性如果有多个同级的JSX元素传递过来,则children是数组的形式。
  10. +
+

注意事项

    +
  1. React使用Fragment标签来充当幽灵标签,用来添加key值,这时候不能使用<></>

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    import { Fragment } from "react";
    +
    +const poem = {
    +  lines: [
    +    "I write, erase, rewrite",
    +    "Erase again, and then",
    +    "A poppy blooms.",
    +  ],
    +};
    +
    +export default function Poem() {
    +  return (
    +    <article>
    +      {poem.lines.map((line, i) => (
    +        <Fragment key={i}>
    +          {i > 0 && <hr />}
    +          <p>{line}</p>
    +        </Fragment>
    +      ))}
    +    </article>
    +  );
    +}
    +
    +

    React只能渲染字符串,数字,布尔值,nullundefined,数组或者React元素

    +
    +
  2. +
  3. React中绑定一个事件的捕获模式,即在事件名称末尾添加Capture

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    {
    +  /* 每个事件分三个阶段传播:
    +
    +  1. 它向下传播,调用所有的 onClickCapture 处理函数。
    +  2. 它执行被点击元素的 onClick 处理函数。
    +  3. 它向上传播,调用所有的 onClick 处理函数。
    +  捕获事件对于路由或数据分析之类的代码很有用,但你可能不会在应用程序代码中使用它们。 */
    +}
    +<div
    +  onClickCapture={() => {
    +    /* 这会首先执行 */
    +  }}
    +>
    +  <button onClick={(e) => e.stopPropagation()} />
    +  <button onClick={(e) => e.stopPropagation()} />
    +</div>;
    +
  4. +
  5. React如果需要触发组件的re-render,除了自身的setState方法,如果父组件的变量作为props传递给子组件,那么如果该变量发生变化也会引起子组件的re-render

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    import { useState } from "react";
    +import { foods, filterItems } from "./data.js";
    +
    +export default function FilterableList() {
    +  const [query, setQuery] = useState("");
    +  const results = filterItems(foods, query);
    +
    +  function handleChange(e) {
    +    setQuery(e.target.value);
    +  }
    +
    +  return (
    +    <>
    +      <SearchBar query={query} onChange={handleChange} />
    +      <hr />
    +      <List items={results} />
    +    </>
    +  );
    +}
    +
    +function SearchBar({ query, onChange }) {
    +  return (
    +    <label>
    +      搜索: <input value={query} onChange={onChange} />
    +    </label>
    +  );
    +}
    +
    +function List({ items }) {
    +  return (
    +    <table>
    +      <tbody>
    +        {items.map((food) => (
    +          <tr key={food.id}>
    +            <td>{food.name}</td>
    +            <td>{food.description}</td>
    +          </tr>
    +        ))}
    +      </tbody>
    +    </table>
    +  );
    +}
    +
  6. +
  7. 井字棋游戏示例代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    import { useState } from "react";
    +
    +function Square({ value, onSquareClick }) {
    +  return (
    +    <button className="square" onClick={onSquareClick}>
    +      {value}
    +    </button>
    +  );
    +}
    +
    +function Board({ xIsNext, squares, onPlay }) {
    +  function handleClick(i) {
    +    if (calculateWinner(squares) || squares[i]) {
    +      return;
    +    }
    +    const nextSquares = squares.slice();
    +    if (xIsNext) {
    +      nextSquares[i] = "X";
    +    } else {
    +      nextSquares[i] = "O";
    +    }
    +    onPlay(nextSquares);
    +  }
    +
    +  const winner = calculateWinner(squares);
    +  let status;
    +  if (winner) {
    +    status = "Winner: " + winner;
    +  } else {
    +    status = "Next player: " + (xIsNext ? "X" : "O");
    +  }
    +
    +  return (
    +    <>
    +      <div className="status">{status}</div>
    +      <div className="board-row">
    +        <Square value={squares[0]} onSquareClick={() => handleClick(0)} />
    +        <Square value={squares[1]} onSquareClick={() => handleClick(1)} />
    +        <Square value={squares[2]} onSquareClick={() => handleClick(2)} />
    +      </div>
    +      <div className="board-row">
    +        <Square value={squares[3]} onSquareClick={() => handleClick(3)} />
    +        <Square value={squares[4]} onSquareClick={() => handleClick(4)} />
    +        <Square value={squares[5]} onSquareClick={() => handleClick(5)} />
    +      </div>
    +      <div className="board-row">
    +        <Square value={squares[6]} onSquareClick={() => handleClick(6)} />
    +        <Square value={squares[7]} onSquareClick={() => handleClick(7)} />
    +        <Square value={squares[8]} onSquareClick={() => handleClick(8)} />
    +      </div>
    +    </>
    +  );
    +}
    +
    +export default function Game() {
    +  const [history, setHistory] = useState([Array(9).fill(null)]);
    +  const [currentMove, setCurrentMove] = useState(0);
    +  const xIsNext = currentMove % 2 === 0;
    +  const currentSquares = history[currentMove];
    +
    +  function handlePlay(nextSquares) {
    +    const nextHistory = [...history.slice(0, currentMove + 1), nextSquares];
    +    setHistory(nextHistory);
    +    setCurrentMove(nextHistory.length - 1);
    +  }
    +
    +  function jumpTo(nextMove) {
    +    setCurrentMove(nextMove);
    +  }
    +
    +  const moves = history.map((squares, move) => {
    +    let description;
    +    if (move > 0) {
    +      description = "Go to move #" + move;
    +    } else {
    +      description = "Go to game start";
    +    }
    +    return (
    +      <li key={move}>
    +        <button onClick={() => jumpTo(move)}>{description}</button>
    +      </li>
    +    );
    +  });
    +
    +  return (
    +    <div className="game">
    +      <div className="game-board">
    +        <Board
    +          xIsNext={xIsNext}
    +          squares={currentSquares}
    +          onPlay={handlePlay}
    +        />
    +      </div>
    +      <div className="game-info">
    +        <ol>{moves}</ol>
    +      </div>
    +    </div>
    +  );
    +}
    +
    +function calculateWinner(squares) {
    +  const lines = [
    +    [0, 1, 2],
    +    [3, 4, 5],
    +    [6, 7, 8],
    +    [0, 3, 6],
    +    [1, 4, 7],
    +    [2, 5, 8],
    +    [0, 4, 8],
    +    [2, 4, 6],
    +  ];
    +  for (let i = 0; i < lines.length; i++) {
    +    const [a, b, c] = lines[i];
    +    if (
    +      squares[a] &&
    +      squares[a] === squares[b] &&
    +      squares[a] === squares[c]
    +    ) {
    +      return squares[a];
    +    }
    +  }
    +  return null;
    +}
    +
  8. +
+

React Router

    +
  1. 创建React Router的基本过程

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import * as React from "react";
    +import * as ReactDOM from "react-dom/client";
    +import { createBrowserRouter, RouterProvider } from "react-router-dom";
    +import "./index.css";
    +
    +const router = createBrowserRouter([
    +  {
    +    path: "/",
    +    element: <div>Hello world!</div>,
    +  },
    +]);
    +
    +ReactDOM.createRoot(document.getElementById("root")).render(
    +  <React.StrictMode>
    +    <RouterProvider router={router} />
    +  </React.StrictMode>
    +);
    +
  2. +
  3. React中子路由的创建,使用<Outlet />渲染路由相当于vue中的<RouterView />

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    const router = createBrowserRouter([
    +  {
    +    path: "/",
    +    element: <Root />,
    +    errorElement: <ErrorPage />,
    +    children: [
    +      {
    +        path: "contacts/:contactId",
    +        element: <Contact />,
    +      },
    +    ],
    +  },
    +]);
    +
    +import { Outlet } from "react-router-dom";
    +
    +export default function Root() {
    +  return (
    +    <>
    +      {/* all the other elements */}
    +      <div id="detail">
    +        <Outlet />
    +      </div>
    +    </>
    +  );
    +}
    +
  4. +
  5. React Router中通过向路由配置的loader配置项传递一个
    具有返回值的函数,然后通过useLoaderData函数进行数据的获取。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /* other imports */
    +import Root, { loader as rootLoader } from "./routes/root";
    +
    +const router = createBrowserRouter([
    +  {
    +    path: "/",
    +    element: <Root />,
    +    errorElement: <ErrorPage />,
    +    loader: rootLoader,
    +    children: [
    +      {
    +        path: "contacts/:contactId",
    +        element: <Contact />,
    +      },
    +    ],
    +  },
    +]);
    +// other file
    +import { useLoaderData } from "react-router-dom";
    +const { contacts } = useLoaderData();
    +
  6. +
  7. React Router会截取页面中表单提交的行为,并将其转发到目前路由的action当中。

    +
      +
    • edit.tsx

      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      import { Form, useLoaderData, redirect } from "react-router-dom";
      +import { updateContact } from "../contacts";
      +
      +export async function action({ request, params }) {
      +  const formData = await request.formData();
      +  const updates = Object.fromEntries(formData);
      +  await updateContact(params.contactId, updates);
      +  return redirect(`/contacts/${params.contactId}`);
      +}
      +
      +/* existing code */
      +
    • +
    • main.tsx

      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      /* existing code */
      +import EditContact, { action as editAction } from "./routes/edit";
      +
      +const router = createBrowserRouter([
      +  {
      +    path: "/",
      +    element: <Root />,
      +    errorElement: <ErrorPage />,
      +    loader: rootLoader,
      +    action: rootAction,
      +    children: [
      +      {
      +        path: "contacts/:contactId",
      +        element: <Contact />,
      +        loader: contactLoader,
      +      },
      +      {
      +        path: "contacts/:contactId/edit",
      +        element: <EditContact />,
      +        loader: contactLoader,
      +        action: editAction,
      +      },
      +    ],
      +  },
      +]);
      +
      +/* existing code */
      +
    • +
    +
  8. +
  9. 使用React Router中的JSX Routes

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    import {
    +  createRoutesFromElements,
    +  createBrowserRouter,
    +  Route,
    +} from "react-router-dom";
    +
    +const router = createBrowserRouter(
    +  createRoutesFromElements(
    +    <Route
    +      path="/"
    +      element={<Root />}
    +      loader={rootLoader}
    +      action={rootAction}
    +      errorElement={<ErrorPage />}
    +    >
    +      <Route errorElement={<ErrorPage />}>
    +        <Route index element={<Index />} />
    +        <Route
    +          path="contacts/:contactId"
    +          element={<Contact />}
    +          loader={contactLoader}
    +          action={contactAction}
    +        />
    +        <Route
    +          path="contacts/:contactId/edit"
    +          element={<EditContact />}
    +          loader={contactLoader}
    +          action={editAction}
    +        />
    +        <Route path="contacts/:contactId/destroy" action={destroyAction} />
    +      </Route>
    +    </Route>
    +  )
    +);
    +
  10. +
  11. React Router集中式路由配置

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    import { BrowserRouter, Routes, Route, useRoutes } from "react-router-dom";
    +
    +import Layout from "./pages/Layout";
    +import Board from "./pages/Board";
    +import Article from "./pages/Article";
    +import NotFound from "./pages/NotFound";
    +
    +// 1. 准备一个路由数组 数组中定义所有的路由对应关系
    +const routesList = [
    +  {
    +    path: "/",
    +    element: <Layout />,
    +    children: [
    +      {
    +        element: <Board />,
    +        index: true, // index设置为true 变成默认的二级路由
    +      },
    +      {
    +        path: "article",
    +        element: <Article />,
    +      },
    +    ],
    +  },
    +  // 增加n个路由对应关系
    +  {
    +    path: "*",
    +    element: <NotFound />,
    +  },
    +];
    +
    +// 2. 使用useRoutes方法传入routesList生成Routes组件
    +function WrapperRoutes() {
    +  let element = useRoutes(routesList);
    +  return element;
    +}
    +
    +function App() {
    +  return (
    +    <div className="App">
    +      <BrowserRouter>
    +        {/* 3. 替换之前的Routes组件 */}
    +        <WrapperRoutes />
    +      </BrowserRouter>
    +    </div>
    +  );
    +}
    +
    +export default App;
    +
  12. +
  13. react routerjsx方式的嵌套路由

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // 引入必要的内置组件
    +import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
    +
    +// 准备俩个路由组件
    +
    +const Home = () => <div>this is home</div>;
    +const About = () => <div>this is about</div>;
    +
    +function App() {
    +  return (
    +    <div className="App">
    +      {/* 按照规则配置路由 */}
    +      <BrowserRouter>
    +        <Link to="/">首页</Link>
    +        <Link to="/about">关于</Link>
    +        <Routes>
    +          <Route path="/" element={<Home />}></Route>
    +          <Route path="/about" element={<About />}></Route>
    +        </Routes>
    +      </BrowserRouter>
    +    </div>
    +  );
    +}
    +
    +export default App;
    +
  14. +
  15. React路由懒加载以及回调显示

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    import { Routes, Route } from "react-router-dom";
    +import { HistoryRouter, history } from "./utils/history";
    +import { AuthRoute } from "./components/AuthRoute";
    +
    +// 导入必要组件
    +import { lazy, Suspense } from "react";
    +// 按需导入路由组件
    +const Login = lazy(() => import("./pages/Login"));
    +const Layout = lazy(() => import("./pages/Layout"));
    +const Home = lazy(() => import("./pages/Home"));
    +const Article = lazy(() => import("./pages/Article"));
    +const Publish = lazy(() => import("./pages/Publish"));
    +
    +function App() {
    +  return (
    +    <HistoryRouter history={history}>
    +      <Suspense
    +        fallback={
    +          <div
    +            style={{
    +              textAlign: "center",
    +              marginTop: 200,
    +            }}
    +          >
    +            loading...
    +          </div>
    +        }
    +      >
    +        <Routes>
    +          {/* 需要鉴权的路由 */}
    +          <Route
    +            path="/"
    +            element={
    +              <AuthRoute>
    +                <Layout />
    +              </AuthRoute>
    +            }
    +          >
    +            {/* 二级路由默认页面 */}
    +            <Route index element={<Home />} />
    +            <Route path="article" element={<Article />} />
    +            <Route path="publish" element={<Publish />} />
    +          </Route>
    +          {/* 不需要鉴权的路由 */}
    +          <Route path="/login" element={<Login />} />
    +        </Routes>
    +      </Suspense>
    +    </HistoryRouter>
    +  );
    +}
    +
    +export default App;
    +
  16. +
  17. vite配置路径别名的方案。

    +
      +
    1. 安装@types/node库,便于后续文件的引用。
    2. +
    3. 打开vite.config.ts进行路径别名的配置,使vite能够识别@

      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      import { fileURLToPath, URL } from "node:url";
      +
      +import { defineConfig } from "vite";
      +import react from "@vitejs/plugin-react-swc";
      +
      +// https://vitejs.dev/config/
      +export default defineConfig({
      +  plugins: [react()],
      +  resolve: {
      +    alias: {
      +      "@": fileURLToPath(new URL("./src", import.meta.url)),
      +    },
      +  },
      +});
      +
    4. +
    5. tsconfig.json中进行配置,使得vscode能使用@进行智能提示。

      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      {
      +  "compilerOptions": {
      +    "composite": true,
      +    "baseUrl": ".",
      +    "paths": {
      +      "@/*": ["./src/*"]
      +    }
      +  }
      +}
      +
    6. +
    +
  18. +
+

Mobx

    +
  1. mobx模块化的基本用法。

    +
      +
    • 定义单独的store

      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      // login.ts
      +import { makeAutoObservable } from "mobx";
      +import request from "@/utils/request";
      +import { clearToken, getToken, setToken } from "@/utils/token";
      +
      +class LoginStore {
      +  token = getToken() || "";
      +  constructor() {
      +    makeAutoObservable(this);
      +  }
      +  login = async (data: { mobile: string; code: string }) => {
      +    const res = await request.post<any, LoginResData>(
      +      "/authorizations",
      +      data
      +    );
      +    this.token = res.data.token;
      +    setToken(res.data.token);
      +  };
      +  logout = async () => {
      +    this.token = "";
      +    clearToken();
      +  };
      +}
      +
      +export default LoginStore;
      +
      +// user.ts
      +import { makeAutoObservable } from "mobx";
      +import request from "@/utils/request";
      +
      +class UserStore {
      +  userInfo = {};
      +  constructor() {
      +    makeAutoObservable(this);
      +  }
      +  getUserInfo = async () => {
      +    const res = await request.get("/user/profile");
      +    this.userInfo = res.data;
      +  };
      +}
      +export default UserStore;
      +
    • +
    • index.ts中统一进行注入

      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      import React from "react";
      +import LoginStore from "./modules/login";
      +import UserStore from "./modules/user";
      +
      +class RootStore {
      +  loginStore: LoginStore;
      +  userStore: UserStore;
      +  constructor() {
      +    this.loginStore = new LoginStore();
      +    this.userStore = new UserStore();
      +  }
      +}
      +const StoresContext = React.createContext(new RootStore());
      +export const useStore = () => React.useContext(StoresContext);
      +
    • +
    • 使用store

      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      import { Layout as AntLayout, Menu, Popconfirm, message } from "antd";
      +import {
      +  HomeOutlined,
      +  DiffOutlined,
      +  EditOutlined,
      +  LogoutOutlined,
      +} from "@ant-design/icons";
      +import { Link, Outlet, useLocation, useNavigate } from "react-router-dom";
      +import { observer } from "mobx-react-lite";
      +import "./index.scss";
      +import { useEffect } from "react";
      +import { useStore } from "@/stores";
      +
      +const { Header, Sider } = AntLayout;
      +const Layout = observer(() => {
      +  const location = useLocation();
      +  const selectedKey = location.pathname;
      +  // load userinfo
      +  const { userStore, loginStore } = useStore();
      +  useEffect(() => {
      +    try {
      +      userStore.getUserInfo();
      +    } catch (error: any) {
      +      message.error(error.response?.data?.message || "获取用户信息失败");
      +    }
      +  }, [userStore]);
      +  // logout function
      +  const navigate = useNavigate();
      +  const handleLogout = () => {
      +    loginStore.logout();
      +    navigate("/login");
      +    message.success("退出登录成功");
      +  };
      +  return (
      +    <AntLayout>
      +      <Header className="header">
      +        <div className="logo" />
      +        <div className="user-info">
      +          <span className="user-name">user.name</span>
      +          <span className="user-logout">
      +            <Popconfirm
      +              title="是否确认退出?"
      +              okText="退出"
      +              cancelText="取消"
      +              onConfirm={handleLogout}
      +            >
      +              <LogoutOutlined /> 退出
      +            </Popconfirm>
      +          </span>
      +        </div>
      +      </Header>
      +      <AntLayout>
      +        <Sider width={200} className="site-layout-background">
      +          <Menu
      +            mode="inline"
      +            theme="dark"
      +            selectedKeys={[selectedKey]}
      +            style={{ height: "100%", borderRight: 0 }}
      +            items={[
      +              {
      +                icon: <HomeOutlined />,
      +                key: "/",
      +                label: <Link to="/">数据概览</Link>,
      +              },
      +              {
      +                icon: <DiffOutlined />,
      +                key: "/article",
      +                label: <Link to="/article">内容管理</Link>,
      +              },
      +              {
      +                icon: <EditOutlined />,
      +                key: "/publish",
      +                label: <Link to="/publish">发布文章</Link>,
      +              },
      +            ]}
      +          ></Menu>
      +        </Sider>
      +        <AntLayout className="layout-content" style={{ padding: 20 }}>
      +          <Outlet />
      +        </AntLayout>
      +      </AntLayout>
      +    </AntLayout>
      +  );
      +});
      +export default Layout;
      +
    • +
    +
  2. +
+

组件通信

    +
  1. 父传子

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    import React from "react";
    +
    +// 函数式子组件
    +function FSon(props) {
    +  console.log(props);
    +  return (
    +    <div>
    +      子组件1
    +      {props.msg}
    +    </div>
    +  );
    +}
    +
    +// 类子组件
    +class CSon extends React.Component {
    +  render() {
    +    return (
    +      <div>
    +        子组件2
    +        {this.props.msg}
    +      </div>
    +    );
    +  }
    +}
    +// 父组件
    +class App extends React.Component {
    +  state = {
    +    message: "this is message",
    +  };
    +  render() {
    +    return (
    +      <div>
    +        <div>父组件</div>
    +        <FSon msg={this.state.message} />
    +        <CSon msg={this.state.message} />
    +      </div>
    +    );
    +  }
    +}
    +
    +export default App;
    +
  2. +
  3. 子传父

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    import React from "react";
    +
    +// 子组件
    +function Son(props) {
    +  function handleClick() {
    +    // 调用父组件传递过来的回调函数 并注入参数
    +    props.changeMsg("this is newMessage");
    +  }
    +  return (
    +    <div>
    +      {props.msg}
    +      <button onClick={handleClick}>change</button>
    +    </div>
    +  );
    +}
    +
    +class App extends React.Component {
    +  state = {
    +    message: "this is message",
    +  };
    +  // 提供回调函数
    +  changeMessage = (newMsg) => {
    +    console.log("子组件传过来的数据:", newMsg);
    +    this.setState({
    +      message: newMsg,
    +    });
    +  };
    +  render() {
    +    return (
    +      <div>
    +        <div>父组件</div>
    +        <Son
    +          msg={this.state.message}
    +          // 传递给子组件
    +          changeMsg={this.changeMessage}
    +        />
    +      </div>
    +    );
    +  }
    +}
    +
    +export default App;
    +
  4. +
  5. 兄弟组件通信,核心思路为变量提升。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    import React from "react";
    +
    +// 子组件
    +function Son(props) {
    +  function handleClick() {
    +    // 调用父组件传递过来的回调函数 并注入参数
    +    props.changeMsg("this is newMessage");
    +  }
    +  return (
    +    <div>
    +      {props.msg}
    +      <button onClick={handleClick}>change</button>
    +    </div>
    +  );
    +}
    +
    +class App extends React.Component {
    +  state = {
    +    message: "this is message",
    +  };
    +  // 提供回调函数
    +  changeMessage = (newMsg) => {
    +    console.log("子组件传过来的数据:", newMsg);
    +    this.setState({
    +      message: newMsg,
    +    });
    +  };
    +  render() {
    +    return (
    +      <div>
    +        <div>父组件</div>
    +        <Son
    +          msg={this.state.message}
    +          // 传递给子组件
    +          changeMsg={this.changeMessage}
    +        />
    +      </div>
    +    );
    +  }
    +}
    +
    +export default App;
    +
  6. +
  7. 跨组件间通信

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    import React, { createContext } from "react";
    +
    +// 1. 创建Context对象
    +const { Provider, Consumer } = createContext();
    +
    +// 3. 消费数据
    +function ComC() {
    +  return <Consumer>{(value) => <div>{value}</div>}</Consumer>;
    +}
    +
    +function ComA() {
    +  return <ComC />;
    +}
    +
    +// 2. 提供数据
    +class App extends React.Component {
    +  state = {
    +    message: "this is message",
    +  };
    +  render() {
    +    return (
    +      <Provider value={this.state.message}>
    +        <div className="app">
    +          <ComA />
    +        </div>
    +      </Provider>
    +    );
    +  }
    +}
    +
    +export default App;
    +
  8. +
  9. 小案例

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    import React from "react";
    +
    +// 子组件
    +function ListItem(props) {
    +  const { name, price, info, id, delHandler } = props;
    +  return (
    +    <div>
    +      <h3>{name}</h3>
    +      <p>{price}</p>
    +      <p>{info}</p>
    +      <button onClick={() => delHandler(id)}>删除</button>
    +    </div>
    +  );
    +}
    +
    +// 父组件
    +class App extends React.Component {
    +  state = {
    +    list: [
    +      {
    +        id: 1,
    +        name: "超级好吃的棒棒糖",
    +        price: 18.8,
    +        info: "开业大酬宾,全场8折",
    +      },
    +      {
    +        id: 2,
    +        name: "超级好吃的大鸡腿",
    +        price: 34.2,
    +        info: "开业大酬宾,全场8折",
    +      },
    +      {
    +        id: 3,
    +        name: "超级无敌的冰激凌",
    +        price: 14.2,
    +        info: "开业大酬宾,全场8折",
    +      },
    +    ],
    +  };
    +
    +  delHandler = (id) => {
    +    this.setState({
    +      list: this.state.list.filter((item) => item.id !== id),
    +    });
    +  };
    +
    +  render() {
    +    return (
    +      <>
    +        {this.state.list.map((item) => (
    +          <ListItem key={item.id} {...item} delHandler={this.delHandler} />
    +        ))}
    +      </>
    +    );
    +  }
    +}
    +
    +export default App;
    +
  10. +
+

Hooks

useState

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { useState } from "react";
+
+function App() {
+  // 参数:状态初始值比如,传入 0 表示该状态的初始值为 0
+  // 返回值:数组,包含两个值:1 状态值(state) 2 修改该状态的函数(setState)
+  const [count, setCount] = useState(0);
+  return (
+    <button
+      onClick={() => {
+        setCount(count + 1);
+      }}
+    >
+      {count}
+    </button>
+  );
+}
+export default App;
+
    +
  1. 如果就是初始化一个普通的数据 直接使用 useState(普通数据) 即可
  2. +
  3. 如果要初始化的数据无法直接得到需要通过计算才能获取到,使用useState(()=>{})
  4. +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { useState } from "react";
+
+function Counter(props) {
+  const [count, setCount] = useState(() => {
+    return props.count;
+  });
+  return (
+    <div>
+      <button onClick={() => setCount(count + 1)}>{count}</button>
+    </div>
+  );
+}
+
+function App() {
+  return (
+    <>
+      <Counter count={10} />
+      <Counter count={20} />
+    </>
+  );
+}
+
+export default App;
+

useEffect

    +
  1. 不添加依赖项

    +
    +

    组件首次渲染执行一次,以及不管是哪个状态更改引起组件更新时都会重新执行

    +
      +
    • 组件初始渲染
    • +
    • 组件更新 (不管是哪个状态引起的更新)
    • +
    +
    +
    1
    2
    3
    useEffect(() => {
    +  console.log("副作用执行了");
    +});
    +
  2. +
  3. 添加空数组

    +
    +

    组件只在首次渲染时执行一次

    +
    +
    1
    2
    3
    useEffect(() => {
    +  console.log("副作用执行了");
    +}, []);
    +
  4. +
  5. 添加特定依赖项

    +
    +

    副作用函数在首次渲染时执行,在依赖项发生变化时重新执行

    +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    function App() {
    +  const [count, setCount] = useState(0);
    +  const [name, setName] = useState("zs");
    +
    +  useEffect(() => {
    +    console.log("副作用执行了");
    +  }, [count]);
    +
    +  return (
    +    <>
    +      <button
    +        onClick={() => {
    +          setCount(count + 1);
    +        }}
    +      >
    +        {count}
    +      </button>
    +      <button
    +        onClick={() => {
    +          setName("cp");
    +        }}
    +      >
    +        {name}
    +      </button>
    +    </>
    +  );
    +}
    +

    useEffect 回调函数中用到的数据(比如,count)就是依赖数据,就应该出现在依赖项数组中,如果不添加依赖项就会报错。

    +
    +
      +
    1. 如果想要清理副作用 可以在副作用函数中的末尾 return 一个新的函数,在新的函数中编写清理副作用的逻辑

      +
        +
      1. 组件卸载时自动执行
      2. +
      3. 组件更新时,下一个 useEffect 副作用函数执行之前自动执行

        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        import { useEffect, useState } from "react";
        +
        +const App = () => {
        +  const [count, setCount] = useState(0);
        +  useEffect(() => {
        +    const timerId = setInterval(() => {
        +      setCount(count + 1);
        +    }, 1000);
        +    return () => {
        +      // 用来清理副作用的事情
        +      clearInterval(timerId);
        +    };
        +  }, [count]);
        +  return <div>{count}</div>;
        +};
        +
        +export default App;
        +
      4. +
      +
    2. +
    3. 异步的正确写法

      +
      1
      2
      3
      4
      5
      6
      useEffect(() => {
      +  async function fetchData() {
      +    const res = await axios.get("http://geek.itheima.net/v1_0/channels");
      +    console.log(res);
      +  }
      +}, []);
      +
    4. +
    +
  6. +
+

useRef

    +
  1. 获取一个在重新渲染中保存,但改变其值不会触发页面重新渲染的对象。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import { useRef } from "react";
    +
    +export default function Counter() {
    +  let ref = useRef(0);
    +
    +  function handleClick() {
    +    ref.current = ref.current + 1;
    +    alert("你点击了 " + ref.current + " 次!");
    +  }
    +
    +  return <button onClick={handleClick}>点击我!</button>;
    +}
    +
  2. +
  3. 获取DOM实例。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    import { useRef } from "react";
    +
    +export default function CatFriends() {
    +  const firstCatRef = useRef(null);
    +  const secondCatRef = useRef(null);
    +  const thirdCatRef = useRef(null);
    +
    +  function handleScrollToFirstCat() {
    +    firstCatRef.current.scrollIntoView({
    +      behavior: "smooth",
    +      block: "nearest",
    +      inline: "center",
    +    });
    +  }
    +
    +  function handleScrollToSecondCat() {
    +    secondCatRef.current.scrollIntoView({
    +      behavior: "smooth",
    +      block: "nearest",
    +      inline: "center",
    +    });
    +  }
    +
    +  function handleScrollToThirdCat() {
    +    thirdCatRef.current.scrollIntoView({
    +      behavior: "smooth",
    +      block: "nearest",
    +      inline: "center",
    +    });
    +  }
    +
    +  return (
    +    <>
    +      <nav>
    +        <button onClick={handleScrollToFirstCat}>Tom</button>
    +        <button onClick={handleScrollToSecondCat}>Maru</button>
    +        <button onClick={handleScrollToThirdCat}>Jellylorum</button>
    +      </nav>
    +      <div>
    +        <ul>
    +          <li>
    +            <img
    +              src="https://placekitten.com/g/200/200"
    +              alt="Tom"
    +              ref={firstCatRef}
    +            />
    +          </li>
    +          <li>
    +            <img
    +              src="https://placekitten.com/g/300/200"
    +              alt="Maru"
    +              ref={secondCatRef}
    +            />
    +          </li>
    +          <li>
    +            <img
    +              src="https://placekitten.com/g/250/200"
    +              alt="Jellylorum"
    +              ref={thirdCatRef}
    +            />
    +          </li>
    +        </ul>
    +      </div>
    +    </>
    +  );
    +}
    +
  4. +
  5. 通过 Ref 函数参数的形式管理 Ref 列表:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    import { useRef } from "react";
    +
    +export default function CatFriends() {
    +  const itemsRef = useRef(null);
    +
    +  function scrollToId(itemId) {
    +    const map = getMap();
    +    const node = map.get(itemId);
    +    node.scrollIntoView({
    +      behavior: "smooth",
    +      block: "nearest",
    +      inline: "center",
    +    });
    +  }
    +
    +  function getMap() {
    +    if (!itemsRef.current) {
    +      // 首次运行时初始化 Map。
    +      itemsRef.current = new Map();
    +    }
    +    return itemsRef.current;
    +  }
    +
    +  return (
    +    <>
    +      <nav>
    +        <button onClick={() => scrollToId(0)}>Tom</button>
    +        <button onClick={() => scrollToId(5)}>Maru</button>
    +        <button onClick={() => scrollToId(9)}>Jellylorum</button>
    +      </nav>
    +      <div>
    +        <ul>
    +          {catList.map((cat) => (
    +            <li
    +              key={cat.id}
    +              ref={(node) => {
    +                const map = getMap();
    +                if (node) {
    +                  map.set(cat.id, node);
    +                } else {
    +                  map.delete(cat.id);
    +                }
    +              }}
    +            >
    +              <img src={cat.imageUrl} alt={"Cat #" + cat.id} />
    +            </li>
    +          ))}
    +        </ul>
    +      </div>
    +    </>
    +  );
    +}
    +
    +const catList = [];
    +for (let i = 0; i < 10; i++) {
    +  catList.push({
    +    id: i,
    +    imageUrl: "https://placekitten.com/250/200?image=" + i,
    +  });
    +}
    +
  6. +
  7. 子组件通过 forwardRef 选择是否暴露 DOM 元素
    它是这样工作的:

    +
      +
    1. <MyInput ref={inputRef} /> 告诉 React 将对应的 DOM 节点放入 inputRef.current 中。但是,这取决于 MyInput 组件是否允许这种行为, 默认情况下是不允许的。
    2. +
    3. MyInput 组件是使用 forwardRef 声明的。 这让从上面接收的 inputRef 作为第二个参数 ref 传入组件,第一个参数是 props
    4. +
    5. MyInput 组件将自己接收到的 ref 传递给它内部的 <input>
    6. +
    +

    现在,单击按钮聚焦输入框起作用了:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import { forwardRef, useRef } from "react";
    +
    +const MyInput = forwardRef((props, ref) => {
    +  return <input {...props} ref={ref} />;
    +});
    +
    +export default function Form() {
    +  const inputRef = useRef(null);
    +
    +  function handleClick() {
    +    inputRef.current.focus();
    +  }
    +
    +  return (
    +    <>
    +      <MyInput ref={inputRef} />
    +      <button onClick={handleClick}>聚焦输入框</button>
    +    </>
    +  );
    +}
    +
  8. +
  9. 通过命令句柄(useImperativeHandle)暴露一部分的 API

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    // 通过命令句柄暴露一部分 API
    +import { forwardRef, useRef, useImperativeHandle } from "react";
    +
    +const MyInput = forwardRef((props, ref) => {
    +  const realInputRef = useRef(null);
    +  useImperativeHandle(ref, () => ({
    +    // 只暴露 focus,没有别的
    +    focus() {
    +      realInputRef.current.focus();
    +    },
    +  }));
    +  return <input {...props} ref={realInputRef} />;
    +});
    +
    +export default function Form() {
    +  const inputRef = useRef(null);
    +
    +  function handleClick() {
    +    inputRef.current.focus();
    +  }
    +
    +  return (
    +    <>
    +      <MyInput ref={inputRef} />
    +      <button onClick={handleClick}>聚焦输入框</button>
    +    </>
    +  );
    +}
    +
  10. +
  11. flushSync 同步更新 DOM:与 Vue 中的 nextTick 的用途类似,都是用来在 DOM 更改后进行某些操作,但Vue是将该操作放在nextTick的回调队列里面执行,包裹的是目的执行操作,而React是让setState同步更新 DOM。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    import { useState, useRef } from "react";
    +import { flushSync } from "react-dom";
    +
    +export default function TodoList() {
    +  const listRef = useRef(null);
    +  const [text, setText] = useState("");
    +  const [todos, setTodos] = useState(initialTodos);
    +
    +  function handleAdd() {
    +    const newTodo = { id: nextId++, text: text };
    +    flushSync(() => {
    +      setText("");
    +      setTodos([...todos, newTodo]);
    +    });
    +    listRef.current.lastChild.scrollIntoView({
    +      behavior: "smooth",
    +      block: "nearest",
    +    });
    +  }
    +
    +  return (
    +    <>
    +      <button onClick={handleAdd}>添加</button>
    +      <input value={text} onChange={(e) => setText(e.target.value)} />
    +      <ul ref={listRef}>
    +        {todos.map((todo) => (
    +          <li key={todo.id}>{todo.text}</li>
    +        ))}
    +      </ul>
    +    </>
    +  );
    +}
    +
    +let nextId = 0;
    +let initialTodos = [];
    +for (let i = 0; i < 20; i++) {
    +  initialTodos.push({
    +    id: nextId++,
    +    text: "待办 #" + (i + 1),
    +  });
    +}
    +

    函数组件由于没有实例,不能使用 ref 获取,如果想获取组件实例,必须是类组件

    +
    +
  12. +
+

useReducer

    +
  • App.js

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    import { useReducer } from "react";
    +import AddTask from "./AddTask.js";
    +import TaskList from "./TaskList.js";
    +import tasksReducer from "./tasksReducer.js";
    +
    +export default function TaskApp() {
    +  const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
    +
    +  function handleAddTask(text) {
    +    dispatch({
    +      type: "added",
    +      id: nextId++,
    +      text: text,
    +    });
    +  }
    +
    +  function handleChangeTask(task) {
    +    dispatch({
    +      type: "changed",
    +      task: task,
    +    });
    +  }
    +
    +  function handleDeleteTask(taskId) {
    +    dispatch({
    +      type: "deleted",
    +      id: taskId,
    +    });
    +  }
    +
    +  return (
    +    <>
    +      <h1>布拉格的行程安排</h1>
    +      <AddTask onAddTask={handleAddTask} />
    +      <TaskList
    +        tasks={tasks}
    +        onChangeTask={handleChangeTask}
    +        onDeleteTask={handleDeleteTask}
    +      />
    +    </>
    +  );
    +}
    +
    +let nextId = 3;
    +const initialTasks = [
    +  { id: 0, text: "参观卡夫卡博物馆", done: true },
    +  { id: 1, text: "看木偶戏", done: false },
    +  { id: 2, text: "打卡列侬墙", done: false },
    +];
    +
  • +
  • tasksReducer.js

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    export default function tasksReducer(tasks, action) {
    +  switch (action.type) {
    +    case "added": {
    +      return [
    +        ...tasks,
    +        {
    +          id: action.id,
    +          text: action.text,
    +          done: false,
    +        },
    +      ];
    +    }
    +    case "changed": {
    +      return tasks.map((t) => {
    +        if (t.id === action.task.id) {
    +          return action.task;
    +        } else {
    +          return t;
    +        }
    +      });
    +    }
    +    case "deleted": {
    +      return tasks.filter((t) => t.id !== action.id);
    +    }
    +    default: {
    +      throw Error("未知 action:" + action.type);
    +    }
    +  }
    +}
    +
  • +
+

useContext

    +
  1. 常规使用

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    import { createContext, useContext } from "react";
    +// 创建Context对象,可以添加默认值
    +const Context = createContext();
    +
    +function Foo() {
    +  return (
    +    <div>
    +      Foo <Bar />
    +    </div>
    +  );
    +}
    +
    +function Bar() {
    +  // 底层组件通过useContext函数获取数据
    +  const name = useContext(Context);
    +  return <div>Bar {name}</div>;
    +}
    +
    +function App() {
    +  return (
    +    // 顶层组件通过Provider 提供数据
    +    <Context.Provider value={"this is name"}>
    +      <div>
    +        <Foo />
    +      </div>
    +    </Context.Provider>
    +  );
    +}
    +
    +export default App;
    +
  2. +
  3. 在相同的组件中提供并使用 context

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    // App.js
    +
    +import Heading from "./Heading.js";
    +import Section from "./Section.js";
    +
    +export default function Page() {
    +  return (
    +    <Section>
    +      <Heading>主标题</Heading>
    +      <Section>
    +        <Heading>副标题</Heading>
    +        <Heading>副标题</Heading>
    +        <Heading>副标题</Heading>
    +        <Section>
    +          <Heading>子标题</Heading>
    +          <Heading>子标题</Heading>
    +          <Heading>子标题</Heading>
    +          <Section>
    +            <Heading>子子标题</Heading>
    +            <Heading>子子标题</Heading>
    +            <Heading>子子标题</Heading>
    +          </Section>
    +        </Section>
    +      </Section>
    +    </Section>
    +  );
    +}
    +
    +// Section.js
    +import { useContext } from 'react';
    +import { LevelContext } from './LevelContext.js';
    +
    +export default function Section({ children }) {
    +  const level = useContext(LevelContext);
    +  return (
    +    <section className="section">
    +      <LevelContext.Provider value={level + 1}>
    +        {children}
    +      </LevelContext.Provider>
    +    </section>
    +  );
    +}
    +
    +// Heading.js
    +import { useContext } from 'react';
    +import { LevelContext } from './LevelContext.js';
    +
    +export default function Heading({ children }) {
    +  const level = useContext(LevelContext);
    +  switch (level) {
    +    case 0:
    +      throw Error('Heading 必须在 Section 内部!');
    +    case 1:
    +      return <h1>{children}</h1>;
    +    case 2:
    +      return <h2>{children}</h2>;
    +    case 3:
    +      return <h3>{children}</h3>;
    +    case 4:
    +      return <h4>{children}</h4>;
    +    case 5:
    +      return <h5>{children}</h5>;
    +    case 6:
    +      return <h6>{children}</h6>;
    +    default:
    +      throw Error('未知的 level:' + level);
    +  }
    +}
    +
    +
    +// LevelContext.js
    +import { createContext } from 'react';
    +// 默认值
    +export const LevelContext = createContext(1);
    +
  4. +
  5. 使用 ContextReducer 封装一个组件

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import AddTask from "./AddTask.js";
    +import TaskList from "./TaskList.js";
    +import { TasksProvider } from "./TasksContext.js";
    +
    +export default function TaskApp() {
    +  return (
    +    <TasksProvider>
    +      <h1>Day off in Kyoto</h1>
    +      <AddTask />
    +      <TaskList />
    +    </TasksProvider>
    +  );
    +}
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    import { createContext, useContext, useReducer } from "react";
    +
    +const TasksContext = createContext(null);
    +
    +const TasksDispatchContext = createContext(null);
    +
    +export function TasksProvider({ children }) {
    +  const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
    +
    +  return (
    +    <TasksContext.Provider value={tasks}>
    +      <TasksDispatchContext.Provider value={dispatch}>
    +        {children}
    +      </TasksDispatchContext.Provider>
    +    </TasksContext.Provider>
    +  );
    +}
    +
    +export function useTasks() {
    +  return useContext(TasksContext);
    +}
    +
    +export function useTasksDispatch() {
    +  return useContext(TasksDispatchContext);
    +}
    +
    +function tasksReducer(tasks, action) {
    +  switch (action.type) {
    +    case "added": {
    +      return [
    +        ...tasks,
    +        {
    +          id: action.id,
    +          text: action.text,
    +          done: false,
    +        },
    +      ];
    +    }
    +    case "changed": {
    +      return tasks.map((t) => {
    +        if (t.id === action.task.id) {
    +          return action.task;
    +        } else {
    +          return t;
    +        }
    +      });
    +    }
    +    case "deleted": {
    +      return tasks.filter((t) => t.id !== action.id);
    +    }
    +    default: {
    +      throw Error("Unknown action: " + action.type);
    +    }
    +  }
    +}
    +
    +const initialTasks = [
    +  { id: 0, text: "Philosopher’s Path", done: true },
    +  { id: 1, text: "Visit the temple", done: false },
    +  { id: 2, text: "Drink matcha", done: false },
    +];
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    import { useState } from "react";
    +import { useTasksDispatch } from "./TasksContext.js";
    +
    +export default function AddTask() {
    +  const [text, setText] = useState("");
    +  const dispatch = useTasksDispatch();
    +  return (
    +    <>
    +      <input
    +        placeholder="Add task"
    +        value={text}
    +        onChange={(e) => setText(e.target.value)}
    +      />
    +      <button
    +        onClick={() => {
    +          setText("");
    +          dispatch({
    +            type: "added",
    +            id: nextId++,
    +            text: text,
    +          });
    +        }}
    +      >
    +        Add
    +      </button>
    +    </>
    +  );
    +}
    +
    +let nextId = 3;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    import { useState } from "react";
    +import { useTasks, useTasksDispatch } from "./TasksContext.js";
    +
    +export default function TaskList() {
    +  const tasks = useTasks();
    +  return (
    +    <ul>
    +      {tasks.map((task) => (
    +        <li key={task.id}>
    +          <Task task={task} />
    +        </li>
    +      ))}
    +    </ul>
    +  );
    +}
    +
    +function Task({ task }) {
    +  const [isEditing, setIsEditing] = useState(false);
    +  const dispatch = useTasksDispatch();
    +  let taskContent;
    +  if (isEditing) {
    +    taskContent = (
    +      <>
    +        <input
    +          value={task.text}
    +          onChange={(e) => {
    +            dispatch({
    +              type: "changed",
    +              task: {
    +                ...task,
    +                text: e.target.value,
    +              },
    +            });
    +          }}
    +        />
    +        <button onClick={() => setIsEditing(false)}>Save</button>
    +      </>
    +    );
    +  } else {
    +    taskContent = (
    +      <>
    +        {task.text}
    +        <button onClick={() => setIsEditing(true)}>Edit</button>
    +      </>
    +    );
    +  }
    +  return (
    +    <label>
    +      <input
    +        type="checkbox"
    +        checked={task.done}
    +        onChange={(e) => {
    +          dispatch({
    +            type: "changed",
    +            task: {
    +              ...task,
    +              done: e.target.checked,
    +            },
    +          });
    +        }}
    +      />
    +      {taskContent}
    +      <button
    +        onClick={() => {
    +          dispatch({
    +            type: "deleted",
    +            id: task.id,
    +          });
    +        }}
    +      >
    +        Delete
    +      </button>
    +    </label>
    +  );
    +}
    +
  6. +
+

useCallback

React 中的 useCallback 是一个用于缓存函数定义的 Hook。它的作用是避免在每次重新渲染时都创建新的函数,从而提高性能和避免不必要的渲染。

+

useCallback 的使用方法如下:

+
    +
  • useCallback 接收两个参数:回调函数和依赖项数组。回调函数是需要缓存的函数,依赖项数组用于确定何时需要重新创建函数实例。
  • +
  • 当依赖项数组中的任何一个值发生变化时,useCallback 将返回一个新的函数实例,否则它将返回之前缓存的函数实例。
  • +
  • useCallback 返回的函数不会被 React 调用,而是由你自己决定何时以及是否调用它。
  • +
  • useCallback 必须在组件的顶层或自定义 Hook 中调用,不能在循环或条件语句中调用。
  • +
+

useCallback 的使用场景有以下几种:

+
    +
  • 当你需要将一个函数作为 prop 传递给子组件时,你可以使用 useCallback 来避免子组件因为父组件的重新渲染而不必要地重新渲染。例如,下面的代码展示了一个使用 useCallback 的计数器组件,它将一个函数作为 prop 传递给子组件 Button,用来增加计数器的值。由于使用了 useCallbackButton 组件只会在 count 发生变化时才重新渲染,而不会因为父组件的其他 state 变化而重新渲染。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    import React, { useState, useCallback } from "react";
    +
    +function Button({ onClick, children }) {
    +  console.log("Button 组件重新渲染");
    +  return <button onClick={onClick}>{children}</button>;
    +}
    +
    +export default function Counter() {
    +  const [count, setCount] = useState(0);
    +  const [name, setName] = useState("");
    +
    +  const handleIncrement = useCallback(() => {
    +    setCount((prevCount) => prevCount + 1);
    +  }, []);
    +
    +  const handleNameChange = (event) => {
    +    setName(event.target.value);
    +  };
    +
    +  return (
    +    <div>
    +      <p>计数器的值:{count}</p>
    +      <Button onClick={handleIncrement}>增加</Button>
    +      <p>姓名:{name}</p>
    +      <input type="text" value={name} onChange={handleNameChange} />
    +    </div>
    +  );
    +}
    +
  • +
  • 当你需要在一个 memoized 的回调函数中更新 state 时,你可以使用 useCallback 来保证回调函数的引用不变,从而避免触发无限循环。例如,下面的代码展示了一个使用 useCallback 的自定义 Hook,用来获取和设置 localStorage 中的数据。由于使用了 useCallbacksetStoredValue 函数的引用不会在每次重新渲染时改变,从而避免在 useEffect 中触发无限循环。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    import { useState, useEffect, useCallback } from "react";
    +
    +function useLocalStorage(key, initialValue) {
    +  // 获取 localStorage 中的数据
    +  const [storedValue, setStoredValue] = useState(() => {
    +    try {
    +      const item = window.localStorage.getItem(key);
    +      return item ? JSON.parse(item) : initialValue;
    +    } catch (error) {
    +      console.error(error);
    +      return initialValue;
    +    }
    +  });
    +
    +  // 设置 localStorage 中的数据
    +  const setValue = useCallback(
    +    (value) => {
    +      try {
    +        const valueToStore =
    +          value instanceof Function ? value(storedValue) : value;
    +        setStoredValue(valueToStore);
    +        window.localStorage.setItem(key, JSON.stringify(valueToStore));
    +      } catch (error) {
    +        console.error(error);
    +      }
    +    },
    +    [key, storedValue]
    +  );
    +
    +  return [storedValue, setValue];
    +}
    +
  • +
  • 当你需要在一个 useEffect 中使用一个函数时,你可以使用 useCallback 来避免因为函数的重新创建而导致 useEffect 的频繁执行。例如,下面的代码展示了一个使用 useCallback 的组件,用来在组件挂载时获取用户的位置信息,并在用户位置发生变化时更新 state。由于使用了 useCallbackgetLocation 函数的引用不会在每次重新渲染时改变,从而避免在 useEffect 中触发不必要的执行。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    import React, { useState, useEffect, useCallback } from "react";
    +
    +export default function Location() {
    +  const [location, setLocation] = useState({});
    +
    +  // 获取用户的位置信息
    +  const getLocation = useCallback(() => {
    +    navigator.geolocation.getCurrentPosition(
    +      (position) => {
    +        setLocation({
    +          latitude: position.coords.latitude,
    +          longitude: position.coords.longitude,
    +        });
    +      },
    +      (error) => {
    +        console.error(error);
    +      }
    +    );
    +  }, []);
    +
    +  // 在组件挂载时获取用户的位置信息
    +  useEffect(() => {
    +    getLocation();
    +  }, [getLocation]);
    +
    +  return (
    +    <div>
    +      <p>你的位置是:</p>
    +      <p>纬度:{location.latitude}</p>
    +      <p>经度:{location.longitude}</p>
    +    </div>
    +  );
    +}
    +
  • +
+

useMemo

useMemo 是一个 React 钩子,它允许你记住函数调用的结果,并在依赖关系发生变化时重新计算它。它可以通过防止不必要的重渲染来优化 React 应用程序的性能。

+

useMemo 的基本语法是:

+
1
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
+

其中,第一个参数是一个函数,它返回需要被记住的值。第二个参数是一个数组,它指定了哪些变量会影响这个值的计算。如果这个数组为空,那么 useMemo 只会在组件第一次渲染时执行一次。如果这个数组包含了一些变量,那么 useMemo 只会在这些变量发生变化时重新执行。如果这个数组包含了所有的状态和属性,那么 useMemo 的行为就和普通的函数一样,每次渲染都会执行一次。

+

useMemo 的使用场景有以下几种:

+
    +
  • 当组件中有一些复杂的计算逻辑,而这些计算逻辑的结果又不会频繁地变化时,可以使用 useMemo 来缓存这些结果,避免每次渲染都重新计算,提高性能。
  • +
  • 当组件中有一些子组件,而这些子组件的属性是由父组件的状态或属性计算而来时,可以使用 useMemo 来缓存这些属性,避免父组件的状态或属性发生变化时,导致子组件不必要的重渲染。
  • +
  • 当组件中有一些引用类型的状态或属性,如对象或数组时,可以使用 useMemo 来缓存这些状态或属性,避免每次渲染都创建新的引用,导致组件的浅比较失效,引起不必要的重渲染。
  • +
+

下面是一个使用 useMemo 的例子,它展示了如何使用 useMemo 来缓存一个复杂的计算结果,并将其作为子组件的属性传递:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import React, { useState, useMemo } from "react";
+
+// 一个子组件,接收一个数字作为属性,并显示它
+function Child(props) {
+  console.log("Child component rendered");
+  return <div>{props.number}</div>;
+}
+
+// 一个父组件,有一个数字状态,一个按钮,和一个子组件
+function Parent() {
+  // 定义一个数字状态,初始值为0
+  const [number, setNumber] = useState(0);
+
+  // 定义一个函数,用来计算一个复杂的值,这里简单地模拟为一个循环
+  function computeExpensiveValue(num) {
+    console.log("computeExpensiveValue called");
+    let i = 0;
+    while (i < 1000000000) i++;
+    return num * 2;
+  }
+
+  // 使用useMemo来缓存计算结果,只有当number发生变化时,才重新计算
+  const memoizedValue = useMemo(() => computeExpensiveValue(number), [number]);
+
+  // 定义一个函数,用来更新数字状态,每次加1
+  function handleClick() {
+    setNumber(number + 1);
+  }
+
+  // 返回一个JSX元素,包含一个按钮,一个子组件,和一个显示useMemo返回值的div
+  return (
+    <div>
+      <button onClick={handleClick}>Click</button>
+      <Child number={number} />
+      <div>{memoizedValue}</div>
+    </div>
+  );
+}
+
+export default Parent;
+

在这个例子中,当点击按钮时,数字状态会加 1,父组件会重新渲染,但是由于使用了 useMemo,只有当数字状态发生变化时,才会重新调用computeExpensiveValue 函数,否则会直接返回缓存的值。这样就避免了每次渲染都执行一个复杂的计算,提高了性能。同时,由于子组件的属性没有变化,所以子组件也不会重新渲染,这也提高了性能。

+
文章作者: 希亚的西红柿
文章链接: http://blog.refrain.site/posts/a1605017/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
+ + + + \ No newline at end of file diff --git a/posts/aac5e919/index.html b/posts/aac5e919/index.html new file mode 100644 index 000000000..7d1205e8f --- /dev/null +++ b/posts/aac5e919/index.html @@ -0,0 +1,2053 @@ +JS手写题大汇总 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + +

JS手写题大汇总

本文主要记录了关于前端面试常考的手写代码题,常看常复习。

+
+

JS 基础篇

深入了解 JS 这门语言的运行逻辑与机制,尝试实现其内部的方法。

+
+

ES5 实现继承

使用 ES5 的语法实现继承,有几个注意点:

+
    +
  • 使用Object.create方法,构造以父类的prototype为原型的新对象,防止对子类原型对象的修改影响到父类的原型对象。
  • +
  • 注意静态方法的继承,使用Object.setPrototypeOf将父类设置为子类的原型。
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Person(name, age) {
+  this.name = name;
+  this.age = age;
+}
+Person.say = () => {
+  console.log("I'm a person!");
+};
+function Man(name, age, address) {
+  Person.call(this, name, age);
+  this.address = address;
+}
+Man.speak = () => {
+  console.log("I'm a man!");
+};
+Object.setPrototypeOf(Man, Person);
+Man.prototype = Object.create(Person.prototype);
+Man.prototype.constructor = Man;
+const person = new Person("person", 10);
+const man = new Man("man", 11, "Tokyo");
+Man.say();
+Man.speak();
+

验证的例子:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Person {
+  constructor(name, age) {
+    this.name = name;
+    this.age = age;
+  }
+  static say = () => {
+    console.log("I'm a person!");
+  };
+}
+class Man extends Person {
+  constructor(name, age, address) {
+    super(name, age);
+    this.address = address;
+  }
+  static speak = () => {
+    console.log("I'm a man!");
+  };
+}
+const person = new Person("person", 10);
+const man = new Man("man", 11, "Tokyo");
+console.log(person, man);
+console.log(Object.getPrototypeOf(Man) === Person);
+Man.say();
+Man.speak();
+

实现 new 函数

首先对问题进行分析,分析new关键字到底干了什么。

+
    +
  1. 创建一个空对象将该对象的原型设置为函数的prototype
  2. +
  3. 执行函数,this指向指向该对象。
  4. +
  5. 若函数返回结果不为空且为对象,则返回该对象,否则返回先前生成的对象。
  6. +
+
1
2
3
4
5
6
7
8
9
10
/**
+ * @param {Function} constructor
+ * @param {any[]} args - argument passed to the constructor
+ * `myNew(constructor, ...args)` should return the same as `new constructor(...args)`
+ */
+const myNew = (func, ...params) => {
+  const newObj = Object.create(func.prototype);
+  const result = func.apply(newObj, params);
+  return typeof result === "object" && result !== null ? result : newObj;
+};
+

手写 instanceof

沿着原型链循环查找,即寻找当前对象实例的__proto____proto__,是否等于构造函数的prototype

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
+ * @param {any} obj
+ * @param {target} target
+ * @return {boolean}
+ */
+const myInstanceOf = (obj, target) => {
+  if (typeof obj !== "object" || obj === null) {
+    return false;
+  }
+  const proto = target.prototype;
+  let curProto = Object.getPrototypeOf(obj);
+  while (curProto) {
+    if (curProto === proto) {
+      return true;
+    }
+    curProto = Object.getPrototypeOf(curProto);
+  }
+  return false;
+};
+

手写数据类型判断函数

Object.prototype.toString调用与正则,需要注意的是call方法的调用与match方法的返回结果。

+
1
2
3
4
5
6
7
8
9
/**
+ * @param {any} data
+ * @return {string}
+ */
+const detectType = (data) =>
+  Object.prototype.toString
+    .call(data)
+    .match(/^\[object (\w+)\]$/)[1]
+    .toLowerCase();
+

手写 promisify 函数

    +
  1. 注意promisify需要返回一个函数,这个函数返回一个promise对象。
  2. +
  3. 注意 Node.js 中常规的回调函数形式,因此在改变this执行的时候使用call方法代码更加简洁。

    +
    1
    fs.readFile("1.txt", "utf8", (err, data) => {});
    +
  4. +
  5. err的情况就先reject,也是利用了回调函数的特性。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /**
    + * @param {(...args) => void} func
    + * @returns {(...args) => Promise<any>}
    + */
    +const promisify = (func) => {
    +  return function (...params) {
    +    return new Promise((resolve, reject) => {
    +      func.call(this, ...params, (err, data) => {
    +        if (err) {
    +          reject(err);
    +        } else {
    +          resolve(data);
    +        }
    +      });
    +    });
    +  };
    +};
    +
  6. +
+

手写 async 方法

asyncawait的语法其实是generator函数与promise结合的语法糖,通过promise实现自动执行最后实现协程的效果。

+
首先查看 generator 函数的基本使用 +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function* helloWorldGenerator() {
+  yield "hello";
+  yield "world";
+  return "ending";
+  yield "what can i say";
+}
+const hw = helloWorldGenerator();
+
+console.log(hw.next());
+console.log(hw.next());
+console.log(hw.next());
+console.log(hw.next());
+// { value: 'hello', done: false }
+// { value: 'world', done: false }
+// { value: 'ending', done: true }
+// { value: undefined, done: true }
+
+function* dataConsumer() {
+  console.log("Started");
+  console.log(`1. ${yield}`);
+  console.log(`2. ${yield}`);
+  return "result";
+}
+
+const genObj = dataConsumer();
+console.log(genObj.next());
+console.log(genObj.next("a"));
+console.log(genObj.next("b"));
+// Started
+// { value: undefined, done: false }
+// 1. a
+// { value: undefined, done: false }
+// 2. b
+// { value: 'result', done: true }
+
+
+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const asyncFunc = () => {
+  spawn(function* () {});
+};
+const spawn = (genF) => {
+  return new Promise((resolve, reject) => {
+    const gen = genF();
+    const step = (nextF) => {
+      let next;
+      try {
+        next = nextF();
+      } catch (e) {
+        return reject(e);
+      }
+      if (next.done) {
+        return resolve(next.value);
+      }
+      Promise.resolve(next.value).then(
+        (v) => step(() => gen.next(v)),
+        (r) => step(() => gen.throw(r))
+      );
+    };
+    step(() => gen.next(undefined));
+  });
+};
+

手写 EventEmitter 函数

    +
  • 注意emit回调函数的this指向以及参数的问题。
  • +
  • 注意once函数可以将函数包装成另一个执行函数,在执行完这个函数后就移除掉。
  • +
+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
class EventEmitter {
+  constructor() {
+    this.callbacks = {};
+  }
+  addListener(type, callback) {
+    if (!this.callbacks[type]) {
+      this.callbacks[type] = [];
+    }
+    this.callbacks[type].push(callback);
+  }
+  prependListener(type, callback) {
+    if (!this.callbacks[type]) {
+      this.callbacks[type] = [];
+    }
+    this.callbacks[type].unshift(callback);
+  }
+  on(type, callback) {
+    this.addListener(type, callback);
+  }
+  removeListener(type, callback) {
+    if (!this.callbacks[type]) {
+      console.warn("没有订阅该事件!");
+      return;
+    }
+    const index = this.callbacks[type].indexOf(callback);
+    if (index > -1) {
+      this.callbacks[type].splice(index, 1);
+    }
+  }
+  off(type, callback) {
+    this.removeListener(type, callback);
+  }
+  emit(type, ...args) {
+    if (!this.callbacks[type]) {
+      console.warn("没有订阅该事件!");
+      return;
+    }
+    this.callbacks[type].forEach((callback) => callback.apply(this, args));
+  }
+  once(type, callback) {
+    function wrapper(...params) {
+      callback.apply(this, params);
+      this.removeListener(type, wrapper);
+    }
+    this.addListener(type, wrapper);
+  }
+}
+const ee = new EventEmitter();
+
+// 注册所有事件
+ee.once("wakeUp", (name) => {
+  console.log(`${name} 1`);
+});
+ee.on("eat", (name) => {
+  console.log(`${name} 2`);
+});
+ee.on("eat", (name) => {
+  console.log(`${name} 3`);
+});
+const meetingFn = (name) => {
+  console.log(`${name} 4`);
+};
+ee.on("work", meetingFn);
+ee.on("work", (name) => {
+  console.log(`${name} 5`);
+});
+ee.emit("wakeUp", "xx");
+ee.emit("wakeUp", "xx"); // 第二次没有触发
+ee.emit("eat", "xx");
+ee.emit("work", "xx");
+ee.off("work", meetingFn); // 移除事件
+ee.emit("work", "xx"); // 再次工作
+

手写 call,apply,bind

注意自己实现需要实现利用对象的方法隐式绑定做到的,注意bind关键字作为new方法调用的时候对this指向做出特殊的。

+
+

super关键字可以作为构造函数在构造函数里面调用super关键字还做作为对象调用父对象上的方法或者静态方法,但在普通方法里面只能调用普通方法而不能调用静态方法,但在子类的静态方法中则可以都可以调用。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Function.prototype.myCall = function (obj, ...params) {
+  const key = Symbol("key");
+  obj[key] = this;
+  const result = obj[key](...params);
+  delete obj[key];
+  return result;
+};
+Function.prototype.myApply = function (obj, params) {
+  const key = Symbol("key");
+  obj[key] = this;
+  const result = obj[key](...params);
+  delete obj[key];
+  return result;
+};
+Function.prototype.myBind = function (obj, ...params) {
+  const func = this;
+  function bound(...args) {
+    const context = Boolean(new.target) ? this : obj;
+    return func.myApply(context, [...params, ...args]);
+  }
+  bound.prototype = Object.create(func.prototype);
+  bound.prototype.constructor = bound;
+  return bound;
+};
+
+function say(arg1, arg2) {
+  console.log(this.age, arg1, arg2);
+  return 1;
+}
+let person = {
+  age: 3,
+};
+
+let bindSay = say.myBind(person, "我叫", "nova");
+console.log(bindSay());
+new bindSay();
+

手写 Object.create

利用new关键字,实例的__proto__属性会指向构造函数的prototype

+
    +
  • new初始化一个对象的规范:如果构造函数的prototype不是一个对象,则将新创建的对象的__proto__设置为标准的内置的对象的原型对象,即Object.prototype
  • +
  • 在使用Object.create方法时,强制把这个对象的__proto__设置为该参数。目前该方法是将对象原型设置为空的唯一手段。
  • +
  • Class类的prototype属性默认不可写。
  • +
+
1
2
3
4
5
6
7
function Foo() {}
+Foo.prototype = null;
+console.log(new Foo().toString); //outputs function toString() { [native code] } (or whatever)
+
+function Foo() {}
+Foo.prototype = Object.create(null);
+console.log(new Foo().toString); //output undefined
+
1
2
3
4
5
6
7
8
9
10
11
12
/**
+ * @param {any} proto
+ * @return {object}
+ */
+const myObjectCreate = (proto) => {
+  if (typeof proto !== "object" || proto === null) {
+    throw new TypeError("Argument must be an object!");
+  }
+  function A() {}
+  A.prototype = proto;
+  return new A();
+};
+

手写数组 push

这种方法有点取巧,很多特性都是 JS 数组特有的行为,比如数组长度会随之数组的最大索引变化,没有值的数据会自动填充为空。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
+ * Adds one or more elements to the end of an array and returns the new length of the array.
+ *
+ * @param {Array} arr - The target array to which elements will be added.
+ * @param {...*} params - The elements to add to the array.
+ * @returns {number} - The new length of the array after adding the elements.
+ */
+const push = (arr, ...params) => {
+  for (let i = 0; i < params.length; i++) {
+    arr[arr.length] = params[i];
+  }
+  return arr.length;
+};
+const a = [1, 2, 3];
+const b = push(a, 1, 2, 3);
+console.log(a, b);
+

手写字符串 repeat

实现字符串的repeat方法,注意递归的核心为保证字符串不变

+
1
2
3
4
5
6
7
8
9
const repeat = (str, n) => {
+  return new Array(n + 1).join(str);
+};
+// 核心为保证 str 不变
+const repeat0 = (str, n) => {
+  return n > 1 ? str.concat(repeat0(str, n - 1)) : str;
+};
+const a = repeat("sasda", 3);
+console.log(a);
+

手写 Object.assign

+

注意对参数为剩余参数,同时Object强制转换对象会返回对象本身。

+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
+ * @param {any} target
+ * @param {any[]} sources
+ * @return {object}
+ */
+const objectAssign = (target, ...sources) => {
+  if (!target) {
+    throw new TypeError(
+      "TypeError: Cannot convert undefined or null to object"
+    );
+  }
+  const ret = Object(target);
+  sources.forEach((source) => {
+    if (source) {
+      source = Object(source);
+      Reflect.ownKeys(source).forEach((key) => {
+        if (Reflect.getOwnPropertyDescriptor(source, key).enumerable) {
+          ret[key] = source[key];
+          if (ret[key] !== source[key]) {
+            throw new Error();
+          }
+        }
+      });
+    }
+  });
+  return ret;
+};
+
+const a = { a: 1 };
+const b = { b: 2 };
+const c = objectAssign(a, b);
+console.log(a, b, c);
+

实现数组的 flat 方法

核心思路为递归,使用数组的reduce方法能减少不少代码量。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
+ * @param { Array } arr
+ * @param { number } depth
+ * @returns { Array }
+ */
+const flat = (arr, depth = 1) => {
+  const res = [];
+  const step = (array, count) => {
+    if (!Array.isArray(array) || count < 0) {
+      res.push(array);
+      return;
+    }
+    for (const item of array) {
+      step(item, count - 1);
+    }
+  };
+  step(arr, depth);
+  return res;
+};
+
+/**
+ * @param { Array } arr
+ * @param { number } depth
+ * @returns { Array }
+ */
+const flat = (arr, depth = 1) =>
+  depth > 0
+    ? arr.reduce(
+        (pre, cur) =>
+          pre.concat(Array.isArray(cur) ? flat(cur, depth - 1) : [cur]),
+        []
+      )
+    : arr;
+

深入 JS 的使用

柯里化

注意外层函数需要命名以便在递归的时候引用,内层函数则是内部也应该返回函数的相关结果。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
+ * @param { (...args: any[]) => any } fn
+ * @returns { (...args: any[]) => any }
+ */
+const curry = (fn) =>
+  function curryInner(...params) {
+    if (params.length < fn.length)
+      return function (...args) {
+        return curryInner(...params, ...args);
+      };
+    else {
+      return fn.apply(this, params);
+    }
+  };
+
+/**
+ * @param { (...args: any[]) => any } fn
+ * @returns { (...args: any[]) => any }
+ */
+const curry = (fn) =>
+  function curryInner(...params) {
+    if (params.length < fn.length || params.includes(curry.placeholder)) {
+      return function (...args) {
+        params = params.map((item) => {
+          if (item === curry.placeholder) {
+            return args.shift();
+          } else {
+            return item;
+          }
+        });
+        return curryInner(...params, ...args);
+      };
+    } else {
+      return fn.apply(this, params);
+    }
+  };
+
+curry.placeholder = Symbol();
+

深拷贝

采用ES6之后的方法进行,原型链与属性修饰符

+
+

Descriptors来全给你拷了。

+
+
1
2
3
4
5
const copy = (obj) =>
+  Object.create(
+    Object.getPrototypeOf(obj),
+    Object.getOwnPropertyDescriptors(obj)
+  );
+

采用for循环实现深拷贝

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const cloneDeep = (data) => {
+  const map = new WeakMap();
+  const clone = (obj) => {
+    if (typeof obj !== "object" || obj === null) return obj;
+    if (map.has(obj)) return map.get(obj);
+    const newObj = new obj.constructor();
+    map.set(obj, newObj);
+    Reflect.ownKeys(obj).forEach((key) => {
+      newObj[key] = clone(obj[key]);
+    });
+    return newObj;
+  };
+  return clone(data);
+};
+

防抖与节流

防抖的复杂实现注意isInvoked的位置放在了返回函数的里面,这样可以保证初始执行的这一次不会触发trailing这一次的执行。有定时器就清除,无论之前有没有定时器都需要继续设置setTimeout

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
+ * @param {(...args: any[]) => any} func
+ * @param {number} wait
+ * @returns {(...args: any[]) => any}
+ */
+const debounce = (func, wait) => {
+  let timer = null;
+  return function (...params) {
+    if (timer) {
+      clearTimeout(timer);
+    }
+    timer = setTimeout(() => {
+      func.apply(this, params);
+      timer = null;
+    }, wait);
+  };
+};
+
+/**
+ * @param {(...args: any[]) => any} func
+ * @param {number} wait
+ * @param {boolean} option.leading
+ * @param {boolean} option.trailing
+ * @returns {(...args: any[]) => any}
+ */
+const debounce = (func, wait, option = { leading: false, trailing: true }) => {
+  let timer = null;
+  return function (...params) {
+    let isInvoked = false;
+    if (timer) {
+      clearTimeout(timer);
+    } else if (option.leading) {
+      func.apply(this, params);
+      isInvoked = true;
+    }
+    timer = setTimeout(() => {
+      if (option.trailing && !isInvoked) {
+        func.apply(this, params);
+      }
+      timer = null;
+    }, wait);
+  };
+};
+

节流的复杂实现在于实现trailing的功能,这边涉及到递归函数的提取。注意之前有定时器的话就只保存参数什么都不做,没定时器才要设置setTimeout

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
+ * @param {(...args:any[]) => any} func
+ * @param {number} wait
+ * @returns {(...args:any[]) => any}
+ */
+const throttle = (func, wait) => {
+  let timer = null;
+  let lastArgs = null;
+  const run = () => {
+    timer = null;
+    if (lastArgs) {
+      func.apply(this, lastArgs);
+      lastArgs = null;
+      timer = setTimeout(run, wait);
+    }
+  };
+  return function (...params) {
+    if (!timer) {
+      func.apply(this, params);
+      timer = setTimeout(run, wait);
+    } else {
+      lastArgs = params;
+    }
+  };
+};
+
+/**
+ * @param {(...args: any[]) => any} func
+ * @param {number} wait
+ * @param {boolean} option.leading
+ * @param {boolean} option.trailing
+ * @returns {(...args: any[]) => any}
+ */
+const throttle = (func, wait, option = { leading: true, trailing: true }) => {
+  let timer = null;
+  let lastArgs = null;
+  const run = () => {
+    timer = null;
+    if (option.trailing && lastArgs) {
+      func.apply(this, lastArgs);
+      lastArgs = null;
+      timer = setTimeout(run, wait);
+    }
+  };
+  return function (...params) {
+    if (!timer) {
+      if (option.leading) {
+        func.apply(this, params);
+      }
+      timer = setTimeout(run, wait);
+    } else {
+      lastArgs = params;
+    }
+  };
+};
+

并发控制

并发池

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const asyncPool = async (poolLimit, array, iteratorFn) => {
+  const ret = [],
+    executing = [];
+  for (const item of array) {
+    const p = Promise.resolve(iteratorFn(item));
+    ret.push(p);
+    if (poolLimit < array.length) {
+      const e = p.finally(() => executing.splice(executing.indexOf(e), 1));
+      executing.push(e);
+      if (executing.length >= poolLimit) {
+        await Promise.race(executing);
+      }
+    }
+  }
+  return Promise.all(ret);
+};
+
+const timeout = (i) =>
+  new Promise((resolve) =>
+    setTimeout(() => {
+      console.log(i);
+      resolve(i);
+    }, i)
+  );
+
+asyncPool(2, [1000, 5000, 3000, 2000], timeout);
+

递归版本,也叫能够实现并发控制的Promise.all版本。注意while循环的巧妙使用,先将能填进去的数组先填进去。为了严格与Promise.all方法保持一致,这里做到了索引与数组直接相同。同时还需要注意递归调用的重要性。同时还有万能并发池的版本,有点缺陷的地方在于数组splice方法的时间复杂度部分。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
+ * @param {Array<() => Promise<any>>} funcs
+ * @param {number} max
+ * @return {Promise}
+ */
+const throttlePromises = (funcs, max = Infinity) => {
+  return new Promise((resolve, reject) => {
+    const length = funcs.length;
+    const res = new Array(length);
+    let count = 0,
+      running = 0,
+      index = 0;
+    const run = () => {
+      while (funcs.length && running < max) {
+        const func = funcs.shift();
+        const idx = index;
+        func().then((v) => {
+          res[idx] = v;
+          running--;
+          count++;
+          if (count === length) {
+            resolve(res);
+          }
+          run();
+        }, reject);
+        running++;
+        index++;
+      }
+    };
+    run();
+  });
+};
+
+/**
+ * @param {Array<() => Promise<any>>} funcs
+ * @param {number} max
+ * @return {Promise}
+ */
+const throttlePromises = async (funcs, max = Infinity) => {
+  const ret = [],
+    executing = [];
+  for (const func of funcs) {
+    const p = Promise.resolve(func());
+    ret.push(p);
+    if (funcs.length > max) {
+      const e = p.finally(() => executing.splice(executing.indexOf(e), 1));
+      executing.push(e);
+      if (executing.length >= max) {
+        await Promise.race(executing);
+      }
+    }
+  }
+  return Promise.all(ret);
+};
+

调度器版本

+

难绷,曾经最熟悉的一题,居然没做出来。🤡 卡点主要在于add方法需要返回一个promise对象,这个promise对象的状态应该由task函数的执行结果来决定,但根据并发控制的需求,不能直接执行task函数,这样违背了并发控制的需求,需要将这个函数再包装一下,包装成另一个函数即() => task().then(resolve, reject),推入任务队列,其他即为递归的调度执行。注意由于任务是一个个加进来的所以使用的是if判断条件,和上面的还是有一些不一样。

+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class Scheduler {
+  constructor(size = 2) {
+    this.size = size;
+    this.queue = [];
+    this.running = 0;
+  }
+  add(task) {
+    return new Promise((resolve, reject) => {
+      this.queue.push(() => task().then(resolve, reject));
+      this.executeNext();
+    });
+  }
+  executeNext() {
+    if (this.queue.length && this.running < this.size) {
+      const task = this.queue.shift();
+      task().finally(() => {
+        this.running--;
+        this.executeNext();
+      });
+      this.running++;
+    }
+  }
+}
+
+const timeout = (time) =>
+  new Promise((resolve) => {
+    setTimeout(resolve, time);
+  });
+const timeout0 = (time) =>
+  new Promise((resolve, reject) => {
+    setTimeout(reject, time);
+  }).catch((e) => e);
+
+const scheduler = new Scheduler();
+const addTask = (time, order) => {
+  scheduler.add(() => timeout(time)).then(() => console.log(order));
+};
+const addTask0 = (time, order) => {
+  scheduler.add(() => timeout0(time)).then(() => console.log(order));
+};
+
+addTask(1000, "1");
+addTask0(500, "2");
+addTask(300, "3");
+addTask(400, "4");
+
+// output: 2 3 1 4
+// 一开始,1、2两个任务进入队列
+// 500ms时,2完成,输出2,任务3进队
+// 800ms时,3完成,输出3,任务4进队
+// 1000ms时,1完成,输出1
+// 1200ms时,4完成,输出4
+

lodash.get

基本思路为解析路径至数组然后使用数组的reduce方法进行递归获取对象

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
+ * @param {object} source
+ * @param {string | string[]} path
+ * @param {any} [defaultValue]
+ * @return {any}
+ */
+const get = (source, path, defaultValue = undefined) => {
+  if (!Array.isArray(path)) {
+    path = path.replace(/\[(\w+)\]/g, ".$1").split(".");
+  }
+  if (path.length === 0) {
+    return defaultValue;
+  }
+  const res = path.reduce((pre, cur) => pre[cur], source);
+  return res ?? defaultValue;
+};
+

lodash.set

这个比较复杂一些,需要注意的细节有点多。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
+ * @param {object} obj
+ * @param {string | string[]} path
+ * @param {any} value
+ */
+const set = (obj, path, value) => {
+  if (!Array.isArray(path)) {
+    path = path.replace(/\[(\w+)\]/g, ".$1").split(".");
+  }
+  const isNumber = (str) => str === String(Number(str));
+  path.reduce((pre, cur, index) => {
+    if (index === path.length - 1) {
+      pre[cur] = value;
+    }
+    if (!pre[cur]) {
+      if (isNumber(path[index + 1])) {
+        pre[cur] = [];
+      } else {
+        pre[cur] = {};
+      }
+    }
+    return pre[cur];
+  }, obj);
+};
+

千分位格式化

两种方式,循环与正则,推荐正则,注意正先行断言-存在使用的一些注意事项。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
+ * @param {number} num
+ * @return {string}
+ */
+const addComma = (num) => {
+  const [p, q] = String(num).split(".");
+  let count = 0,
+    res = "";
+  const regex = /[0-9]/;
+  for (let i = p.length - 1; i >= 0; i--) {
+    res = p[i] + res;
+    if (++count % 3 === 0 && regex.test(p[i - 1])) {
+      res = "," + res;
+    }
+  }
+  if (!q) {
+    return res;
+  }
+  return res + "." + q;
+};
+
+/**
+ * @param {number} num
+ * @return {string}
+ */
+const addComma = (num) => {
+  let [p, q] = String(num).split(".");
+  p = p.replace(/(\d)(?=(\d{3})+$)/g, "$1,");
+  if (!q) {
+    return p;
+  }
+  return p + "." + q;
+};
+
+console.log(addComma(-10000000000.102));
+

LazyMan

很经典的手写题,值得注意的地方有:

+
    +
  1. 维护一个tasks队列存储所有任务。
  2. +
  3. 使用setTimeout函数将任务推入宏任务队列,并且使用asyncawait语法配合for循环控制任务的执行顺序。
  4. +
  5. 返回一个对象,对象身上有相关的执行方法,每个执行方法都返回对象本身,实现链式调用,注意相关方法不能使用箭头函数,这样this指向会出现问题,不会指向返回的对象。
  6. +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// interface Laziness {
+//   sleep: (time: number) => Laziness
+//   sleepFirst: (time: number) => Laziness
+//   eat: (food: string) => Laziness
+// }
+
+/**
+ * @param {string} name
+ * @param {(log: string) => void} logFn
+ * @returns {Laziness}
+ */
+const LazyMan = (name, logFn) => {
+  const tasks = [() => logFn(`Hi, I'm ${name}.`)];
+  const eat = (food) => logFn(`Eat ${food}.`);
+  const sleep = (time) =>
+    new Promise((resolve) => setTimeout(() => resolve(), 1000 * time)).then(
+      () => logFn(`Wake up after ${time} second${time > 1 ? "s" : ""}.`)
+    );
+  setTimeout(async () => {
+    for (const func of tasks) {
+      await func();
+    }
+  });
+  return {
+    eat(food) {
+      tasks.push(() => eat(food));
+      return this;
+    },
+    sleep(time) {
+      tasks.push(() => sleep(time));
+      return this;
+    },
+    sleepFirst(time) {
+      tasks.unshift(() => sleep(time));
+      return this;
+    },
+  };
+};
+
+const HardMan = (name) => {
+  const tasks = [() => console.log(`I am ${name}`)];
+  const rest = (i) =>
+    new Promise((resolve) => setTimeout(resolve, i * 1000)).then(() =>
+      console.log(`Start learning after ${i} seconds`)
+    );
+  const learn = (subject) => console.log(`Learning ${subject}`);
+  setTimeout(async () => {
+    for (const task of tasks) {
+      await task();
+    }
+  });
+  return {
+    rest(i) {
+      tasks.push(() => rest(i));
+      return this;
+    },
+    restFirst(i) {
+      tasks.unshift(() => rest(i));
+      return this;
+    },
+    learn(subject) {
+      tasks.push(() => learn(subject));
+      return this;
+    },
+  };
+};
+
+// HardMan("jack");
+// I am jack
+
+// HardMan("jack").rest(10).learn("computer");
+// 输出
+// I am jack
+// 等待10秒
+// Start learning after 10 seconds
+// Learning computer
+
+// HardMan("jack").restFirst(5).learn("chinese");
+// 输出
+// 等待5秒
+// Start learning after 5 seconds
+// I am jack
+// Learning chinese
+

数组转树

JSON文件转化为树结构的问题,关键在于利用对象的引用和map结构来进行操作。

面试的时候这么简单的题居然没做出来,顶级 🤡,DFS入脑了看啥都想DFS,其实只要对每个对象建立一个Map,然后遍历对象进行赋值就好了,主要还是利用了对象本身的唯一性。

+

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
const arr = [
+  { id: 1, name: "部门1", pid: 0 },
+  { id: 2, name: "部门2", pid: 1 },
+  { id: 3, name: "部门3", pid: 1 },
+  { id: 4, name: "部门4", pid: 3 },
+  { id: 5, name: "部门5", pid: 4 },
+];
+
+const target = [
+  {
+    id: 1,
+    name: "部门1",
+    pid: 0,
+    children: [
+      {
+        id: 2,
+        name: "部门2",
+        pid: 1,
+        children: [],
+      },
+      {
+        id: 3,
+        name: "部门3",
+        pid: 1,
+        children: [],
+      },
+    ],
+  },
+];
+const convertToTree = (arr) => {
+  const map = new Map();
+  for (const item of arr) {
+    map.set(item.id, item);
+  }
+  let res = null;
+  for (const item of arr) {
+    if (item.pid === 0) {
+      res = item;
+      continue;
+    }
+    const parent = map.get(item.pid);
+    if (!parent.children) {
+      parent.children = [];
+    }
+    parent.children.push(item);
+  }
+  return res;
+};
+console.dir(convertToTree(arr), { depth: null });
+
+// {
+//   id: 1,
+//   name: '部门1',
+//   pid: 0,
+//   children: [
+//     { id: 2, name: '部门2', pid: 1 },
+//     {
+//       id: 3,
+//       name: '部门3',
+//       pid: 1,
+//       children: [
+//         {
+//           id: 4,
+//           name: '部门4',
+//           pid: 3,
+//           children: [ { id: 5, name: '部门5', pid: 4 } ]
+//         }
+//       ]
+//     }
+//   ]
+// }
+

斐波那契数列的两种实现方式

尾调用优化解决问题。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const fib = (n, a = 0, b = 1) => {
+  if (n === 0) {
+    return a;
+  }
+  return fib(n - 1, b, a + b);
+};
+
+const fib = (n) => {
+  if (n === 0) return 0;
+  if (n === 1) return 1;
+  return fib(n - 1) + fib(n - 2);
+};
+
+fib(10); // 55
+fib(1000); // timeout
+

业务场景题

    +
  1. 利用迭代器来使asyncawait来阻塞当前函数的执行线程。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    const sleep = (delay) =>
    +  new Promise((resolve) => {
    +    setTimeout(resolve, delay);
    +  });
    +
    +async function test() {
    +  console.log(1);
    +  await sleep(1000);
    +  console.log("Stop for 1s!");
    +  await sleep(2000);
    +  console.log("Stop for 2s!");
    +  await sleep(3000);
    +  console.log("Stop for 3s!");
    +}
    +test();
    +
  2. +
  3. 使用promise封装一个ajax请求

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    <body>
    +  <button>发送ajax请求</button>
    +  <script>
    +    //1.获取DOM元素对象
    +    let btn = document.querySelector("button");
    +    //2.绑定事件
    +    btn.onclick = function () {
    +      //3.创建promise实例对象
    +      const p = new Promise((resolve, reject) => {
    +        //4.创建ajax实例对象
    +        const xhr = new XMLHttpRequest();
    +        //5.打开请求
    +        xhr.open(
    +          "get",
    +          "https://www.yiketianqi.com/free/day?appid=82294778&appsecret=4PKVFula&unescape=1"
    +        );
    +        //6.发送请求
    +        xhr.send();
    +        //7.利用onreadystatechange事件
    +        xhr.onreadystatechange = function () {
    +          //8.判断
    +          if (xhr.readyState == 4) {
    +            if (xhr.status == 200) {
    +              resolve(xhr.responseText);
    +            } else {
    +              reject(xhr.response);
    +            }
    +          }
    +        };
    +      });
    +      p.then(
    +        (value) => {
    +          console.log(JSON.parse(value));
    +        },
    +        (reason) => {
    +          console.log("获取信息失败");
    +        }
    +      );
    +    };
    +  </script>
    +</body>
    +
  4. +
  5. 实现日期格式化函数

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const dateFormat = (dateString, format) => {
    +  const date = new Date(dateString);
    +  const day = date.getDay();
    +  const month = date.getMonth() + 1;
    +  const year = date.getFullYear();
    +  format = format.replace(/yyyy/, year);
    +  format = format.replace(/MM/, month);
    +  format = format.replace(/dd/, day);
    +  return format;
    +};
    +dateFormat("2020-12-01", "yyyy/MM/dd"); // 2020/12/01
    +dateFormat("2020-04-01", "yyyy/MM/dd"); // 2020/04/01
    +dateFormat("2020-04-01", "yyyy年MM月dd日"); // 2020年04月01日
    +
  6. +
  7. 交换a, b的值不能用临时变量

    +
    1
    2
    3
    b = a + b;
    +a = b - a;
    +b = b - a;
    +
  8. +
  9. 注意ajax一定需要send方法

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    function getJSON(url) {
    +  const p = new Promise((resolve, reject) => {
    +    const xhr = new XMLHttpRequest();
    +    xhr.open("GET", url);
    +    xhr.responseType = "json";
    +    xhr.setRequestHeader("Accept", "application/json");
    +    xhr.onreadystatechange = () => {
    +      if (xhr.readyState === XMLHttpRequest.DONE) {
    +        if (xhr.status === 200) {
    +          return resolve(xhr.responseText);
    +        }
    +      }
    +      reject(xhr.statusText);
    +    };
    +    xhr.send();
    +  });
    +  return p;
    +}
    +
  10. +
  11. 数组的乱序输出

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    const a = [1, 2, 3, 4, 5];
    +const randomOutput = (arr) => {
    +  for (let i = 0; i < arr.length; i++) {
    +    const index = Math.floor(Math.random() * (arr.length - i)) + i;
    +    [arr[index], arr[i]] = [arr[i], arr[index]];
    +    console.log(arr[i]);
    +  }
    +};
    +const randomOutput0 = (arr) => {
    +  let length = arr.length;
    +  while (length) {
    +    const index = Math.floor(Math.random() * length--);
    +    [arr[index], arr[length]] = [arr[length], arr[index]];
    +    console.log(arr[length]);
    +  }
    +};
    +randomOutput(a);
    +console.log(a);
    +
  12. +
  13. 实现数组的斜向打印

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    function printMatrix(arr) {
    +  const m = arr.length,
    +    n = arr[0].length;
    +  const result = [];
    +  for (let i = 0; i < n; i++) {
    +    for (let j = 0, k = i; k >= 0 && j < m; j++, k--) {
    +      result.push(arr[j][k]);
    +    }
    +  }
    +  for (let i = 1; i < m; i++) {
    +    for (let j = n - 1, k = i; j > 0 && k < m; k++, j--) {
    +      result.push(arr[k][j]);
    +    }
    +  }
    +  return result;
    +}
    +console.log(
    +  printMatrix([
    +    [1, 2, 3, 4],
    +    [4, 5, 6, 4],
    +    [7, 8, 9, 4],
    +  ])
    +);
    +
  14. +
  15. 电话号码的打印:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const changeNum = (str) => {
    +  return str.replace(/(\d{3})(\d{4})(\d{4})/, "$1****$2");
    +};
    +const changeNum0 = (str) => {
    +  const arr = Array.from(str);
    +  arr.splice(3, 4, "****");
    +  return arr.join("");
    +};
    +const changeNum1 = (str) => {
    +  return str.replace(str.slice(3, 7), "****");
    +};
    +
    +console.log(changeNum1("15727709770"));
    +
  16. +
  17. 循环打印方案

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    const task = (light, delay, callback) => {
    +  setTimeout(() => {
    +    if (light === "red") {
    +      console.log("yellow");
    +    } else if (light === "yellow") {
    +      console.log("green");
    +    } else if (light === "green") {
    +      console.log("red");
    +    }
    +    callback();
    +  }, delay);
    +};
    +const step = () => {
    +  task("green", 1000, () =>
    +    task("red", 1000, () => task("yellow", 1000, () => step()))
    +  );
    +};
    +step();
    +// 利用 promise 包装对象链式递归调用
    +const task = (delay, light) => {
    +  return new Promise((resolve, reject) => {
    +    setTimeout(() => {
    +      if (light === "red") {
    +        console.log("red");
    +      } else if (light === "yellow") {
    +        console.log("yellow");
    +      } else if (light === "green") {
    +        console.log("green");
    +      }
    +      resolve();
    +    }, delay);
    +  });
    +};
    +
    +const step = () => {
    +  const p = new Promise((resolve, reject) => {
    +    resolve(task(1000, "red"));
    +  });
    +  p.then(() => task(1000, "green"))
    +    .then(() => task(1000, "yellow"))
    +    .then(step);
    +};
    +step();
    +
    +const taskMaker = () => {
    +  let run = true;
    +  const stop = () => (run = false);
    +  const execute = async () => {
    +    while (run) {
    +      await task(1000, "red");
    +      await task(1000, "yellow");
    +      await task(1000, "green");
    +    }
    +  };
    +  return { stop, execute };
    +};
    +const { stop, execute } = taskMaker();
    +execute();
    +console.time("Test");
    +setTimeout(() => {
    +  console.log("stop");
    +  stop();
    +  console.timeEnd("Test");
    +}, 4000);
    +
  18. +
  19. 丢手帕问题

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    const Josephu = (num, count) => {
    +  const circle = [];
    +  for (let i = 0; i < num; i++) {
    +    circle[i] = i + 1;
    +  }
    +  let counter = 0;
    +  let out = 0;
    +  for (let i = 0; i < circle.length; i++) {
    +    if (out >= circle.length - 1) break;
    +    if (circle[i]) {
    +      counter++;
    +      if (counter === count) {
    +        circle[i] = 0;
    +        counter = 0;
    +        out++;
    +      }
    +    }
    +    if (i === circle.length - 1) i = -1;
    +  }
    +  for (let i = 0; i < circle.length; i++) {
    +    if (circle[i]) return circle[i];
    +  }
    +};
    +function childNum(num, count) {
    +  let allplayer = [];
    +  for (let i = 0; i < num; i++) {
    +    allplayer[i] = i + 1;
    +  }
    +
    +  let exitCount = 0; // 离开人数
    +  let counter = 0; // 记录报数
    +  let curIndex = 0; // 当前下标
    +
    +  while (exitCount < num - 1) {
    +    if (allplayer[curIndex] !== 0) counter++;
    +
    +    if (counter == count) {
    +      allplayer[curIndex] = 0;
    +      counter = 0;
    +      exitCount++;
    +    }
    +    curIndex++;
    +    if (curIndex == num) {
    +      curIndex = 0;
    +    }
    +  }
    +  for (i = 0; i < num; i++) {
    +    if (allplayer[i] !== 0) {
    +      return allplayer[i];
    +    }
    +  }
    +}
    +console.log(Josephu(39, 6));
    +console.log(childNum(39, 6));
    +
  20. +
  21. 查找文章中出现频率最高的单词

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    const findMostWord = (article) => {
    +  if (!article) {
    +    console.error("Argument cannot be undefined or null!");
    +  }
    +  const str = article.trim().toLowerCase();
    +  const words = str.match(/[a-z]+/g);
    +  let maxCount = 0,
    +    maxStr = "",
    +    set = new Set();
    +  words.forEach((item) => {
    +    if (!set.has(item)) {
    +      set.add(item);
    +      const count = str.match(new RegExp(`\\b${item}\\b`, "g")).length;
    +      if (count > maxCount) {
    +        maxStr = item;
    +        maxCount = count;
    +      }
    +    }
    +  });
    +  return maxStr + ":" + maxCount;
    +};
    +const findMostWord0 = (article) => {
    +  // 合法性判断
    +  if (!article) return;
    +  // 参数处理
    +  article = article.trim().toLowerCase();
    +  let wordList = article.match(/[a-z]+/g),
    +    visited = [],
    +    maxNum = 0,
    +    maxWord = "";
    +  article = " " + wordList.join("  ") + " ";
    +  // 遍历判断单词出现次数
    +  wordList.forEach(function (item) {
    +    if (visited.indexOf(item) < 0) {
    +      // 加入 visited
    +      visited.push(item);
    +      let word = new RegExp(" " + item + " ", "g"),
    +        num = article.match(word).length;
    +      if (num > maxNum) {
    +        maxNum = num;
    +        maxWord = item;
    +      }
    +    }
    +  });
    +  return maxWord + "  " + maxNum;
    +};
    +console.log(findMostWord0("a a a a a a bbb bbb bbb b b b b b b b b b b b"));
    +
  22. +
  23. setTimeout模仿setInterval

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const mySetInterval = (fn, wait) => {
    +  const timer = { flag: true };
    +  const step = () => {
    +    if (timer.flag) {
    +      fn();
    +      setTimeout(step, wait);
    +    }
    +  };
    +  step();
    +  return timer;
    +};
    +const timer = mySetInterval(() => console.log(10), 1000);
    +setTimeout(() => (timer.flag = false), 5000);
    +
  24. +
  25. 判断对象中是否存在循环引用

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    const isCircle = (target) => {
    +  const set = new Set();
    +  const step = (obj) => {
    +    if (typeof obj !== "object" || obj === null) return false;
    +    if (set.has(obj)) return true;
    +    set.add(obj);
    +    for (const key of Reflect.ownKeys(obj)) {
    +      const result = step(obj[key]);
    +      if (result) return true;
    +    }
    +  };
    +  return step(target);
    +};
    +const a = { a: 1 };
    +a.b = a;
    +console.log(a);
    +console.log(isCircle(a.b));
    +
  26. +
  27. 手写一个undefinedToNull函数

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /**
    + * @param {any} arg
    + * @returns any
    + */
    +const undefinedToNull = (arg) => {
    +  if (arg === undefined) return null;
    +  if (typeof arg !== "object" || arg === null) return arg;
    +  for (const key in arg) {
    +    arg[key] = undefinedToNull(arg[key]);
    +  }
    +  return arg;
    +};
    +
  28. +
  29. 判断字符串的有效数字

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
    + * @param {string} str
    + * @returns {boolean}
    + */
    +const validateNumberString = (str) => {
    +  return str !== "" && !isNaN(str);
    +};
    +const validateNumberString = (str) => {
    +  return /^[+-]?(\d+(\.\d*)?|\d*\.\d+)(e[+-]?\d+)?$/i.test(str);
    +};
    +
  30. +
  31. 实现一个累加器

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
    + * @param {number} num
    + */
    +const sum = (count) => {
    +  function sumInner(number) {
    +    return sum(count + number);
    +  }
    +  sumInner.valueOf = () => count;
    +  return sumInner;
    +};
    +
  32. +
  33. counter function自执行包裹

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const count = (() => {
    +  let num = 0;
    +  function func() {
    +    return ++num;
    +  }
    +  func.reset = () => {
    +    num = 0;
    +  };
    +  return func;
    +})();
    +
  34. +
  35. counter对象,简单的数据代理

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /**
    + * @returns { {count: number}}
    + */
    +const createCounter = () => {
    +  let count = 0;
    +  return Object.defineProperty({}, "count", {
    +    get() {
    +      return count++;
    +    },
    +    set() {
    +      console.log("it cannot be altered");
    +    },
    +  });
    +};
    +
  36. +
  37. 失败后自动发起请求,超时后停止。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    /**
    + * @param {() => Promise<any>} fetcher
    + * @param {number} maximumRetryCount
    + * @return {Promise<any>}
    + */
    +function fetchWithAutoRetry(fetcher, maximumRetryCount) {
    +  // your code here
    +  return new Promise((resolve, reject) => {
    +    let count = 0;
    +    const run = () => {
    +      fetcher().then(resolve, (r) => {
    +        count++;
    +        if (count > maximumRetryCount) return reject(r);
    +        run();
    +      });
    +    };
    +    run();
    +  });
    +}
    +
  38. +
  39. 封装一个fetch请求

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    class HTTPRequestUtil {
    +  async get(url) {
    +    const res = await fetch(url);
    +    const data = await res.json();
    +    return data;
    +  }
    +  async post(url, data) {
    +    const res = await fetch(url, {
    +      method: "POST",
    +      headers: {
    +        "Content-type": "application/json",
    +      },
    +      body: JSON.stringify(data),
    +    });
    +    const result = await res.json();
    +    return result;
    +  }
    +  async put(url, data) {
    +    const res = await fetch(url, {
    +      method: "PUT",
    +      headers: {
    +        "Content-type": "application/json",
    +      },
    +      body: JSON.stringify(data),
    +    });
    +    const result = await res.json();
    +    return result;
    +  }
    +  async delete(url, data) {
    +    const res = await fetch(url, {
    +      method: "DELETE",
    +      headers: {
    +        "Content-type": "application/json",
    +      },
    +      body: JSON.stringify(data),
    +    });
    +    const result = await res.json();
    +    return result;
    +  }
    +}
    +
  40. +
+
文章作者: 希亚的西红柿
文章链接: http://blog.refrain.site/posts/aac5e919/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
+ + + + \ No newline at end of file diff --git a/posts/acc0017d/index.html b/posts/acc0017d/index.html new file mode 100644 index 000000000..93a39e3d4 --- /dev/null +++ b/posts/acc0017d/index.html @@ -0,0 +1,1459 @@ +数据结构与算法笔记 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + + + + +

数据结构与算法笔记

本篇文章将介绍数据结构与算法的基本概念(前端版)。

+
+

去年这个时间我在写数据结构与算法的博文,今年还在写,令人感叹。

+
+

基础

算法的基本概念

算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制
也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出
算法的五大特性:

+
    +
  • 有限性(Finiteness):一个算法必须保证执行有限步之后结束
  • +
  • 确切性(Definiteness): 一个算法的每一步骤必须有确切的定义
  • +
  • 输入(Input):一个算法有零个或多个输入,以刻画运算对象的初始情况,所谓零个输入是指算法本身给定了初始条件
  • +
  • 输出(Output):一个算法有一个或多个输出。没有输出的算法毫无意义
  • +
  • 可行性(Effectiveness):算法中执行的任何计算步骤都是可以被分解为基本的可执行的操作步骤,即每个计算步骤都可以在有限时间内完成(也称之为有效性
  • +
+

算法的复杂度

时间复杂度是指执行一个算法所需要的计算工作量,它反映了算法的执行效率和数据规模的关系。我们通常用大 O 符号来表示时间复杂度,例如$O(n)、O(log_n)、O(n^2)$等。时间复杂度越小,算法的效率越高。

+

空间复杂度是指执行一个算法所需要的内存空间,它反映了算法的存储开销和数据规模的关系。我们也用大 O 符号来表示空间复杂度,例如$O(1)、O(n)、O(n^2)$等。空间复杂度越小,算法的空间利用率越高。

+
查看常用排序算法的复杂度 +
+
排序算法平均时间复杂度最好情况最坏情况空间复杂度排序方式稳定性
冒泡排序O(n2)O(n)O(n2)O(1)In-place稳定
选择排序O(n2)O(n2)O(n2)O(1)In-place不稳定
插入排序O(n2)O(n)O(n2)O(1)In-place稳定
希尔排序O(n log n)O(n log2 n)O(n log2 n)O(1)In-place不稳定
归并排序O(n log n)O(n log n)O(n log n)O(n)Out-place稳定
快速排序O(n log n)O(n log n)O(n2)O(log n)In-place不稳定
堆排序O(n log n)O(n log n)O(n log n)O(1)In-place不稳定
计数排序O(n + k)O(n + k)O(n + k)O(k)Out-place稳定
桶排序O(n + k)O(n + k)O(n2)O(n + k)Out-place稳定
基数排序O(n x k)O(n x k)O(n x k)O(n + k)Out-place稳定

相关术语解释:

  • 稳定:如果 a 原本在 b 前面,而 a=b,排序之后,a 仍然在 b 的前面
  • 不稳定:不满足稳定定义
  • 内排序(In-place):所有排序操作都在内存中完成
  • 外排序(Out-place):由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行。
  • 时间复杂度:一个算法执行所耗费的时间
  • 空间复杂度:运行完一个程序所需内存的大小
  • n:数据规模
  • k:「桶」的个数
  • In-place:不占用额外内存
  • Out-place:占用额外内存
+
+
+

数据结构

栈与队列

    +
  1. 栈是一种先进后出的线性表,JS中的数组可以实现一个栈(限定poppush操作)。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    class Stack {
    +  private stack: number[];
    +  private length: number;
    +  constructor(length: number) {
    +    this.length = length;
    +    this.stack = [];
    +  }
    +  pop() {
    +    if (!this.stack.length) {
    +      console.error("Stack is empty!");
    +      return;
    +    }
    +    console.log(this.stack.pop());
    +  }
    +  push(el: number) {
    +    if (this.stack[this.length - 1]) {
    +      console.error("Stack is full!");
    +      return;
    +    }
    +    this.stack.push(el);
    +  }
    +  peek() {
    +    if (!this.stack.length) {
    +      console.error("Stack is empty!");
    +      return;
    +    }
    +    return this.stack[this.stack.length - 1];
    +  }
    +}
    +
    查看栈的应用,表达式的计算 +
    +

    中缀表达式计算问题

    前缀表达式求值过程

    1. 右到左 扫描表达式

    2. 遇到 数字 时,将数字压入堆栈

    3. 遇到 运算符

      弹出栈顶的两个数(栈顶和次顶),用运算符对它们做相应的计算,并将结果入栈。

      计算顺序是: 弹出来的 (运算符) 弹出来的

    然后重复以上步骤,直到表达式的最左端,最后运算出的值则是表达式的值。

    看完前缀表达式的计算逻辑,那么你要明白的是,从一个 中缀表达式 转换为 前缀表达式 时,优先级顺序是已经处理好的,因为在求值时,不进行优先级的判定

    例如:(3+4)x5-6 对应的前缀表达式为:- x + 3 4 5 6,前缀表达式求值步骤如下:

    1. 从右到左扫描,将 6、5、4、3 压入栈

    2. 遇到 + 运算符时:

      将弹出 3 和 4(3 为栈顶元素,4 为次顶元素),计算出 3 + 4 = 7,将结果压入栈

    3. 遇到 x 运算符时

      将弹出 7 和 5,计算出 7 x 5 = 35,将 35 压入栈

    4. 遇到 - 运算符时

      将弹出 35 和 6,计算出 35 - 6 = 29,压入栈

    5. 扫描结束,栈中留下的唯一一个数字 29 则是表达式的值

    后缀表达式(逆波兰表达式)

    后缀表达式 又称为 逆波兰表达式,与前缀表达式类似,只是 运算符 位于 操作数之后

    比如:(3+4)x5-6 对应的后缀表达式 3 4 + 5 x 6 -

    再比如:

    中缀表达式后缀表达式
    a + ba b +
    a + (b-c)a b c - +
    a+(b-c)*da b c - d * +
    a+d*(b-c)a d b c - * +
    a=1+3a 1 3 + =

    后缀表达式求值过程

    1. 左到右 扫描表达式

    2. 遇到 数字 时,将数字压入堆栈

    3. 遇到 运算符

      弹出栈顶的两个数(栈顶和次顶),用运算符对它们做相应的计算,并将结果入栈。

      计算顺序是: 弹出来的 (运算符) 弹出来的

    然后重复以上步骤,直到表达式的最右端,最后运算出的值则是表达式的值。

    比如:(3+4)x5-6 对应的后缀表达式 3 4 + 5 x 6 -

    1. 从左到右扫描,将 3、4 压入堆栈

    2. 扫描到 + 运算符时

      将弹出 4 和 3,计算 3 + 4 = 7,将 7 压入栈

    3. 将 5 入栈

    4. 扫描到 x 运算符时

      将弹出 5 和 7 ,计算 7 x 5 = 35,将 35 入栈

    5. 将 6 入栈

    6. 扫描到 - 运算符时

      将弹出 6 和 35,计算 35 - 6 = 29,将 29 压入栈

    7. 扫描表达式结束,29 是表达式的值

    中缀表达式转后缀表达式

    1. 初始化两个栈:

      • 运算符栈:s1
      • 中间结果栈:s2
    2. 从左到右扫描中缀表达式

    3. 遇到操作数时,将其压入 s2

    4. 遇到运算符时

      比较 它 与 s1 栈顶运算符的优先级:

      1. 如果 s1 为空,或则栈顶运算符号为 ( ,则将其压入符号栈 s1

      2. 如果:优先级比栈顶运算符 ,也将其压入符号栈 s1

      3. 如果:优先级比栈顶运算符 低 或 相等,将 s1 栈顶的运算符 弹出,并压入到 s2 中

    再重复第 4.1 步骤,与新的栈顶运算符比较(因为 4.3 将 s1 栈顶运算符弹出了)

    这里重复的步骤在实现的时候有点难以理解,下面进行解说:

    1. 如果 s1 栈顶符号 优先级比 当前符号 高或则等于,那么就将其 弹出,压入 s2 中(循环做,是只要 s1 不为空)

      如果栈顶符号为 (,优先级是 -1,就不会弹出,就跳出循环了

    2. 跳出循环后,则将当前符号压入 s1

    3. 遇到括号时:

      1. 如果是左括号 ( :则直接压入 s1

      2. 如果是右括号 )

      则依次弹出 s1 栈顶的运算符,并压入 s2,直到遇到 左括号 为止,此时将这一对括号 丢弃

    4. 重复步骤 2 到 5,直到表达式最右端

    5. 将 s1 中的运算符依次弹出并压入 s2

    6. 依次弹出 s2 中的元素并输出,结果的 逆序 即为:中缀表达式转后缀表达式

    下面进行举例说明:

    将中缀表达式:1+((2+3)*4)-5 转换为后缀表达式

    扫描到的元素s2(栈底->栈顶)s1(栈底->栈顶)说明
    11null遇到操作数直接压入 s2
    +1+s1为空,直接压入
    (1+ (运算符为(,直接压入
    (1+ ( (运算符为(,直接压入
    21 2+ ( (遇到操作数直接压入 s2
    +1 2+ ( ( +栈顶元素为(,直接压入 s1
    31 2 3+ ( ( +遇到操作数直接压入 s2
    )1 2 3 ++ (遇到)时,依次弹出 s1栈顶的运算符,并压入 s2 直到遇到 左括号 为止,此时将这一对括号 丢弃
    *1 2 3 ++ ( *栈顶元素为(,直接压入s1
    41 2 3 + 4+ ( *遇到操作数直接压入 s2
    )1 2 3 + 4 *+遇到)时,依次弹出 s1栈顶的运算符,并压入 s2 直到遇到 左括号 为止,此时将这一对括号 丢弃
    -1 2 3 + 4 * +-优先级比栈顶运算符 低或者相等 ,则弹出 s1栈顶元素,将其压入 s2 中,然后继续循环步骤 4,直至当前符号压入 s1
    51 2 3 + 4 * + 5-遇到操作数直接压入 s2
    null1 2 3 + 4 * + 5 -nulls1 的运算符依次弹出并压入 s2 ,结果的 逆序 即为:中缀表达式转后缀表达式的结果

    由于 s2 是一个栈,弹出是从栈顶弹出,因此逆序后结果就是 1 2 3 + 4 * + 5 -

    +
    +
    +
  2. +
  3. 队列是一种先进先出的线性表,JS中的数组可以实现一个队列(限定pushshift操作)。

    +

    若要充分利用数组的空间,可实现一个环形队列的数据结构。
    需要留一个空位的原因为:需要留出一个位置区分队列满与队列空的情况

    +
      +
    • 队列为空:front === rear
    • +
    • 队列为满:(rear + 1) % maxSize === front
    • +
    • 队列长度:(rear - front + Maxsize) % maxSize
    • +
    • 队列遍历:从front累加size次,获取每次的arr[i % maxSize]的元素。
    • +
    • 取队尾元素:注意考虑rear索引为0的情况。
    • +
    +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    const readline = require("readline");
    +const rl = readline.createInterface({
    +  input: process.stdin,
    +  output: process.stdout,
    +});
    +class CircleQueue {
    +  private maxSize: number;
    +  private front: number;
    +  private rear: number;
    +  private arr: number[];
    +  constructor(maxSize: number) {
    +    this.maxSize = maxSize;
    +    this.front = 0;
    +    this.rear = 0;
    +    this.arr = new Array(maxSize);
    +  }
    +  isEmpty(): boolean {
    +    return this.rear === this.front;
    +  }
    +  isFull(): boolean {
    +    return (this.rear + 1) % this.maxSize === this.front;
    +  }
    +  add(el: number) {
    +    if (this.isFull()) {
    +      console.warn("队列已满");
    +      return;
    +    }
    +    this.arr[this.rear++] = el;
    +    this.rear = this.rear % this.maxSize;
    +  }
    +  get(): number | undefined {
    +    if (this.isEmpty()) {
    +      console.warn("队列为空");
    +      return;
    +    }
    +    const el = this.arr[this.front++];
    +    this.front = this.front % this.maxSize;
    +    return el;
    +  }
    +  head(): number | undefined {
    +    if (this.isEmpty()) {
    +      console.warn("队列为空");
    +      return;
    +    }
    +    return this.arr[this.front];
    +  }
    +  tail(): number | undefined {
    +    if (this.isEmpty()) {
    +      console.warn("队列为空");
    +      return;
    +    }
    +    return this.rear - 1 < 0
    +      ? this.arr[this.maxSize - 1]
    +      : this.arr[this.rear - 1];
    +  }
    +  show() {
    +    for (let i = this.front; i < this.size() + this.front; i++) {
    +      const index = i % this.maxSize;
    +      console.log(this.arr[index]);
    +    }
    +  }
    +  size() {
    +    return (this.rear + this.maxSize - this.front) % this.maxSize;
    +  }
    +}
    +const queue = new CircleQueue(4);
    +const switchInput = (input: string) => {
    +  switch (input) {
    +    case "s":
    +      queue.show();
    +      break;
    +    case "e":
    +      rl.close(); // 关闭 readline 实例
    +      break;
    +    case "a":
    +      rl.question("请输入一个数字", (line: string) => {
    +        queue.add(Number(line));
    +      });
    +      break;
    +    case "g":
    +      {
    +        const result = queue.get();
    +        result && console.log(result);
    +      }
    +      break;
    +    case "h":
    +      {
    +        const result = queue.head();
    +        result && console.log(result);
    +      }
    +
    +      break;
    +    case "t":
    +      {
    +        const result = queue.tail();
    +        result && console.log(result);
    +      }
    +
    +      break;
    +    case "p":
    +      console.log("空?" + queue.isFull());
    +      break;
    +    default:
    +      console.log("您输入了无效的字符,请重新输入");
    +  }
    +};
    +rl.question(
    +  "s(show): 显示队列\ne(exit): 退出程序\na(add): 添加数据到队列\ng(get): 从队列取出数据\nh(head): 查看队列头的数据\nt(tail): 查看队列尾的数据\np(isEmpty): 队列是否为空\n",
    +  (input: string) => {
    +    switchInput(input);
    +    rl.on("line", (line: string) => {
    +      switchInput(line);
    +    });
    +  }
    +);
    +
  4. +
+

链表

链表(Linked List)是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的,由一系列结点(链表中每一个元素称为结点)组成
每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域
注意反转链表的基本操作:

+
    +
  • 保存当前节点的下个节点(防止下一个节点丢失)
  • +
  • 将当前节点插入道链表的头部。
  • +
  • 将保存的下个节点取出,然后循环
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
class ThiefNode {
+  public no: number;
+  private name?: string;
+  public nickName?: string;
+  public next: ThiefNode | null;
+  constructor(no: number = 0, name?: string, nickname?: string) {
+    this.no = no;
+    this.name = name;
+    this.nickName = nickname;
+    this.next = null;
+  }
+  toString() {
+    return this.no + (this.nickName || "HEAD");
+  }
+}
+
+class LinkedList {
+  private head: ThiefNode;
+  constructor() {
+    this.head = new ThiefNode();
+  }
+  add(el: ThiefNode) {
+    let temp: ThiefNode = this.head;
+    while (true) {
+      if (temp.next === null) {
+        temp.next = el;
+        break;
+      }
+      if (el.no === temp.next.no) {
+        console.warn("Element already exists!");
+        return;
+      }
+      temp = temp.next;
+    }
+  }
+  print() {
+    let temp = this.head.next;
+    while (true) {
+      if (temp) {
+        console.log(temp.toString());
+        temp = temp.next;
+      } else {
+        break;
+      }
+    }
+  }
+  addByOrder(el: ThiefNode) {
+    let temp = this.head;
+    while (true) {
+      if (temp.next === null) {
+        temp.next = el;
+        break;
+      } else if (el.no === temp.no) {
+        console.warn("Element already exists!");
+        return;
+      } else if (el.no < temp.next.no) {
+        el.next = temp.next;
+        temp.next = el;
+        return;
+      }
+      temp = temp.next;
+    }
+  }
+  update(no: number, nickName: string) {
+    let temp = this.head.next;
+    while (true) {
+      if (temp === null) {
+        console.warn("Thief not in this linked list!");
+        break;
+      }
+      if (temp.no === no) {
+        temp.nickName = nickName;
+        break;
+      }
+      temp = temp.next;
+    }
+  }
+  delete(no: number) {
+    let temp = this.head;
+    while (true) {
+      if (temp.next === null) {
+        console.warn("Thief not in this linked list!");
+        break;
+      }
+      if (temp.next.no === no) {
+        temp.next = temp.next.next;
+        break;
+      }
+      temp = temp.next;
+    }
+  }
+  length(): number {
+    let temp = this.head.next;
+    let count: number = 0;
+    while (true) {
+      if (temp !== null) {
+        count++;
+      } else {
+        break;
+      }
+      temp = temp.next;
+    }
+    return count;
+  }
+  lastIndexOf(num: number) {
+    const length = this.length();
+    if (num > length) {
+      console.warn("Index out of Range!");
+    }
+    const index = length - num;
+    let temp = this.head;
+    for (let i = 0; i <= index; i++) {
+      temp = temp.next!;
+    }
+    return temp;
+  }
+  reverse() {
+    let temp = this.head.next;
+    const newHead = new ThiefNode();
+    while (true) {
+      if (temp !== null) {
+        const next = temp.next;
+        temp.next = newHead.next;
+        newHead.next = temp;
+        temp = next;
+      } else break;
+    }
+    this.head.next = newHead.next;
+  }
+  reversePrint() {
+    let temp = this.head.next;
+    const stack: Array<string> = [];
+    while (true) {
+      if (temp !== null) {
+        stack.push(temp.toString());
+      } else {
+        break;
+      }
+      temp = temp.next;
+    }
+    for (let i = 0; i < stack.length; i++) {
+      console.log(stack[stack.length - i - 1]);
+    }
+  }
+}
+const test = () => {
+  const hero1 = new ThiefNode(1, "Amamiya Ren", "Joker");
+  const hero2 = new ThiefNode(2, "Ryuji Sakamoto", "Skull");
+  const hero3 = new ThiefNode(3, "Anne Takamaki", "Panther");
+  const hero4 = new ThiefNode(4, "Yusuke Kitagawa", "Fox");
+  const singleLinkedList = new LinkedList();
+  singleLinkedList.add(hero4); // 添加顺序提前
+  singleLinkedList.add(hero2);
+  singleLinkedList.add(hero1);
+  singleLinkedList.add(hero3);
+  singleLinkedList.add(hero3);
+  singleLinkedList.delete(3);
+  singleLinkedList.print();
+  console.log(singleLinkedList.lastIndexOf(1).toString());
+  console.log(singleLinkedList.lastIndexOf(2).toString());
+  console.log(singleLinkedList.lastIndexOf(3).toString());
+  console.log(singleLinkedList.length());
+  singleLinkedList.reverse();
+  singleLinkedList.print();
+  singleLinkedList.reversePrint();
+};
+
+test();
+export default LinkedList;
+

    +
  1. 二叉树
    二叉树是一种树形结构,它的特点是每个节点最多只有两个子树,分别称为左子树和右子树。二叉树是有序的,也就是说,左子树和右子树的位置是不能互换的。二叉树是一种递归定义的结构,即一个二叉树要么是空的,要么是由一个根节点和两个非空的子二叉树组成。二叉树中的每个元素也称为一个节点,节点包含一个数据元素和若干指向子树的指针。
  2. +
  3. 分类
      +
    • 满二叉树:如果二叉树中除了叶子结点,每个结点的度都为 2。
    • +
    • 完全二叉树:如果二叉树中除去最后一层节点为满二叉树,且最后一层的结点依次从左到右分布。
    • +
    +
  4. +
  5. 二叉树的遍历

    +
      +
    • 前序遍历:顶点 -> 左节点 -> 右节点
    • +
    • 中序遍历:左节点 -> 顶点 -> 右节点
    • +
    • 后续遍历:左节点 -> 右节点 -> 顶点
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    class TreeNode {
    +  constructor(val, left, right) {
    +    this.val = val;
    +    this.left = left;
    +    this.right = right;
    +  }
    +  preOrder(root) {
    +    if (!root) return;
    +    console.log(root.val);
    +    this.preOrder(root.left);
    +    this.preOrder(root.right);
    +  }
    +  preOrderLoop(root) {
    +    if (!root) return;
    +    const stack = [root];
    +    while (stack.length) {
    +      const n = stack.pop();
    +      console.log(n.val);
    +      if (n.right) stack.push(n.right);
    +      if (n.left) stack.push(n.left);
    +    }
    +  }
    +  infixOrder(root) {
    +    if (!root) return;
    +    this.infixOrder(root.left);
    +    console.log(root.val);
    +    this.infixOrder(root.right);
    +  }
    +  infixOrderLoop(root) {
    +    if (!root) return;
    +    const stack = [];
    +    let p = root;
    +    while (stack.length || p) {
    +      while (p) {
    +        stack.push(p);
    +        p = p.left;
    +      }
    +      const n = stack.pop();
    +      console.log(n.val);
    +      p = n.right;
    +    }
    +  }
    +  postOrder(root) {
    +    if (!root) return;
    +    this.postOrder(root.left);
    +    this.postOrder(root.right);
    +    console.log(root.val);
    +  }
    +  postOrderLoop(root) {
    +    // 定义一个栈和一个结果数组
    +    let stack = [];
    +    // 定义一个变量来记录上一次弹出的节点
    +    let lastPopNode = null;
    +    // 当栈不为空或者当前节点不为空时,循环执行
    +    while (stack.length > 0 || root) {
    +      // 如果当前节点不为空,将其压入栈中,然后访问其左子节点
    +      if (root) {
    +        stack.push(root);
    +        root = root.left;
    +      } else {
    +        // 如果当前节点为空,说明左子树已经遍历完毕,取出栈顶节点
    +        root = stack[stack.length - 1];
    +        // 如果栈顶节点有右子节点,并且右子节点不是上一次弹出的节点,说明右子树还没有遍历,访问其右子节点
    +        if (root.right && root.right !== lastPopNode) {
    +          root = root.right;
    +        } else {
    +          // 否则,说明左右子树都已经遍历,弹出栈顶节点,将其值加入结果数组,更新上一次弹出的节点,将当前节点置为空
    +          root = stack.pop();
    +          console.log(root.val);
    +          lastPopNode = root;
    +          root = null;
    +        }
    +      }
    +    }
    +  }
    +}
    +const node1 = new TreeNode(1);
    +const node2 = new TreeNode(2);
    +const node3 = new TreeNode(3);
    +const node4 = new TreeNode(4);
    +const node5 = new TreeNode(5);
    +const node6 = new TreeNode(6);
    +const node7 = new TreeNode(7);
    +
    +node1.left = node2;
    +node2.left = node3;
    +node2.right = node4;
    +
    +node1.right = node5;
    +node5.left = node6;
    +node5.right = node7;
    +
    +node1.postOrder(node1);
    +
  6. +

  7. 堆是一棵完全二叉树,满足

    +
      +
    • 堆中某个结点的值总是不大于或不小于其父结点的值
    • +
    • 堆总是一棵完全二叉树
    • +
    +

    堆又可以分成最大堆和最小堆:

    +
      +
    • 最大堆:每个根结点,都有根结点的值大于两个孩子结点的值
    • +
    • 最小堆:每个根结点,都有根结点的值小于孩子结点的值
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    // 完全二叉树可以用数组来表示
    +class MinHeap {
    +  constructor() {
    +    // 存储堆元素
    +    this.heap = [];
    +  }
    +  // 获取父元素坐标
    +  getParentIndex(i) {
    +    return (i - 1) >> 1;
    +  }
    +
    +  // 获取左节点元素坐标
    +  getLeftIndex(i) {
    +    return i * 2 + 1;
    +  }
    +
    +  // 获取右节点元素坐标
    +  getRightIndex(i) {
    +    return i * 2 + 2;
    +  }
    +
    +  // 交换元素
    +  swap(i1, i2) {
    +    const temp = this.heap[i1];
    +    this.heap[i1] = this.heap[i2];
    +    this.heap[i2] = temp;
    +  }
    +
    +  // 查看堆顶元素
    +  peek() {
    +    return this.heap[0];
    +  }
    +
    +  // 获取堆元素的大小
    +  size() {
    +    return this.heap.length;
    +  }
    +  // 插入元素
    +  insert(value) {
    +    this.heap.push(value);
    +    this.shifUp(this.heap.length - 1);
    +  }
    +
    +  // 上移操作
    +  shiftUp(index) {
    +    if (index === 0) {
    +      return;
    +    }
    +    const parentIndex = this.getParentIndex(index);
    +    if (this.heap[parentIndex] > this.heap[index]) {
    +      this.swap(parentIndex, index);
    +      this.shiftUp(parentIndex);
    +    }
    +  }
    +
    +  // 删除元素
    +  pop() {
    +    this.heap[0] = this.heap.pop();
    +    this.shiftDown(0);
    +  }
    +
    +  // 下移操作
    +  shiftDown(index) {
    +    const leftIndex = this.getLeftIndex(index);
    +    const rightIndex = this.getRightIndex(index);
    +    if (this.heap[leftIndex] < this.heap[index]) {
    +      this.swap(leftIndex, index);
    +      this.shiftDown(leftIndex);
    +    }
    +    if (this.heap[rightIndex] < this.heap[index]) {
    +      this.swap(rightIndex, index);
    +      this.shiftDown(rightIndex);
    +    }
    +  }
    +}
    +
  8. +
+

在计算机科学中,图是一种抽象的数据类型,在图中的数据元素通常称为结点,V 是所有顶点的集合,E 是所有边的集合

+

如果两个顶点 v,w,只能由 vw,而不能由 wv,那么我们就把这种情况叫做一个从 vw 的有向边。v 也被称做初始点,w 也被称为终点。这种图就被称做有向图

+

如果 vw 是没有顺序的,从 v 到达 w 和从 w 到达 v 是完全相同的,这种图就被称为无向图

+

图的结构比较复杂,任意两个顶点之间都可能存在联系,因此无法以数据元素在存储区中的物理位置来表示元素之间的关系

+

常见表达图的方式有如下:

+
    +
  1. 邻接矩阵
    通过使用一个二维数组G[N][N]进行表示 N 个点到 N-1 编号,通过邻接矩阵可以立刻看出两顶点之间是否存在一条边,只需要检查邻接矩阵行 i 和列 j 是否是非零值,对于无向图,邻接矩阵是对称的。

    +
  2. +
  3. 邻接表

    +

    JS中可以使用普通对象或者Map来表示

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    const graph = {
    +  A: [2, 3, 5],
    +  B: [2],
    +  C: [0, 1, 3],
    +  D: [0, 2],
    +  E: [6],
    +  F: [0, 6],
    +  G: [4, 5],
    +};
    +
  4. +
  5. 图的遍历

    +
      +
    • 深度遍历:即遇到新节点就访问新节点,然后该节点上访问该节点的节点,也就是说尽可能往深处访问。
    • +
    • 广度遍历:利用队列的数据结构。
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    const graph = {
    +  0: [1, 4],
    +  1: [2, 4],
    +  2: [2, 3],
    +  3: [],
    +  4: [3],
    +};
    +
    +const visit1 = new Set();
    +const dfs = (n) => {
    +  console.log(n);
    +  visit1.add(n);
    +  graph[n].forEach((el) => {
    +    if (!visit1.has(el)) {
    +      dfs(el);
    +    }
    +  });
    +};
    +
    +const visit2 = new Set();
    +const bfs = (n) => {
    +  visit2.add(n);
    +  const queue = [n];
    +  while (queue.length) {
    +    const n = queue.shift();
    +    console.log(n);
    +    graph[n].forEach((el) => {
    +      if (!visit2.has(el)) {
    +        visit2.add(el);
    +        queue.push(el);
    +      }
    +    });
    +  }
    +};
    +
    +dfs(0);
    +console.log();
    +bfs(0);
    +
  6. +
+

常见算法

排序算法

    +
  1. 冒泡排序

    +

    典中典,无需多言。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function bubbleSort(arr) {
    +  const len = arr.length;
    +  for (let i = 0; i < len - 1; i++) {
    +    for (let j = 0; j < len - 1 - i; j++) {
    +      if (arr[j] > arr[j + 1]) {
    +        // 相邻元素两两对比
    +        let temp = arr[j + 1]; // 元素交换
    +        arr[j + 1] = arr[j];
    +        arr[j] = temp;
    +      }
    +    }
    +  }
    +  return arr;
    +}
    +
  2. +
  3. 选择排序
    同样经典,无需多言

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function selectionSort(arr) {
    +  let len = arr.length;
    +  let minIndex, temp;
    +  for (let i = 0; i < len - 1; i++) {
    +    minIndex = i;
    +    for (let j = i + 1; j < len; j++) {
    +      if (arr[j] < arr[minIndex]) {
    +        // 寻找最小的数
    +        minIndex = j; // 将最小数的索引保存
    +      }
    +    }
    +    temp = arr[i];
    +    arr[i] = arr[minIndex];
    +    arr[minIndex] = temp;
    +  }
    +  return arr;
    +}
    +
  4. +
  5. 插入排序

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    const insertSort = (arr) => {
    +  const length = arr.length;
    +  let pre, current;
    +  for (let i = 1; i < length; i++) {
    +    pre = i - 1;
    +    current = arr[i];
    +    while (pre >= 0 && current < arr[pre]) {
    +      arr[pre + 1] = arr[pre];
    +      pre--;
    +    }
    +    arr[pre + 1] = current;
    +  }
    +  return arr;
    +};
    +console.log(insertSort([2, 5, 6, 2346, 56, 47, 567]));
    +
  6. +
  7. 归并排序

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    const mergeSort = (arr) => {
    +  const length = arr.length;
    +  if (length < 2) return arr;
    +  const middle = Math.ceil(length / 2);
    +  let left = arr.slice(0, middle),
    +    right = arr.slice(middle);
    +  return merge(mergeSort(left), mergeSort(right));
    +};
    +
    +const merge = (left, right) => {
    +  const result = [];
    +  while (left.length && right.length) {
    +    if (left[0] <= right[0]) {
    +      result.push(left.shift());
    +    } else {
    +      result.push(right.shift());
    +    }
    +  }
    +  while (left.length) result.push(left.shift());
    +  while (right.length) result.push(right.shift());
    +  return result;
    +};
    +console.log(mergeSort([13, 27, 38, 49, 49, 65, 76, 97]));
    +
  8. +
  9. 快速排序

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    const quickSort = (arr) => {
    +  if (arr.length <= 1) return arr;
    +  const left = [];
    +  const right = [];
    +  const mid = arr[0];
    +  for (let i = 1; i < arr.length; i++) {
    +    if (arr[i] <= mid) {
    +      left.push(arr[i]);
    +    } else {
    +      right.push(arr[i]);
    +    }
    +  }
    +  return [...quickSort(left), mid, ...quickSort(right)];
    +};
    +
    +const arr = quickSort([49, 38, 65, 97, 76, 13, 27, 49]);
    +console.log(arr);
    +
  10. +
+

查找算法

    +
  1. 二分查找算法

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    const binarySearchFirst = (arr, target) => {
    +  if (arr.length < 1) return -1;
    +  let low = 0,
    +    high = arr.length - 1;
    +  while (low <= high) {
    +    const mid = Math.floor((low + high) / 2);
    +    if (target > arr[mid]) {
    +      low = mid + 1;
    +    } else if (target < arr[mid]) {
    +      high = mid - 1;
    +    } else {
    +      if (mid === 0 || arr[mid - 1] < arr[mid]) return mid;
    +      else high = mid - 1;
    +    }
    +  }
    +  return -1;
    +};
    +const binarySearchFirst2 = (arr, target, low = 0, high = arr.length) => {
    +  if (low > high) return -1;
    +  const mid = Math.floor((low + high) / 2);
    +  if (arr[mid] > target)
    +    return binarySearchFirst2(arr, target, low, mid - 1);
    +  else if (arr[mid] < target)
    +    return binarySearchFirst2(arr, target, mid + 1, high);
    +  else {
    +    if (mid === 0 || arr[mid - 1] < arr[mid]) return mid;
    +    return binarySearchFirst2(arr, target, low, mid - 1);
    +  }
    +};
    +console.log(binarySearchFirst2([13, 27, 38, 49, 49, 65, 76, 97], 65));
    +
  2. +
+

动态规划

    +
  1. 01 背包和无限背包问题

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    // 01背包问题的js实现
    +function knapsack(w, v, W) {
    +  // w是物品重量数组,v是物品价值数组,W是背包总重量
    +  let n = w.length; // 物品个数
    +  let dp = new Array(W + 1).fill(0); // 初始化dp数组为全0
    +  for (let i = 0; i < n; i++) {
    +    // 遍历物品
    +    for (let j = W; j >= w[i]; j--) {
    +      // 逆向遍历背包容量
    +      dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]); // 状态转移方程
    +    }
    +  }
    +  return dp[W]; // 返回最大价值
    +}
    +
    +function knapsack1(w, v, W) {
    +  // w是物品重量数组,v是物品价值数组,W是背包总重量
    +  let n = w.length; // 物品个数
    +  let dp = new Array(W + 1).fill(0); // 初始化dp数组为全0
    +  for (let i = 0; i < n; i++) {
    +    // 遍历物品
    +    for (let j = w[i]; j <= W; j++) {
    +      // 正向遍历背包容量
    +      dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]); // 状态转移方程
    +    }
    +  }
    +  return dp[W]; // 返回最大价值
    +}
    +
    +// 测试用例
    +let w = [2, 3, 4]; // 物品重量数组
    +let v = [3, 4, 5]; // 物品价值数组
    +let W = 6; // 背包总重量
    +let expected = 8; // 期望的输出结果
    +let actual = knapsack1(w, v, W); // 实际的输出结果
    +console.log("Expected:", expected); // 打印期望的输出结果
    +console.log("Actual:", actual); // 打印实际的输出结果
    +console.log("Test passed:", expected === actual); // 比较期望和实际的输出结果,打印测试是否通过
    +
  2. +
+

回溯算法

    +
  1. 八皇后问题

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    class Queen8 {
    +  private readonly N: number;
    +  private arr: Array<number>;
    +  private order: number;
    +  private print() {
    +    console.log(this.order + ":" + this.arr.join(" "));
    +  }
    +  constructor(N: number) {
    +    this.N = N;
    +    this.arr = Array.of(N).fill(0);
    +    this.order = 0;
    +  }
    +  private judge(row: number) {
    +    for (let i = 0; i < row; i++) {
    +      if (
    +        this.arr[i] === this.arr[row] ||
    +        row - i === Math.abs(this.arr[row] - this.arr[i])
    +      )
    +        return false;
    +    }
    +    return true;
    +  }
    +  public check(count: number) {
    +    if (count === this.N) {
    +      this.order++;
    +      this.print();
    +      return;
    +    }
    +    for (let i = 0; i < this.N; i++) {
    +      this.arr[count] = i;
    +      if (this.judge(count)) {
    +        this.check(count + 1);
    +      }
    +    }
    +  }
    +}
    +
    +const queen = new Queen8(9);
    +queen.check(0);
    +
  2. +
+

约瑟夫环问题

    +
  1. 数组实现

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    // 定义一个函数,接受两个参数:n 表示人数,m 表示报数的最大值
    +function josephus(n: number, m: number): number {
    +  // 创建一个长度为 n 的数组,用来存放每个人的编号,初始值为 1 到 n
    +  let arr: number[] = [];
    +  for (let i = 0; i < n; i++) {
    +    arr[i] = i + 1;
    +  }
    +  // 定义一个变量 index 来表示当前报数的人的索引,初始值为 0
    +  let index: number = 0;
    +  // 定义一个变量 count 来表示当前报数的值,初始值为 1
    +  let count: number = 1;
    +  // 定义一个变量 alive 来表示当前圆圈中还剩下的人数,初始值为 n
    +  let alive: number = n;
    +  // 使用一个循环,当 alive 大于 1 时,重复以下步骤
    +  while (alive > 1) {
    +    // 如果当前报数的人的编号不为 0 ,说明他还没有被淘汰,那么就判断 count 是否等于 m
    +    if (arr[index] !== 0) {
    +      // 如果 count 等于 m ,说明他要被淘汰,那么就将他的编号设为 0 ,并将 alive 减 1 ,将 count 重置为 1
    +      if (count === m) {
    +        arr[index] = 0;
    +        alive--;
    +        count = 1;
    +      } else {
    +        // 如果 count 不等于 m ,说明他还要继续报数,那么就将 count 加 1
    +        count++;
    +      }
    +    }
    +    // 将 index 加 1 ,并对 n 取模,以实现循环报数的效果
    +    index = (index + 1) % n;
    +  }
    +  // 最后,遍历数组,找到那个编号不为 0 的元素,它就是最后留下的那个人的编号
    +  for (let i = 0; i < n; i++) {
    +    if (arr[i] !== 0) {
    +      return arr[i];
    +    }
    +  }
    +  // 如果没有找到,返回 -1 表示出错
    +  return -1;
    +}
    +
    +// 测试
    +console.log(josephus(5, 3)); // 输出 4
    +console.log(josephus(41, 3)); // 输出 31
    +
  2. +
+
文章作者: 希亚的西红柿
文章链接: http://blog.refrain.site/posts/acc0017d/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
+ + + + \ No newline at end of file diff --git a/posts/adf5c49/index.html b/posts/adf5c49/index.html new file mode 100644 index 000000000..8a24bb711 --- /dev/null +++ b/posts/adf5c49/index.html @@ -0,0 +1,970 @@ +Vue2笔记 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + + + + +

Vue2笔记

本文主要记录了关于 Vue2 的相关笔记,主要记录 Vue2 的一些基础概念与知识,帮助理解 Vue3。

+
+

Vue 的核心特性

数据驱动(MVVM)

数据驱动(MVVM):MVVM表示的是Model-View-ViewModel

+

MVVM 模型由以下三个部分组成:

+
    +
  • View:表示当前页面所渲染的 DOM 结构,负责将数据模型转化为 UI 展现出来。View 层通常是由 HTMLCSS 来编写的,也可以使用模板语言或组件化的方式来构建。
  • +
  • Model:表示当前页面渲染时所依赖的数据源,可以在 Model 层中定义数据修改和操作的业务逻辑。Model 层通常是由 Javascript 对象或数组来表示的,也可以使用 Vuex 等状态管理工具来管理。
  • +
  • ViewModel:表示 Vue 的实例,它是 MVVM 的核心。ViewModel 负责连接 ViewModel,保证视图和数据的一致性,这种轻量级的架构让前端开发更加高效、便捷。ViewModel 层通常是由 Vue 的选项和方法来定义的,也可以使用组合式 API 等新特性来增强。
  • +
+
+

MVVM 模型的工作原理是这样的:

+
    +
  • 当用户在 View 层进行操作时,如点击按钮,输入文本等,ViewModel 层会监听到这些事件,并根据相应的逻辑来修改 Model 层的数据。
  • +
  • Model 层的数据发生变化时,ViewModel 层会通过双向数据绑定的机制,自动将数据的变化反映到 View 层,更新视图的内容。
  • +
  • 这样,View 层和 Model 层之间的同步工作完全是自动的,无需人为干预,开发者只需关注业务逻辑,无需手动操作 DOM,复杂的数据状态维护交给 MVVM 统一来管理。
  • +
+

+

组件化

    +
  1. 什么是组件化
    一句话来说就是把图形、非图形的各种逻辑均抽象为一个统一的概念(组件)来实现开发的模式,在Vue中每一个.vue文件都可以视为一个组件。
  2. +
  3. 组件化的优势

    +
      +
    • 降低整个系统的耦合度,在保持接口不变的情况下,我们可以替换不同的组件快速完成需求,例如输入框,可以替换为日历、时间、范围等组件作具体的实现。
    • +
    • 调试方便,由于整个系统是通过组件组合起来的,在出现问题的时候,可以用排除法直接移除组件,或者根据报错的组件快速定位问题,之所以能够快速定位,是因为每个组件之间低耦合,职责单一,所以逻辑会比分析整个系统要简单。
    • +
    • 提高可维护性,由于每个组件的职责单一,并且组件在系统中是被复用的,所以对代码进行优化可获得系统的整体升级。
    • +
    +
  4. +
+

指令系统

指令 (Directives) 是带有 v- 前缀的特殊属性作用:当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM
常用的指令

+
    +
  • 条件渲染指令 v-if
  • +
  • 列表渲染指令 v-for
  • +
  • 属性绑定指令 v-bind
  • +
  • 事件绑定指令 v-on
  • +
  • 双向数据绑定指令 v-model
  • +
+

SPA

SPA 是单页面应用的缩写,它是一种前端开发的模式,主要特点是在浏览器中只加载一个 HTML 文件,然后通过 JavaScript 动态地更新页面内容,实现无刷新的用户体验。SPA的优点是页面切换快,用户体验好,适合开发复杂的交互式应用。SPA 的缺点是首屏加载速度慢,不利于 SEO,需要额外的技术来解决这些问题。

+
+

访问页面时的应用初始化即为直接通过url访问的解决方案。
If the URL doesn't match any static assets, it should serve the same index.html page that your app lives in. Beautiful, again! —- 摘自 vue-router 官方文档。

+
+
    +
  • Hash模式

    +
      +
    1. 定义路由,定义路由缓存对象。
    2. +
    3. 定义路由内容访问函数,如已做路由缓存,则直接返回结果,否则模拟异步请求。
    4. +
    5. 定义更新页面的函数。
    6. +
    7. 监听window对象的hashchange事件,当url改变的时候,请求路由,更新页面。
    8. +
    9. 注意在一开始的时候也需要执行一次函数。
    10. +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    <!DOCTYPE html>
    +<html lang="zh-CN">
    +  <head>
    +    <meta charset="UTF-8" />
    +    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    +    <title>Hash 模式</title>
    +  </head>
    +  <body>
    +    <header>
    +      <nav>
    +        <ul>
    +          <li><a href="#/">首页</a></li>
    +          <li><a href="#/about">关于</a></li>
    +          <li><a href="#/contact">联系我们</a></li>
    +        </ul>
    +      </nav>
    +    </header>
    +    <div id="content"></div>
    +  </body>
    +  <script>
    +    // 响应的路由及内容
    +    const routes = {
    +      "/": "这是首页",
    +      "/about": "这是关于页面",
    +      "/contact": "这是联系我们页面",
    +    };
    +    // 缓存路由对象
    +    const cache = {};
    +    // 获取需要展示的页面内容
    +    const getContent = (route) => {
    +      if (cache[route]) {
    +        return Promise.resolve(cache[route]);
    +      } else {
    +        // 模拟异步请求
    +        return new Promise((resolve, reject) => {
    +          setTimeout(() => {
    +            if (routes[route]) {
    +              cache[route] = routes[route];
    +              resolve(routes[route]);
    +            } else {
    +              reject("页面不存在");
    +            }
    +          }, 500);
    +        });
    +      }
    +    };
    +    // 更新页面内容
    +    const updateContent = (content) => {
    +      const app = document.querySelector("#content");
    +      app.innerHTML = content;
    +    };
    +    // 当哈希值发生改变,即 haschange 事件触发的时候,进行处理
    +    const handleNav = () => {
    +      console.log("hashchange事件触发了");
    +      // 获取当前的路径,注意需要去除 # 号
    +      const route = window.location.hash.slice(1);
    +      // 获取路由信息并尝试展示
    +      getContent(route).then(
    +        (value) => {
    +          updateContent(value);
    +        },
    +        (err) => {
    +          updateContent(err);
    +        }
    +      );
    +    };
    +    window.onhashchange = handleNav;
    +    handleNav();
    +  </script>
    +</html>
    +
  • +
  • History模式

    +
    +

    前三步与hash模式基本相同,主要记录后面不一样的地方。

    +
    +
      +
    1. 对于History模式需要监听的是window对象的popstate事件,此事件只能由浏览器记录的前进后退,和a标签的锚点触发,在这个时间上绑定更新页面的函数。
    2. +
    3. 此外需要阻止超链接事件的点击的默认事件,阻止其跳转,并使用pushStatereplaceState更新url记录,并更新页面内容。
    4. +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    <!DOCTYPE html>
    +<html lang="zh-CN">
    +  <head>
    +    <meta charset="UTF-8" />
    +    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    +    <title>Hash 模式</title>
    +  </head>
    +  <body>
    +    <header>
    +      <nav>
    +        <ul>
    +          <li><a href="/">首页</a></li>
    +          <li><a href="/about/">关于</a></li>
    +          <li><a href="/contact/">联系我们</a></li>
    +        </ul>
    +      </nav>
    +    </header>
    +    <div id="content"></div>
    +  </body>
    +  <script>
    +    // 响应的路由及内容
    +    const routes = {
    +      "/": "这是首页",
    +      "/about/": "这是关于页面",
    +      "/contact/": "这是联系我们页面",
    +    };
    +    // 缓存路由对象
    +    const cache = {};
    +    // 获取需要展示的页面内容
    +    const getContent = (route) => {
    +      if (cache[route]) {
    +        return Promise.resolve(cache[route]);
    +      } else {
    +        // 模拟异步请求
    +        return new Promise((resolve, reject) => {
    +          setTimeout(() => {
    +            if (routes[route]) {
    +              cache[route] = routes[route];
    +              resolve(routes[route]);
    +            } else {
    +              reject("页面不存在");
    +            }
    +          }, 500);
    +        });
    +      }
    +    };
    +    // 更新页面内容
    +    const updateContent = (content) => {
    +      const app = document.querySelector("#content");
    +      app.innerHTML = content;
    +    };
    +    // popstate 事件触发的时候,进行处理
    +    const handleNav = (pathname = window.location.pathname) => {
    +      // 为空则为 '/'
    +      const route = pathname || "/";
    +      // 获取路由信息并尝试展示
    +      getContent(route).then(
    +        (value) => {
    +          updateContent(value);
    +        },
    +        (err) => {
    +          updateContent(err);
    +        }
    +      );
    +    };
    +    window.onpopstate = handleNav;
    +
    +    // 拦截连接跳转事件
    +    const navLinks = document.querySelectorAll("nav a");
    +    navLinks.forEach((item) => {
    +      item.addEventListener("click", (e) => {
    +        e.preventDefault();
    +        const pathname = item.getAttribute("href");
    +        history.pushState(null, null, pathname);
    +        handleNav(pathname);
    +      });
    +    });
    +    handleNav();
    +  </script>
    +</html>
    +
  • +
+

SPA 首屏加载慢处理方案

    +
  • 减小入口文件体积。常用的方案为路由懒加载

    +
    1
    2
    3
    4
    5
    routes:[
    +  path: 'Blogs',
    +  name: 'ShowBlogs',
    +  component: () => import('./components/ShowBlogs.vue')
    +]
    +
  • +
  • 静态资源本地缓存
    后端手段

    +
      +
    • http常见缓存手段,如Cache-ControlEtagLast-Modified等响应头对静态资源进行缓存。
    • +
    • 采用Service Worker离线缓存,代表方案PWA
    • +
    +

    前端手段

    +
      +
    • 合理利用localStorage
    • +
    +
  • +
  • UI框架按需加载与避免组件重复打包。核心为去除冗余的部分。

    +
  • +
  • 图片与文件资源的压缩。
  • +
  • SSR:改善首屏加载时间与SEO的通用解决方案。
  • +
+

Vue 中的 data 属性

Vue中,组件中的data函数只能为一个函数而不能为一个对象,是因为组件是可以复用的,如果data是一个对象,那么所有复用的组件实例会共享同一个data对象,导致数据相互影响,无法保证组件的独立性和一致性。而如果data是一个函数,那么每个组件实例都会返回一个全新的data对象,保证了数据的隔离和安全。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
const app = new Vue({
+  el: "#app",
+  // 对象格式
+  data: {
+    foo: "foo",
+  },
+  // 函数格式
+  data() {
+    return {
+      foo: "foo",
+    };
+  },
+});
+// 组件形式
+const school = Vue.extend({
+  template: `
+      <div class="demo">
+        <h2>学校名称:{{schoolName}}</h2>
+        <h2>学校地址:{{address}}</h2>
+        <button @click="showName">点我提示学校名</button>	
+      </div>
+			`,
+  // el:'#root', //组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器。
+  data() {
+    return {
+      schoolName: "sss",
+      address: "ssss",
+    };
+  },
+  methods: {
+    showName() {
+      alert(this.schoolName);
+    },
+  },
+});
+

Vue2 响应式的缺陷

Vue2 响应式的缺陷主要有以下几点:

+
    +
  • 无法检测到对象属性的添加或删除,只能检测到已存在的属性的变化。这是因为Vue2使用了Object.defineProperty()方法来实现数据的劫持和监听,这个方法只能对对象的已有属性进行拦截,无法对新添加或删除的属性进行响应。
  • +
  • 无法检测到数组的变动,除非使用 Vue 提供的特殊方法,如push(), pop(), splice()等。这是因为 Vue2 对数组的处理是通过重写数组的原型方法来实现的,如果直接修改数组的长度或索引,Vue2无法捕获到这些变化。
  • +
  • 无法检测到嵌套对象的变化,需要手动调用 Vue.set()Vue.delete()方法。这是因为 Vue2 对嵌套对象的处理是通过递归遍历的方式来实现的,如果对象的层级过深,Vue2 无法对所有的属性进行监听,需要手动触发更新。
  • +
  • 需要对每个属性进行遍历和劫持,消耗了一定的性能和内存。这是因为 Vue2 对数据的响应式处理是通过遍历对象的所有属性,并为每个属性创建一个订阅者(Watcher)来实现的,这样会增加初始化的时间和内存的占用。
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 无法检测到对象属性的添加或删除的例子
+var vm = new Vue({
+  data: {
+    a: 1,
+  },
+});
+// `vm.a` 是响应式的
+
+vm.b = 2;
+// `vm.b` 是非响应式的
+
+delete vm.a;
+// `vm.a` 仍然是响应式的,无法删除
+
+// 无法检测到数组的变动的例子
+var vm = new Vue({
+  data: {
+    items: ["a", "b", "c"],
+  },
+});
+vm.items[1] = "x"; // 不是响应性的
+vm.items.length = 2; // 不是响应性的
+
+// 无法检测到嵌套对象的变化的例子
+var vm = new Vue({
+  data: {
+    user: {
+      name: "Alice",
+    },
+  },
+});
+vm.user.age = 18; // 不是响应性的
+

Vue 的 nextTick 函数

vue 组件的生命周期与 JS 浏览器的事件循环有一定的关系,主要体现在以下几个方面:

+
    +
  • vue 组件的生命周期是指组件从创建到销毁的过程中,经历的一系列阶段,每个阶段都有对应的钩子函数,可以在这些函数中执行一些自定义的逻辑。
  • +
  • JS 浏览器的事件循环是指浏览器在执行 JS 代码时,采用的一种机制,可以将同步任务和异步任务分别放入不同的队列中,按照一定的顺序执行。
  • +
  • vue 组件的生命周期钩子函数是同步任务,它们会按照组件的创建顺序依次执行,直到所有组件都完成相应的阶段。
  • +
  • JS 浏览器的事件循环会在每个宏任务(macro task)执行完毕后,检查微任务(micro task)队列,如果有未执行的微任务,就会依次执行它们,直到微任务队列为空。
  • +
  • vue 组件的更新过程是一个微任务,它会在组件的数据发生变化后,异步地将变化的数据应用到 DOM 上,这样可以避免多次不必要的 DOM 操作。
  • +
  • vue 组件的生命周期钩子函数和组件的更新过程之间存在一定的先后顺序,具体如下:
      +
    • 当组件的数据发生变化时,会触发 beforeUpdate 钩子函数,这是一个同步任务,会立即执行。
    • +
    • 然后,组件的更新过程会被推入微任务队列,等待执行。
    • +
    • 接着,如果当前宏任务执行完毕,事件循环会检查微任务队列,发现组件的更新过程,就会执行它,将变化的数据应用到 DOM 上。
    • +
    • 最后,组件的更新过程执行完毕后,会触发 updated 钩子函数,这也是一个同步任务,会立即执行。
    • +
    +
  • +
+

vue 组合式 apinextTick 是一个微任务,它的作用是在下一次 DOM 更新循环结束之后执行延迟回调。它可以用来等待 DOM 更新完成后再进行一些操作,比如获取更新后的 DOM 元素的属性值等。
vue 组合式 apinextTick 的实现原理是:首先检测当前环境是否支持 Promise,如果支持,就使用 Promise.resolve().then()创建一个微任务;如果不支持,就使用 setTimeout(fn, 0)创建一个宏任务。但是,由于 setTimeout 的最小延迟时间在不同浏览器中有差异,可能导致 nextTick 的回调执行顺序不一致,所以 vue 还使用了一个内部的队列来维护 nextTick 的回调函数,确保它们按照注册的顺序执行。

+

配合源码的简单实现理解:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 定义一个异步队列
+const queue = [];
+// 定义一个执行队列的函数
+let runQueue = () => {
+  // 遍历并执行队列中的函数
+  for (let fn of queue) {
+    fn();
+  }
+  // 清空队列
+  queue.length = 0;
+};
+// 定义一个调度函数,根据环境选择合适的异步方式
+let nextTick = () => {
+  // 如果支持Promise,则使用Promise.then()
+  if (typeof Promise !== "undefined") {
+    return Promise.resolve().then(runQueue);
+  }
+  // 如果支持MutationObserver,则使用它
+  if (typeof MutationObserver !== "undefined") {
+    let counter = 1;
+    let observer = new MutationObserver(runQueue);
+    let textNode = document.createTextNode(String(counter));
+    observer.observe(textNode, {
+      characterData: true,
+    });
+    counter = (counter + 1) % 2;
+    textNode.data = String(counter);
+    return;
+  }
+  // 如果支持setImmediate,则使用它
+  if (typeof setImmediate !== "undefined") {
+    return setImmediate(runQueue);
+  }
+  // 最后使用setTimeout
+  setTimeout(runQueue, 0);
+};
+// 定义一个暴露给外部的nextTick函数
+export function nextTick(fn) {
+  // 将传入的函数推入队列
+  queue.push(fn);
+  // 调用调度函数
+  nextTick();
+}
+
查看在 Vue3 中 nextTick 的使用 +
+

vue3 组合式 api 中的 setup 语法糖中,你可以从 vue 模块中导入 nextTick 函数,然后在 setup 函数中使用它。你有两种方式来使用 nextTick

  • 方式一:传入一个回调函数作为参数,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <script setup>
    +import { ref, nextTick } from "vue";
    +
    +const msg = ref("Hello");
    +
    +const changeMsg = () => {
    +  msg.value = "World";
    +  nextTick(() => {
    +    console.log(msg.value); // 'World'
    +  });
    +};
    +</script>
  • 方式二:不传入任何参数,得到一个返回 promise 的函数,然后使用 async/await 语法,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <script setup>
    +import { ref, nextTick } from "vue";
    +
    +const msg = ref("Hello");
    +
    +const changeMsg = async () => {
    +  msg.value = "World";
    +  await nextTick();
    +  console.log(msg.value); // 'World'
    +};
    +</script>
+
+
+
文章作者: 希亚的西红柿
文章链接: http://blog.refrain.site/posts/adf5c49/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
+ + + + \ No newline at end of file diff --git a/posts/b06cc6ec/index.html b/posts/b06cc6ec/index.html new file mode 100644 index 000000000..85be38e6f --- /dev/null +++ b/posts/b06cc6ec/index.html @@ -0,0 +1,756 @@ +数据结构绪论 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + + +

数据结构绪论

前言

在数据结构课程结课之后,我打算重开之前一直搁置的数据结构教程书写,一方面为加深数据结构的学习印象,另一方面为以后复习做一个参考。

+
+

本教程编程语言采用 JAVA ,文章框架参考自 HUST数据结构PPT ,源码内容参考自 尚硅谷 JAVA 数据结构教程

+
+

绪论部分主要以定义介绍为主,掌握概念即可,复杂度方面将具体对复杂度进行分析,其中具体对时间复杂度进行分析

+
+

数据结构的定义

    +
  1. 数据结构本身概念的基本定义:
    数据结构是计算机中存储、组织数据的方式
  2. +
  3. 数据结构这门学科的基本定义:
    数据结构是一门研究非数值计算的程序设计问题中计算机的操作对象以及它们之间的关系和操作等相关问题的学科。
  4. +
+

基本概念和术语

    +
  1. 数据(data):所有能输入到计算机中并被计算机程序加工、处理的符号的总称。
    如:整数、实数、字符、声音、图象、图形等。
  2. +
  3. 数据元素(data element):数据的基本单位。(元素、记录、结点、顶点)在计算机程序中通常作为一个整体进行考虑和处理。
  4. +
  5. 数据项(data item):是数据的不可分割的最小单位。如:姓名、年龄等。一个数据元素可由一个或多个数据项组成。
    如: (姓名、年龄)
  6. +
  7. 数据对象(data object): 由性质相同(类型相同)的数据元素组成的集合。

    数据对象是数据的一个子集。

    +
  8. +
  9. 数据结构(data structure):数据结构就是相互之间存在一种或多种特定关系的数据元素的集合。

    在任何问题中,数据元素都不是孤立存在的,而是在它们之间存在着某种关系,这种数据元素相互之间的关系称为结构(Structure)。
    按照研究角度来分,数据结构可分为:

    +
      +
    • 逻辑结构:面向数据之间的逻辑概念的关系。
    • +
    • 物理结构:面向数据之间的物理储存等面向计算机方面的关系。
    • +
    +

    逻辑结构:
    数据之间的相互关系称为逻辑结构,即从逻辑关系上描述数据,与数据的存储无关,是独立于计算机的。通常分为四类基本结构:

    +
      +
    • 集合
    • +
    • 线性结构
    • +
    • 树形结构
    • +
    • 图(网)状结构
    • +
    +

    物理结构:
    物理结构亦称存储结构,是数据的逻辑结构在计算机存储器内的表示(或映像),依赖于计算机。存储结构可以分为四大类:

    +
      +
    1. 顺序存储结构(向量,一维数组)
    2. +
    3. 非顺序存储结构(链接表、链式存储结构)
    4. +
    5. 索引存储结构
    6. +
    7. 哈希(Hash)存储结构
    8. +
    +
    6. 数据类型(data type):是一个值的集合和定义在这个值上的一组操作的总称。数据类型可分为两类:
  10. +
+
    +
  • 原子类型(如:int,char,float 等)
  • +
  • 结构类型(如:数组,结构,联合体等)
  • +
+
    +
  1. 抽象数据类型(Abstract Data Type):与计算机的实现无关的数据类型。
  2. +
+

抽象数据类型

抽象数据类型(Abstract Data Type,ADT)是电脑科学中具有类似行为的特定类别的数据结构的数学模型;或者具有类似语义的一种或多种程序设计语言的数据类型。抽象数据类型是间接定义的,通过其上的可执行的操作以及这些操作的效果的数学约束(与可能的代价)。
例如,抽象的堆栈(stack)由 3 个操作定义:推入 push,弹出 pop(接受约束:每次弹出返回的是最新被推入且没有被弹出的数据,也就是后进先出),查看堆栈顶端数据 peek。当分析使用堆栈算法的效率,所有这 3 个操作用时相同,无论堆栈中包含多少项数据;并且对每项数据栈使用了常量大小的存储。
抽象数据类型(ADT)是纯粹理论实体,用于简化描述抽象算法,分类与评价数据结构,形式描述程序设计语言的类型系统。一个 ADT 可以用特定数据类型或数据结构实现,在许多程序设计语言中有许多种实现方式;或者用形式规范语言描述。ADT 常实现为模块(module):模块的接口声明了对应于 ADT 操作的例程(procedure),有时用注释描述了约束。

+
+

算法与算法分析

    +
  1. 算法定义:是对特定问题求解步骤的一种描述,算法是指令的有限序列,其中每一条指令表示一个或多个操作。
  2. +
  3. 算法的五个特征:
      +
    • 有穷性:在有限步(或有限时间)之后算法终止。
    • +
    • 确定性:无二义性。
    • +
    • 可行性:算法中的操作都是已经实现的基本运算执行有限次来实现的。
    • +
    • 输入:有 0 或多个输入量。
    • +
    • 输出:至少有一个输出量。
    • +
    +
  4. +
  5. 算法设计要求:

    +
      +
    1. 正确性:

      +
        +
      • 无语法错误
      • +
      • 对 n 组输入产生正确结果
      • +
      • 对特殊输入产生正确结果
      • +
      • 对所有输入产生正确结果
      • +
      +
    2. +
    3. 可读性:“算法主要是为了人的阅读与交流”

      +
    4. +
    5. 高效率与低存储量
    6. +
    +
  6. +
  7. 算法效率的度量:

    +
      +
    • 事后统计: 收集此算法的执行时间和实际占用空间的统计资料
    • +
    • 事先分析: 求出该算法的时间复杂度
    • +
    • 将一个算法转换成程序并在计算机上执行时,其运行所需要的时间取决于下列因素:
        +
      1. 硬件的速度。
      2. +
      3. 书写程序的语言。
      4. +
      5. 编译程序所生成目标代码的质量。
      6. +
      7. 问题的规模。
      8. +
      +
    • +
    +
  8. +
+

在各种因素都不能确定的情况下,很难比较出算法的执行时间。因此,使用执行算法的绝对时间来衡量算法的效率是不合适的。为此,将上述各种与计算机相关的软、硬件因素都确定下来,这样一个特定算法的运行工作量的大小就只依赖于问题的规模(通常用正整数 n 表示),或者说它是问题规模的函数。

+
+
    +
  1. 算法的时间复杂度:

    +
      +
    1. 时间频度:一个算法中的原操作执行次数称为语句频度频度,记为 f(n)
    2. +
    3. 时间复杂度:
        +
      • T(n) = O(f(n))O(f(n))为算法的渐近时间复杂度,简称时间复杂度。大O(Order 的缩写,意指数量级)表示随问题规模 n 的增大,算法执行时间的增长率和f(n)的增长率相同。
      • +
      • 大 O 操作的本质是求f(n)的同阶无穷大。当 n 趋近于无穷大时,f(n)/g(n)的极限值为不等于零的常数,则称g(n)f(n)的同数量级函数。
      • +
      +
    4. +
    +
  2. +
  3. 算法的空间复杂度:
    一个算法在计算机存储器上所占用的存储空间,包括:

    +
      +
    1. 存储算法本身所占用的存储空间
    2. +
    3. 算法的输入输出数据所占用的存储空间
    4. +
    5. 算法在运行过程中临时占用的存储空间
    6. +
    +
  4. +
+

算法时间复杂度具体实例分析

    +
  1. 常量阶

    +
    1
    2
    3
    int s = 0;
    +s++;
    +System.out.println(s);
      +
    1. 语句频度: f(n)=3
    2. +
    3. 时间复杂度为:T(n)=O(f(n))=O(3)=O(1)
    4. +
    5. O(1)称成为常量阶/常量数量级
    6. +
    +
  2. +
  3. 线性阶

    +
    1
    2
    3
    4
    5
    6
    7
    void sum(int a[], int n) {
    +  int s=0,i; // 1次
    +  for(i=0;i<n;i++){ // n次
    +  s = s + a[i]; // n次
    +  printf("%d",s);// 1次
    +  }
    +}
      +
    1. 语句频度: f(n)=1+n+n+1
    2. +
    3. 时间复杂度为:T(n)=O(f(n))=O(2n+2)=O(n)
    4. +
    5. O(n)称成为线性阶/线性数量级
    6. +
    3. 平方阶 +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    void sum(int n) {
    +  int i, j, s = 0; // 1次
    +  for(i = 1;i <= n; i++){ // n次
    +    for(j=1;j<=i;j++){ // n(n+1)/2 次
    +        s++; // n(n+1)/2次
    +    }
    +    System.out.println(s); // n次
    +  }
    +}
      +
    1. 语句频度: $ f(n)=1+n+n(n+1)+n = n^2+3n+1 $
    2. +
    3. 时间复杂度为:$ T(n)=O(f(n))=O(n^2) $
    4. +
    5. $ O(n^2) $ 称成为线性阶/线性数量级
    6. +
    +
  4. +
  5. 复杂度比较

    +
  6. +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
常数阶对数阶线性阶线性对数阶平方阶立方阶k 次方阶指数阶
$ O(1) $$ O(log_2 n) $$ O(n) $$ O(nlog_2 n) $$ O(n^2) $$ O(n^3) $$ O(n^k) $$ O(2^n) $
+
+

平均时间复杂度与最坏时间复杂度

    +
  1. 平均时间复杂度
    指所有可能的输入实例 均以等概率出现 的情况下,该算法的运行时间
  2. +
  3. 最坏时间复杂度
    最坏情况下的世界复杂度称为最坏时间复杂度。

    一般讨论的时间复杂度均是最坏情况下的时间复杂度,因为:最坏情况下的时间复杂度是算法在 任何输入实例上运行时间的界限,这就保证了算法的运行时间不会比最坏情况更长

    +
    +
  4. +
+
文章作者: 希亚的西红柿
文章链接: http://blog.refrain.site/posts/b06cc6ec/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
+ + + + \ No newline at end of file diff --git a/posts/b2b6ba06/index.html b/posts/b2b6ba06/index.html new file mode 100644 index 000000000..63ae1600e --- /dev/null +++ b/posts/b2b6ba06/index.html @@ -0,0 +1,2053 @@ +JS拾遗笔记 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + + + +

JS拾遗笔记

本文主要记录了 JS 基础以及 ES6 的相关笔记,随缘更新。

+
+

JS 基础

基本概念

    +
  1. JS 中 label 标签用来终止或者跳过外层循环

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    top: for (var i = 0; i < 3; i++) {
    +  for (var j = 0; j < 3; j++) {
    +    if (i === 1 && j === 1) break top;
    +    console.log("i=" + i + ", j=" + j);
    +  }
    +}
    +// i=0, j=0
    +// i=0, j=1
    +// i=0, j=2
    +// i=1, j=0
    +
    +// 直接进入下一轮外层循环
    +top: for (var i = 0; i < 3; i++) {
    +  for (var j = 0; j < 3; j++) {
    +    if (i === 1 && j === 1) continue top;
    +    console.log("i=" + i + ", j=" + j);
    +  }
    +}
    +// i=0, j=0
    +// i=0, j=1
    +// i=0, j=2
    +// i=1, j=0
    +// i=2, j=0
    +// i=2, j=1
    +// i=2, j=2
    +
  2. +
  3. 在 ES5 之前,JS 有六种基本对象

    +
      +
    • number
    • +
    • string
    • +
    • boolean
    • +
    • object
    • +
    • undefined
    • +
    • null
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    typeof window; // "object"
    +typeof {}; // "object"
    +typeof []; // "object"
    +function f() {}
    +typeof f;
    +// "function"
    +v;
    +// ReferenceError: v is not defined
    +typeof NaN; // 'number'
    +typeof v;
    +// "undefined"
    +typeof null; // "object"
    +
    +

    typeof null的结果是object,这是由于 JS 历史遗留问题造成的。

    +
    +
  4. +
  5. JS 中所有数字都是以 64 位浮点数表示的,而有些小数用二进制不能精确的表示,例如

    +

    要理解这个问题,我们需要知道十进制和二进制的区别。十进制是以 10 为基数的数制,也就是说,每个位上的数字都是 10 的幂次方的系数。比如,123.45 可以表示为:

    +

    二进制是以 2 为基数的数制,也就是说,每个位上的数字都是 2 的幂次方的系数。比如,101.11 可以表示为:

    +

    当我们把一个十进制小数转换成二进制小数时,我们需要不断地用 2 去 x 这个小数的小数部分,并把余数作为二进制位。比如,0.1 转换成二进制的过程如下:

    +

    可以看到,当我们遇到了之前出现过的余数 0.2 时,就会出现循环。所以,0.1 的二进制表示就是:

    +

    其中$\overline{0011}$表示 0011 这个部分是无限重复的。

    +

    如果一个十进制小数能被 2 整除,那么它就能用有限的二进制位来表示。比如,0.5 可以表示为$(0.1)_2$,因为$0.5 \div 2 = 0 … 1$。但如果一个十进制小数不能被 2 整除,那么它就可能用无限的二进制位来表示。比如,0.3 可以表示为$(0.\overline{010011})_2$³。

    +
  6. +
  7. 数值转换函数。

    +
      +
    • parseInt:将一个字符串转化为整数。
    • +
    • parseFloat:将一个字符串转化为浮点数。
    • +
    • isFinite:判断是否是正常的数值。先转化成数值再进行判断。
    • +
    • isNaN:判断是否是NaN。先转换成数值再进行判断。
    • +
    +
  8. +
  9. JS 在圆括号里面只能是表达式,在遇到花括号的时候一律解释为代码块。

    +
  10. +
  11. delete用于删除对象的属性,但有注意点,一是不能通过delete语句的结果来判断删除属性是否成功,因为删除一个不存在的属性delete也会返回true,只有当对象属性设置为不可配置的时候返回false。而且只能删除自身的属性,不能删除继承的属性。

    +
    1
    2
    3
    4
    5
    6
    var obj = { p: 1 };
    +Object.keys(obj); // ["p"]
    +
    +delete obj.p; // true
    +obj.p; // undefined
    +Object.keys(obj); // []
    +
  12. +
  13. in运算符可以判断对象属性是否含有某个key,注意会沿着对象的原型链进行查找。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    var person = { name: "老张" };
    +
    +for (var key in person) {
    +  if (person.hasOwnProperty(key)) {
    +    console.log(key);
    +  }
    +}
    +// name
    +
    +

    你可以传递任意数量的参数给Function构造函数,只有最后一个参数会被当做函数体,如果只有一个参数,该参数就是函数体。

    +
    1
    2
    3
    4
    5
    6
    var foo = new Function('return "hello world";');
    +
    +// 等同于
    +function foo() {
    +  return "hello world";
    +}
    +
    +
  14. +
  15. var提升只会把声明提升,不会提升赋值语句。

    +
    1
    2
    3
    4
    5
    6
    7
    f();
    +var f = function () {};
    +// TypeError: undefined is not a function
    +// 等同于
    +var f;
    +f();
    +f = function () {};
  16. +
  17. 函数的name属性会显示函数的名字,函数的length会显示函数预期传入的参数的个数。这个属性可用于函数的方法的重载。
    +

    为何内置函数的toString方法返回的是function (){[native code]},这是因为JS内部方法的函数不是通过原生JS实现的,而是由C++等代码实现的。

    +
    +
  18. +
  19. 闭包的最大用处有两个,一个是可以读取外层函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。闭包的另一个用处,是封装对象的私有属性和私有方法。
  20. +
  21. 为了避免解析的歧义,JavaScript规定,如果function关键字出现在行首,一律解释成语句。因此,引擎看到行首是function关键字之后,认为这一段都是函数的定义,不应该以圆括号结尾,所以就报错了。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    function(){ /* code */ }();
    +// SyntaxError: Unexpected token (
    +  var f = function f(){ return 1}();
    +f // 1
    +
    +(function(){ /* code */ }());
    +// 或者
    +(function(){ /* code */ })();
    +
  22. +
+

对象 Object

    +
  1. 数组的length是一个动态属性,只要是数组,就一定有length属性。该属性是一个动态的值,等于键名中的最大整数加上1,没有显示的元素填空值。更改length属性会导致,数组长度增大,添加空值。或减小,直接裁剪元素。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var arr = ["a", "b"];
    +arr.length; // 2
    +
    +arr[2] = "c";
    +arr.length; // 3
    +
    +arr[9] = "d";
    +arr.length; // 10
    +
    +arr[1000] = "e";
    +arr.length; // 1001
    +
  2. +
  3. 注意一些方法对于数组空位的处理

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    var a = [, , ,];
    +
    +a.forEach(function (x, i) {
    +  console.log(i + ". " + x);
    +});
    +// 不产生任何输出
    +
    +for (var i in a) {
    +  console.log(i);
    +}
    +// 不产生任何输出
    +
    +Object.keys(a);
    +// []
    +
    +var a = [undefined, undefined, undefined];
    +
    +a.forEach(function (x, i) {
    +  console.log(i + ". " + x);
    +});
    +// 0. undefined
    +// 1. undefined
    +// 2. undefined
    +
    +for (var i in a) {
    +  console.log(i);
    +}
    +// 0
    +// 1
    +// 2
    +
    +Object.keys(a);
    +// ['0', '1', '2']
    +
  4. +
  5. Object.keys()Object.getOwnPropertyNames二者返回结果在大多数情况下都是相同的,但前者只会返回可枚举的属性,后者可以返回不可枚举的属性。二者都不能获取原型链上面的属性。

    +
    1
    2
    3
    4
    var a = ["Hello", "World"];
    +
    +Object.keys(a); // ["0", "1"]
    +Object.getOwnPropertyNames(a); // ["0", "1", "length"]
    +
  6. +
  7. valueOf 方法的主要用途是,JavaScript 自动类型转换时会默认调用这个方法。

    +
    1
    2
    3
    4
    5
    6
    var obj = new Object();
    +obj.valueOf = function () {
    +  return 2;
    +};
    +
    +1 + obj; // 3
    +
  8. +
  9. Object.defineProperty方法的详细解释
    Object.defineProperty 方法是一个 JavaScript 的静态方法,它可以用来在一个对象上直接定义一个新属性,或者修改一个已经存在的属性,并返回该对象。这个方法的作用是可以精确地控制对象属性的特征,比如是否可写、是否可枚举、是否可配置等。Object.defineProperty 方法的语法如下:

    +
    1
    Object.defineProperty(obj, prop, descriptor);
    +

    其中,obj 是要定义或修改属性的对象,prop 是要定义或修改的属性名,descriptor 是一个对象,用来描述该属性的特征。descriptor 对象可以包含以下几个键:

    +
      +
    • value:该属性的值,可以是任何有效的 JavaScript 值,默认为 undefined
    • +
    • writable:该属性是否可写,如果为 true,则可以通过赋值运算符修改该属性的值,否则不能,默认为 false
    • +
    • enumerable:该属性是否可枚举,如果为 true,则可以通过 for...in 循环或 Object.keys 方法遍历该属性,否则不能,默认为 false
    • +
    • configurableconfigurable是一个布尔值,表示属性的可配置性,默认为false。如果设为false,将阻止某些操作改写属性描述对象,比如无法删除该属性,也不得改变各种元属性(value属性除外)。也就是说,configurable属性控制了属性描述对象的可写性。
    • +
    • get:该属性的 getter 函数,用来返回该属性的值,如果没有 getter 函数,则为 undefined。当访问该属性时,会调用这个函数,并把 this 绑定到所属对象上。默认为 undefined
    • +
    • set:该属性的 setter 函数,用来设置该属性的值,如果没有 setter函数,则为undefined。当给该属性赋值时,会调用这个函数,并把this绑定到所属对象上。默认为 undefined
    • +
    +

    注意:descriptor 对象中不能同时出现 valuewritablegetset 这两组键,否则会抛出异常。也就是说,一个属性要么是数据描述符(有 valuewritable),要么是访问器描述符(有 getset)。

    +

    使用 Object.defineProperty 方法可以创建或修改对象的属性,并且可以精细地控制其特征。例如:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    // 创建一个空对象
    +let obj = {};
    +
    +// 定义一个普通的数据属性
    +Object.defineProperty(obj, "a", {
    +  value: 1,
    +  writable: true,
    +  enumerable: true,
    +  configurable: true,
    +});
    +
    +// 定义一个只读的数据属性
    +Object.defineProperty(obj, "b", {
    +  value: 2,
    +  writable: false,
    +  enumerable: true,
    +  configurable: true,
    +});
    +
    +// 定义一个不可枚举的数据属性
    +Object.defineProperty(obj, "c", {
    +  value: 3,
    +  writable: true,
    +  enumerable: false,
    +  configurable: true,
    +});
    +
    +// 定义一个不可配置的数据属性
    +Object.defineProperty(obj, "d", {
    +  value: 4,
    +  writable: true,
    +  enumerable: true,
    +  configurable: false,
    +});
    +
    +// 定义一个访问器属性
    +let eValue = 5;
    +Object.defineProperty(obj, "e", {
    +  get() {
    +    return eValue;
    +  },
    +  set(newValue) {
    +    eValue = newValue;
    +  },
    +  enumerable: true,
    +  configurable: true,
    +});
    +
    +console.log(obj.a); // 1
    +console.log(obj.b); // 2
    +console.log(obj.c); // 3
    +console.log(obj.d); // 4
    +console.log(obj.e); // 5
    +
    +obj.a = 10; // 可以修改a的值
    +obj.b = 20; // 不可以修改b的值
    +obj.e = 50; // 可以通过setter函数修改e的值
    +
    +console.log(obj.a); // 10
    +console.log(obj.b); // 2
    +console.log(obj.e); // 50
    +
    +for (let key in obj) {
    +  console.log(key); // a b d e (c不可枚举)
    +}
    +
    +delete obj.a; // 可以删除a属性
    +delete obj.b; // 可以删除b属性
    +delete obj.c; // 可以删除c属性
    +delete obj.d; // 不可以删除d属性(不可配置)
    +delete obj.e; // 可以删除e属性
    +
    +console.log(obj.a); // undefined
    +console.log(obj.b); // undefined
    +console.log(obj.c); // undefined
    +console.log(obj.d); // 4
    +console.log(obj.e); // undefined
    +
    +// 修改d的特征(不可配置)
    +Object.defineProperty(obj, "d", {
    +  value: 40,
    +  writable: false,
    +  enumerable: false,
    +  configurable: true, // 报错
    +});
    +
    +// 有关 get 与 set 的设置
    +let Person = {};
    +let temp = null;
    +
    +// 定义一个有名字的函数
    +function test(val) {
    +  if (arguments.length == 0) {
    +    // 如果没有参数,表示是getter
    +    return temp;
    +  } else {
    +    // 如果有参数,表示是setter
    +    temp = val;
    +  }
    +}
    +
    +// 把有名字的函数赋给set和get
    +Object.defineProperty(Person, "name", {
    +  get: test, // 不加括号
    +  set: test, // 不加括号
    +});
    +
  10. +
  11. Object.getOwnPropertyDescriptor()方法可以获取属性描述对象。它的第一个参数是目标对象,第二个参数是一个字符串,对应目标对象的某个属性名。不能用于获取继承属性。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    var obj = { p: "a" };
    +
    +Object.getOwnPropertyDescriptor(obj, "p");
    +// Object { value: "a",
    +//   writable: true,
    +//   enumerable: true,
    +//   configurable: true
    +// }
    +
  12. +
  13. Object.getOwnPropertyNames方法返回一个数组,成员是参数对象自身的全部属性的属性名,不管该属性是否可遍历。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var obj = Object.defineProperties(
    +  {},
    +  {
    +    p1: { value: 1, enumerable: true },
    +    p2: { value: 2, enumerable: false },
    +  }
    +);
    +
    +Object.getOwnPropertyNames(obj);
    +// ["p1", "p2"]
    +
  14. +
  15. 实例对象的propertyIsEnumerable()方法返回一个布尔值,用来判断某个属性是否可遍历。注意,这个方法只能用于判断对象自身的属性,对于继承的属性一律返回false

    +
    1
    2
    3
    4
    5
    var obj = {};
    +obj.p = 123;
    +
    +obj.propertyIsEnumerable("p"); // true
    +obj.propertyIsEnumerable("toString"); // false
    +
  16. +
  17. 如果原型对象的某个属性的writablefalse,那么子对象将无法自定义这个属性。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    var proto = Object.defineProperty({}, "foo", {
    +  value: "a",
    +  writable: false,
    +});
    +
    +var obj = Object.create(proto);
    +
    +obj.foo = "b";
    +obj.foo; // 'a'
    +
    +// 规避方法
    +var proto = Object.defineProperty({}, "foo", {
    +  value: "a",
    +  writable: false,
    +});
    +
    +var obj = Object.create(proto);
    +Object.defineProperty(obj, "foo", {
    +  value: "b",
    +});
    +
    +obj.foo; // "b"
    +
  18. +
  19. 具体来说,如果一个属性的enumerablefalse,下面三个操作不会取到该属性。

    +
      +
    • for..in循环
    • +
    • Object.keys方法
    • +
    • JSON.stringify方法
    • +
    +
  20. +
  21. 当一个对象的属性的configurable设置为false的时候,writable属性只有在false改为true时会报错,true改为false是允许的。value属性的情况比较特殊。只要writableconfigurable有一个为true,就允许改动value

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    var obj = Object.defineProperty({}, "p", {
    +  writable: true,
    +  configurable: false,
    +});
    +
    +Object.defineProperty(obj, "p", { writable: false });
    +// 修改成功
    +
    +var o1 = Object.defineProperty({}, "p", {
    +  value: 1,
    +  writable: true,
    +  configurable: false,
    +});
    +
    +Object.defineProperty(o1, "p", { value: 2 });
    +// 修改成功
    +
    +var o2 = Object.defineProperty({}, "p", {
    +  value: 1,
    +  writable: false,
    +  configurable: true,
    +});
    +
    +Object.defineProperty(o2, "p", { value: 2 });
    +// 修改成功
    +
  22. +
  23. getset关键字的使用

    +
    +

    上面两种写法,虽然属性 p 的读取和赋值行为是一样的,但是有一些细微的区别。第一种写法,属性 p 的 configurable 和 enumerable 都为 false,从而导致属性 p 是不可遍历的;第二种写法,属性 p 的 configurable 和 enumerable 都为 true,因此属性 p 是可遍历的。实际开发中,写法二更常用。

    +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 写法二
    +var obj = {
    +  get p() {
    +    return "getter";
    +  },
    +  set p(value) {
    +    console.log("setter: " + value);
    +  },
    +};
    +
  24. +
+

内置对象

    +
  1. 利用Object.prototype.toString方法来写一个能够判断所有数据类型的函数

    +
    1
    2
    3
    4
    5
    6
    7
    const getType = (obj) => {
    +  const type = typeof obj;
    +  if (type !== "object") return type;
    +  return Object.prototype.toString
    +    .call(obj)
    +    .replace(/^\[object (\S+)\]$/, "$1");
    +};
    +
  2. +
  3. 数组的toString方法,注意数组的shiftpop方法会返回当前弹出的值。数组的join方法,如果数组成员是undefinednull或空位,会被转成空字符串。数组的splice方法也能使用负数索引,只使用一个参数就会把从当前索引往后全部删除掉。大部分遍历类型的数组原生函数都会跟三个对象element, index, arr

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    var a = ["a", "b", "c", "d", "e", "f"];
    +a.splice(4, 2, 1, 2); // ["e", "f"]
    +a; // ["a", "b", "c", "d", 1, 2]
    +var a = [1, 1, 1];
    +a.splice(1, 0, 2); // []
    +a; // [1, 2, 1, 1]
    +var a = [1, 2, 3, 4];
    +a.splice(2); // [3, 4]
    +a; // [1, 2]
    +var arr = [1, 2, 3];
    +arr.toString(); // "1,2,3"
    +
    +var arr = [1, 2, 3, [4, 5, 6]];
    +arr.toString(); // "1,2,3,4,5,6"
    +var a = [1, 2, 3, 4];
    +
    +a.join(" "); // '1 2 3 4'
    +a.join(" | "); // "1 | 2 | 3 | 4"
    +a.join(); // "1,2,3,4"
    +
    +Array.prototype.join.call("hello", "-");
    +// "h-e-l-l-o"
    +
    +var obj = { 0: "a", 1: "b", length: 2 };
    +Array.prototype.join.call(obj, "-");
    +// 'a-b'
    +
    +var a = ["a", "b", "c", "d", "e", "f"];
    +a.splice(-4, 2); // ["c", "d"]
    +
    +function log(element, index, array) {
    +  console.log("[" + index + "] = " + element);
    +}
    +
    +[2, 5, 9].forEach(log);
    +// [0] = 2
    +// [1] = 5
    +// [2] = 9
    +
  4. +
  5. 注意,对于空数组,some方法返回falseevery方法返回true,回调函数都不会执行。

    +
  6. +
  7. toFixed()方法先将一个数转为指定位数的小数,然后返回这个小数对应的字符串。

    +
    +

    由于浮点数的原因,小数 5 的四舍五入是不确定的,使用的时候必须小心。

    +
    +
    1
    2
    3
    4
    5
    (10).toFixed(2); // "10.00"
    +(10.005).toFixed(2); // "10.01"
    +
    +(10.055).toFixed(2); // 10.05
    +(10.005).toFixed(2); // 10.01
    +
  8. +
  9. Number.prototype.toPrecision方法用于将一个数转为指定位数的有效数字。

    +
    +

    由于浮点数的原因,该方法四舍五入依然不可靠。

    +
    +
    1
    2
    3
    4
    5
    (12.34).toPrecision(1); // "1e+1"
    +(12.34).toPrecision(2); // "12"
    +(12.34).toPrecision(3); // "12.3"
    +(12.34).toPrecision(4); // "12.34"
    +(12.34).toPrecision(5); // "12.340"
    +
  10. +
  11. toLocaleString方法的使用。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    (123).toLocaleString("zh-Hans-CN", { style: "currency", currency: "CNY" });
    +// "¥123.00"
    +
    +(123).toLocaleString("de-DE", { style: "currency", currency: "EUR" });
    +// "123,00 €"
    +
    +(123).toLocaleString("en-US", { style: "currency", currency: "USD" });
    +// "$123.00"
    +
  12. +
  13. Math对象的相关方法。

    +
      +
    • Math.abs():绝对值
    • +
    • Math.ceil():向上取整
    • +
    • Math.floor():向下取整
    • +
    • Math.max():最大值
    • +
    • Math.min():最小值
    • +
    • Math.pow():幂运算
    • +
    • Math.sqrt():平方根
    • +
    • Math.log():自然对数
    • +
    • Math.exp():e 的指数
    • +
    • Math.round():四舍五入
    • +
    • Math.random():随机数
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    function ToInteger(x) {
    +  x = Number(x);
    +  return x < 0 ? Math.ceil(x) : Math.floor(x);
    +}
    +
    +ToInteger(3.2); // 3
    +ToInteger(3.5); // 3
    +ToInteger(3.8); // 3
    +ToInteger(-3.2); // -3
    +ToInteger(-3.5); // -3
    +ToInteger(-3.8); // -3
    +
    +Math.round(0.1); // 0
    +Math.round(0.5); // 1
    +Math.round(0.6); // 1
    +
    +// 等同于
    +Math.floor(x + 0.5);
    +Math.round(-1.1); // -1
    +Math.round(-1.5); // -1
    +Math.round(-1.6); // -2
    +
  14. +
  15. Date对象可以作为普通函数直接调用,返回一个代表当前时间的字符串。Date实例求值默认调用的是toString方法而不是valueOf方法。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    // 参数为时间零点开始计算的毫秒数
    +new Date(1378218728000);
    +// Tue Sep 03 2013 22:32:08 GMT+0800 (CST)
    +
    +// 参数为日期字符串
    +new Date("January 6, 2013");
    +// Sun Jan 06 2013 00:00:00 GMT+0800 (CST)
    +
    +// 参数为多个整数,
    +// 代表年、月、日、小时、分钟、秒、毫秒
    +new Date(2013, 0, 1, 0, 0, 0, 0);
    +// Tue Jan 01 2013 00:00:00 GMT+0800 (CST)
    +
    +new Date(2013);
    +// Thu Jan 01 1970 08:00:02 GMT+0800 (CST)
    +
    +new Date(2013, 0);
    +// Tue Jan 01 2013 00:00:00 GMT+0800 (CST)
    +new Date(2013, 0, 1);
    +// Tue Jan 01 2013 00:00:00 GMT+0800 (CST)
    +new Date(2013, 0, 1, 0);
    +// Tue Jan 01 2013 00:00:00 GMT+0800 (CST)
    +new Date(2013, 0, 1, 0, 0, 0, 0);
    +// Tue Jan 01 2013 00:00:00 GMT+0800 (CST)
    +
    +// 返回时间戳
    +Date.now(); // 1364026285194
    +
    +new Date("2013-2-15");
    +new Date("2013/2/15");
    +new Date("02/15/2013");
    +new Date("2013-FEB-15");
    +new Date("FEB, 15, 2013");
    +new Date("FEB 15, 2013");
    +new Date("February, 15, 2013");
    +new Date("February 15, 2013");
    +new Date("15 Feb 2013");
    +new Date("15, February, 2013");
    +// Fri Feb 15 2013 00:00:00 GMT+0800 (CST)
    +Date.parse("Aug 9, 1995");
    +Date.parse("January 26, 2011 13:51:50");
    +Date.parse("Mon, 25 Dec 1995 13:30:00 GMT");
    +Date.parse("Mon, 25 Dec 1995 13:30:00 +0430");
    +Date.parse("2011-10-10");
    +Date.parse("2011-10-10T14:48:00");
    +
  16. +
  17. JSON的格式规定

    +
      +
    1. 复合类型的值只能是数组或对象,不能是函数、正则表达式对象、日期对象。
    2. +
    3. 原始类型的值只有四种:字符串、数值(必须以十进制表示)、布尔值和 null(不能使用 NaN, Infinity, -Infinityundefined)。
    4. +
    5. 字符串必须使用双引号表示,不能使用单引号。
    6. +
    7. 对象的键名必须放在双引号里面。
    8. +
    9. 数组或对象最后一个成员的后面,不能加逗号。
    10. +
    +
      +
    • JSON.stringfy可以接受三个参数自定义行为。
    • +
    • toJSON用于自定义返回值作为参数。
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    // 默认输出
    +JSON.stringify({ p1: 1, p2: 2 });
    +// JSON.stringify({ p1: 1, p2: 2 })
    +
    +// 分行输出
    +JSON.stringify({ p1: 1, p2: 2 }, null, "\t");
    +// {
    +// 	"p1": 1,
    +// 	"p2": 2
    +// }
    +
    +var obj = { a: { b: 1 } };
    +
    +function f(key, value) {
    +  console.log("[" + key + "]:" + value);
    +  return value;
    +}
    +
    +JSON.stringify(obj, f);
    +// []:[object Object]
    +// [a]:[object Object]
    +// [b]:1
    +// '{"a":{"b":1}}'
    +
    +var user = {
    +  firstName: "三",
    +  lastName: "张",
    +
    +  get fullName() {
    +    return this.lastName + this.firstName;
    +  },
    +
    +  toJSON: function () {
    +    return {
    +      name: this.lastName + this.firstName,
    +    };
    +  },
    +};
    +
    +JSON.stringify(user);
    +// "{"name":"张三"}"
    +
  18. +
  19. 通过defineProperty实现对象的深拷贝。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    function copyObject(orig) {
    +  return Object.create(
    +    Object.getPrototypeOf(orig),
    +    Object.getOwnPropertyDescriptors(orig)
    +  );
    +}
    +
    +function copyObject(orig) {
    +  var copy = Object.create(Object.getPrototypeOf(orig));
    +  copyOwnPropertiesFrom(copy, orig);
    +  return copy;
    +}
    +
    +function copyOwnPropertiesFrom(target, source) {
    +  Object.getOwnPropertyNames(source).forEach(function (propKey) {
    +    var desc = Object.getOwnPropertyDescriptor(source, propKey);
    +    Object.defineProperty(target, propKey, desc);
    +  });
    +  return target;
    +}
    +
  20. +
+

API

    +
  1. MutationObserver

    +
      +
    • options配置选项。
      childList:子节点的变动(指新增,删除或者更改)。
      attributes:属性的变动。
      characterData:节点内容或节点文本的变动。
      subtree:布尔值,表示是否将该观察器应用于该节点的所有后代节点。
      attributeOldValue:布尔值,表示观察attributes变动时,是否需要记录变动前的属性值。
      characterDataOldValue:布尔值,表示观察characterData变动时,是否需要记录变动前的值。
      attributeFilter:数组,表示需要观察的特定属性(比如['class','src']

      +
    • +
    • MutationRecord对象
      MutationRecord 对象包含了 DOM 的相关信息,有如下属性:
      type:观察的变动类型(attributescharacterData或者 childList)。
      target:发生变动的 DOM 节点。
      addedNodes:新增的 DOM 节点。
      removedNodes:删除的 DOM 节点。
      previousSibling:前一个同级节点,如果没有则返回 null
      nextSibling:下一个同级节点,如果没有则返回 null
      attributeName:发生变动的属性。如果设置了 attributeFilter,则只返回预先指定的属性。
      oldValue:变动前的值。这个属性只对 attributecharacterData 变动有效,如果发生 childList 变动,则返回 null

      +
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    // Select the node that will be observed for mutations
    +const targetNode = document.getElementById("some-id");
    +
    +// Options for the observer (which mutations to observe)
    +const config = { attributes: true, childList: true, subtree: true };
    +
    +// Callback function to execute when mutations are observed
    +const callback = (mutationList, observer) => {
    +  for (const mutation of mutationList) {
    +    if (mutation.type === "childList") {
    +      console.log("A child node has been added or removed.");
    +    } else if (mutation.type === "attributes") {
    +      console.log(`The ${mutation.attributeName} attribute was modified.`);
    +    }
    +  }
    +};
    +
    +// Create an observer instance linked to the callback function
    +const observer = new MutationObserver(callback);
    +
    +// Start observing the target node for configured mutations
    +observer.observe(targetNode, config);
    +
    +// Later, you can stop observing
    +observer.disconnect();
    +
    +// 保存所有没有被观察器处理的变动
    +var changes = mutationObserver.takeRecords();
    +
    +// 停止观察
    +mutationObserver.disconnect();
    +
  2. +
  3. addEventListener第三个参数配置。
    其次,第三个参数除了布尔值 useCapture,还可以是一个监听器配置对象,定制事件监听行为。该对象有以下属性。

    +

    capture:布尔值,如果设为 true,表示监听函数在捕获阶段触发,默认为 false,在冒泡阶段触发。
    once:布尔值,如果设为 true,表示监听函数执行一次就会自动移除,后面将不再监听该事件。该属性默认值为 false
    passive:布尔值,设为 true 时,表示禁止监听函数调用 preventDefault()方法。如果调用了,浏览器将忽略这个要求,并在控制台输出一条警告。该属性默认值为 false
    signal:该属性的值为一个 AbortSignal 对象,为监听器设置了一个信号通道,用来在需要时发出信号,移除监听函数。

    +
  4. +
  5. JS 中的 dispatchEvent 是一个用于在代码中生成和触发事件的方法。它可以用来模拟用户的操作,如点击或按键,或者自定义一些事件,如动画结束或数据更新。使用 dispatchEvent 的步骤如下:

    +
      +
    • 首先,使用 Event 构造函数创建一个新的 Event 对象,指定事件的类型和一些可选的属性,如是否可冒泡或是否可取消。
    • +
    • 然后,使用 element.dispatchEvent()方法将事件派发到一个指定的事件目标,如一个元素或一个文档。这个方法会同步地调用所有监听该事件的事件处理函数,并返回一个布尔值,表示事件是否被取消了。
    • +
    • 最后,如果需要,可以在事件处理函数中使用 event.preventDefault()方法来阻止事件的默认行为,或者使用 event.stopPropagation()方法来阻止事件的进一步传播。
    • +
    +

    下面是一个简单的例子,演示了如何使用 dispatchEvent 来触发一个自定义的 build 事件:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 创建一个新的Event对象,指定事件类型为build
    +const event = new Event("build");
    +
    +// 监听build事件
    +elem.addEventListener(
    +  "build",
    +  (e) => {
    +    // 在事件处理函数中执行一些操作
    +    console.log("build event triggered");
    +  },
    +  false
    +);
    +
    +// 将build事件派发到elem元素
    +elem.dispatchEvent(event);
    +
    +

    Event.stopImmediatePropagation方法阻止同一个事件的其他监听函数被调用,不管监听函数定义在当前节点还是其他节点。也就是说,该方法阻止事件的传播,比Event.stopPropagation()更彻底。

    +
    +
  6. +
  7. 鼠标事件的种类

    +
      +
    • 点击事件
        +
      • click:按下鼠标(通常是按下主按钮)时触发。
      • +
      • dblclick:在同一个元素上双击鼠标时触发。
      • +
      • mousedown:按下鼠标键时触发。
      • +
      • mouseup:释放按下的鼠标键时触发。
      • +
      +
    • +
    • 移动事件
        +
      • mousemove:当鼠标在一个节点内部移动时触发。当鼠标持续移动时,该事件会连续触发。为了避免性能问题,建议对该事件的监听函数做一些限定,比如限定一段时间内只能运行一次。
      • +
      • mouseenter:鼠标进入一个节点时触发,进入子节点不会触发这个事件。
      • +
      • mouseover:鼠标进入一个节点时触发,进入子节点会再一次触发这个事件。
      • +
      • mouseout:鼠标离开一个节点时触发,离开父节点也会触发这个事件。
      • +
      • mouseleave:鼠标离开一个节点时触发,离开父节点不会触发这个事件。
      • +
      +
    • +
    +
  8. +
  9. 键盘事件的种类

    +
      +
    • keydown:按下键盘时触发。
    • +
    • keypress:按下有值的键时触发,即按下 CtrlAltShiftMeta 这样无值的键,这个事件不会触发。对于有值的键,按下时先触发 keydown 事件,再触发这个事件。
    • +
    • keyup:松开键盘时触发该事件。
    • +
    +
  10. +
  11. 进度事件的种类
    进度事件用来描述资源加载的进度,主要由 AJAX 请求、<img>、<audio>、<video>、<style>、<link>等外部资源的加载触发,继承了 ProgressEvent 接口。它主要包含以下几种事件。

    +
      +
    • abort:外部资源中止加载时(比如用户取消)触发。如果发生错误导致中止,不会触发该事件。
    • +
    • error:由于错误导致外部资源无法加载时触发。
    • +
    • load:外部资源加载成功时触发。
    • +
    • loadstart:外部资源开始加载时触发。
    • +
    • loadend:外部资源停止加载时触发,发生顺序排在 error、abort、load 等事件的后面。
    • +
    • progress:外部资源加载过程中不断触发。
    • +
    • timeout:加载超时时触发。
    • +
    +
  12. +
  13. 表单事件

    +
      +
    • inputinput事件当<input>、<select>、<textarea>的值发生变化时触发。对于复选框(<input type=checkbox>)或单选框(<input type=radio>),用户改变选项时,也会触发这个事件。另外,对于打开 contenteditable 属性的元素,只要值发生变化,也会触发 input 事件。
    • +
    • selectselect事件当在<input>、<textarea>里面选中文本时触发。

      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      // HTML 代码如下
      +// <input id="test" type="text" value="Select me!" />
      +var elem = document.getElementById("test");
      +elem.addEventListener(
      +  "select",
      +  function (e) {
      +    console.log(e.type); // "select"
      +  },
      +  false
      +);
      +
    • +
    • change事件当<input>、<select>、<textarea>的值发生变化时触发。它与input事件的最大不同,就是不会连续触发,只有当全部修改完成时才会触发,另一方面input事件必然伴随change事件。具体来说,分成以下几种情况。

      +
    • +
    • invalid:用户提交表单时,如果表单元素的值不满足校验条件,就会触发invalid事件。
    • +
    +
  14. +
  15. 拖拉事件

    +
      +
    • 事件:dragdragstart
    • +
    • DataTransfer接口:
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    div.addEventListener("drop", function (e) {
    +  e.preventDefault();
    +  e.stopPropagation();
    +
    +  var fileList = e.dataTransfer.files;
    +  if (fileList.length > 0) {
    +    var file = fileList[0];
    +    var reader = new FileReader();
    +    reader.onloadend = function (e) {
    +      if (e.target.readyState === FileReader.DONE) {
    +        var content = reader.result;
    +        div.innerHTML = "File: " + file.name + "\n\n" + content;
    +      }
    +    };
    +    reader.readAsBinaryString(file);
    +  }
    +});
    +
  16. +
  17. 动态加载脚本防止浏览器假死

    +
    1
    2
    3
    4
    5
    6
    ["a.js", "b.js"].forEach(function (src) {
    +  var script = document.createElement("script");
    +  script.src = src;
    +  script.async = false;
    +  document.head.appendChild(script);
    +});
  18. +
  19. popstate 事件在浏览器的 history 对象的当前记录发生显式切换时触发。注意,调用 history.pushState()history.replaceState(),并不会触发 popstate 事件。该事件只在用户在 history 记录之间显式切换时触发,比如鼠标点击“后退/前进”按钮,或者在脚本中调用 history.back()history.forward()history.go()时触发。
    该事件对象有一个 state 属性,保存 history.pushState 方法和 history.replaceState 方法为当前记录添加的 state 对象。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    window.onpopstate = function (event) {
    +  console.log("state: " + event.state);
    +};
    +history.pushState({ page: 1 }, "title 1", "?page=1");
    +history.pushState({ page: 2 }, "title 2", "?page=2");
    +history.replaceState({ page: 3 }, "title 3", "?page=3");
    +history.back(); // state: {"page":1}
    +history.back(); // state: null
    +history.go(2); // state: {"page":3}
    +
  20. +
  21. hashchange事件在 URLhash 部分(即#号后面的部分,包括#号)发生变化时触发。该事件一般在window对象上监听。
    hashchange的事件实例具有两个特有属性:oldURL属性和newURL属性,分别表示变化前后的完整 URL

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // URL 是 http://www.example.com/
    +window.addEventListener("hashchange", myFunction);
    +function myFunction(e) {
    +  console.log(e.oldURL);
    +  console.log(e.newURL);
    +}
    +location.hash = "part2";
    +// http://www.example.com/
    +// http://www.example.com/#part2
    +
  22. +
  23. 浏览器的弹窗

    +
      +
    • alert:方法弹出的对话框,只有一个“确定”按钮,往往用来通知用户某些信息。用户只有点击“确定”按钮,对话框才会消失。对话框弹出期间,浏览器窗口处于冻结状态,如果不点“确定”按钮,用户什么也干不了。window.alert()方法的参数只能是字符串,没法使用CSS样式,但是可以用\n指定换行。

      +
      1
      2
      window.alert("Hello World");
      +alert("本条提示\n分成两行");
      +
    • +
    • promptwindow.prompt()方法弹出的对话框,提示文字的下方,还有一个输入框,要求用户输入信息,并有“确定”和“取消”两个按钮。它往往用来获取用户输入的数据。
      window.prompt()的返回值有两种情况,可能是字符串(有可能是空字符串),也有可能是null。具体分成三种情况。

      +
        +
      • 用户输入信息,并点击“确定”,则用户输入的信息就是返回值。
      • +
      • 用户没有输入信息,直接点击“确定”,则输入框的默认值就是返回值。
      • +
      • 用户点击了“取消”(或者按了ESC按钮),则返回值是null
      • +
      +
      1
      var result = prompt("您的年龄?", 25);
      +
    • +
    • confirmwindow.confirm()方法弹出的对话框,除了提示信息之外,只有“确定”和“取消”两个按钮,往往用来征询用户是否同意。

      +
      1
      2
      3
      4
      5
      6
      var okay = confirm("Please confirm this message.");
      +if (okay) {
      +  // 用户按下“确定”
      +} else {
      +  // 用户按下“取消”
      +}
      +
    • +
    +
  24. +
  25. window窗口的相关的方法

    +
      +
    • open:新建一个浏览器窗口
      1
      2
      3
      4
      5
      var popup = window.open(
      +  "somepage.html",
      +  "DefinitionsWindows",
      +  "height=200,width=200,location=no,status=yes,resizable=yes,scrollbars=yes"
      +);
    • +
    • close:关闭当前的窗口
    • +
    • stop:停止资源的加载
    • +
    +
  26. +
  27. window.navigator属性指向一个包含浏览器和系统信息的Navigator对象。脚本通过这个属性了解用户的环境信息。
  28. +
  29. ajax的基本使用

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    //接口地址 https://api.apiopen.top/getJoke
    +//获取元素对象
    +const btn = document.querySelector("#btn");
    +
    +btn.addEventListener("click", function () {
    +  //创建 Promise
    +  const p = new Promise((resolve, reject) => {
    +    //1.创建对象
    +    const xhr = new XMLHttpRequest();
    +    //2. 初始化
    +    xhr.open("GET", "https://api.apiopen.top/getJoke");
    +    //3. 发送
    +    xhr.send();
    +    //4. 处理响应结果
    +    xhr.onreadystatechange = function () {
    +      if (xhr.readyState === 4) {
    +        //判断响应状态码 2xx
    +        if (xhr.status >= 200 && xhr.status < 300) {
    +          //控制台输出响应体
    +          resolve(xhr.response);
    +        } else {
    +          //控制台输出响应状态码
    +          reject(xhr.status);
    +        }
    +      }
    +    };
    +  });
    +  //调用then方法
    +  p.then(
    +    (value) => {
    +      console.log(value);
    +    },
    +    (reason) => {
    +      console.warn(reason);
    +    }
    +  );
    +});
    +
  30. +
+

浏览器的相关知识

    +
  1. 同源策略(same-origin policy
    要求

    +
      +
    • 协议相同
    • +
    • 域名相同
    • +
    • 端口相同
    • +
    +

    但是,浏览器对于cookie的处理并没有完全遵守同源策略的规定。实际上,浏览器允许同一个域名下的不同端口之间共享cookie。也就是说,如果一个网址是http://www.example.com:8000,它可以读取和写入http://www.example.com:8001的cookie,反之亦然。这是因为浏览器实现来说,“cookie区分域,而不区分端口”。

    +
    +
  2. +
  3. JSONP的使用

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function addScriptTag(src) {
    +  var script = document.createElement("script");
    +  script.setAttribute("type", "text/javascript");
    +  script.src = src;
    +  document.body.appendChild(script);
    +}
    +window.onload = function () {
    +  addScriptTag("http://example.com/ip?callback=foo");
    +};
    +function foo(data) {
    +  console.log("Your public IP address is: " + data.ip);
    +}
    +
    +foo({
    +  ip: "8.8.8.8",
    +});
    +
  4. +
  5. CORS的使用

    +
      +
    • 满足简单请求的条件:
        +
      1. 请求方法是以下三种方法之一
          +
        • GET
        • +
        • POST
        • +
        • HEAD
        • +
        +
      2. +
      3. HTTP 的头信息不超出以下几种字段。
          +
        • Accept
        • +
        • Accept-Language
        • +
        • Content-Language
        • +
        • Last-Event-ID
        • +
        • Content-Type:只限于三个值application/x-www-form-urlencodedmultipart/form-datatext/plain
        • +
        +
      4. +
      +
    • +
    • 对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。
    • +
    +
    1
    2
    3
    4
    5
    6
    GET /cors HTTP/1.1
    +Origin: http://api.bob.com
    +Host: api.alice.com
    +Accept-Language: en-US
    +Connection: keep-alive
    +User-Agent: Mozilla/5.0...
    +
    1
    2
    3
    4
    Access-Control-Allow-Origin: http://api.bob.com
    +Access-Control-Allow-Credentials: true
    +Access-Control-Expose-Headers: FooBar
    +Content-Type: text/html; charset=utf-8
    +
      +
    • 相应预检请求的node.js
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    // 导入http模块
    +const http = require("http");
    +
    +// 创建一个http服务器
    +const server = http.createServer((req, res) => {
    +  // 获取请求方法和请求头
    +  const method = req.method;
    +  const headers = req.headers;
    +
    +  // 如果请求方法是OPTIONS,表示是预检请求
    +  if (method === "OPTIONS") {
    +    // 设置响应头,允许跨域请求,允许的请求方法和请求头
    +    res.setHeader("Access-Control-Allow-Origin", "*");
    +    res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
    +    res.setHeader(
    +      "Access-Control-Allow-Headers",
    +      "Content-Type, Authorization"
    +    );
    +    // 结束响应,不返回任何数据
    +    res.end();
    +  } else {
    +    // 如果请求方法不是OPTIONS,表示是正式请求
    +    // 设置响应头,允许跨域请求,返回数据的类型和编码
    +    res.setHeader("Access-Control-Allow-Origin", "*");
    +    res.setHeader("Content-Type", "text/plain; charset=utf-8");
    +    // 返回一些数据
    +    res.end("这是一个跨域请求的响应");
    +  }
    +});
    +
    +// 监听3000端口
    +server.listen(3000, () => {
    +  console.log("服务器启动成功,监听3000端口");
    +});
    +
  6. +
  7. 读取生成URL

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    var droptarget = document.getElementById("droptarget");
    +droptarget.ondrop = function (e) {
    +  var files = e.dataTransfer.files;
    +  for (var i = 0; i < files.length; i++) {
    +    var type = files[i].type;
    +    if (type.substring(0, 6) !== "image/") continue;
    +    var img = document.createElement("img");
    +    img.src = URL.createObjectURL(files[i]);
    +    img.onload = function () {
    +      this.width = 100;
    +      document.body.appendChild(this);
    +      URL.revokeObjectURL(this.src);
    +    };
    +  }
    +};
    +// HTML 代码如下
    +// <input type="file" onchange="onChange(event)">
    +function onChange(event) {
    +  var file = event.target.files[0];
    +  var reader = new FileReader();
    +  reader.onload = function (event) {
    +    console.log(event.target.result);
    +  };
    +  reader.readAsText(file);
    +}
    +
  8. +
+

ES6

块级作用域

letconst都不会发生变量提升,并且都是块级作用域
这是因为 var 和 let/const 在变量声明时有不同的机制。var 声明的变量会发生变量提升,也就是说,它们的声明会被提升到作用域的顶部,但是赋值操作不会被提升。所以在声明之前使用 var 变量,不会报错,但是变量的值是 undefined,而不是赋值后的值。例如:

+
1
2
console.log(a); // 输出undefined
+var a = 10; // 声明和赋值都被提升
+

上面的代码相当于:

+
1
2
3
var a; // 声明提升
+console.log(a); // 输出undefined
+a = 10; // 赋值保留
+

而 let/const 声明的变量不会发生变量提升,也就是说,它们的声明和赋值都不会被提升。而且,在声明之前使用 let/const 变量会报错,因为 let/const 声明的变量在初始化之前存在一个暂时性死区(Temporal Dead Zone),在这个区域内,变量不能被访问或者赋值。例如:

+
1
2
console.log(b); // 报错,b未定义
+let b = 10; // 声明提升,赋值不提升
+

寄生式继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 原型链继承
+function Animal(name) {
+  this.name = name;
+}
+
+Animal.prototype.eat = function () {
+  console.log(this.name + " is eating.");
+};
+
+function Dog(name, breed) {
+  Animal.call(this, name); // 调用父类的构造函数
+  this.breed = breed;
+}
+
+Dog.prototype = Object.create(Animal.prototype); // 设置原型链,让 Dog 继承 Animal
+Dog.prototype.constructor = Dog; // 修正构造函数指向
+Dog.prototype.bark = function () {
+  console.log(this.name + " is barking.");
+};
+
+var dog = new Dog("Buddy", "Labrador");
+dog.eat(); // 调用从 Animal 继承的方法
+dog.bark(); // 调用 Dog 的方法
+
+// ES6 类继承
+class Animal {
+  constructor(name) {
+    this.name = name;
+  }
+
+  eat() {
+    console.log(this.name + " is eating.");
+  }
+}
+
+class Dog extends Animal {
+  constructor(name, breed) {
+    super(name); // 调用父类的构造函数
+    this.breed = breed;
+  }
+
+  bark() {
+    console.log(this.name + " is barking.");
+  }
+}
+
+const dog = new Dog("Buddy", "Labrador");
+dog.eat(); // 调用从 Animal 继承的方法
+dog.bark(); // 调用 Dog 的方法
+

手写一个 new 函数

+

关键在于如果函数返回了一个对象则以这个对象为new的返回结果。

+
+
1
2
3
4
5
const newFunc = (constructor, ...params) => {
+  const obj = Object.create(constructor.prototype);
+  const result = constructor.apply(obj, params);
+  return typeof result === "object" && result !== null ? result : obj;
+};
+

数组的新增方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
const array1 = ["a", "b", "c", "d", "e"];
+// Copy to index 0 the element at index 3
+console.log(array1.copyWithin(0, 3, 4));
+// Expected output: Array ["d", "b", "c", "d", "e"]
+// Copy to index 1 all elements from index 3 to the end
+console.log(array1.copyWithin(1, 3));
+// Expected output: Array ["d", "d", "e", "d", "e"]
+// 将3号位复制到0号位
+[1, 2, 3, 4, 5].copyWithin(0, 3, 4);
+// [4, 2, 3, 4, 5]
+// -2相当于3号位,-1相当于4号位
+[1, 2, 3, 4, 5].copyWithin(0, -2, -1);
+// [4, 2, 3, 4, 5]
+// 将3号位复制到0号位
+[].copyWithin.call({ length: 5, 3: 1 }, 0, 3);
+// {0: 1, 3: 1, length: 5}
+// 将2号位到数组结束,复制到0号位
+let i32a = new Int32Array([1, 2, 3, 4, 5]);
+i32a.copyWithin(0, 2);
+// Int32Array [3, 4, 5, 4, 5]
+// 对于没有部署 TypedArray 的 copyWithin 方法的平台
+// 需要采用下面的写法
+[].copyWithin.call(new Int32Array([1, 2, 3, 4, 5]), 0, 3, 4);
+// Int32Array [4, 2, 3, 4, 5]
+
+["a", "b", "c"].fill(7);
+// [7, 7, 7]
+new Array(3).fill(7)[
+  // [7, 7, 7]
+  ("a", "b", "c")
+].fill(7, 1, 2);
+// ['a', 7, 'c']
+let arr = new Array(3).fill({ name: "Mike" });
+arr[0].name = "Ben";
+arr;
+// [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}]
+let arr = new Array(3).fill([]);
+arr[0].push(5);
+arr;
+// [[5], [5], [5]]
+
+for (let index of ["a", "b"].keys()) {
+  console.log(index);
+}
+// 0
+// 1
+for (let elem of ["a", "b"].values()) {
+  console.log(elem);
+}
+// 'a'
+// 'b'
+for (let [index, elem] of ["a", "b"].entries()) {
+  console.log(index, elem);
+}
+// 0 "a"
+// 1 "b"
+

空值运算符

它的行为类似||,但是只有运算符左侧的值为 nullundefined 时,才会返回右侧的值。

+
1
2
3
const headerText = response.settings.headerText ?? "Hello, world!";
+const animationDuration = response.settings.animationDuration ?? 300;
+const showSplashScreen = response.settings.showSplashScreen ?? true;
+

Symbol

    +
  1. 基本使用

    +
    1
    2
    3
    4
    5
    6
    7
    let s1 = Symbol("foo");
    +let s2 = Symbol("bar");
    +s1; // Symbol(foo)
    +s2; // Symbol(bar)
    +s1.toString(); // "Symbol(foo)"
    +s2.toString(); // "Symbol(bar)"
    +s1.description; // "foo"
    +
  2. +
  3. 获取Symbol对象属性名

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    const obj = {};
    +let a = Symbol("a");
    +let b = Symbol("b");
    +obj[a] = "Hello";
    +obj[b] = "World";
    +const objectSymbols = Object.getOwnPropertySymbols(obj);
    +objectSymbols;
    +// [Symbol(a), Symbol(b)]
    +
    +let obj = {
    +  [Symbol("my_key")]: 1,
    +  enum: 2,
    +  nonEnum: 3,
    +};
    +Reflect.ownKeys(obj);
    +//  ["enum", "nonEnum", Symbol(my_key)]
    +
  4. +
  5. Symbol值的重用

    +
    +

    有时,我们希望重新使用同一个 Symbol 值,Symbol.for()方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局。

    +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    let s1 = Symbol.for("foo");
    +let s2 = Symbol.for("foo");
    +s1 === s2; // true
    +
    +let s1 = Symbol.for("foo");
    +Symbol.keyFor(s1); // "foo"
    +let s2 = Symbol("foo");
    +Symbol.keyFor(s2); // undefined
    +
  6. +
+

Proxy

    +
  • get(target, propKey, receiver):拦截对象属性的读取,比如proxy.fooproxy['foo']
  • +
  • set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = vproxy['foo'] = v,返回一个布尔值。
  • +
  • has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值。
  • +
  • deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值。
  • +
  • ownKeys(target):拦截Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
  • +
  • getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
  • +
  • defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDescs),返回一个布尔值。
  • +
  • preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。
  • +
  • getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。
  • +
  • isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。
  • +
  • setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
  • +
  • apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)proxy.call(object, ...args)proxy.apply(...)
  • +
  • construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)

    +
  • +
  • Reflect配合Proxy实现观察者模式。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var obj = new Proxy(
    +  {},
    +  {
    +    get: function (target, propKey, receiver) {
    +      console.log(`getting ${propKey}!`);
    +      return Reflect.get(target, propKey, receiver);
    +    },
    +    set: function (target, propKey, value, receiver) {
    +      console.log(`setting ${propKey}!`);
    +      return Reflect.set(target, propKey, value, receiver);
    +    },
    +  }
    +);
    +
  • +
  • Proxy.revocable()方法返回一个可取消的 Proxy 实例。

    +
    1
    2
    3
    4
    5
    6
    7
    let target = {};
    +let handler = {};
    +let { proxy, revoke } = Proxy.revocable(target, handler);
    +proxy.foo = 123;
    +proxy.foo; // 123
    +revoke();
    +proxy.foo; // TypeError: Revoked
    +
  • +
+

Reflect

+

Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。Reflect对象的设计目的有这样几个。

+
    +
  1. Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在ObjectReflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法。
  2. +
  3. 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false
  4. +
  5. Object操作都变成函数行为。某些Object操作是命令式,比如name in objdelete obj[name],而Reflect.has(obj, name)Reflect.deleteProperty(obj, name)让它们变成了函数行为。
  6. +
  7. Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。
  8. +
+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 老写法
+try {
+  Object.defineProperty(target, property, attributes);
+  // success
+} catch (e) {
+  // failure
+}
+// 新写法
+if (Reflect.defineProperty(target, property, attributes)) {
+  // success
+} else {
+  // failure
+}
+
+// 老写法
+"assign" in Object; // true
+// 新写法
+Reflect.has(Object, "assign"); // true
+
+Proxy(target, {
+  set: function (target, name, value, receiver) {
+    var success = Reflect.set(target, name, value, receiver);
+    if (success) {
+      console.log("property " + name + " on " + target + " set to " + value);
+    }
+    return success;
+  },
+});
+
+var loggedObj = new Proxy(obj, {
+  get(target, name) {
+    console.log("get", target, name);
+    return Reflect.get(target, name);
+  },
+  deleteProperty(target, name) {
+    console.log("delete" + name);
+    return Reflect.deleteProperty(target, name);
+  },
+  has(target, name) {
+    console.log("has" + name);
+    return Reflect.has(target, name);
+  },
+});
+
+// 老写法
+Function.prototype.apply.call(Math.floor, undefined, [1.75]); // 1
+// 新写法
+Reflect.apply(Math.floor, undefined, [1.75]); // 1
+

iterator

    +
  1. 简单遍历器实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var it = makeIterator(["a", "b"]);
    +it.next(); // { value: "a", done: false }
    +it.next(); // { value: "b", done: false }
    +it.next(); // { value: undefined, done: true }
    +function makeIterator(array) {
    +  var nextIndex = 0;
    +  return {
    +    next: function () {
    +      return nextIndex < array.length
    +        ? { value: array[nextIndex++], done: false }
    +        : { value: undefined, done: true };
    +    },
    +  };
    +}
  2. +
  3. return方法的使用场合是,如果for...of循环提前退出(通常是因为出错,或者有break语句),就会调用return方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return方法。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    // return() 方法必须返回一个对象
    +function readLinesSync(file) {
    +  return {
    +    [Symbol.iterator]() {
    +      return {
    +        next() {
    +          return { done: false };
    +        },
    +        return() {
    +          file.close();
    +          return { done: true };
    +        },
    +      };
    +    },
    +  };
    +}
    +
    +// 以下两种情况会导致 return 方法的执行
    +// 情况一
    +for (let line of readLinesSync(fileName)) {
    +  console.log(line);
    +  break;
    +}
    +// 情况二
    +for (let line of readLinesSync(fileName)) {
    +  console.log(line);
    +  throw new Error();
    +}
    +
  4. +
+

generator

    +
  1. 基本实现

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function* helloWorldGenerator() {
    +  yield "hello";
    +  yield "world";
    +  return "ending";
    +}
    +var hw = helloWorldGenerator();
    +
    +hw.next();
    +// { value: 'hello', done: false }
    +hw.next();
    +// { value: 'world', done: false }
    +hw.next();
    +// { value: 'ending', done: true }
    +hw.next();
    +// { value: undefined, done: true }
    +
  2. +
  3. throw方法和return方法。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    var g = function* () {
    +  try {
    +    yield;
    +  } catch (e) {
    +    console.log("内部捕获", e);
    +  }
    +};
    +var i = g();
    +i.next();
    +try {
    +  i.throw("a");
    +  i.throw("b");
    +} catch (e) {
    +  console.log("外部捕获", e);
    +}
    +// 内部捕获 a
    +// 外部捕获 b
    +
    +function* gen() {
    +  yield 1;
    +  yield 2;
    +  yield 3;
    +}
    +var g = gen();
    +g.next(); // { value: 1, done: false }
    +g.return("foo"); // { value: "foo", done: true }
    +g.next(); // { value: undefined, done: true }
    +
    +function* numbers() {
    +  yield 1;
    +  try {
    +    yield 2;
    +    yield 3;
    +  } finally {
    +    yield 4;
    +    yield 5;
    +  }
    +  yield 6;
    +}
    +var g = numbers();
    +g.next(); // { value: 1, done: false }
    +g.next(); // { value: 2, done: false }
    +g.return(7); // { value: 4, done: false }
    +g.next(); // { value: 5, done: false }
    +g.next(); // { value: 7, done: true }
    +
  4. +
+

async 函数的实现原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const asyncFunc = () => {
+  return spawn(function* () {});
+};
+const spawn = (genF) => {
+  return new Promise((resolve, reject) => {
+    let gen = genF();
+    const step = (nextF) => {
+      let next;
+      try {
+        next = nextF();
+      } catch (error) {
+        reject(error);
+      }
+      if (next.done) {
+        return resolve(next.value);
+      }
+      Promise.resolve(next.value).then(
+        (v) => step(() => gen.next(v)),
+        (r) => step(() => gen.throw(r))
+      );
+    };
+    step(() => gen.next(undefined));
+  });
+};
+

super关键字的注意点

+

由于this指向子类实例,所以如果通过super对某个属性赋值,这时super就是this,赋值的属性会变成子类实例的属性。

+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A {
+  constructor() {
+    this.x = 1;
+  }
+}
+class B extends A {
+  constructor() {
+    super();
+    this.x = 2;
+    super.x = 3;
+    console.log(super.x); // undefined
+    console.log(this.x); // 3
+  }
+}
+let b = new B();
+

ES6 属性的遍历

ES6 一共有 5 种方法可以遍历对象的属性。

+
    +
  • for...in:循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)
  • +
  • Object.keys(obj):返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名
  • +
  • Object.getOwnPropertyNames(obj):回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名
  • +
  • Object.getOwnPropertySymbols(obj):返回一个数组,包含对象自身的所有 Symbol 属性的键名
  • +
  • Reflect.ownKeys(obj):返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举
  • +
+

上述遍历,都遵守同样的属性遍历的次序规则:

+
    +
  • 首先遍历所有数值键,按照数值升序排列
  • +
  • 其次遍历所有字符串键,按照加入时间升序排列
  • +
  • 最后遍历所有 Symbol 键,按照加入时间升序排
  • +
+
1
2
Reflect.ownKeys({ [Symbol()]: 0, b: 0, 10: 0, 2: 0, a: 0 });
+// ['2', '10', 'b', 'a', Symbol()]
+
文章作者: 希亚的西红柿
文章链接: http://blog.refrain.site/posts/b2b6ba06/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
+ + + + \ No newline at end of file diff --git a/posts/b672a0f6/index.html b/posts/b672a0f6/index.html new file mode 100644 index 000000000..63660c67c --- /dev/null +++ b/posts/b672a0f6/index.html @@ -0,0 +1,2317 @@ +LeetCode 算法笔记 Part2 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + + +

LeetCode 算法笔记 Part2

本文主要记录了博主的 Leetcode 算法刷题记录,方便以后查询复习。

+
+

链表

相交链表

LeetCode 原题链接:160. 相交链表

+
+

哈希表存储遍历的节点,然后进行操作。第二种方法技巧性比较强,利用路径长度相同会回到相交的点的做法来求解。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/**
+ * Definition for singly-linked list.
+ * function ListNode(val) {
+ *     this.val = val;
+ *     this.next = null;
+ * }
+ */
+
+/**
+ * @param {ListNode} headA
+ * @param {ListNode} headB
+ * @return {ListNode}
+ */
+const getIntersectionNode = (headA, headB) => {
+  const set = new Set();
+  while (headA) {
+    set.add(headA);
+    headA = headA.next;
+  }
+  while (headB) {
+    if (set.has(headB)) {
+      return headB;
+    } else {
+      headB = headB.next;
+    }
+  }
+  return null;
+};
+
+/**
+ * Definition for singly-linked list.
+ * function ListNode(val) {
+ *     this.val = val;
+ *     this.next = null;
+ * }
+ */
+
+/**
+ * @param {ListNode} headA
+ * @param {ListNode} headB
+ * @return {ListNode}
+ */
+const getIntersectionNode = (headA, headB) => {
+  if (!headA || !headB) {
+    return null;
+  }
+  let p1 = headA,
+    p2 = headB;
+  while (p1 !== p2) {
+    p1 = p1 ? p1.next : headB;
+    p2 = p2 ? p2.next : headA;
+  }
+  return p1;
+};
+
+// 测试用例,写的满头大汗,还挺多坑的
+class ListNode {
+  constructor(val) {
+    this.val = val;
+    this.next = null;
+  }
+}
+
+const p1 = new ListNode();
+const p2 = new ListNode();
+let temp = p1;
+for (let i = 0; i < 5; i++) {
+  temp.next = new ListNode(i);
+  temp = temp.next;
+}
+temp = p2;
+for (let i = 0; i < 2; i++) {
+  temp.next = new ListNode(i + 10);
+  temp = temp.next;
+}
+
+let temp0 = p1;
+for (let i = 0; i < 3; i++) {
+  temp0 = temp0.next;
+}
+temp.next = temp0;
+
+console.dir(p1, { depth: null });
+console.dir(p2, { depth: null });
+console.log(getIntersectionNode(p1.next, p2.next));
+

反转链表

LeetCode 原题链接:206. 反转链表

+
+

循环和递归,递归比较难以理解,参考意义不是很大。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/**
+ * Definition for singly-linked list.
+ * function ListNode(val, next) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.next = (next===undefined ? null : next)
+ * }
+ */
+/**
+ * @param {ListNode} head
+ * @return {ListNode}
+ */
+const reverseList = (head) => {
+  const newHead = new ListNode();
+  while (head) {
+    const nextEl = head.next;
+    head.next = newHead.next;
+    newHead.next = head;
+    head = nextEl;
+  }
+  return newHead.next;
+};
+
+/**
+ * Definition for singly-linked list.
+ * function ListNode(val, next) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.next = (next===undefined ? null : next)
+ * }
+ */
+/**
+ * @param {ListNode} head
+ * @return {ListNode}
+ */
+const reverseList = (head) => {
+  if (!head || !head.next) {
+    return head;
+  }
+  const newHead = reverseList(head.next);
+  head.next.next = head;
+  head.next = null;
+  return newHead;
+};
+
+class ListNode {
+  constructor(val) {
+    this.val = val;
+    this.next = null;
+  }
+}
+
+const p1 = new ListNode();
+let temp = p1;
+
+for (let i = 0; i < 5; i++) {
+  temp.next = new ListNode(i);
+  temp = temp.next;
+}
+
+console.dir(reverseList(p1.next), { depth: null });
+

回文链表

LeetCode 原题链接:234. 回文链表

+
+

将值放在数组中然后进行判断

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
+ * Definition for singly-linked list.
+ * function ListNode(val, next) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.next = (next===undefined ? null : next)
+ * }
+ */
+/**
+ * @param {ListNode} head
+ * @return {boolean}
+ */
+const isPalindrome = (head) => {
+  const valArr = [];
+  while (head) {
+    valArr.push(head.val);
+    head = head.next;
+  }
+  let i = 0,
+    j = valArr.length - 1;
+  while (i < j) {
+    if (valArr[i++] !== valArr[j--]) {
+      return false;
+    }
+  }
+  return true;
+};
+
+class ListNode {
+  constructor(val) {
+    this.val = val;
+    this.next = null;
+  }
+}
+
+const p1 = new ListNode();
+let temp = p1;
+
+for (let i = 0; i < 5; i++) {
+  temp.next = new ListNode(Math.abs(i - 2));
+  temp = temp.next;
+}
+
+console.dir(isPalindrome(p1.next), { depth: null });
+

环形链表

LeetCode 原题链接:141. 环形链表

+
+

哈希表与快慢指针,无需多言。🥰

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/**
+ * Definition for singly-linked list.
+ * function ListNode(val) {
+ *     this.val = val;
+ *     this.next = null;
+ * }
+ */
+
+/**
+ * @param {ListNode} head
+ * @return {boolean}
+ */
+const hasCycle = (head) => {
+  if (!head) {
+    return false;
+  }
+  const set = new WeakSet();
+  let slow = head,
+    fast = head.next;
+  while (fast) {
+    set.add(slow);
+    if (set.has(fast)) {
+      return true;
+    }
+    slow = slow.next;
+    fast = fast.next?.next;
+  }
+  return false;
+};
+
+/**
+ * Definition for singly-linked list.
+ * function ListNode(val) {
+ *     this.val = val;
+ *     this.next = null;
+ * }
+ */
+
+/**
+ * @param {ListNode} head
+ * @return {boolean}
+ */
+const hasCycle = (head) => {
+  if (!head) {
+    return false;
+  }
+  let slow = head,
+    fast = head.next;
+  while (fast) {
+    slow = slow.next;
+    fast = fast.next?.next;
+    if (slow === fast) {
+      return true;
+    }
+  }
+  return false;
+};
+
+class ListNode {
+  constructor(val) {
+    this.val = val;
+    this.next = null;
+  }
+}
+
+const p1 = new ListNode();
+let temp = p1;
+for (let i = 0; i < 5; i++) {
+  temp.next = new ListNode(i);
+  temp = temp.next;
+}
+temp.next = p1.next;
+
+console.dir(hasCycle(p1.next), { depth: null });
+

环形链表 Ⅱ

LeetCode 原题链接:142. 环形链表 Ⅱ

+
+

哈希表与快慢指针求解,第二种方法技巧性比较强,参考性不是很高。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/**
+ * Definition for singly-linked list.
+ * function ListNode(val) {
+ *     this.val = val;
+ *     this.next = null;
+ * }
+ */
+
+/**
+ * @param {ListNode} head
+ * @return {ListNode}
+ */
+const detectCycle = (head) => {
+  const set = new WeakSet();
+  let p = head;
+  while (p) {
+    if (set.has(p)) {
+      return p;
+    }
+    set.add(p);
+    p = p.next;
+  }
+  return null;
+};
+
+/**
+ * Definition for singly-linked list.
+ * function ListNode(val) {
+ *     this.val = val;
+ *     this.next = null;
+ * }
+ */
+
+/**
+ * @param {ListNode} head
+ * @return {ListNode}
+ */
+const detectCycle = (head) => {
+  if (head === null) {
+    return null;
+  }
+  let slow = head,
+    fast = head;
+  while (fast !== null) {
+    slow = slow.next;
+    if (fast.next !== null) {
+      fast = fast.next.next;
+    } else {
+      return null;
+    }
+    if (fast === slow) {
+      let ptr = head;
+      while (ptr !== slow) {
+        ptr = ptr.next;
+        slow = slow.next;
+      }
+      return ptr;
+    }
+  }
+  return null;
+};
+
+class ListNode {
+  constructor(val) {
+    this.val = val;
+    this.next = null;
+  }
+}
+
+const p1 = new ListNode();
+let temp = p1;
+for (let i = 0; i < 5; i++) {
+  temp.next = new ListNode(i);
+  temp = temp.next;
+}
+temp.next = p1.next;
+
+const p2 = new ListNode();
+temp = p2;
+for (let i = 0; i < 5; i++) {
+  temp.next = new ListNode(i + 10);
+  temp = temp.next;
+}
+temp.next = p1.next;
+
+console.dir(p2.next, { depth: null });
+console.dir(detectCycle(p2.next), { depth: null });
+

合并两个有序链表

LeetCode 原题链接:21. 合并两个有序链表

+
+

有递归和循环两种写法。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/**
+ * Definition for singly-linked list.
+ * function ListNode(val, next) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.next = (next===undefined ? null : next)
+ * }
+ */
+/**
+ * @param {ListNode} list1
+ * @param {ListNode} list2
+ * @return {ListNode}
+ */
+const mergeTwoLists = (list1, list2) => {
+  if (!list1) {
+    return list2;
+  }
+  if (!list2) {
+    return list1;
+  }
+  if (list1.val < list2.val) {
+    list1.next = mergeTwoLists(list1.next, list2);
+    return list1;
+  } else {
+    list2.next = mergeTwoLists(list1, list2.next);
+    return list2;
+  }
+};
+
+/**
+ * Definition for singly-linked list.
+ * function ListNode(val, next) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.next = (next===undefined ? null : next)
+ * }
+ */
+/**
+ * @param {ListNode} list1
+ * @param {ListNode} list2
+ * @return {ListNode}
+ */
+const mergeTwoLists = (list1, list2) => {
+  const dummy = new ListNode();
+  let temp = dummy;
+  while (list1 && list2) {
+    if (list1.val < list2.val) {
+      temp.next = list1;
+      list1 = list1.next;
+    } else {
+      temp.next = list2;
+      list2 = list2.next;
+    }
+    temp = temp.next;
+  }
+  temp.next = list1 || list2;
+  return dummy.next;
+};
+
+class ListNode {
+  constructor(val) {
+    this.val = val;
+    this.next = null;
+  }
+}
+
+const p1 = new ListNode();
+let temp = p1;
+for (let i = 0; i < 5; i++) {
+  temp.next = new ListNode(i * 2);
+  temp = temp.next;
+}
+
+const p2 = new ListNode();
+temp = p2;
+for (let i = 0; i < 5; i++) {
+  temp.next = new ListNode(i * 2 + 1);
+  temp = temp.next;
+}
+
+console.dir(p1.next, { depth: null });
+console.dir(p2.next, { depth: null });
+console.dir(mergeTwoLists(p1.next, p2.next), { depth: null });
+

两数相加

LeetCode 原题链接:2. 两数相加

+
+

注意进位的处理与归零,其他的都比较简单,思路正确照着写就行。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**
+ * Definition for singly-linked list.
+ * function ListNode(val, next) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.next = (next===undefined ? null : next)
+ * }
+ */
+/**
+ * @param {ListNode} l1
+ * @param {ListNode} l2
+ * @return {ListNode}
+ */
+const addTwoNumbers = (l1, l2) => {
+  let head = null,
+    tail = null;
+  let carry = 0;
+  while (l1 || l2 || carry) {
+    const n1 = l1 ? l1.val : 0;
+    const n2 = l2 ? l2.val : 0;
+    const sum = n1 + n2 + carry;
+    if (!head) {
+      head = tail = new ListNode(sum % 10);
+    } else {
+      tail.next = new ListNode(sum % 10);
+      tail = tail.next;
+    }
+    carry = Math.floor(sum / 10);
+    if (l1) {
+      l1 = l1.next;
+    }
+    if (l2) {
+      l2 = l2.next;
+    }
+  }
+  return head;
+};
+
+class ListNode {
+  constructor(val) {
+    this.val = val;
+    this.next = null;
+  }
+}
+
+const p1 = new ListNode();
+let temp = p1;
+for (let i = 0; i < 5; i++) {
+  temp.next = new ListNode(i * 2);
+  temp = temp.next;
+}
+
+const p2 = new ListNode();
+temp = p2;
+for (let i = 0; i < 5; i++) {
+  temp.next = new ListNode(i * 2 + 1);
+  temp = temp.next;
+}
+
+console.dir(p1.next, { depth: null });
+console.dir(p2.next, { depth: null });
+console.dir(addTwoNumbers(p1.next, p2.next), { depth: null });
+

删除链表的倒数第 N 个节点

+

采用计算链表长度或者双指针进行求解

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
+ * Definition for singly-linked list.
+ * function ListNode(val, next) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.next = (next===undefined ? null : next)
+ * }
+ */
+/**
+ * @param {ListNode} head
+ * @param {number} n
+ * @return {ListNode}
+ */
+const removeNthFromEnd = (head, n) => {
+  let length = 0,
+    temp = head;
+  const newHead = new ListNode(0, head);
+  while (temp) {
+    length++;
+    temp = temp.next;
+  }
+  temp = newHead;
+  for (let i = 0; i < length - n; i++) {
+    temp = temp.next;
+  }
+  temp.next = temp.next.next;
+  return newHead.next;
+};
+
+class ListNode {
+  constructor(val, next) {
+    this.val = val;
+    this.next = next;
+  }
+}
+
+const p1 = new ListNode();
+let temp = p1;
+for (let i = 0; i < 5; i++) {
+  temp.next = new ListNode(i);
+  temp = temp.next;
+}
+
+console.dir(p1.next, { depth: null });
+console.dir(removeNthFromEnd(p1.next, 2), { depth: null });
+

两两交换链表中的节点

LeetCode 原题链接:24. 两两交换链表中的节点

+
+

注意指针需要用temp.next.next,便于直接删除。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
+ * Definition for singly-linked list.
+ * function ListNode(val, next) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.next = (next===undefined ? null : next)
+ * }
+ */
+/**
+ * @param {ListNode} head
+ * @return {ListNode}
+ */
+const swapPairs = (head) => {
+  const newHead = new ListNode(0, head);
+  let temp = newHead;
+  while (temp.next?.next) {
+    const firstNode = temp.next;
+    const SecondNode = temp.next.next;
+    const next = SecondNode.next;
+    temp.next = SecondNode;
+    SecondNode.next = firstNode;
+    firstNode.next = next;
+    temp = firstNode;
+  }
+  return newHead.next;
+};
+
+class ListNode {
+  constructor(val, next) {
+    this.val = val;
+    this.next = next;
+  }
+}
+
+const p1 = new ListNode();
+let temp = p1;
+for (let i = 0; i < 5; i++) {
+  temp.next = new ListNode(i);
+  temp = temp.next;
+}
+
+console.dir(p1.next, { depth: null });
+console.dir(swapPairs(p1.next), { depth: null });
+

k 个一组翻转链表

LeetCode 原题链接:25. k 个一组翻转链表

+
+

该题思路比较简单,但处理情况比较繁琐,十分考量面试者的代码设计能力,推荐将可以翻转的判断与翻转函数书写,同时需要注意翻转后的链表需要连接回原来的链表,所以需要保留上一链表的节点,所以翻转的时候推荐使用temp.next进行翻转。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/**
+ * Definition for singly-linked list.
+ * function ListNode(val, next) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.next = (next===undefined ? null : next)
+ * }
+ */
+/**
+ * @param {ListNode} head
+ * @param {number} k
+ * @return {ListNode}
+ */
+const reverseKGroup = (head, k) => {
+  const canReverse = (node) => {
+    let count = 0;
+    while (node) {
+      count++;
+      node = node.next;
+      if (count === k) {
+        return true;
+      }
+    }
+    return false;
+  };
+  const reverse = (node) => {
+    const newHead = new ListNode();
+    const tail = node;
+    for (let i = 0; i < k; i++) {
+      const next = node.next;
+      node.next = newHead.next;
+      newHead.next = node;
+      node = next;
+    }
+    tail.next = node;
+    return newHead.next;
+  };
+  const newHead = new ListNode(0, head);
+  let temp = newHead;
+  while (canReverse(temp.next)) {
+    const tail = temp.next;
+    temp.next = reverse(temp.next);
+    temp = tail;
+  }
+  return newHead.next;
+};
+
+class ListNode {
+  constructor(val, next) {
+    this.val = val;
+    this.next = next;
+  }
+}
+
+const p1 = new ListNode();
+let temp = p1;
+for (let i = 0; i < 5; i++) {
+  temp.next = new ListNode(i);
+  temp = temp.next;
+}
+
+console.dir(p1.next, { depth: null });
+console.dir(reverseKGroup(p1.next, 3), { depth: null });
+

随机链表的复制

LeetCode 原题链接:138. 随机链表的复制

+
+

采取哈希表存储原链表与复制之后链表之间节点的对应关系,从而实现复制功能,还可以通过递归实现,但基本思路大致相同

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/**
+ * // Definition for a Node.
+ * function Node(val, next, random) {
+ *    this.val = val;
+ *    this.next = next;
+ *    this.random = random;
+ * };
+ */
+
+/**
+ * @param {Node} head
+ * @return {Node}
+ */
+const copyRandomList = (head, map = new WeakMap()) => {
+  if (!head) {
+    return head;
+  }
+  if (!map.has(head)) {
+    map.set(head, new Node(head.val));
+    Object.assign(map.get(head), {
+      next: copyRandomList(head.next, map),
+      random: copyRandomList(head.random, map),
+    });
+  }
+  return map.get(head);
+};
+
+/**
+ * // Definition for a Node.
+ * function Node(val, next, random) {
+ *    this.val = val;
+ *    this.next = next;
+ *    this.random = random;
+ * };
+ */
+
+/**
+ * @param {Node} head
+ * @return {Node}
+ */
+const copyRandomList = (head) => {
+  const newHead = new Node();
+  let temp = newHead,
+    temp0 = head;
+  const map = new WeakMap();
+  while (temp0) {
+    temp.next = new Node(temp0.val);
+    map.set(temp0, temp.next);
+    temp0 = temp0.next;
+    temp = temp.next;
+  }
+  temp = newHead.next;
+  temp0 = head;
+  while (temp0) {
+    const random = temp0.random;
+    if (random) {
+      temp.random = map.get(random);
+    } else {
+      temp.random = random;
+    }
+    temp = temp.next;
+    temp0 = temp0.next;
+  }
+  return newHead.next;
+};
+

排序链表

LeetCode 原题链接:148. 排序链表

+
+

使用归并排序的思路,求解,左闭右开,同时注意fast指针需要严格遵守fast !== tail的规则来使用。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/**
+ * Definition for singly-linked list.
+ * function ListNode(val, next) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.next = (next===undefined ? null : next)
+ * }
+ */
+/**
+ * @param {ListNode} head
+ * @return {ListNode}
+ */
+const sortList = (head) => mergeSort(head, null);
+
+const mergeSort = (head, tail) => {
+  if (!head) {
+    return null;
+  }
+
+  if (head.next === tail) {
+    head.next = null;
+    return head;
+  }
+
+  let slow = head,
+    fast = head;
+  while (fast !== tail) {
+    slow = slow.next;
+    fast = fast.next;
+    if (fast !== tail) {
+      fast = fast.next;
+    }
+  }
+  return mergeTwoLists(mergeSort(head, slow), mergeSort(slow, tail));
+};
+
+const mergeTwoLists = (p1, p2) => {
+  const dummy = new ListNode();
+  let temp = dummy;
+  while (p1 && p2) {
+    if (p1.val < p2.val) {
+      temp.next = p1;
+      p1 = p1.next;
+    } else {
+      temp.next = p2;
+      p2 = p2.next;
+    }
+    temp = temp.next;
+  }
+  temp.next = p1 || p2;
+  return dummy.next;
+};
+
+class ListNode {
+  constructor(val, next) {
+    this.val = val === undefined ? 0 : val;
+    this.next = next === undefined ? null : next;
+  }
+}
+
+const p1 = new ListNode();
+let temp = p1;
+for (let i = 0; i < 5; i++) {
+  temp.next = new ListNode(Math.floor(Math.random() * 5));
+  temp = temp.next;
+}
+console.dir(p1.next, { depth: null });
+console.dir(mergeSort(p1.next, null), { depth: null });
+

合并 k 个升序链表

LeetCode 原题链接:23. 合并 k 个升序链表

+
+

分治与归并排序,掌握思路就比较简单。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
+ * Definition for singly-linked list.
+ * function ListNode(val, next) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.next = (next===undefined ? null : next)
+ * }
+ */
+/**
+ * @param {ListNode[]} lists
+ * @return {ListNode}
+ */
+const mergeKLists = (lists) => {
+  if (!lists || lists.length === 0) {
+    return null;
+  }
+  if (lists.length === 1) {
+    return lists[0];
+  }
+  if (lists.length === 2) {
+    return merge(lists[0], lists[1]);
+  }
+  const mid = Math.floor(lists.length / 2);
+  return merge(mergeKLists(lists.slice(0, mid)), mergeKLists(lists.slice(mid)));
+};
+
+const merge = (l1, l2) => {
+  if (!l1) {
+    return l2;
+  }
+  if (!l2) {
+    return l1;
+  }
+  if (l1.val < l2.val) {
+    l1.next = merge(l1.next, l2);
+    return l1;
+  } else {
+    l2.next = merge(l1, l2.next);
+    return l2;
+  }
+};
+

LRU 算法

LeetCode 原题链接:146. LRU 缓存

+
+

双向链表加上哈希表解决,算法不难,重点是了解其基本思想,注意过量操作需要删除节点。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class Node {
+  constructor(key, val) {
+    this.key = key;
+    this.val = val;
+    this.pre = null;
+    this.next = null;
+  }
+}
+
+class LRUCache {
+  constructor(size) {
+    this.size = size;
+    this.dummy = new Node();
+    this.dummy.next = this.dummy;
+    this.dummy.pre = this.dummy;
+    this.map = new Map();
+  }
+
+  addFront(el) {
+    el.next = this.dummy.next;
+    this.dummy.next = el;
+    el.pre = this.dummy;
+    el.next.pre = el;
+  }
+
+  delete(el) {
+    el.pre.next = el.next;
+    el.next.pre = el.pre;
+  }
+
+  put(key, val) {
+    if (this.map.has(key)) {
+      const el = this.map.get(key);
+      el.val = val;
+      this.delete(el);
+      this.addFront(el);
+    } else {
+      const el = new Node(key, val);
+      this.map.set(key, el);
+      this.addFront(el);
+      if (this.map.size > this.size) {
+        const el = this.dummy.pre;
+        this.map.delete(el.key);
+        this.delete(el);
+      }
+    }
+  }
+
+  get(key) {
+    if (!this.map.has(key)) {
+      return -1;
+    } else {
+      const el = this.map.get(key);
+      this.delete(el);
+      this.addFront(el);
+      return el.val;
+    }
+  }
+}
+

二叉树

二叉树的前序遍历

LeetCode 原题链接:144. 二叉树的前序遍历

+
+

可采用递归和迭代的两种方式,详细理解一下前序遍历迭代的套路。

+
    +
  • 维护一个结果res与栈stack
  • +
  • 采用while循环循环压入栈,判断条件为root || stack.length
  • +
  • 与中序遍历的区别在于在内部while循环中需要推入res数组,而不是在外部推入。
  • +
  • 注意在循环向左压入数字,栈需要更新root,而不是新建一个变量。
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {number[]}
+ */
+const preorderTraversal = (root) => {
+  const res = [];
+  const dfs = (root) => {
+    if (!root) {
+      return;
+    }
+    res.push(root.val);
+    dfs(root.left);
+    dfs(root.right);
+  };
+  dfs(root);
+  return res;
+};
+
+/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {number[]}
+ */
+const preorderTraversal = (root) => {
+  const stack = [],
+    res = [];
+  while (root || stack.length) {
+    while (root) {
+      res.push(root.val);
+      stack.push(root.right);
+      root = root.left;
+    }
+    root = stack.pop();
+  }
+  return res;
+};
+

二叉树的中序遍历

LeetCode 原题链接:94. 二叉树的中序遍历

+
+

可采用递归和迭代的两种方式,详细理解一下中序遍历迭代的套路。

+
    +
  • 维护一个结果res与栈stack
  • +
  • 采用while循环循环压入栈,判断条件为root || stack.length
  • +
  • 注意在循环向左压入数字,栈需要更新root,而不是新建一个变量。
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {number[]}
+ */
+const inorderTraversal = (root) => {
+  const res = [];
+  const step = (root) => {
+    if (!root) {
+      return null;
+    }
+    step(root.left);
+    res.push(root.val);
+    step(root.right);
+  };
+  step(root);
+  return res;
+};
+
+/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {number[]}
+ */
+const inorderTraversal = (root) => {
+  const res = [];
+  const stack = [];
+  while (root || stack.length) {
+    while (root) {
+      stack.push(root);
+      root = root.left;
+    }
+    root = stack.pop();
+    res.push(root.val);
+    root = root.right;
+  }
+  return res;
+};
+

二叉树的后序遍历

LeetCode 原题链接:145. 二叉树的后序遍历

+
+

可采用递归和迭代的两种方式,详细理解一下后序遍历迭代的套路。

+
    +
  • 维护一个结果res与栈stack
  • +
  • 采用while循环循环压入栈,判断条件为root || stack.length
  • +
  • 注意在循环向左压入数字,栈需要更新root,而不是新建一个变量。
  • +
  • 注意与中序遍历不同的是需要将父节点推入stack同时在“触底”的时候记录right变量,防止再一次回溯。
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {number[]}
+ */
+const postorderTraversal = (root) => {
+  const res = [];
+  const dfs = (root) => {
+    if (!root) {
+      return;
+    }
+    dfs(root.left);
+    dfs(root.right);
+    res.push(root.val);
+  };
+  dfs(root);
+  return res;
+};
+
+/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {number[]}
+ */
+const postorderTraversal = (root) => {
+  const res = [],
+    stack = [];
+  let prev = null;
+  while (root || stack.length) {
+    while (root) {
+      stack.push(root);
+      root = root.left;
+    }
+    root = stack.pop();
+    if (root.right === null || root.right === prev) {
+      res.push(root.val);
+      prev = root;
+      root = null;
+    } else {
+      stack.push(root);
+      root = root.right;
+    }
+  }
+  return res;
+};
+

二叉树的最大深度

LeetCode 原题链接:104. 二叉树的最大深度

+
+

采取递归的思路求解,推荐使用回溯算法,即每个节点从叶子节点开始回溯,每次都返回当前节点左右子节点的最大深度加一,最后返回根节点的最大深度。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {number}
+ */
+const maxDepth = (root) => {
+  if (!root) {
+    return 0;
+  }
+  const left = maxDepth(root.left);
+  const right = maxDepth(root.right);
+  return Math.max(left, right) + 1;
+};
+
+/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {number}
+ */
+const maxDepth = (root) => {
+  let max = 0;
+  const step = (root, depth = 0) => {
+    if (!root) {
+      max = Math.max(max, depth);
+      return;
+    }
+    step(root.left, depth + 1);
+    step(root.right, depth + 1);
+  };
+  step(root);
+  return max;
+};
+

翻转二叉树

LeetCode 原题链接:226. 翻转二叉树

+
+

也是同样的回溯思想,叶子节点开始反转,一直反转到根节点,核心思路为先递归到叶子节点然后一步步返回。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {TreeNode}
+ */
+const invertTree = (root) => {
+  if (!root) {
+    return root;
+  }
+  const left = invertTree(root.left);
+  const right = invertTree(root.right);
+  root.left = right;
+  root.right = left;
+  return root;
+};
+

对称二叉树

LeetCode 原题链接:101. 对称二叉树

+
+

初看这个题目或许比较复杂,但简而言之还是按照深度一层层比较,掌握技巧后就比较简单,注意的是判断条件和递归和迭代的实现思路的细微区别。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {boolean}
+ */
+const isSymmetric = (root) => {
+  const step = (p, q) => {
+    if (!p && !q) {
+      return true;
+    }
+    if (!p || !q) {
+      return false;
+    }
+    return p.val === q.val && step(p.left, q.right) && step(p.right, q.left);
+  };
+  return step(root.left, root.right);
+};
+
+/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {boolean}
+ */
+const isSymmetric = (root) => {
+  const queue = [root.left, root.right];
+  while (queue.length) {
+    const p = queue.shift();
+    const q = queue.shift();
+
+    if (!p && !q) {
+      continue;
+    }
+    if (!p || !q || p.val !== q.val) {
+      return false;
+    }
+
+    queue.push(p.left, q.right, p.right, q.left);
+  }
+  return true;
+};
+

二叉树的直径

LeetCode 原题链接:543. 二叉树的直径

+
+

由二叉树的最大深度稍加改进而来,还是一样,先递归到子节点,然后回溯返回。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {number}
+ */
+const diameterOfBinaryTree = (root) => {
+  let maxD = 0;
+  const dfs = (root) => {
+    if (!root) {
+      return 0;
+    }
+    const left = dfs(root.left);
+    const right = dfs(root.right);
+    maxD = Math.max(maxD, left + right);
+    return Math.max(left, right) + 1;
+  };
+  dfs(root);
+  return maxD;
+};
+

二叉树的层序遍历

LeetCode 原题链接:102. 二叉树的层序遍历

+
+

层序遍历属于广度优先遍历,与深度优先遍历不同,广度优先遍历一般使用队列与循环完成。不需要二维数组,每次遍历都清空queue
这个算法有两个需要注意的点

+
    +
  1. 只有值不为空的节点才能推入下一次需要遍历的队列
  2. +
  3. 注意当下一次需要遍历的队列为空的时候,不能推入,防止死循环。
  4. +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {number[][]}
+ */
+const levelOrder = (root) => {
+  if (!root) {
+    return [];
+  }
+
+  const queue = [root];
+  const res = [];
+
+  while (queue.length) {
+    const arr = [];
+    const resArr = [];
+    while (queue.length) {
+      const node = queue.shift();
+      resArr.push(node.val);
+      if (node.left) {
+        arr.push(node.left);
+      }
+      if (node.right) {
+        arr.push(node.right);
+      }
+    }
+    queue.push(...arr);
+    res.push(resArr);
+  }
+  return res;
+};
+

将有序数组转化为二叉搜索树

+
查看二叉搜索树的详细解释 +
+

当然可以。二叉搜索树(Binary Search Tree,简称 BST),也称为二叉查找树或二叉排序树,是一种特殊的二叉树。它具有以下性质:

  1. 有序性:对于树中的任意一个节点,其左子树中的所有节点的值都小于该节点的值,而右子树中的所有节点的值都大于该节点的值。这意味着所有的元素能够通过比较操作被明确地排序。

  2. 递归性质:BST 的左子树和右子树也都是二叉搜索树。这意味着 BST 的定义是递归的,我们可以递归地在子树中进行查找和其他操作。

  3. 无重复值:在 BST 中,每个元素必须是唯一的,因为树的定义不允许含有相同值的节点。

二叉搜索树的主要优势在于,它结合了链表和数组的优点:链表的快速插入和删除操作,以及数组的快速查找性能。在平均情况下,查找、插入和删除操作的时间复杂度都是 $O(log n)$ ,其中 $n$ 是树中节点的数量。然而,在最坏的情况下,如果树变得不平衡,这些操作的时间复杂度可能会退化到 $O(n)$ 。

二叉搜索树在计算机科学中应用广泛,特别是在那些需要频繁查找、插入和删除数据的场景中,如数据库和文件系统。

+
+
+

采用二分法将有序数组转化为二叉搜索树,判断条件与二分法基本相同,有着相同的边界处理情况。

+
    +
  1. left = 0, right = nums.length - 1对应low = 0, high = nums.length - 1
  2. +
  3. left > right对应low <= high
  4. +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {number[]} nums
+ * @return {TreeNode}
+ */
+const sortedArrayToBST = (nums) => {
+  const step = (left, right) => {
+    if (left > right) {
+      return null;
+    }
+    const mid = Math.floor((left + right) / 2);
+    const root = new TreeNode(nums[mid]);
+    root.left = step(left, mid - 1);
+    root.right = step(mid + 1, right);
+    return root;
+  };
+  return step(0, nums.length - 1);
+};
+

验证二叉搜索树

LeetCode 原题链接:98. 验证二叉搜索树

+
+

一种方式是采用上下界递归求解,注意左右节点上下界的选取,以及在JS中正负Infinity的使用。另一种方法则是利用二叉搜索树的中序遍历是升序序列来进行判断,可采用递归和循环两种方式,循环方式代码更加优雅一些。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {boolean}
+ */
+const isValidBST = (root) => {
+  const step = (root, lower, upper) => {
+    if (!root) {
+      return true;
+    }
+    if (root.val <= lower || root.val >= upper) {
+      return false;
+    }
+    return (
+      step(root.left, lower, root.val) && step(root.right, root.val, upper)
+    );
+  };
+  return step(root, -Infinity, Infinity);
+};
+
+/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {boolean}
+ */
+const isValidBST = (root) => {
+  const stack = [];
+  let pre = -Infinity;
+  while (root || stack.length) {
+    while (root) {
+      stack.push(root);
+      root = root.left;
+    }
+    root = stack.pop();
+    if (pre >= root.val) {
+      return false;
+    }
+    pre = root.val;
+    root = root.right;
+  }
+  return true;
+};
+
+/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {boolean}
+ */
+const isValidBST = (root) => {
+  let pre = -Infinity;
+  const dfs = (root) => {
+    if (!root) {
+      return true;
+    }
+    const left = dfs(root.left);
+    if (pre >= root.val) {
+      return false;
+    }
+    pre = root.val;
+    const right = dfs(root.right);
+    return left && right;
+  };
+  return dfs(root);
+};
+

二叉搜索树中第 k 小的元素

+

记住中序遍历的套路直接秒。😍

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @param {number} k
+ * @return {number}
+ */
+const kthSmallest = (root, k) => {
+  const stack = [];
+  let count = 0;
+  while (root || stack.length) {
+    while (root) {
+      stack.push(root);
+      root = root.left;
+    }
+    root = stack.pop();
+    count++;
+    if (count === k) {
+      return root.val;
+    }
+    root = root.right;
+  }
+};
+

二叉树的右视图

LeetCode 原题链接:199. 二叉树的右视图

+
+

和层序遍历大致相同,注意基本思路即可。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {number[]}
+ */
+const rightSideView = (root) => {
+  if (!root) {
+    return [];
+  }
+
+  const queue = [root];
+  const res = [];
+
+  while (queue.length) {
+    const arr = [];
+    const resArr = [];
+    while (queue.length) {
+      const node = queue.shift();
+      resArr.push(node.val);
+      if (node.left) {
+        arr.push(node.left);
+      }
+      if (node.right) {
+        arr.push(node.right);
+      }
+    }
+    queue.push(...arr);
+    res.push(resArr[resArr.length - 1]);
+  }
+  return res;
+};
+

二叉树展开为链表

LeetCode 原题链接:144. 二叉树展开为链表

+
+

采用前序遍历的操作来解决,其实相当于中序遍历迭代写法的变式。因为在内层while循环中已经开始遍历,所以没有弹出栈后往右遍历的步骤。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {void} Do not return anything, modify root in-place instead.
+ */
+const flatten = (root) => {
+  const stack = [];
+  const dummy = new TreeNode();
+  let temp = dummy;
+  while (root || stack.length) {
+    while (root) {
+      stack.push(root.right);
+      temp.right = root;
+      temp = temp.right;
+      const left = root.left;
+      root.left = null;
+      root = left;
+    }
+    root = stack.pop();
+  }
+  return dummy.right;
+};
+

从前序与中序遍历序列构造二叉树

+

这道题技巧性比较强,主要利用了前序数组与中序数组的相关的性质进行求解。进行根元素和左右元素进行分割。其实本质上也是二分,和将有序数组转化为二叉搜索树相同。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {number[]} preorder
+ * @param {number[]} inorder
+ * @return {TreeNode}
+ */
+const buildTree = (preorder, inorder) => {
+  const map = new Map();
+  for (let i = 0; i < inorder.length; i++) {
+    map.set(inorder[i], i);
+  }
+  const step = (p_start, p_end, i_start, i_end) => {
+    if (p_start > p_end) {
+      return null;
+    }
+    const mid = map.get(preorder[p_start]);
+    const leftNum = mid - i_start;
+    const root = new TreeNode(preorder[p_start]);
+    root.left = step(p_start + 1, p_start + leftNum, i_start, mid - 1);
+    root.right = step(p_start + leftNum + 1, p_end, mid + 1, i_end);
+    return root;
+  };
+  return step(0, preorder.length - 1, 0, inorder.length - 1);
+};
+

路径总和 III

LeetCode 原题链接:437. 路径总和 III

+
+

不考虑时间复杂度直接深度遍历暴力求解,考虑时间复杂度可以使用前缀和的方式进行求解。和和为-k-的子数组思路大致相同。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @param {number} targetSum
+ * @return {number}
+ */
+const pathSum = (root, targetSum) => {
+  const prefix = new Map([[0, 1]]);
+
+  const dfs = (root, curr) => {
+    if (!root) {
+      return 0;
+    }
+    curr += root.val;
+    let ret = prefix.get(curr - targetSum) || 0;
+
+    prefix.set(curr, (prefix.get(curr) || 0) + 1);
+    ret += dfs(root.left, curr);
+    ret += dfs(root.right, curr);
+    prefix.set(curr, (prefix.get(curr) || 0) - 1);
+
+    return ret;
+  };
+  return dfs(root, 0);
+};
+
+/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @param {number} targetSum
+ * @return {number}
+ */
+const pathSum = (root, targetSum) => {
+  let count = 0;
+  const step = (root, sum = 0) => {
+    if (!root) {
+      return;
+    }
+    sum += root.val;
+    if (targetSum === sum) {
+      count++;
+    }
+    step(root.left, sum);
+    step(root.right, sum);
+  };
+  const preOrder = (root) => {
+    if (!root) {
+      return;
+    }
+    step(root);
+    preOrder(root.left);
+    preOrder(root.right);
+  };
+  preOrder(root);
+  return count;
+};
+

二叉树的最近公共祖先

LeetCode 原题链接:236. 二叉树的最近公共祖先

+
+

采用递归的方式求解,注意条件之类的判断,当最深的祖先节点被找到之后,之后的节点不会再满足要求。
主要的判断条件(leftSon && rightSon) || ((p.val === root.val || q.val === root.val) && (leftSon || rightSon)),返回leftSon || rightSon || root.val === p.val || root.val === q.val

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
+ * Definition for a binary tree node.
+ * function TreeNode(val) {
+ *     this.val = val;
+ *     this.left = this.right = null;
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @param {TreeNode} p
+ * @param {TreeNode} q
+ * @return {TreeNode}
+ */
+const lowestCommonAncestor = (root, p, q) => {
+  let ans = null;
+  const step = (root) => {
+    if (!root) {
+      return null;
+    }
+    const leftSon = step(root.left);
+    const rightSon = step(root.right);
+    if (
+      (leftSon && rightSon) ||
+      ((root.val === p.val || root.val === q.val) && (leftSon || rightSon))
+    ) {
+      ans = root;
+    }
+    return leftSon || rightSon || root.val === p.val || root.val === q.val;
+  };
+  step(root);
+  return ans;
+};
+

124. 二叉树的最大路径和

LeetCode 原题链接:124. 二叉树中的最大路径和

+
+

在二叉树的直径与最大深度的代码上改良。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {number}
+ */
+const maxPathSum = (root) => {
+  let max = -Infinity;
+  const step = (root) => {
+    if (!root) {
+      return 0;
+    }
+    const left = step(root.left);
+    const right = step(root.right);
+    const sum = Math.max(left, right) + root.val;
+    max = Math.max(max, left + right + root.val);
+    return sum > 0 ? sum : 0;
+  };
+  step(root);
+  return max;
+};
+

二叉树中和为目标值的路径

+

节省数组空间可采用堆栈弹入或者弹出。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @param {number} target
+ * @return {number[][]}
+ */
+const pathTarget = (root, target) => {
+  const res = [];
+  const step = (root, sum, arr) => {
+    if (!root) {
+      return;
+    }
+    sum += root.val;
+    arr.push(root.val);
+    if (!root.left && !root.right && sum === target) {
+      res.push([...arr]);
+    }
+    step(root.left, sum, arr);
+    step(root.right, sum, arr);
+    arr.pop();
+  };
+  step(root, 0, []);
+  return res;
+};
+
文章作者: 希亚的西红柿
文章链接: http://blog.refrain.site/posts/b672a0f6/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
+ + + + \ No newline at end of file diff --git a/posts/b67f488/index.html b/posts/b67f488/index.html new file mode 100644 index 000000000..45463eab5 --- /dev/null +++ b/posts/b67f488/index.html @@ -0,0 +1,628 @@ +Win10/11任务栏透明美化 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + + + +

Win10/11任务栏透明美化

本文主要通过 TranslucentTB 和 RoundedTB 两款轻量软件来实现 win10/11 的任务栏美化,在阅读本教程之前,请确保你的电脑安装了 Microsoft Store 并可以正常下载应用

+
+

教程除任务栏透明化外还有进阶的边角美化教程,读者可根据需要自行选择阅读

+
+

安装应用

点击查看安装应用 +
+
  1. 打开 Microsoft Store(微软商店),搜索 TranslucentTB
    搜索TranslucentTB
  2. 安装 TranslucenTB
    安装TranslucentTB
+
+
+

注意安装的是 TransclucentTB 原版而不是汉化版,汉化版本只适用与 win10 任务栏完全透明化

+
+

TranslucentTB 在 2022 年 12 月的一次更新中添加了中文版本,并解决了之前版本任务栏透明顶部仍有一条细线的问题(之前此问题可配合RoundedTB解决),现阶段 TranslucentTB 支持中文并且可独立实现 Windows 任务栏透明美化

+
+

基本设置介绍

+

Normal:正常

+
+
+

Opaque: 不透明

+
+
+

Clear: 透明

+
+
+

Acrylic: 亚克力(即毛玻璃样式)

+
+

正常:

+
+
+

不透明:

+
+
+

透明:

+
+
+

亚克力:

+
+

Win11 推荐设置

点击查看推荐设置 +
+

返回桌面时:清晰

开启任何窗口时:清晰

窗口最大化时:亚克力

开启开始菜单时:清晰

+
+
+

其余三种情况可根据自己喜好自行设置,效果图我就不放了,自己设置完试试就知道了

+
+

如果你只是追求任务栏透明的话,教程到此就可以不用看了,接下来讲任务栏的进阶美化教程

+
+

进阶美化教程

任务栏除透明美化外,还可以通过修改任务栏的边距和弧角来仿照 Mac 或 Linux 系统的任务栏

+
+
点击查看进阶美化教程 +
+
  1. 打开微软商店,安装 RoundedTB
  2. 打开 RoundedTB
  3. 基本设置介绍

    Corner radius:角半径

    Top Margin: 任务栏顶部间距(Bottom Margin同理)

    Left Margin: 任务栏左侧间距(Right Margin同理)

    顶部间距设置为10:

    左侧间距设置为10:

  4. 进阶设置

    Dynamic mode:启用右下角任务栏系统托盘隐藏选项
    Show system tray:显示系统托盘
    Show system tray on hover:只在鼠标悬停时显示系统托盘
    Fill taskbar when maximised:最大化时填充任务栏,即复原
    Fill taskbar on alt + tab: atl+tab 切换程序时填充任务栏,即复原

    1. Corner Radius = 35
      Top Margin = 5
      Bottom Margin = 5
      Left Margin = 0
      Right Margin = 0

      Dynamic mode

      Show system tray

      Show system tray on hover

      预览效果
    2. Corner Radius = 35
      Top Margin = 5
      Bottom Margin = 5
      Left Margin = 0
      Right Margin = 0

      Dynamic mode

      Show system tray

      Show system tray on hover

      预览效果

    这里我也只是简单的做了个示例,具体的样式读者可自行调试获得

+
+
+
文章作者: 希亚的西红柿
文章链接: http://blog.refrain.site/posts/b67f488/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/posts/d3748e5f/index.html b/posts/d3748e5f/index.html new file mode 100644 index 000000000..ec0e498ce --- /dev/null +++ b/posts/d3748e5f/index.html @@ -0,0 +1,748 @@ +Windows/Ubuntu双系统安装教程 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + + + +

Windows/Ubuntu双系统安装教程

本文主要记录笔者 PC 安装 Windows/Ubuntu 双系统的过程、Windows 平台下 VMware 虚拟机安装 Ubuntu 的配置过程、以及双系统 Ubuntu 卸载的注意事项。

+
+

本文中 Ubuntu 安装环境:CPU:Intel i7-12700h,GPU:RTX 3060 laptop GPU,OS:Windows 11 Professional

+
+
查看参考教程 + +
+

制作 Ubuntu 启动盘

注意:制作Ubuntu启动盘将格式化 U 盘内的所有内容,U 盘内有文件请及时做好备份。

+
+
    +
  1. 准备一个容量大于 5GB的 U 盘,此 U 盘将用来制作Ubuntu启动盘。
  2. +
  3. 下载USB启动盘制作工具,前往Rufus - 轻松创建 USB 启动盘,在下载板块下载Rufus 3.21 (1.3 MB)
  4. +
  5. 前往Ubuntu 官方网站下载 Ubuntu 镜像,下载Ubuntu 22.04.2 LTS版本。
  6. +
  7. RufusUbuntu镜像下载完成后,打开Rufus,将下面五个选项值设置如下,其余值默认即可。
  8. +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
选项
设备所要制作成启动盘的 U 盘
引导类型选择下载的Ubuntu镜像
分区类型GPT
目标系统类型UEFI(非CSM)
文件系统FAT32
+
+

最后点击开始,在弹出窗口界面选择以 ISO 镜像模式写入,即默认,等待写入完成。

+

利用磁盘管理划分空间

    +
  1. 打开Windows Search,搜索磁盘管理,打开创建并格式化硬盘分区。
  2. +
  3. 在磁盘管理界面,找到一块剩余空间较大的一块盘,右键点击压缩卷,压缩出一块未分配的磁盘空间。
  4. +
+

+

分配给Ubuntu空间大小参考:

+
    +
  • 简单学习:30GB
  • +
  • 软件开发:50GB
  • +
  • 学习 ROS:80GB+
  • +
  • AI 相关:100GB+
  • +
+
+

准备工作

准备工作十分重要且因机型而异,请读者严格按照教程进行操作,并记录 BIOS 设置更改项,方便回退。

+
+

关闭 BitLocker

BitLocker是微软出品的用于保护硬盘的加密系统,用于禁止任何非 windows 的系统或设备对硬盘进行写入操作。在Win10/11的家庭版中移除了该选项面板,改为设备加密作为替代。

+
+

打开Windows Search搜索BitLocker将其关闭,家庭版则需要前往设置>更新与安全中关闭设备加密。

+

前往 BIOS 关闭安全启动和快速启动

不同的电脑进入 BIOS 的按键可能不同,请上网查询,一般是F2F12等按键。

+
+

进入 BIOS 关闭安全启动(Secure Boot)和快速启动(Fast Boot)。戴尔电脑关闭安全启动参考如何启用戴尔设备的安全启动功能,另外启动模式必须调整为UEFI

+
其他设置 +
+

一般来说,此选项和Secure boot一起,在 Secure(安全)选项卡下。

我在安装的时候没管这个似乎也没啥问题,如果有问题的再尝试关闭这项。

进入 BIOS 关闭 Intel VMD 选项。

目前Ubuntu 22.04.2 LTS已集成intel RST驱动,实际测试不关闭也能安装。

PS:这玩意给重装系统带来了比较多的麻烦,WinPE 并没有集成这一驱动,导致进 PE 系统无法读盘,目前的解决办法只有进 BIOS 关闭 VMD,安装系统,打开 BIOS 中 vmd 设置,用工具注入 vmd 驱动,进系统安装 intel rts 驱动。

+
+
+

安装配置

    +
  1. 在确保上述步骤完成之后,重启电脑,按F12选择从 U 盘启动,进入 grub 界面,并且选择第一项try and install ubuntu
  2. +
  3. 之后就会进入到ubuntu安装界面。
  4. +
  5. 语言选择中文简体,键盘布局就默认的Chinese就好。
  6. +
  7. 不要连接网络并选择最小安装。
  8. +
  9. 安装类型选择其他选项
  10. +
  11. 找到刚刚 Windows 上分出来的空闲空间,根据大小判断
  12. +
  13. 选中选中空闲的空间,点击底下的 ➕,我们总共需要分 3 个分区,EFI系统分区根目录home目录,相关参数如下
  14. +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
分区名大小新分区的类型新分区的位置用于分区描述挂载点
EFI系统分区512MB逻辑分区空间起始位置EFI系统分区即启动分区,系统将从这里加载启动,内核文件也都放在这里-
根目录磁盘1/2的空间多一点主分区空间起始位置Ext4 日志文件系统即根目录/
home目录剩下的空间主分区空间起始位置Ext4 日志文件系统即用户自己的目录/home
+
+

安装启动引导器(bootloader)的设备要选择刚刚新分配的 EFI 分区,千万别和windows的EFI分区(一般为 260MB)搞错了,不然直接凉凉。可以通过大小来区别两者(我们分配的大小会是 511MB)。

+
+

之后点击现在安装,然后的事情就简单了,相关设置设置完之后会提示你重启,重启后会提示你移除 U 盘(installation medium),拔掉 U 盘再进入就好了。

+
+

VMware 虚拟机安装 Ubuntu 的注意事项

注意安装VMware Workstation前先要关闭内核隔离。

+
+

安装 VMware Workstation 的注意事项:

+
    +
  • 增强型键盘驱动程序和控制台工具添加的系统path两个选项都勾上。
  • +
  • 更新和提升计划两个别勾选。
  • +
+

安装Ubuntu的注意事项:

+
    +
  • 配置采用自定义。
  • +
  • 选择稍后安装操作系统。
  • +
  • 网络类型选择使用网络地址转换(NAT)(E)
  • +
  • 磁盘选择新建磁盘,将虚拟磁盘拆分成多个文件。
  • +
  • 自定义文件选择镜像。
  • +
+

安装 VMware tools 的注意事项:

+
    +
  • 安装完后需要重新安装vmware-tools
  • +
+

Ubuntu EFI 分区的删除

PC 安装双系统后,Ubuntu 的EFI分区无法删除,下文将讲述如何利用Windows系统下的diskpart来删除Ubuntu EFI分区和启动项。

+
+

删除 EFI 分区

    +
  1. 使用管理员权限打开 cmd,输入diskpart,利用list disk来查询磁盘信息。
  2. +
  3. 选择安装了Ubuntu的硬盘,输入select disk 1
  4. +
  5. 查看磁盘 1 下所有分区信息list partition,根据分区大小选择EFI分区,选择该分区select partition 4
  6. +
  7. 删除该分区delete partition override
  8. +
+

修改 Windows 启动项

如果这样结束,开机按F9F12会发现启动项里还会有Ubuntu启动项:这是因为在安装Ubuntu后,Ubuntu的引导信息也写在了win10EFI启动分区里。如果不删除的话,以后再安装Ubuntu会出现很多个Ubuntu启动项。

+
+
    +
  1. 选择磁盘 0,即Windows系统所在的磁盘:select disk 0
  2. +
  3. 查看磁盘 0 下所有分区信息:list partition
  4. +
  5. 根据容量(Windows EFI分区的容量大概为 260MB)选择分区:select partition 1
  6. +
  7. Windows EFI分区分配盘符:assign letter = p,此处p为盘符,注意不要与其他已存在的盘符重复。
  8. +
  9. 使用管理员权限打开记事本,用记事本菜单栏里的打开来访问P盘,打开EFI文件夹,删除ubuntu文件夹。
  10. +
  11. 删除EFI分区盘符Premove letter = p
  12. +
+

基本原理:P 盘需要管理员权限访问,采用管理员权限打开记事本后记事本就被赋予了管理员权限。

+
+
文章作者: 希亚的西红柿
文章链接: http://blog.refrain.site/posts/d3748e5f/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
+ + + + \ No newline at end of file diff --git a/posts/e89bd903/index.html b/posts/e89bd903/index.html new file mode 100644 index 000000000..56c83c10c --- /dev/null +++ b/posts/e89bd903/index.html @@ -0,0 +1,1096 @@ +REGEX in JavaScript | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + + + +

REGEX in JavaScript

本文主要介绍正则表达式的基本概念与用法,并对 JS 中的正则表达式的常用方法进行总结,方便后续记忆与复习。

+
+
查看参考教程 +
+
参考方向教程原帖
Learn regex the easy wayLearn regex the easy way
菜鸟教程-正则表达式教程Runoob 正则表达式教程
JS 中正则表达式MDN-RegExp
+
+
+

正则表达式基本概念

元字符

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
元字符描述
.句号匹配任意单个字符除了换行符。
[ ]字符种类。匹配方括号内的任意字符。
[^ ]否定的字符种类。匹配除了方括号里的任意字符
*匹配>=0个重复的在*号之前的字符。
+匹配>=1个重复的+号前的字符。
?标记?之前的字符为可选.
{n,m}匹配num个大括号之前的字符或字符集 (n <= num <= m).
(xyz)字符集,匹配与 xyz 完全相等的字符串.
| 或运算符,匹配符号前或后的字符.
\ 转义字符,用于匹配一些保留的字符 [ ] ( ) { } . * + ? ^ $ \ |
^从开始行开始匹配.
$从末端开始匹配.
+
+
    +
  1. [.]只匹配字符.,表达式 ar[.] 匹配 ar.字符串。
  2. +
  3. ()会形成一个捕获组并获取当前匹配,后续可以通过\1\2等方式进行反向引用。
  4. +
+
+

简写字符集

正则表达式提供一些常用的字符集简写。如下:

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
简写描述
.除换行符外的所有字符
\w匹配所有字母数字以及下划线,等同于 [a-zA-Z0-9_]
\W匹配所有非字母数字,即符号,等同于: [^\w]
\d匹配数字: [0-9]
\D匹配非数字: [^\d]
\s匹配所有空格字符,等同于: [\t\n\f\r\p{Z}]
\S匹配所有非空格字符: [^\s]
\f匹配一个换页符
\n匹配一个换行符
\r匹配一个回车符
\t匹配一个制表符
\v匹配一个垂直制表符
\p匹配 CR/LF(等同于 \r\n),用来匹配 DOS 行终止符
+
+

零宽度断言

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
符号描述实例
?=正先行断言-存在exp1(?=exp2):查找 exp2 前面的 exp1
?!负先行断言-排除exp1(?!exp2):查找后面不是 exp2exp1
?<=正后发断言-存在(?<=exp2)exp1:查找 exp2 后面的 exp1
?<!负后发断言-排除(?<!exp2)exp1:查找前面不是 exp2exp1
+
+

?:?=?!都是非捕获元,不会获取匹配结果。零宽度断言都是非捕获元。

+
+

匹配模式

+ + + + + + + + + + + + + + + + + + + + + + + + + +
标志描述
i忽略大小写。
g全局搜索。返回多个匹配项。
m多行修饰符:锚点元字符 ^ $ 工作范围在每行的起始。即每行以特定字符开始或者结束。
s特殊字符圆点 . 中包含换行符 \n
+
+

TIPS

    +
  1. \b\B的区别
    在正则表达式中,\b\B 是用于匹配单词边界的特殊元字符。

    +
      +
    • \b(单词边界)匹配以下三种情况之一:
        +
      1. 单词的开头:如果 \b 出现在一个字母或数字之前,或者在字符串的开头,它会匹配一个单词的开始位置。
      2. +
      3. 单词的结尾:如果 \b 出现在一个字母或数字之后,或者在字符串的末尾,它会匹配一个单词的结束位置。
      4. +
      5. 单词的内部:如果 \b 出现在两个连续的字母或数字之间,它不会匹配任何内容,因为没有单词边界。
      6. +
      +
    • +
    +

    以下是一些使用 \b 的实例:

    +
      +
    • 正则表达式 \bword\b 匹配整个单词 “word”,但不匹配 “words” 或 “sword”。
    • +
    • 正则表达式 \b\d+\b 匹配一个完整的数字,例如 “123”,但不匹配 “abc123”。
    • +
    • 正则表达式 \b [A-Z]+\b 匹配一个完整的大写字母单词,例如 “HELLO”,但不匹配 “HELLO WORLD”。
    • +
    +

    需要注意的是,\b 是一个零宽度断言,它不匹配实际的字符,只匹配位置。因此,在想要匹配实际字符时,请不要使用 \b,而应该使用其他字符或字符组合。

    +
      +
    • \B(非单词边界)在正则表达式中表示非单词边界,即匹配不在单词边界处的位置。具体来说:
        +
      1. 单词的内部:如果 \B 出现在两个连续的字母或数字之间,它会匹配这两个字符之间的位置,表示它们不是单词的边界。
      2. +
      3. 非单词的开头或结尾:如果 \B 出现在一个字母或数字之前或之后,它会匹配这个位置,表示它不是单词的开头或结尾。
      4. +
      +
    • +
    +

    以下是一些使用 \B 的实例:

    +
      +
    • 正则表达式 \Bword\B 匹配 “swords” 中的 “word”,但不匹配 “word” 或 “sword”。
    • +
    • 正则表达式 \B\d+\B 匹配 “abc123def” 中的 “123”。
    • +
    • 正则表达式 \B [A-Z]+\B 匹配 “HELLO WORLD” 中的 “ELL”和”ORL”。
    • +
    +

    需要注意的是,与 \b 不同,\B 也是一个零宽度断言,只匹配位置而不匹配实际字符。

    +
  2. +
  3. 捕获组的相关概念

    +

    当我们在正则表达式中使用捕获组时,我们可以将子表达式匹配的内容保存到内存中,以便后续引用。这对于处理复杂的文本匹配和替换非常有用。让我详细解释一下捕获组的相关概念。

    +
    +
      +
    1. 普通捕获组

      +
        +
      • 普通捕获组是按照左括号出现的顺序进行分组。
      • +
      • 从正则表达式左侧开始,每出现一个左括号 ( 记做一个分组,分组编号从 1 开始。编号 0 代表整个表达式。
      • +
      • 例如,对于时间字符串 2017-04-25,以下正则表达式有 4 个左括号,所以有 4 个分组:

        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        const pattern = /(\d{4})-((\d{2})-(\d{2}))/;
        +console.log("2017-04-25".match(pattern));
        +// {
        +//   "0": "2017-04-25",
        +//   "1": "2017",
        +//   "2": "04-25",
        +//   "3": "04",
        +//   "4": "25",
        +//   "index": 0,
        +//   "input": "2017-04-25"
        +//   "groups": undefined
        +// }
        +
          +
        • 编号 0: (\d{4})-((\d{2})-(\d{2})) 匹配整个日期字符串 2017-04-25
        • +
        • 编号 1: (\d{4}) 匹配年份 2017
        • +
        • 编号 2: ((\d{2})-(\d{2})) 匹配月份和日期 04-25
        • +
        • 编号 3: (\d{2}) 匹配月份 04
        • +
        • 编号 4: (\d{2}) 匹配日期 25
        • +
        +
      • +
      +
    2. +
    3. 命名捕获组

      +
        +
      • 命名捕获组是为了给捕获组命名,方便后续引用。
      • +
      • 每个以左括号开始的捕获组都紧跟着 ? ,而后才是正则表达式。
      • +
      • 例如,对于时间字符串 2017-04-25,以下正则表达式有 4 个命名的捕获组:

        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        const pattern =
        +  /(?<year>\d{4})-(?<md>((?<month>\d{2})-(?<date>\d{2})))/;
        +console.log("2017-04-25".match(pattern));
        +// {
        +//   "0": "2017-04-25",
        +//   "1": "2017",
        +//   "2": "04-25",
        +//   "3": "04-25",
        +//   "4": "04",
        +//   "5": "25",
        +//   "index": 0,
        +//   "input": "2017-04-25",
        +//   "groups": {
        +//       "year": "2017",
        +//       "md": "04-25",
        +//       "month": "04",
        +//       "date": "25"
        +//   }
        +// }
        +
          +
        • 名称 year: (\d{4}) 匹配年份 2017
        • +
        • 名称 md: ((?<month>\d{2})-(?<date>\d{2})) 匹配月份和日期 04-25
        • +
        • 名称 month: (\d{2}) 匹配月份 04
        • +
        • 名称 date: (\d{2}) 匹配日期 25
        • +
        +
      • +
      +
    4. +
    5. 非捕获组

      +
        +
      • 在正则中可以使用非捕获元字符 ?:?=?! 来重写捕获组,以 (?:Expression) 开头的捕获组就是非捕获组。
      • +
      • 非捕获组不会保存匹配到的文本内容到内存中,因此不占用内存且无分组编号,也不可被反向引用。
      • +
      +
    6. +
    +
  4. +
  5. 捕获组的反向引用

    +
    1
    2
    3
    4
    var str = "Is is the cost of of gasoline going up up";
    +var patt1 = /\b([a-z]+) \1\b/gim;
    +document.write(str.match(patt1));
    +// 结果为:Is is,of of,up up
    +
  6. +
+

JS 中的正则表达式

RegExp 对象的方法

    +
  1. exec:使用当前的正则表达式对象循环匹配字符串

    +

    如果匹配失败,exec() 方法返回 null,并将正则表达式的 lastIndex 重置为 0

    +
    +

    如果匹配成功,exec() 方法返回一个数组,并更新正则表达式对象的 lastIndex 属性。完全匹配成功的文本将作为返回数组的第一项,从第二项起,后续每项都对应一个匹配的捕获组。

    +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    const regex1 = RegExp("foo*", "g");
    +const str1 = "table football, foosball";
    +let array1;
    +
    +while ((array1 = regex1.exec(str1)) !== null) {
    +  console.log(`Found ${array1[0]}. Next starts at ${regex1.lastIndex}.`);
    +  // Expected output: "Found foo. Next starts at 9."
    +  // Expected output: "Found foo. Next starts at 19."
    +}
    +

    当正则表达式设置 g 标志位时,可以多次执行 exec 方法来查找同一个字符串中的成功匹配。当你这样做时,查找将从正则表达式的 lastIndex 属性指定的位置开始。(test() 也会更新 lastIndex 属性)。注意,即使再次查找的字符串不是原查找字符串时,lastIndex 也不会被重置,它依旧会从记录的 lastIndex 开始。

    +
    +
  2. +
  3. testtest() 方法执行一个检索,用来查看正则表达式与指定的字符串是否匹配。返回 truefalse

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    const str = "table football";
    +
    +const regex = new RegExp("foo*");
    +const globalRegex = new RegExp("foo*", "g");
    +
    +console.log(regex.test(str));
    +// Expected output: true
    +
    +console.log(globalRegex.lastIndex);
    +// Expected output: 0
    +
    +console.log(globalRegex.test(str));
    +// Expected output: true
    +
    +console.log(globalRegex.lastIndex);
    +// Expected output: 9
    +
    +console.log(globalRegex.test(str));
    +// Expected output: false
    +
  4. +
+

String 对象的方法

    +
  1. matchmatch()方法检索字符串与正则表达式进行匹配的结果。

    +

    返回值:一个 Array,其内容取决于是否存在全局(g)标志,如果没有匹配,则返回 null

    +

    如果使用 g 标志,则将返回与完整正则表达式匹配的所有结果,但不会返回捕获组。
    如果没有使用 g 标志,则只返回第一个完整匹配及其相关捕获组。在这种情况下,match() 方法将返回与 RegExp.prototype.exec() 相同的结果(一个带有一些额外属性的数组)。

    +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    const str = "For more information, see Chapter 3.4.5.1";
    +const re = /see (chapter \d+(\.\d)*)/i;
    +const found = str.match(re);
    +
    +console.log(found);
    +// [
    +//   'see Chapter 3.4.5.1',
    +//   'Chapter 3.4.5.1',
    +//   '.1',
    +//   index: 22,
    +//   input: 'For more information, see Chapter 3.4.5.1',
    +//   groups: undefined
    +// ]
    +const str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    +const regexp = /[A-E]/gi;
    +const matches = str.match(regexp);
    +
    +console.log(matches);
    +// ['A', 'B', 'C', 'D', 'E', 'a', 'b', 'c', 'd', 'e']
    +
  2. +
  3. matchAllmatchAll()方法返回一个迭代器,该迭代器包含了检索字符串与正则表达式进行匹配的所有结果(包括捕获组)。

    +

    注意如果参数是一个正则表达式则一定需要全局匹配
    返回值为:一个匹配结果的可迭代迭代器对象 (en-US)(它不可重新开始)。每个匹配结果都是一个数组,其形状与 RegExp.prototype.exec() 的返回值相同。

    +
    +

    注意matchAll方法内部会对Regex对象进行一次克隆,所以其并不会改变原来的Regex对象的lastIndex属性。

    +
    +

    matchAll方法对比match方法可以更好的获得捕获组。

    +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    // exec 方法
    +const regexp = /foo[a-z]*/g;
    +const str = "table football, foosball";
    +let match;
    +
    +while ((match = regexp.exec(str)) !== null) {
    +  console.log(
    +    `找到 ${match[0]} 起始位置=${match.index} 结束位置=${regexp.lastIndex}。`
    +  );
    +}
    +// 找到 football 起始位置=6 结束位置=14。
    +// 找到 foosball 起始位置=16 结束位置=24。
    +
    +// matchAll 方法
    +const regexp = /foo[a-z]*/g;
    +const str = "table football, foosball";
    +const matches = str.matchAll(regexp);
    +
    +for (const match of matches) {
    +  console.log(
    +    `找到 ${match[0]} 起始位置=${match.index} 结束位置=${
    +      match.index + match[0].length
    +    }.`
    +  );
    +}
    +// 找到 football 起始位置=6 结束位置=14.
    +// 找到 foosball 起始位置=16 结束位置=24.
    +
    +// 匹配迭代器在 for...of 迭代后用尽
    +// 再次调用 matchAll 以创建新的迭代器
    +Array.from(str.matchAll(regexp), (m) => m[0]);
    +// [ "football", "foosball" ]
    +
    +// matchAll 内部做了一个 regexp 的复制,所以不像 regexp.exec(),lastIndex 在字符串扫描后不会改变。然而,这也意味着,与在循环中使用 regexp.exec() 不同,你不能更改 lastIndex 来使正则表达式前进或倒退。
    +
    +const regexp = /[a-c]/g;
    +regexp.lastIndex = 1;
    +const str = "abc";
    +Array.from(str.matchAll(regexp), (m) => `${regexp.lastIndex} ${m[0]}`);
    +// [ "1 b", "1 c" ]
    +
    +// 使用 matchAll 方法可以更好的获得捕获组
    +const regexp = /t(e)(st(\d?))/g;
    +const str = "test1test2";
    +const array = [...str.matchAll(regexp)];
    +array[0];
    +// ['test1', 'e', 'st1', '1', index: 0, input: 'test1test2', length: 4]
    +array[1];
    +// ['test2', 'e', 'st2', '2', index: 5, input: 'test1test2', length: 4]
    +
  4. +
  5. replacereplace() 方法返回一个新字符串,其中一个、多个或所有匹配的 pattern 被替换为 replacementpattern 可以是字符串或 RegExpreplacement 可以是字符串或一个在每次匹配时调用的函数。如果 pattern 是字符串,则只会替换第一个匹配项。原始的字符串不会改变。

    +

    该方法并不改变调用它的字符串本身,而是返回一个新的字符串。
    字符串模式只会被替换一次。要执行全局搜索和替换,请使用带有 g 标志的正则表达式或使用 replaceAll()

    +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    const re = /(\w+)\s(\w+)/;
    +const str = "Maria Cruz";
    +const newstr = str.replace(re, "$2, $1");
    +console.log(newstr); // Cruz, Maria
    +
    +const p =
    +  "The quick brown fox jumps over the lazy dog. If the dog reacted, was it really lazy?";
    +
    +console.log(p.replace("dog", "monkey"));
    +// Expected output: "The quick brown fox jumps over the lazy monkey. If the dog reacted, was it really lazy?"
    +
    +const regex = /Dog/i;
    +console.log(p.replace(regex, "ferret"));
    +// Expected output: "The quick brown fox jumps over the lazy ferret. If the dog reacted, was it really lazy?"
    +// 函数调用的情况
    +function replacer(match, p1, p2, p3, offset, string) {
    +  // p1 is non-digits, p2 digits, and p3 non-alphanumerics
    +  return [p1, p2, p3].join(" - ");
    +}
    +const newString = "abc12345#$*%".replace(/([^\d]*)(\d*)([^\w]*)/, replacer);
    +console.log(newString); // abc - 12345 - #$*%
    +
  6. +
  7. replaceAll:配置属性与replace基本相同,相当于replace的全局配置版本。

    +

    使用字符串作为pattern和使用Regex对象作为pattern行为上有些区别

    +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function unsafeRedactName(text, name) {
    +  return text.replace(new RegExp(name, "g"), "[REDACTED]");
    +}
    +function safeRedactName(text, name) {
    +  return text.replaceAll(name, "[REDACTED]");
    +}
    +
    +const report =
    +  "A hacker called ha.*er used special characters in their name to breach the system.";
    +
    +console.log(unsafeRedactName(report, "ha.*er")); // "A [REDACTED]s in their name to breach the system."
    +console.log(safeRedactName(report, "ha.*er")); // "A hacker called [REDACTED] used special characters in their name to breach the system."
    +
  8. +
  9. searchsearch() 方法用于在 String 对象中执行正则表达式的搜索,寻找匹配项。

    +

    如果匹配成功,则返回正则表达式在字符串中首次匹配的索引;否则,返回 -1。

    +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const paragraph =
    +  "The quick brown fox jumps over the lazy dog. If the dog barked, was it really lazy?";
    +
    +// Any character that is not a word character or whitespace
    +const regex = /[^\w\s]/g;
    +
    +console.log(paragraph.search(regex));
    +// Expected output: 43
    +
    +console.log(paragraph[paragraph.search(regex)]);
    +// Expected output: "."
    +
  10. +
  11. split:使用正则表达式来作为参数进行字符串分割。

    +
      +
    • 匹配的正则表达式存在捕获组的话,返回的数组会包含捕获组中的元素。
    • +
    • 第二个参数limit可以限制返回数组的长度
    • +
    +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    const names =
    +  "Harry Trump ;Fred Barney; Helen Rigby ; Bill Abel ;Chris Hand ";
    +
    +console.log(names);
    +
    +const re = /\s*(?:;|$)\s*/;
    +const nameList = names.split(re);
    +
    +console.log(nameList);
    +
    +// 运行结果
    +// Harry Trump ;Fred Barney; Helen Rigby ; Bill Abel ;Chris Hand
    +// [ "Harry Trump", "Fred Barney", "Helen Rigby", "Bill Abel", "Chris Hand", "" ]
    +const myString = "Hello 1 word. Sentence number 2.";
    +const splits = myString.split(/(\d)/);
    +
    +console.log(splits);
    +// [ "Hello ", "1", " word. Sentence number ", "2", "." ]
    +
    +// 限制返回结果的数组长度
    +const myString = "Hello World. How are you doing?";
    +const splits = myString.split(" ", 3);
    +console.log(splits); // [ "Hello", "World.", "How" ]
    +
  12. +
+
文章作者: 希亚的西红柿
文章链接: http://blog.refrain.site/posts/e89bd903/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
+ + + + \ No newline at end of file diff --git a/posts/fccf75e5/index.html b/posts/fccf75e5/index.html new file mode 100644 index 000000000..e656e8fd7 --- /dev/null +++ b/posts/fccf75e5/index.html @@ -0,0 +1,2847 @@ +前端知识笔记 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + +

前端知识笔记

本文主要记录了关于前端面试常考与常问的知识笔记,内容较多,不间断更新。

+
+

CSS

    +
  1. CSS中盒模型有传统的content-boxborder-box,二者区别在于前者的widthheight设置的是content-box,而后者设置的是border-box,注意背景图之类的属性显示依然相同,默认的background-origin属性就是padding-box,即背景图从padding-box开始显示,注意这个属性不要与background-color混淆,background-color默认全部显示。
  2. +
  3. CSS优先级

    +
      +
    1. ID选择器的个数。
    2. +
    3. 类选择器,属性选择器,伪类选择器的个数。
    4. +
    5. 标签选择器,伪元素选择器的个数。
    6. +
    +
  4. +
  5. 使用rem的移动端适配方案

    +
    +

    核心思路为:

    +
      +
    1. 设置网页元素的width属性为rem单位。
    2. +
    3. 通过js获取客户viewport宽度,并除以初始设置宽度,得到放大比例scale
    4. +
    5. 修改html元素的font-size,达到等比例适配效果。
    6. +
    +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    <!DOCTYPE html>
    +<html lang="zh-CN">
    +  <head>
    +    <meta charset="UTF-8" />
    +    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    +    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    +    <title>Document</title>
    +    <style>
    +      * {
    +        padding: 0;
    +        margin: 0;
    +      }
    +      .w-550px {
    +        width: 550rem;
    +        height: 100px;
    +        background-color: rgb(209, 255, 240);
    +      }
    +      .full {
    +        width: 750rem;
    +        height: 100px;
    +        background-color: rgb(195, 200, 199);
    +      }
    +    </style>
    +  </head>
    +  <body>
    +    <div class="w-550px"></div>
    +    <div class="full"></div>
    +    <script>
    +      function setRem() {
    +        // 当前页面宽度相对于 750 宽的缩放比例,可根据自己需要修改
    +        const scale = document.documentElement.clientWidth / 750;
    +        document.documentElement.style.fontSize = scale + "px";
    +      }
    +      setRem();
    +      window.onresize = setRem;
    +    </script>
    +  </body>
    +</html>
    +

    确定浏览器窗口尺寸的使用方案

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    var w =
    +  window.innerWidth ||
    +  document.documentElement.clientWidth ||
    +  document.body.clientWidth;
    +
    +var h =
    +  window.innerHeight ||
    +  document.documentElement.clientHeight ||
    +  document.body.clientHeight;
    +
  6. +
  7. DPI 的相关概念
    DPI 的全称是 Dots Per Inch,意思是每英寸点数。它是一个度量单位,用于表示点阵数码影像的分辨率,也就是每一英寸长度中,取样、可显示或输出点的数目。DPI 也可以用来衡量打印机、鼠标等设备的分辨率,一般来说,DPI 值越高,表明设备的分辨率越高,图像的清晰度越高 。

    +

    DPI 有以下几种应用场景:

    +
      +
    • 图片 DPI:图片 DPI 是指每英寸的像素,类似于密度,即每英寸图片上的像素点数量,用来表示图片的清晰度。由于受网络传输速度的影响,web 上使用的图片都是 72dpi,但是冲洗照片不能使用这个参数,必须是 300dpi 或者更高 350dpi。
    • +
    • 打印精度 DPI:打印精度 DPI 是指打印机在每英寸可打印的点数,至少应有 300dpi 的分辨率,才能使打印效果得到基本保证。打印尺寸、图像的像素数与打印分辨率之间的关系可以利用下列的计算公式加以表示:图像的横向(竖向)像素数=打印横向(竖向)分辨率 × 打印的横向(竖向)尺寸,图像的横向(竖向)像素数/打印横向(竖向)分辨率=打印的横向(竖向)尺寸。
    • +
    • 鼠标 DPI:鼠标 DPI 是指鼠标的定位精度,单位是 dpi 或 cpi,指鼠标移动中,每移动一英寸能准确定位的最大信息数。DPI 是每英寸点数,也就是鼠标每移动一英寸指针在屏幕上移动的点数。比如 400DPI 的鼠标,他在移动一英寸的时候,屏幕上的指针可以移动 400 个点。鼠标 DPI 不是越高越好,不同的 DPI 适合不同的使用场景和用户习惯。
    • +
    +
  8. +
  9. 前端中 DPR 与 PPI 的相关概念
    设备像素:即屏幕能够显示的最小像素,一般为屏幕的参数。
    设备独立像素:即屏幕真正显示的像素,对于高分屏可能将几个像素点合为一个像素点显示。
    DPR:device pixel ratio,即设备像素与设备独立像素的比值,可通过window.devicePixelRatio获取。
    PPI:pixel per inch,即单位英寸数的像素点体积。

    +
  10. +
  11. BFC(Block Formatting Context):块级格式化上下文,可以创造出一处独立的空间,使得元素内部的影响不会到外部。
    触发条件:

    +
      +
    • html根元素。
    • +
    • 脱离文档流的元素,如浮动,定位里面的absolutefixed
    • +
    • overflow属性不为visible,为hiddenscrollauto
    • +
    • flexinline-flexgridinline-gridtableinline-tableinline-block
      作用:
    • +
    • 解决了子元素浮动父元素高度塌陷的的问题。
    • +
    • 解决了margin合并的问题。
    • +
    • 自己不会被浮动元素所覆盖。
    • +
    +
    +

    注意BFC内部子元素的布局逻辑与正常文档流仍然相同,也就是仍然会出现margin塌陷等问题,BFC的作用主要是消除对开启BFC元素本身的影响。

    +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    <style>
    +  .wrap {
    +    overflow: hidden; // 新的BFC
    +  }
    +  p {
    +    color: #f55;
    +    background: #fcc;
    +    width: 200px;
    +    line-height: 100px;
    +    text-align: center;
    +    margin: 100px;
    +  }
    +</style>
    +<body>
    +  <p>Haha</p>
    +  <div class="wrap">
    +    <p>Hehe</p>
    +  </div>
    +</body>
    +
    +<style>
    +  body {
    +    width: 300px;
    +    position: relative;
    +  }
    +
    +  .aside {
    +    width: 100px;
    +    height: 150px;
    +    float: left;
    +    background: #f66;
    +  }
    +
    +  .main {
    +    height: 200px;
    +    background: #fcc;
    +    overflow: hidden;
    +  }
    +</style>
    +<body>
    +  <div class="aside"></div>
    +  <div class="main"></div>
    +</body>
    +
  12. +
  13. 实现元素水平垂直居中的方式

    +
      +
    • 行内元素或者行内块元素

      +
      +

      注意line-height倍数参考的是元素自身font-size的百分比。

      +
      +
      1
      2
      3
      4
      5
      span {
      +  height: 100px;
      +  line-height: 100px;
      +  vertical-align: middle;
      +}
      +
    • +
    • 块元素居中

      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      /* 利用定位 */
      +.father {
      +  position: relative;
      +}
      +.son {
      +  position: fixed;
      +  left: 0;
      +  right: 0;
      +  top: 0;
      +  bottom: 0;
      +  margin: auto;
      +}
      +
      +/* 利用定位加上margin */
      +.father {
      +  position: relative;
      +}
      +.son {
      +  position: fixed;
      +  left: 50%;
      +  right: 50%;
      +  margin-left: -50px;
      +  margin-top: -50px;
      +  width: 100px;
      +  height: 100px;
      +}
      +
      +/* 利用定位加上transform */
      +.father {
      +  position: relative;
      +}
      +.son {
      +  position: fixed;
      +  left: 50%;
      +  right: 50%;
      +  transform: translate(-50%, -50%);
      +}
      +
      +/* 利用margin与maring-top */
      +.son {
      +  margin: 0 auto;
      +  margin-top: 父元素高度减子元素高度的一半;
      +}
      +
      +/* table布局 */
      +.father {
      +  display: table-cell;
      +  width: 200px;
      +  height: 200px;
      +  background: skyblue;
      +  vertical-align: middle;
      +  text-align: center;
      +}
      +.son {
      +  display: inline-block;
      +  width: 100px;
      +  height: 100px;
      +  background: red;
      +}
      +/* flex布局 */
      +.father {
      +  display: flex;
      +  justify-content: center;
      +  align-items: center;
      +  width: 200px;
      +  height: 200px;
      +  background: skyblue;
      +}
      +.son {
      +  width: 100px;
      +  height: 100px;
      +  background: red;
      +}
      +.outer {
      +  width: 400px;
      +  height: 400px;
      +  background-color: #888;
      +  display: flex;
      +}
      +.inner {
      +  width: 100px;
      +  height: 100px;
      +  background-color: orange;
      +  margin: auto;
      +}
      +
      +/* grid网格布局 */
      +.father {
      +  display: grid;
      +  align-items: center;
      +  justify-content: center;
      +  width: 200px;
      +  height: 200px;
      +  background: skyblue;
      +}
      +.son {
      +  width: 10px;
      +  height: 10px;
      +  border: 1px solid red;
      +}
      +
    • +
    +
  14. +
  15. 关于flex数值属性统一设置的问题。

    +
    +

    有关快捷值:auto (1 1 auto)none (0 0 auto)

    +
      +
    • 只有一个非负值:视为flex-grow的值,flex-shrink视为1flex-basis视为0
    • +
    • 有两个非负值:视为flex-growflex-shrink的值,flex-basis视为0
    • +
    • 一个非负值与一个百分数:视为flex-growflex-basis的值,flex-shrink视为1
    • +
    • 当只有一个百分数或者长度的时候:视为flex-basis的值,flex-grow1flex-shrink1
    • +
    +
    +
  16. +
  17. grid布局

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    .wrapper {
    +  display: grid;
    +  /*  声明了三列,宽度分别为 200px 200px 200px */
    +  grid-template-columns: 200px 200px 200px;
    +  grid-gap: 5px;
    +  /*  声明了两行,行高分别为 50px 50px  */
    +  grid-template-rows: 50px 50px;
    +}
    +/* 通过repeat减少重复代码 */
    +.wrapper {
    +  display: grid;
    +  grid-template-columns: repeat(3, 200px);
    +  grid-gap: 5px;
    +  grid-template-rows: repeat(2, 50px);
    +}
    +
    +

    grid-template-columns: repeat(auto-fill, 200px) 表示列宽是 200 px,但列的数量是不固定的,只要浏览器能够容纳得下,就可以放置元素
    grid-template-columns: 200px 1fr 2fr 表示第一个列宽设置为 200px,后面剩余的宽度分为两部分,宽度分别为剩余宽度的 1/3 和 2/3
    minmax(100px, 1fr)表示列宽不小于 100px,不大于 1fr
    grid-template-columns: 100px auto 100px 表示第一第三列为 100px,中间由浏览器决定长度

    +
    +

    grid-row-gap: 10px 表示行间距是 10px

    +

    grid-column-gap: 20px 表示列间距是 20px

    +

    grid-gap: 10px 20px 等同上述两个属性

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    .container {
    +  display: grid;
    +  grid-template-columns: 100px 100px 100px;
    +  grid-template-rows: 100px 100px 100px;
    +  grid-template-areas:
    +    "a b c"
    +    "d e f"
    +    "g h i";
    +}
    +/* 上面代码先划分出9个单元格,然后将其定名为a到i的九个区域,分别对应这九个单元格。
    +多个单元格合并成一个区域的写法如下 */
    +grid-template-areas:
    +  "a a a"
    +  "b b b"
    +  "c c c";
    +

    如果某些区域不需要利用,则使用”点”(.)表示
    grid-auto-flow类似flex-direction,设置横向排列还是纵向排列。

    +
  18. +
  19. 如何理解回流与重绘
    解析 HTML,生成 DOM 树,解析 CSS,生成 CSSOM 树

    +

    将 DOM 树和 CSSOM 树结合,生成渲染树(Render Tree)

    +

    Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小)

    +

    Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素

    +

    Display:将像素发送给 GPU,展示在页面上

    +

    优化手段:尽量使用类名,不使用内联的样式,使用fixedabsolute定位。脱离文档流减少对其他元素的影响。使用transform,opacity,filter等做动画,效率更高

    +
  20. +
  21. css性能优化

    +
      +
    • 首屏内联关键css:防止外部导入css阻塞html显示。
    • +
    • 资源压缩,异步加载。
    • +
    • 避免使用昂贵的CSS属性,例如border-radius
    • +
    +
  22. +
  23. 文本超出省略显示

    +
    1
    2
    3
    4
    5
    .line {
    +  overflow: hidden;
    +  white-space: nowrap;
    +  text-overflow: ellipsis;
    +}
    +
  24. +
  25. 使用CSS画一个三角形

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <!DOCTYPE html>
    +<html lang="zh-CN">
    +  <head>
    +    <meta charset="UTF-8" />
    +    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    +    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    +    <title>Document</title>
    +    <style>
    +      .border {
    +        border-style: solid;
    +        width: 0;
    +        height: 0;
    +        border-width: 0 25px 25px;
    +        border-color: transparent transparent #ffad60;
    +      }
    +    </style>
    +  </head>
    +  <body>
    +    <div class="border"></div>
    +  </body>
    +</html>
    +
  26. +
  27. 使用flex实现一个九宫格布局。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    <!DOCTYPE html>
    +<html lang="zh-CN">
    +  <head>
    +    <meta charset="UTF-8" />
    +    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    +    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    +    <title>Document</title>
    +    <style>
    +      .container {
    +        display: flex;
    +        flex-wrap: wrap;
    +        width: 304px;
    +        height: 304px;
    +        justify-content: space-evenly;
    +        align-content: space-evenly;
    +        background-color: black;
    +      }
    +      .container > .item {
    +        width: 100px;
    +        height: 100px;
    +        background-color: white;
    +      }
    +    </style>
    +  </head>
    +  <body>
    +    <div class="container">
    +      <div class="item"></div>
    +      <div class="item"></div>
    +      <div class="item"></div>
    +      <div class="item"></div>
    +      <div class="item"></div>
    +      <div class="item"></div>
    +      <div class="item"></div>
    +      <div class="item"></div>
    +      <div class="item"></div>
    +    </div>
    +  </body>
    +</html>
    +
  28. +
  29. sass

    +
      +
    • 变量

      +
      +

      需要注意的是sass中变量也有作用域

      +
      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      // sass的变量声明
      +$highlight-color: #f90;
      +$highlight-border: 1px solid $highlight-color;
      +.selected {
      +  border: $highlight-border;
      +}
      +
      +//编译后
      +.selected {
      +  border: 1px solid #f90;
      +}
      +
    • +
    • 嵌套css规则

      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      #content {
      +  article {
      +    h1 {
      +      color: #333;
      +    }
      +    p {
      +      margin-bottom: 1.4em;
      +    }
      +  }
      +  aside {
      +    background-color: #eee;
      +  }
      +}
      +
      +/* 编译后 */
      +#content article h1 {
      +  color: #333;
      +}
      +#content article p {
      +  margin-bottom: 1.4em;
      +}
      +#content aside {
      +  background-color: #eee;
      +}
      +
      +// 对于伪类元素需要使用父选择器的标识符
      +article a {
      +  color: blue;
      +  &:hover {
      +    color: red;
      +  }
      +}
      +
      +// 编译后
      +article a {
      +  color: blue;
      +}
      +article a:hover {
      +  color: red;
      +}
      +
      +// 群组选择器的嵌套
      +.container {
      +  h1,
      +  h2,
      +  h3 {
      +    margin-bottom: 0.8em;
      +  }
      +}
      +
      +nav,
      +aside {
      +  a {
      +    color: blue;
      +  }
      +}
      +
      +// 子组合选择器,和同层选择器
      +article {
      +  ~ article {
      +    border-top: 1px dashed #ccc;
      +  }
      +  > section {
      +    background: #eee;
      +  }
      +  dl > {
      +    dt {
      +      color: #333;
      +    }
      +    dd {
      +      color: #555;
      +    }
      +  }
      +  nav + & {
      +    margin-top: 0;
      +  }
      +}
      +
      +article ~ article {
      +  border-top: 1px dashed #ccc;
      +}
      +article > footer {
      +  background: #eee;
      +}
      +article dl > dt {
      +  color: #333;
      +}
      +article dl > dd {
      +  color: #555;
      +}
      +nav + article {
      +  margin-top: 0;
      +}
      +
      +// 嵌套属性
      +nav {
      +  border: 1px solid #ccc {
      +    left: 0px;
      +    right: 0px;
      +  }
      +}
      +
      +nav {
      +  border: 1px solid #ccc;
      +  border-left: 0px;
      +  border-right: 0px;
      +}
      +
    • +
    • 混合器

      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      // 混合器中不仅可以包含属性,也可以包含css规则,包含选择器和选择器中的属性
      +@mixin no-bullets {
      +  list-style: none;
      +  li {
      +    list-style-image: none;
      +    list-style-type: none;
      +    margin-left: 0px;
      +  }
      +}
      +ul.plain {
      +  color: #444;
      +  @include no-bullets;
      +}
      +
      +ul.plain {
      +  color: #444;
      +  list-style: none;
      +}
      +ul.plain li {
      +  list-style-image: none;
      +  list-style-type: none;
      +  margin-left: 0px;
      +}
      +
    • +
    +
  30. +
+

JS

谈谈对闭包的理解

闭包是一个函数可以记住并访问它的词法作用域,即时这个函数在这个变量的作用域外执行。换言之,即闭包为函数和它的外部变量的组合
其作用有:

+
    +
  • 创建私有变量。
  • +
  • 延长变量的声明周期。
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 防抖
+const debounce = (func, delay, immediate) => {
+  let timer = null;
+  return function (...params) {
+    if (timer) clearTimeout(timer);
+    else if (immediate) func.apply(this, params);
+    timer = setTimeout(() => {
+      func.apply(this, params);
+      timer = null;
+    }, delay);
+  };
+};
+// 节流
+const throttle = (func, delay, immediate) => {
+  let startTime = 0;
+  let timer = null;
+  return function (...params) {
+    if (!startTime) startTime = Date.now();
+    const remainTime = delay - (Date.now() - startTime);
+    if (remainTime > 0) {
+      if (timer) clearTimeout(timer);
+      else if (immediate) func.apply(this, params);
+      timer = setTimeout(() => {
+        func.apply(this, params);
+        timer = null;
+        startTime = 0;
+      }, remainTime);
+    }
+  };
+};
+

循环递归深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function deepClone(obj, hash = new WeakMap()) {
+  if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
+  if (obj instanceof Date) return new Date(obj);
+  if (obj instanceof RegExp) return new RegExp(obj);
+  // 可能是对象或者普通的值  如果是函数的话是不需要深拷贝
+  if (typeof obj !== "object") return obj;
+  // 是对象的话就要进行深拷贝
+  if (hash.get(obj)) return hash.get(obj);
+  let cloneObj = new obj.constructor();
+  // 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
+  hash.set(obj, cloneObj);
+  for (let key in obj) {
+    if (obj.hasOwnProperty(key)) {
+      // 实现一个递归拷贝
+      cloneObj[key] = deepClone(obj[key], hash);
+    }
+  }
+  return cloneObj;
+}
+

作用域与执行上下文

    +
  1. 作用域
    作用域指的是变量与函数的可访问范围。JS 有三种类型的作用域

    +
      +
    • 全局作用域
    • +
    • 函数作用域
    • +
    • 块级作用域
    • +
    +

    JS 中的作用域链是指在查找变量时沿着作用域层级所形成的链条,它由当前执行上下文及其所有父级执行上下文的变量对象组成。

    +
  2. +
  3. 执行上下文
    执行上下文指的是代码运行时的环境

    +
      +
    • 全局执行上下文
    • +
    • 函数执行上下文
    • +
    • eval执行上下文。
    • +
    +
  4. +
  5. 代码的执行过程

    +
      +
    • 当执行全局代码时,会创建一个全局执行上下文,并将其压入执行栈。
    • +
    • 当执行全局代码中的一个函数时,会创建一个函数执行上下文,并将其压入执行栈。
    • +
    • 在函数执行上下文中,会先创建一个变量对象,并初始化其中的变量和函数声明。
    • +
    • 然后会创建一个作用域链,并将当前变量对象添加到作用域链的最前端。
    • +
    • 接着会确定 this 值,并将其赋值给当前执行上下文。
    • +
    • 最后会逐行执行函数内部的代码,并根据作用域链来访问和操作变量和函数。
    • +
    +
  6. +
+

实现一个严格相等符(===)

先判断类型再判断值是否相等

+
1
2
3
4
5
6
7
8
9
10
11
const getType = (variable) => {
+  const type = typeof variable;
+  if (type !== "object") return type;
+  return Object.prototype.toString
+    .call(variable)
+    .match(/^\[Object (\w+)\]$/)[1]
+    .toLowerCase();
+};
+const strictEqual = (left, right) => {
+  return getType(left) == getType(right) && left == right;
+};
+

Ajax

    +
  • 封装一个简单的ajax请求

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    //封装一个ajax请求
    +function ajax(options) {
    +  //创建XMLHttpRequest对象
    +  const xhr = new XMLHttpRequest();
    +
    +  //初始化参数的内容
    +  options = options || {};
    +  options.type = (options.type || "GET").toUpperCase();
    +  options.dataType = options.dataType || "json";
    +  const params = options.data;
    +
    +  //发送请求
    +  if (options.type === "GET") {
    +    xhr.open("GET", options.url + "?" + params, true);
    +    xhr.send(null);
    +  } else if (options.type === "POST") {
    +    xhr.open("POST", options.url, true);
    +    xhr.send(params);
    +
    +    //接收请求
    +    xhr.onreadystatechange = function () {
    +      if (xhr.readyState === 4) {
    +        let status = xhr.status;
    +        if (status >= 200 && status < 300) {
    +          options.success &&
    +            options.success(xhr.responseText, xhr.responseXML);
    +        } else {
    +          options.fail && options.fail(status);
    +        }
    +      }
    +    };
    +  }
    +}
    +
  • +
  • 使用

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    ajax({
    +  type: "post",
    +  dataType: "json",
    +  data: {},
    +  url: "https://xxxx",
    +  success: function (text, xml) {
    +    //请求成功后的回调函数
    +    console.log(text);
    +  },
    +  fail: function (status) {
    +    ////请求失败后的回调函数
    +    console.log(status);
    +  },
    +});
    +
  • +
+

原型链

原型链的尽头

+
    +
  • Function → Function.prototype → Object.prototype → null
  • +
  • Object → Function.prototype → Object.prototype → null
  • +
+

通过隐式绑定 this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Function.prototype.myCall = function (obj, ...params) {
+  const key = Symbol("key");
+  obj[key] = this;
+  const result = obj[key](...params);
+  delete obj[key];
+  return result;
+};
+Function.prototype.myApply = function (obj, params) {
+  const key = Symbol("key");
+  obj[key] = this;
+  const result = obj[key](...params);
+  delete obj[key];
+  return result;
+};
+Function.prototype.myBind = function (obj, ...params) {
+  let func = this;
+  const bound = function (...args) {
+    const isNew = Boolean(new.target);
+    const isValue = isNew ? this : obj;
+    func.myApply(isValue, [...params, ...args]);
+  };
+  if (func.prototype) {
+    bound.prototype = Object.create(func.prototype);
+  }
+  return bound;
+};
+

instanceof 函数手写实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const myInstanceOf = (left, right) => {
+  if (
+    left === null ||
+    (typeof left !== "object" && typeof left !== "function") ||
+    typeof right !== "function"
+  )
+    return false;
+  const proto = Object.getPrototypeOf(left);
+  while (true) {
+    if (proto === null) return false;
+    if (proto === right.prototype) return true;
+    proto = Object.getPrototypeOf(proto);
+  }
+};
+class Name {}
+const name1 = new Name();
+console.log(myInstanceOf(name1, Name));
+console.log(myInstanceOf(Name, Function));
+

new 操作符手写实现

1
2
3
4
5
const newFunc = (constructor, ...params) => {
+  const obj = Object.create(constructor.prototype);
+  const result = constructor.apply(obj, params);
+  return typeof result === "object" && result !== null ? result : obj;
+};
+

XSS 攻击

XSS(Cross Site Scripting),跨站脚本攻击,允许攻击者将恶意代码植入到提供给其它用户使用的页面中

+
    +
  • 储存型:恶意代码提交到对应数据库,用户请求拼接在html中返回。
  • +
  • 反射型:url拼接参数,服务端返回恶意代码。
  • +
  • DOM 型:js的自身漏洞,利用js插入恶意代码。
  • +
+

储存型和反射型都是服务端漏洞,DOM型是前端JS的漏洞。

+
+

解决手段

+
    +
  • 对用户的输入进行合法性检查和过滤,避免接受或存储包含恶意脚本的数据。
  • +
  • 对用户的输出进行转义或编码,避免浏览器将输出的数据当作脚本来执行。
  • +
  • 使用 HTTP 头部的 Content-Security-Policy 来限制浏览器加载或执行的资源,避免加载或执行来自不可信来源的脚本。
  • +
  • 使用 HTTP 头部的 X-XSS-Protection 来启用浏览器的 XSS 防护功能,避免浏览器执行检测到的 XSS 攻击。
  • +
  • 使用其他的安全措施,比如 HTTPS、HTTPOnly、SameSite 等,来增强用户的安全性和隐私性。
  • +
+

CSRF 攻击

CSRF(Cross Site Request Forgery)攻击是一种利用用户在已登录的网站上执行非本意的操作的攻击方法。

+

CSRF 攻击的原理是,攻击者构造一个包含恶意请求的网页或链接,然后诱使用户访问这个网页或链接。当用户访问时,浏览器会自动携带用户在目标网站的 cookie 信息,从而使得恶意请求看起来像是用户自己发出的。如果目标网站没有对请求进行有效的验证,就会执行恶意请求的操作,比如转账、修改密码、删除数据等。

+

CSRF 攻击的危害是非常严重的,它可以导致用户的隐私泄露、账号被盗、财产损失、甚至被控制或劫持。

+

CSRF 攻击的防御方法主要有以下几种:

+
    +
  • 验证 HTTP Referer 字段,检查请求是否来自合法的来源。
  • +
  • 在非 GET 请求中增加 token 或验证码,并在服务器端验证其有效性。
  • +
  • 使用 SameSite 属性来限制浏览器发送跨站请求时携带的 cookie 信息。
  • +
  • 使用其他的安全措施,比如 HTTPS、CORS、X-Frame-Options 等,来增强用户的安全性和隐私性。
  • +
+

断点续传的大致思路

    +
  1. 服务端到客户端。下载类似(IDM,与 Aria2 等工具)
    断点续传是一种文件传输技术,它可以在网络中断或其他异常情况下,从上次传输的位置继续传输文件,而不是从头开始。断点续传的原理是利用 HTTP 协议中的 Range 和 Content-Range 头,以及 Etag 或 Last-Modified 头,来指定文件的分片范围和校验文件的唯一性。断点续传的步骤如下:

    +
      +
    • 客户端向服务器发送一个带有 Range 头的请求,指定要获取的文件的部分内容,例如:Range: bytes=500-999 表示请求第 500-999 字节的内容。
    • +
    • 服务器收到请求后,判断是否支持断点续传,如果支持,就返回相应的分片内容和状态码 206,以及 Content-Range 头,指定返回的分片范围和文件总大小,例如:Content-Range: bytes 500-999/22400 表示返回的是第 500-999 字节的内容,文件总大小为 22400 字节。
    • +
    • 客户端收到分片内容后,将其拼接到已经下载的部分,然后继续发送下一个分片的请求,直到文件下载完成。
    • +
    • 在下载过程中,如果发生中断,客户端可以记录已经下载的分片范围,然后在恢复下载时,从下一个分片开始请求。
    • +
    • 为了避免文件在服务器端发生变化,导致下载的内容不一致,客户端可以在请求时带上 If-Range 头,指定文件的 Etag 或 Last-Modified 值,用于校验文件的唯一性。如果文件没有变化,服务器会继续返回分片内容,如果文件发生变化,服务器会返回整个文件和状态码 200。
    • +
    +
  2. +
  3. 客户端到服务端。

    +
      +
    1. 分片上传
      分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(Part)来进行分片上传
      大致流程如下:

      +
        +
      • 将需要上传的文件按照一定的分割规则,分割成相同大小的数据块;
      • +
      • 初始化一个分片上传任务,返回本次分片上传唯一标识;
      • +
      • 按照一定的策略(串行或并行)发送各个分片数据块;
      • +
      • 发送完成后,服务端根据判断数据上传是否完整,如果完整,则进行数据块合成得到原始文件
      • +
      +
    2. +
    3. 断点续传
      断点续传指的是在下载或上传时,将下载或上传任务人为的划分为几个部分

      +

      每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载。用户可以节省时间,提高速度

      +

      一般实现方式有两种:

      +
        +
      • 服务器端返回,告知从哪开始
      • +
      • 浏览器端自行处理
      • +
      +

      上传过程中将文件在服务器写为临时文件,等全部写完了(文件上传完),将此临时文件重命名为正式文件即可

      +

      如果中途上传中断过,下次上传的时候根据当前临时文件大小,作为在客户端读取文件的偏移量,从此位置继续读取文件数据块,上传到服务器从此偏移量继续写入文件即可

      +
    4. +
    +
  4. +
+

计网

httphttps的区别

    +
  • http(HyperText Transfer Protocol),即超文本运输协议,是实现网络通信的一种规范。其传输的数据并不是计算机底层上的二进制包,而是完整的,有意义的数据,如HTML文件,图片文件,查询结果等超文本。能够被上层应用识别到。在实际的应用当中,http协议常用于Web浏览器与网站服务器之间传递消息,以明文方式发送内容,不提供任何方式的数据加密。
  • +
  • 特点:
      +
    • 支持客户/服务器模式。
    • +
    • 简单快速:客户向服务器请求服务的时候,只需要传送请求方法和路径。由于http协议简单,使得http服务器的程序规模小,因而通信速度很快。
    • +
    • 灵活:http允许传输任意类型的数据对象。正在传输的对象用Content-Type加以标记。
    • +
    • 无连接:限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
    • +
    • 无状态:http协议无法根据之前的状态进行本次的请求处理。
    • +
    +
  • +
  • https,即运行在SSL/TLS上的http协议,SSL/TLS运行在TCP/IP协议与各种应用层协议之间。
      +
    • 过程:
        +
      • 浏览器向使用SSL/TLS的网站发送请求的时候,网站服务器会返回一个包含自己的公钥和身份信息的证书,这个证书通常是由可信任的机构颁发的。
      • +
      • 浏览器会验证证书的有效性和真实性,如果证书合法,自己生成一段随机的对称密钥,并用公钥加密发送给服务端。
      • +
      • 服务段用自己的私钥解密对称密钥,然后用这个对称密钥加密数据,实现加密通信。
      • +
      • 浏览器段使用该对称密钥解密数据,显示在页面上。
      • +
      • 这样,浏览器和网站之间的通信就是通过对称密钥加密的,第三方无法窃取或篡改数据,从而实现了安全通信。
      • +
      +
    • +
    +
  • +
+

+

https相对于http的优势

+

http的一些问题

+
    +
  • 使用明文通信,内容可能被窃听
  • +
  • 不验证通信方的身份,因此有可能遭遇伪装。
  • +
+
+

https 为何更安全

+
    +
  • 对称加密:加密与解密所用的密钥都是同一个,如果保证了密钥的安全,那么就保证了通信的机密性。
  • +
  • 非对称加密:密钥分为公钥和私钥,公钥加密只能用私钥解密,私钥加密只能用公钥解密。
  • +
  • 混合加密:https采用的是对称加密+非对称加密,https采用非对称加密来解决密钥交换的问题。即浏览器使用公钥来加密对称密钥,服务端使用私钥来解密对称密钥,实现密钥的私密性。
  • +
  • 摘要算法:通过摘要算法压缩数据,给数据生成独一无二的ID,验证数据的完整性。
  • +
  • 数字签名:将ID用私钥加密,确保了消息确实由发送方发送出来的。
  • +
  • CA 机构:使用 CA 公钥解密证书的数字签名,确保证书和公钥的有效性。
    CA 对公钥的签名认证要求包括序列号、用途、颁发者、有效时间等等,把这些打成一个包再签名,完整地证明公钥关联的各种信息,形成“数字证书”
  • +
+

复杂请求的基本概念

复杂请求是指不满足简单请求条件的跨域请求。简单请求的条件是:

+
    +
  • 请求方法是 GET、POST 或 HEAD 之一
  • +
  • 请求头只包含 Accept、Accept-Language、Content-Language 和 Content-Type 四种,且 Content-Type 的值只能是 text/plain、multipart/form-data 或 application/x-www-form-urlencoded 之一
  • +
  • 请求中没有使用 XMLHttpRequestUpload 对象注册事件监听器,也没有使用 ReadableStream 对象
  • +
+

复杂请求在发送实际请求之前,需要先发送一个预检请求(preflight request),用 OPTIONS 方法向服务器询问是否允许该跨域请求。预检请求中包含以下两个特殊的请求头:

+
    +
  • Access-Control-Request-Method:表示实际请求的方法
  • +
  • Access-Control-Request-Headers:表示实际请求的自定义头部
  • +
+

一个基本示例

+
1
2
3
4
5
6
7
8
9
OPTIONS /resources/post-here/ HTTP/1.1
+Host: bar.example
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Connection: keep-alive
+Origin: https://foo.example
+Access-Control-Request-Method: POST
+Access-Control-Request-Headers: X-PINGOTHER, Content-Type
+

服务器根据预检请求中的信息,返回一个预检响应(preflight response),告诉浏览器是否允许该跨域请求。预检响应中包含以下几个重要的响应头:

+
    +
  • Access-Control-Allow-Origin:表示允许的请求来源,可以是具体的域名,也可以是*表示任意域名
  • +
  • Access-Control-Allow-Methods:表示允许的请求方法,是一个以逗号分隔的列表
  • +
  • Access-Control-Allow-Headers:表示允许的请求头,是一个以逗号分隔的列表
  • +
  • Access-Control-Allow-Credentials:表示是否允许携带身份凭证(如 cookies),只能是 true 或者省略
  • +
  • Access-Control-Max-Age:表示预检响应的有效时间,单位是秒
  • +
+

只有当预检响应中的 Access-Control-Allow-Origin 和实际请求的 Origin 相匹配,且 Access-Control-Allow-Methods 和 Access-Control-Allow-Headers 包含实际请求的方法和头部时,浏览器才会发送实际请求。否则,浏览器会报错,拒绝执行该跨域请求。
一个基本示例

+
1
2
3
4
5
6
7
8
9
10
HTTP/1.1 200 OK
+Date: Mon, 01 Dec 2008 01:15:39 GMT
+Server: Apache/2.0.61 (Unix)
+Access-Control-Allow-Origin: https://foo.example
+Access-Control-Allow-Methods: POST, GET, OPTIONS
+Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
+Access-Control-Max-Age: 86400
+Vary: Accept-Encoding, Origin
+Keep-Alive: timeout=2, max=100
+Connection: Keep-Alive
+

原生方法对预检请求进行响应

使用原生方法对跨域请求的预检请求进行响应,需要在服务器端设置一些响应头,来告诉浏览器是否允许该请求,以及允许的方法、头部、凭证等。具体来说,有以下几个步骤:

+
    +
  • 首先,判断请求是否是跨域请求,可以通过检查请求头中的 Origin 字段,如果该字段存在且不等于当前服务器的域名,说明是跨域请求。
  • +
  • 其次,判断请求是否是预检请求,可以通过检查请求方法是否是 OPTIONS,如果是,说明是预检请求。
  • +
  • 然后,根据业务逻辑,决定是否允许该跨域请求,如果允许,就在响应头中添加以下字段:

    +
      +
    • Access-Control-Allow-Origin: 表示允许的来源域,可以是具体的域名,也可以是 * 表示任意域名。
    • +
    • Access-Control-Allow-Methods: 表示允许的请求方法,可以是多个,用逗号分隔,例如 GET,POST,PUT,DELETE 等。
    • +
    • Access-Control-Allow-Headers: 表示允许的请求头,可以是多个,用逗号分隔,例如 Content-Type,Authorization 等。
    • +
    • Access-Control-Max-Age: 表示预检请求的有效期,单位是秒,表示在这个时间内,不需要再发送预检请求。
    • +
    • Access-Control-Allow-Credentials: 表示是否允许携带凭证,例如 CookieHTTP 认证,可以是 truefalse
    • +
    +
  • +
  • 最后,返回响应,结束预检请求,浏览器会根据响应头中的信息,决定是否继续发送真正的请求。

    +
  • +
+

下面是一个使用原生方法的 Node.js 代码示例:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 引入 http 模块
+const http = require("http");
+// 创建一个 http 服务器
+const server = http.createServer(function (req, res) {
+  // 获取请求头中的 Origin 字段
+  const origin = req.headers.origin;
+  // 获取请求方法
+  const method = req.method;
+  // 判断是否是跨域请求
+  if (origin && origin !== "http://localhost:3000") {
+    // 判断是否是预检请求
+    if (method === "OPTIONS") {
+      // 设置响应头,允许跨域请求
+      res.setHeader("Access-Control-Allow-Origin", origin); // 允许任意域名跨域
+      res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE"); // 允许的请求方法
+      res.setHeader(
+        "Access-Control-Allow-Headers",
+        "Content-Type, Authorization"
+      ); // 允许的请求头
+      res.setHeader("Access-Control-Max-Age", "86400"); // 预检请求的有效期为一天
+      res.setHeader("Access-Control-Allow-Credentials", "true"); // 允许携带凭证
+      // 返回响应
+      res.end();
+    } else {
+      // 处理其他请求,例如 GET, POST 等
+      // ...
+    }
+  } else {
+    // 处理非跨域请求,例如本地请求
+    // ...
+  }
+});
+// 监听 3000 端口
+server.listen(3000, function () {
+  console.log("Server is running on port 3000");
+});
+

DNS查询过程

    +
  1. 查询浏览器 DNS 缓存。
  2. +
  3. 查询操作系统 DNS 缓存。
  4. +
  5. 查询本地域名服务器 DNS 缓存。
  6. +
  7. 查询根域名服务器,返回顶级域名服务器。
  8. +
  9. 本地服务器向顶级域名服务器发送查询请求,返回权限域名服务器的地址。
  10. +
  11. 向权限域名服务器发送请求,获取 IP 地址。
  12. +
  13. 获得 IP 地址形成访问。
  14. +
+

+

CDN原理

    +
  1. CDN 的基本原理:CDN 的全称为 Content Delivery Network,即内容分发网络,可作为网站的静态资源服务站点,用来减轻源站的网络请求压力。

    +
      +
    • CDN的作用为返回离用户最近节点的资源,减轻源站网络请求压力,提升用户体验。
    • +
    • CDN的工作流程为:
        +
      1. 浏览器请求CDN链接,经过DNS解析流程,返回CNAME记录指向负载均衡系统。
      2. +
      3. 负载均衡系统通过计算返回离用户最近的节点(即边缘节点)的IP地址。
      4. +
      5. 浏览器再向边缘节点发送请求获取静态资源。(注:DNS 解析记录的缓存一般只缓存第一次的解析结果,所以边缘节点的IP地址一般不会被缓存这也与直觉相同。)
      6. +
      +
    • +
    +
  2. +
  3. 衡量CDN服务的指标

    +
      +
    • 命中率:用户访问的资源恰好在缓存系统里,可以直接返回给用户,命中次数与所有访问次数之比。
    • +
    • 回源率:缓存里没有,必须用代理的方式回源站取,回源次数与所有访问次数之比。
    • +
    +
  4. +
  5. 缓存代理
    缓存系统也可以划分出层次,分成一级缓存节点和二级缓存节点。一级缓存配置高一些,直连源站,二级缓存配置低一些,直连用户。回源的时候二级缓存只找一级缓存,一级缓存没有才回源站,可以有效地减少真正的回源。现在的商业 CDN 命中率都在 90% 以上,相当于把源站的服务能力放大了 10 倍以上。

    +
  6. +
+

UDPTCP的简单理解,两个协议都位于 OSI 的传输层

    +
  • UDP(User Datagram Protocol):是一个面向用户数据包的通信协议,即对应用层交下来的报文,不合并,不拆分,只是在其上面加上首部后就交给了下面的网络层。
  • +
  • TCP(Transmission Control Protocol):是一种可靠的面向字节流的协议,可以想象成流水形式的,发送方 TCP 会将数据放入“蓄水池”(缓存区),等到可以发送的时候就发送,不能发送就等着,TCP 会根据当前网络的拥塞状态来确定每个报文段的大小。
  • +
+

HTTP/1.0/1.1/2.0

    +
  1. HTTP 1.0 和 HTTP 1.1 之间有哪些区别

    +
      +
    • 连接方面http1.0 默认使用非持久连接,而 http1.1 默认使用持久连接。http1.1 通过使用持久连接来使多个 http 请求复用同一个 TCP 连接,以此来避免使用非持久连接时每次需要建立连接的时延。

      +
    • +
    • 资源请求方面,在 http1.0 中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,http1.1 则在请求头引入了 range 头域,它允许只请求资源的某个部分,即返回码是 206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。

      +
    • +
    • 缓存方面,在 http1.0 中主要使用 header 里的 If-Modified-SinceExpires 来做为缓存判断的标准,http1.1 则引入了更多的缓存控制策略,例如 EtagIf-Unmodified-SinceIf-MatchIf-None-Match 等更多可供选择的缓存头来控制缓存策略。

      +
    • +
    • http1.1 中新增了 host 字段,用来指定服务器的域名。http1.0 中认为每台服务器都绑定一个唯一的 IP 地址,因此,请求消息中的 URL 并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机,并且它们共享一个 IP 地址。因此有了 host 字段,这样就可以将请求发往到同一台服务器上的不同网站。
    • +
    • http1.1 相对于 http1.0 还新增了很多请求方法,如 PUT、HEAD、OPTIONS 等。
    • +
    +
  2. +
  3. HTTP 1.1 和 HTTP 2.0 之间有哪些区别

    +
      +
    • 二进制协议HTTP/2 是一个二进制协议。在 HTTP/1.1 版中,报文的头信息必须是文本(ASCII 编码),数据体可以是文本,也可以是二进制。HTTP/2 则是一个彻底的二进制协议,头信息和数据体都是二进制,并且统称为”帧”,可以分为头信息帧和数据帧。多个帧之间可以乱序发送,根据帧首部的流标识可以重新组装,这也是多路复用同时发送数据的实现条件。

      +
    • +
    • 多路复用HTTP/2 实现了多路复用,HTTP/2 仍然复用 TCP 连接,但是在一个连接里,客户端和服务器都可以同时发送多个请求或回应,而且不用按照顺序一一发送,这样就避免了”队头堵塞”的问题。

      +
    • +
    • 数据流HTTP/2 使用了数据流的概念,因为 HTTP/2 的数据包是不按顺序发送的,同一个连接里面连续的数据包,可能属于不同的请求。因此,必须要对数据包做标记,指出它属于哪个请求。HTTP/2 将每个请求或回应的所有数据包,称为一个数据流。每个数据流都有一个独一无二的编号。数据包发送时,都必须标记数据流 ID ,用来区分它属于哪个数据流。

      +
    • +
    • 头信息压缩HTTP/2 实现了头信息压缩,由于 HTTP 1.1 协议不带状态,每次请求都必须附上所有信息。所以,请求的很多字段都是重复的,比如 CookieUser Agent ,一模一样的内容,每次请求都必须附带,这会浪费很多带宽,也影响速度。HTTP/2 对这一点做了优化,引入了头信息压缩机制,头信息表充当状态。一方面,头信息使用 gzipcompress 压缩后再发送;另一方面,客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就能提高速度了。

      +
    • +
    • 服务器推送HTTP/2 允许服务器未经请求,主动向客户端发送资源,这叫做服务器推送。使用服务器推送提前给客户端推送必要的资源,这样就可以相对减少一些延迟时间。这里需要注意的是 http2 下服务器主动推送的是静态资源,和 WebSocket 以及使用 SSE 等方式向客户端发送即时数据的推送是不同的。
    • +
    +
    +

    队头阻塞的概念:队头阻塞是由 HTTP 基本的“请求 - 应答”模型所导致的。HTTP 规定报文必须是“一发一收”,这就形成了一个先进先出的“串行”队列。队列里的请求是没有优先级的,只有入队的先后顺序,排在最前面的请求会被最优先处理。如果队首的请求因为处理的太慢耽误了时间,那么队列里后面的所有请求也不得不跟着一起等待,结果就是其他的请求承担了不应有的时间成本,造成了队头堵塞的现象。

    +
    +
  4. +
+

HTTP常用状态码

100:服务器已收到请求,需进一步响应。
101:用于httpwebsocket协议升级。
200:常规资源请求成功。
201:请求成功并在服务端创建了新的资源。
301:重定向。
302:临时移动。
400:错误请求。
401:未授权。
502:错误网关。
503:服务不可用。

+

协商缓存

举个例子,假设客户端第一次请求一个图片文件,服务器返回 200 OK 的状态码,以及图片的内容,同时在响应头中设置了 Last-Modified: Wed, 10 Nov 2021 07:00:51 GMTEtag: "1234567890",表示该图片的最后修改时间和唯一标识。客户端会将这些信息和图片一起缓存到本地。当客户端再次请求该图片时,会在请求头中添加 If-Modified-Since: Wed, 10 Nov 2021 07:00:51 GMTIf-None-Match: "1234567890",表示只有当图片在这个时间之后被修改过,或者图片的标识发生变化时,才需要重新获取图片。如果服务器检查发现图片没有变化,就会返回 304 Not Modified 的状态码,不会返回图片的内容,客户端就可以直接使用本地缓存的图片。如果服务器检查发现图片有变化,就会返回 200 OK 的状态码,以及新的图片内容,客户端就会更新本地缓存,并显示新的图片。

+

URL 跳转网站发生了什么

    +
  1. URL解析
  2. +
  3. DNS查询
  4. +
  5. TCP连接
  6. +
  7. 发出HTTP请求
  8. +
  9. 响应请求
  10. +
  11. 页面渲染
  12. +
+

WebSocket

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,它使得浏览器和服务器之间可以实现实时双向的数据交换。WebSocket 的优点是:

+
    +
  • 可以减少服务器的资源和带宽消耗,提高通信效率和性能。
  • +
  • 可以实现服务端主动推送数据给客户端,不需要客户端频繁地轮询服务器。
  • +
  • 可以支持多种数据格式,包括文本、二进制、图像、音频、视频等。
  • +
  • 可以兼容多种浏览器和平台,只要支持 HTML5 的浏览器都可以使用 WebSocket
  • +
+

WebSocket 的工作流程如下:

+
    +
  1. 客户端向服务器发起一个 HTTP 请求,请求头中包含一个 Upgrade 字段,表示要升级协议为 WebSocket
  2. +
  3. 服务器收到请求后,如果同意升级,就会返回一个 HTTP 响应,响应头中包含一个 Upgrade 字段,表示已经切换到 WebSocket 协议。
  4. +
  5. 客户端和服务器之间建立一个 WebSocket 连接,之后就可以通过这个连接进行双向的数据传输。
  6. +
  7. 客户端或服务器可以随时关闭连接,发送一个关闭帧,然后断开 TCP 连接。
  8. +
+

WebSocket 的使用方法如下:

+
    +
  • JavaScript 中,可以使用 WebSocket 对象来创建和管理一个 WebSocket 连接,以及发送和接收数据。WebSocket 对象的构造函数接受一个参数,即服务器的 URL,例如var socket = new WebSocket("ws://example.com")
  • +
  • WebSocket 对象有以下几个属性:
      +
    • socket.readyState:表示连接的当前状态,有四种可能的值:0(连接尚未建立),1(连接已经建立,可以通信),2(连接正在关闭),3(连接已经关闭或者连接失败)。
    • +
    • socket.url:表示连接的绝对 URL。
    • +
    • socket.protocol:表示服务器选择的子协议,如果没有选择,就是空字符串。
    • +
    • socket.binaryType:表示二进制数据的类型,可以是blob或者arraybuffer
    • +
    • socket.bufferedAmount:表示还有多少字节的数据没有发送出去,可以用来判断发送是否结束。
    • +
    +
  • +
  • WebSocket 对象有以下几个方法:
      +
    • socket.send(data):向服务器发送数据,可以是文本或者二进制数据。
    • +
    • socket.close(code, reason):关闭连接,可以指定一个数字的状态码和一个字符串的原因。
    • +
    +
  • +
  • WebSocket 对象有以下几个事件:
      +
    • open:当连接建立时触发,可以使用socket.onopen属性或者socket.addEventListener("open", handler)方法来监听。
    • +
    • message:当收到服务器的数据时触发,可以使用socket.onmessage属性或者socket.addEventListener("message", handler)方法来监听。事件对象有一个data属性,表示收到的数据,可以是文本或者二进制数据。
    • +
    • error:当发生错误时触发,可以使用socket.onerror属性或者socket.addEventListener("error", handler)方法来监听。事件对象没有提供错误的具体信息,只能通过socket.readyState来判断连接的状态。
    • +
    • close:当连接关闭时触发,可以使用socket.onclose属性或者socket.addEventListener("close", handler)方法来监听。事件对象有三个属性:code表示关闭的状态码,reason表示关闭的原因,wasClean表示是否是正常关闭。
    • +
    +
  • +
+

Node.js

Node.js 的优点与缺点

优点:

+
    +
  • 处理高并发场景性能更佳。
  • +
  • 适合 I/O 密集型应用,值的是应用在运行极限时,CPU 占用率仍然比较低,大部分时间是在做 I/O 硬盘内存读写操作。
  • +
+

因为Node.js单线程所带来的缺点

+
    +
  • 不适合 CPU 密集型应用。
  • +
  • 只支持单核 CPU,不能充分利用 CPU。
  • +
  • 可靠性低,一旦代码的某个环节崩溃,整个系统都会崩溃。
  • +
+

Node.js 的常见全局对象。

+

全局对象分为两类:

+
    +
  • 真正的全局对象
  • +
  • 模块的全局变量
  • +
+
+

真正的全局对象

+
    +
  • Buffer
  • +
  • global
  • +
  • setTimeout, setInterval, clearTimeout, clearInterval
  • +
  • process
  • +
  • console
  • +
+

模块级别的全局变量

+
    +
  • __dirname:当前文件所在的路径,不包括后面的文件名。
  • +
  • __filename:当前文件所在的路径与名称,包括文件的名称。
  • +
  • exports
  • +
  • module
  • +
  • require
  • +
+

Node.js中的process对象

    +
  • process.env:获取不同环境项目的配置信息。
  • +
  • process.pid:获取当前进程的pid
  • +
  • process.ppid:获取当前进程对应的父进程。
  • +
  • process.cwd():获得当前进程的工作目录。
  • +
  • process.platform:获得当前运行进程的操作系统平台。
  • +
  • process.uptime():获得当前进程已运行的时间。
  • +
  • process.on('uncaughtException', cb)捕获异常信息、 process.on('exit', cb)进程退出监听。
  • +
  • 三个标准流process.stdinprocess.stdoutprocess.stderr
  • +
  • process.title:指定进程名称。
  • +
  • process.argv:传入的命令行参数。
  • +
+

Node.js中的fs模块

    +
  • writeFile/writeFileSync
  • +
  • appendFile/appendFileSync
  • +
  • createWriteStream
  • +
  • readFile/readFileSync
  • +
  • createReadStream
  • +
  • rename/renameSync
  • +
  • unlink/unlinkSync
  • +
  • mkdir/mkdirSync
  • +
  • readdir/readdirSync
  • +
  • rmdir/rmdirSync
  • +
  • stat/statSync
  • +
  • copyfile/copyfileSync
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
//将 『三人行,必有我师焉。』 写入到当前文件夹下的『座右铭.txt』文件中
+fs.writeFile("./座右铭.txt", "三人行,必有我师焉。", (err) => {
+  //如果写入失败,则回调函数调用时,会传入错误对象,如写入成功,会传入 null
+  if (err) {
+    console.log(err);
+    return;
+  }
+  console.log("写入成功");
+});
+
+fs.writeFileSync("2.txt", "Hello world");
+let data = fs.readFileSync("2.txt", "utf8");
+
+console.log(data); // Hello world
+
+fs.appendFile("3.txt", " world", (err) => {
+  if (!err) {
+    fs.readFile("3.txt", "utf8", (err, data) => {
+      console.log(data); // Hello world
+    });
+  }
+});
+
+fs.appendFileSync("3.txt", " world");
+let data = fs.readFileSync("3.txt", "utf8");
+
+let ws = fs.createWriteStream("./观书有感.txt");
+ws.write("半亩方塘一鉴开\r\n");
+ws.write("天光云影共徘徊\r\n");
+ws.write("问渠那得清如许\r\n");
+ws.write("为有源头活水来\r\n");
+ws.end();
+
+fs.readFile("1.txt", "utf8", (err, data) => {
+  if (!err) {
+    console.log(data); // Hello
+  }
+});
+
+let buf = fs.readFileSync("1.txt");
+let data = fs.readFileSync("1.txt", "utf8");
+
+console.log(buf); // <Buffer 48 65 6c 6c 6f>
+console.log(data); // Hello
+
+//创建读取流对象
+let rs = fs.createReadStream("./观书有感.txt");
+//每次取出 64k 数据后执行一次 data 回调
+rs.on("data", (data) => {
+  console.log(data);
+  console.log(data.length);
+});
+//读取完毕后, 执行 end 回调
+rs.on("end", () => {
+  console.log("读取完成");
+});
+fs.rename("./观书有感.txt", "./论语/观书有感.txt", (err) => {
+  if (err) throw err;
+  console.log("移动完成");
+});
+fs.renameSync("./座右铭.txt", "./论语/我的座右铭.txt");
+
+fs.unlink("./test.txt", (err) => {
+  if (err) throw err;
+  console.log("删除成功");
+});
+fs.unlinkSync("./test2.txt");
+fs.mkdir("./page", (err) => {
+  if (err) throw err;
+  console.log("创建成功");
+});
+//递归异步创建
+fs.mkdir("./1/2/3", { recursive: true }, (err) => {
+  if (err) throw err;
+  console.log("递归创建成功");
+});
+//递归同步创建文件夹
+fs.mkdirSync("./x/y/z", { recursive: true });
+
+//异步读取
+fs.readdir("./论语", (err, data) => {
+  if (err) throw err;
+  console.log(data);
+});
+//同步读取
+let data = fs.readdirSync("./论语");
+console.log(data);
+//异步删除文件夹
+fs.rmdir("./page", (err) => {
+  if (err) throw err;
+  console.log("删除成功");
+});
+//异步递归删除文件夹
+fs.rmdir("./1", { recursive: true }, (err) => {
+  if (err) {
+    console.log(err);
+  }
+  console.log("递归删除");
+});
+//同步递归删除文件夹
+fs.rmdirSync("./x", { recursive: true });
+//异步获取状态
+fs.stat("./data.txt", (err, data) => {
+  if (err) throw err;
+  console.log(data);
+});
+//同步获取状态
+let data = fs.statSync("./data.txt");
+
+fs.copyFileSync("3.txt", "4.txt");
+let data = fs.readFileSync("4.txt", "utf8");
+
+console.log(data); // Hello world
+
+fs.copyFile("3.txt", "4.txt", () => {
+  fs.readFile("4.txt", "utf8", (err, data) => {
+    console.log(data); // Hello world
+  });
+});
+

Buffer

Buffer(缓冲区)是Node.js用于表示固定长度的字节序列,本质上是一段内存空间,专门用来处理二进制数据。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//创建了一个长度为 10 字节的 Buffer,相当于申请了 10 字节的内存空间,每个字节的值为 0
+let buf_1 = Buffer.alloc(10); // 结果为 <Buffer 00 00 00 00 00 00 00 00 00 00>
+//创建了一个长度为 10 字节的 Buffer,buffer 中可能存在旧的数据, 可能会影响执行结果,所以叫
+// unsafe
+let buf_2 = Buffer.allocUnsafe(10);
+
+//通过字符串创建 Buffer
+let buf_3 = Buffer.from("hello");
+//通过数组创建 Buffer
+let buf_4 = Buffer.from([105, 108, 111, 118, 101, 121, 111, 117]);
+
+let buf_4 = Buffer.from([105, 108, 111, 118, 101, 121, 111, 117]);
+console.log(buf_4.toString());
+
+const buffer = Buffer.from("你好", "utf-8 ");
+console.log(buffer);
+// <Buffer e4 bd a0 e5 a5 bd>
+const str = buffer.toString("ascii");
+console.log(str);
+// d= e%=
+

IOstream流进行管道读写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const fs = require("fs");
+
+const inputStream = fs.createReadStream("input.txt"); // 创建可读流
+const outputStream = fs.createWriteStream("output.txt"); // 创建可写流
+
+inputStream.pipe(outputStream); // 管道读写
+
+// 文件操作
+const path = require("path");
+
+// 两个文件名
+const fileName1 = path.resolve(__dirname, "data.txt");
+const fileName2 = path.resolve(__dirname, "data-bak.txt");
+// 读取文件的 stream 对象
+const readStream = fs.createReadStream(fileName1);
+// 写入文件的 stream 对象
+const writeStream = fs.createWriteStream(fileName2);
+// 通过 pipe执行拷贝,数据流转
+readStream.pipe(writeStream);
+// 数据读取完成监听,即拷贝完成
+readStream.on("end", function () {
+  console.log("拷贝完成");
+});
+

Node.js 中的 EventEmitter

手写实现

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
class EventEmitter {
+  constructor() {
+    this.callbacks = {};
+  }
+  addListener(type, handler) {
+    if (!this.callbacks[type]) {
+      this.callbacks[type] = [];
+    }
+    this.callbacks[type].push(handler);
+  }
+  on(type, handler) {
+    this.addListener(type, handler);
+  }
+  prependListener(type, handler) {
+    if (!this.callbacks[type]) {
+      this.callbacks[type] = [];
+    }
+    this.callbacks[type].unshift(handler);
+  }
+  emit(type, ...args) {
+    this.callbacks[type].forEach((callback) =>
+      Reflect.apply(callback, this, args)
+    );
+  }
+  removeListener(type, handler) {
+    const index = this.callbacks[type].findIndex(
+      (callback) => callback === handler
+    );
+    this.callbacks[type].splice(index, 1);
+  }
+  off(type, handler) {
+    this.removeListener(type, handler);
+  }
+  once(type, handler) {
+    this.addListener(type, this._onceWrap(handler, type));
+  }
+  _onceWrap(handler, type) {
+    let isFired = false;
+    const wrapFUnc = function (...args) {
+      if (!isFired) {
+        Reflect.apply(handler, this, args);
+        isFired = true;
+        this.removeListener(type, wrapFUnc);
+      }
+    };
+    return wrapFUnc;
+  }
+}
+const ee = new EventEmitter();
+
+// 注册所有事件
+ee.once("wakeUp", (name) => {
+  console.log(`${name} 1`);
+});
+ee.on("eat", (name) => {
+  console.log(`${name} 2`);
+});
+ee.on("eat", (name) => {
+  console.log(`${name} 3`);
+});
+const meetingFn = (name) => {
+  console.log(`${name} 4`);
+};
+ee.on("work", meetingFn);
+ee.on("work", (name) => {
+  console.log(`${name} 5`);
+});
+ee.emit("wakeUp", "xx");
+ee.emit("wakeUp", "xx"); // 第二次没有触发
+ee.emit("eat", "xx");
+ee.emit("work", "xx");
+ee.off("work", meetingFn); // 移除事件
+ee.emit("work", "xx"); // 再次工作
+

Node.js 模块加载流程

+

从上图可以看见,文件模块存在缓存区,寻找模块路径的时候都会优先从缓存中加载已经存在的模块

+
+

在模块中使用 require 传入文件路径即可引入文件
require 使用的一些注意事项:

+
    +
  • 缓存的模块优先级最高
  • +
  • 如果是内置模块,则直接返回,优先级仅次缓存的模块
  • +
  • 如果是绝对路径 / 开头,则从根目录找
  • +
  • 如果是相对路径 ./开头,则从当前 require 文件相对位置找
  • +
  • 如果文件没有携带后缀,先从 js、json、node 按顺序查找
  • +
  • 如果是目录,则根据 package.jsonmain 属性值决定目录下入口文件,默认情况为 index.js
  • +
  • 如果文件为第三方模块,则会引入 node_modules 文件,如果不在当前仓库文件中,则自动从上级递归查找,直到根目录
  • +
+

+

koa 洋葱模型

koa 是一个基于 node.js 的轻量级的 web 框架,它使用了一种独特的中间件执行流程,被称为洋葱模型。洋葱模型指的是以 next()函数为分割点,先由外到内执行请求的逻辑,再由内到外执行响应的逻辑。通过洋葱模型,可以实现中间件之间的通信和协作,以及优雅的错误处理。koa 的洋葱模型的实现主要依赖于 async/awaitPromise 的特性,以及一个名为koa-compose 的库,它可以将多个中间件组合成一个函数,然后按照顺序执行。

+

洋葱模型的核心思想是,每个中间件都会接收两个参数:ctxnextctx 是一个封装了请求和响应的对象,可以通过它来获取或修改请求和响应的信息。next 是一个函数,它表示下一个要执行的中间件。每个中间件都可以选择调用或不调用 next,从而控制中间件栈的执行流程。如果调用了 next,那么当前中间件会暂停执行,等待下一个中间件执行完毕后,再继续执行当前中间件的剩余部分。如果没有调用 next,那么当前中间件就是最后一个执行的中间件,之后的中间件都不会执行。

+

这样的设计使得中间件可以实现类似于洋葱的层层包裹的效果,每个中间件都可以在请求进入时和响应返回时执行一些操作,而且可以访问或修改前面或后面中间件所添加或修改的信息。这样可以实现很多功能,比如日志记录、错误处理、身份验证、路由匹配、数据缓存等等。

+

下面是一个简单的例子,演示了洋葱模型的执行过程:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 引入koa模块
+const Koa = require("koa");
+// 创建一个koa实例
+const app = new Koa();
+
+// 定义一个中间件,打印请求的方法和路径
+app.use(async (ctx, next) => {
+  console.log(`请求方法:${ctx.method},请求路径:${ctx.path}`);
+  // 调用next,进入下一个中间件
+  await next();
+  // 当下一个中间件执行完毕后,继续执行当前中间件的后续逻辑
+  console.log("第一个中间件结束");
+});
+
+// 定义一个中间件,模拟一个异步操作,比如数据库查询
+app.use(async (ctx, next) => {
+  console.log("开始异步操作");
+  // 使用setTimeout模拟一个耗时的异步操作
+  await new Promise((resolve) => setTimeout(resolve, 1000));
+  // 异步操作完成后,调用next,进入下一个中间件
+  await next();
+  // 当下一个中间件执行完毕后,继续执行当前中间件的后续逻辑
+  console.log("异步操作结束");
+});
+
+// 定义一个中间件,设置响应的内容和状态码
+app.use(async (ctx) => {
+  console.log("设置响应");
+  // 设置响应内容
+  ctx.body = "Hello Koa";
+  // 设置响应状态码
+  ctx.status = 200;
+  // 没有调用next,表示响应已经结束,不需要执行后面的中间件
+});
+
+// 监听3000端口
+app.listen(3000, () => {
+  console.log("服务器启动成功,监听在3000端口");
+});
+

当我们访问http://localhost:3000时,可以在控制台看到如下的输出:

+
1
2
3
4
5
请求方法:GET,请求路径:/
+开始异步操作
+设置响应
+异步操作结束
+第一个中间件结束
+

可以看到,中间件的执行顺序是:

+
    +
  • 第一个中间件的前半部分
  • +
  • 第二个中间件的前半部分
  • +
  • 第三个中间件的全部
  • +
  • 第二个中间件的后半部分
  • +
  • 第一个中间件的后半部分
  • +
+

JWT

JWT(JSON Web Token),本质就是一个字符串书写规范,如下图,作用是用来在用户和服务器之间传递安全可靠的信息

+
+
    +
  1. Token,分成了三个部分HeaderPayloadSignature

    +
      +
    • Header:个JWT都会带有头部信息,这里主要声明使用的算法。声明算法的字段名为alg,同时还有一个typ的字段,默认JWT即可。以下示例中算法为HS256

      +
      1
      2
      { "alg": "HS256", "typ": "JWT" }
      +// base64URL 编码 后 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
      +
    • +
    • Payload:载荷即消息体,这里会存放实际的内容,也就是Token的数据声明,例如用户的idname,默认情况下也会携带令牌的签发时间iat,通过还可以设置过期时间,如下:

      +
        +
      • ss (issuer):签发人
      • +
      • exp (expiration time):过期时间
      • +
      • sub (subject):主题
      • +
      • aud (audience):受众
      • +
      • nbf (Not Before):生效时间
      • +
      • iat (Issued At):签发时间
      • +
      • jti (JWT ID):编号
      • +
      +
      1
      2
      3
      4
      5
      6
      {
      +  "sub": "1234567890",
      +  "name": "John Doe",
      +  "iat": 1516239022
      +}
      +// base64URL 编码后 eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
      +
    • +
    • Signature:签名是对头部和载荷内容进行签名,一般情况,设置一个secretKey,对前两个的结果进行HMACSHA25算法,这里计算完毕后不需要用base64URL再进行编码,公式如下:

      +
      1
      2
      3
      4
      Signature = HMACSHA256(
      +  base64Url(header) + "." + base64Url(payload),
      +  secretKey
      +);
      +

      +
    • +
    +
  2. +
  3. 生成Token

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    const crypto = require("crypto"),
    +  jwt = require("jsonwebtoken");
    +// TODO:使用数据库
    +// 这里应该是用数据库存储,这里只是演示用
    +let userList = [];
    +
    +class UserController {
    +  // 用户登录
    +  static async login(ctx) {
    +    const data = ctx.request.body;
    +    if (!data.name || !data.password) {
    +      return (ctx.body = {
    +        code: "000002",
    +        message: "参数不合法",
    +      });
    +    }
    +    const result = userList.find(
    +      (item) =>
    +        item.name === data.name &&
    +        item.password ===
    +          crypto.createHash("md5").update(data.password).digest("hex")
    +    );
    +    if (result) {
    +      // 生成token
    +      const token = jwt.sign(
    +        {
    +          name: result.name,
    +        },
    +        "test_token", // secret
    +        { expiresIn: 60 * 60 } // 过期时间:60 * 60 s
    +      );
    +      return (ctx.body = {
    +        code: "0",
    +        message: "登录成功",
    +        data: {
    +          token,
    +        },
    +      });
    +    } else {
    +      return (ctx.body = {
    +        code: "000002",
    +        message: "用户名或密码错误",
    +      });
    +    }
    +  }
    +}
    +
    +module.exports = UserController;
    +
  4. +
  5. 校验Token

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    // 导入 jwt 模块
    +const jwt = require("jsonwebtoken");
    +// 导入 config 模块
    +const { SECRET } = require("../config/config");
    +module.exports = (req, res, next) => {
    +  // 校验 token
    +  let token = req.get("token");
    +  // 判断
    +  if (!token) {
    +    return res.json({
    +      code: "2003",
    +      msg: "token 缺失",
    +      data: null,
    +    });
    +  }
    +  // 校验 token
    +  jwt.verify(token, SECRET, (err, data) => {
    +    // 检测 token 是否正确
    +    if (err) {
    +      return res.json({
    +        code: "2004",
    +        msg: "token校验失败",
    +        data: null,
    +      });
    +    }
    +    req.user = data;
    +    next();
    +  });
    +};
    +
  6. +
+

版本控制系统

版本管理及其工具

    +
  1. 本地版本控制系统
      +
    • 优点
        +
      • 简单,很多系统都有内置。
      • +
      • 适合管理文本,如配置文件。
      • +
      +
    • +
    • 缺点
        +
      • 其不支持远程操作,因此不适合多人版本开发。
      • +
      +
    • +
    +
  2. +
  3. 集中式版本管理系统

    +
      +
    • 优点
        +
      • 适合多人团队协作开发。
      • +
      • 代码集中化管理
      • +
      +
    • +
    • 缺点
        +
      • 单点故障
      • +
      • 必须联网,无法单击工作
      • +
      +
    • +
    +
    +

    代表工具:SVNCVS

    +
    +
  4. +
  5. 分布式版本控制系统

    优点

    +
      +
    • 适合多人团队协作开发。
    • +
    • 代码集中化管理。
    • +
    • 可以离线工作。
    • +
    • 每个计算机都是一个完整的仓库。
    • +
    +
  6. +
+

对于git命令的详细说明,请参考 Git 笔记

+
+

操作系统

对操作系统的基本理解

    +
  • 操作系统是管理计算机硬件与软件资源的程序,是计算机的基石。
  • +
  • 操作系统本质上是一个运行在计算机上的软件程序 ,用于管理计算机硬件和软件资源。
  • +
  • 操作系统存在屏蔽了硬件层的复杂性。 操作系统就像是硬件使用的负责人,统筹着各种相关事项。
  • +
  • 操作系统的内核(Kernel)是操作系统的核心部分,它负责系统的内存管理,硬件设备的管理,文件系统的管理以及应用程序的管理。 内核是连接应用程序和硬件的桥梁,决定着系统的性能和稳定性。
  • +
+

进程与线程

    +
  1. 进程:进程是系统进行资源分配和调度的基本单位
    进程一般由三部分组成:程序,数据集和进程控制块。
      +
    • 程序用于描述进程要完成的功能,是控制进程执行的指令集。
    • +
    • 数据集合是程序在执行时所需要的数据和工作区。
    • +
    • 程序控制块,包含进程的描述信息和控制信息,是进程存在的唯一标志。
    • +
    +
  2. +
  3. 线程:是操作系统能够进行运算调度的最小单位
    线程是当前进程中的一个执行任务(执行单元),负责当前进程中程序的执行。
  4. +
+

软链接与硬链接

软链接和硬链接是Linux文件系统中两种不同的链接方式,它们的区别和用途如下:

+
    +
  • 软链接(也叫符号链接)是一种特殊的文件,它包含了另一个文件的路径名。软链接可以看作是一个快捷方式,它可以指向任意的文件或目录,无论它们是否存在或在哪个文件系统中。软链接的文件属性中有一个l标志,表示它是一个链接文件。软链接的创建和删除不会影响原文件或目录的内容和状态。
  • +
  • 硬链接是一种为文件创建多个名字的方式,它们共享同一个索引节点(inode),也就是说,它们指向同一个文件的数据块。硬链接的文件属性中没有l标志,它们和原文件没有区别。硬链接的创建和删除不会影响文件的数据和引用计数,只有当所有的硬链接都被删除后,文件才会被真正删除。
  • +
+

Windows系统中,也有类似的概念,但是有一些不同之处:

+
    +
  • Windows系统中的快捷方式相当于 Linux 中的软链接,它们都是一个指向目标文件或目录的文件,可以放在任何位置,可以有不同的名称和图标,可以添加参数和备注,可以通过属性查看和修改。但是,Windows系统中的快捷方式是以.lnk为扩展名的二进制文件,而 Linux 中的软链接是以目标文件的路径名为内容的文本文件。
  • +
  • Windows系统中的硬链接和 Linux 中的硬链接类似,它们都是指向同一个文件的多个名字,它们的文件属性和内容都相同,它们的创建和删除不会影响文件的数据和引用计数。但是,Windows系统中的硬链接只能在同一个卷中创建,不能跨分区或跨磁盘,而 Linux 中的硬链接只能在同一个文件系统中创建,不能跨文件系统。
  • +
  • Windows系统中还有一种叫做符号链接(Symbolic Link)的链接方式,它和 Linux 中的软链接类似,但是有一些区别。Windows系统中的符号链接可以是文件或目录,可以跨分区或跨磁盘,可以指向本地或网络的目标,可以通过属性查看和修改。但是,Windows系统中的符号链接需要管理员权限才能创建,需要特殊的命令或工具才能创建,需要特殊的标志才能识别,需要特殊的处理才能删除。
  • +
+

设计模式

基本概念

在软件工程中,设计模式是对软件设计中普遍存在的各种问题所提出的解决方案。设计模式并不直接用来完成代码的编写,而是描述在各种不同情况下,要怎么解决问题的一种方案。设计模式能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免会引起麻烦的紧耦合,以增强软件设计面对并适应变化的能力。

+

常用的设计模式

+
    +
  • 单例模式
  • +
  • 工厂模式
  • +
  • 策略模式
  • +
  • 代理模式
  • +
  • 中介者模式
  • +
  • 装饰者模式
  • +
+

单例模式(Singleton)

单例模式(Singleton Pattern):创建型模式,提供了一种创建对象的最佳方式,这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。核心为只创建一次,后续只使用这个创建的对象

+
    +
  • 采用类的静态方法与静态变量进行创建。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // 定义一个类
    +function Singleton(name) {
    +  this.name = name;
    +  this.instance = null;
    +}
    +// 原型扩展类的一个方法getName()
    +Singleton.prototype.getName = function () {
    +  console.log(this.name);
    +};
    +// 获取类的实例
    +Singleton.getInstance = function (name) {
    +  if (!this.instance) {
    +    this.instance = new Singleton(name);
    +  }
    +  return this.instance;
    +};
    +
    +// 获取对象1
    +const a = Singleton.getInstance("a");
    +// 获取对象2
    +const b = Singleton.getInstance("b");
    +// 进行比较
    +console.dir(Singleton);
    +console.log(a, b);
    +console.log(a === b);
    +
  • +
  • 采用闭包

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    function Singleton(name) {
    +  this.name = name;
    +}
    +// 原型扩展类的一个方法getName()
    +Singleton.prototype.getName = function () {
    +  console.log(this.name);
    +};
    +// 获取类的实例
    +Singleton.getInstance = (function () {
    +  var instance = null;
    +  return function (name) {
    +    if (!instance) {
    +      instance = new Singleton(name);
    +    }
    +    return instance;
    +  };
    +})();
    +
    +// 获取对象1
    +const a = Singleton.getInstance("a");
    +// 获取对象2
    +const b = Singleton.getInstance("b");
    +// 进行比较
    +console.dir(Singleton);
    +console.log(a, b);
    +console.log(a === b);
    +
  • +
  • 使用函数表达式写成一个构造函数

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    // 单例构造函数
    +function CreateSingleton(name) {
    +  this.name = name;
    +  this.getName();
    +}
    +
    +// 获取实例的名字
    +CreateSingleton.prototype.getName = function () {
    +  console.log(this.name);
    +};
    +// 单例对象
    +const Singleton = (function () {
    +  var instance;
    +  return function (name) {
    +    if (!instance) {
    +      instance = new CreateSingleton(name);
    +    }
    +    return instance;
    +  };
    +})();
    +
    +// 创建实例对象 1
    +const a = new Singleton("a");
    +// 创建实例对象 2
    +const b = new Singleton("b");
    +
    +console.log(a === b); // true
    +
  • +
+

工厂模式(Factory)

工厂模式是用来创建对象的一种最常用的设计模式,不暴露创建对象的具体逻辑,而是将将逻辑封装在一个函数中,那么这个函数就可以被视为一个工厂。
适用场景:

+
    +
  • 编程中,在一个 A 类中通过 new 的方式实例化了类 B,那么 A 类和 B 类之间就存在关联(耦合)
  • +
  • 后期因为需要修改了 B 类的代码和使用方式,比如构造函数中传入参数,那么 A 类也要跟着修改,一个类的依赖可能影响不大,但若有多个类依赖了 B 类,那么这个工作量将会相当的大,容易出现修改错误,也会产生很多的重复代码,这无疑是件非常痛苦的事;
  • +
  • 这种情况下,就需要将创建实例的工作从调用方(A类)中分离,与调用方解耦,也就是使用工厂方法创建实例的工作封装起来(减少代码重复),由工厂管理对象的创建逻辑,调用方不需要知道具体的创建过程,只管使用,而降低调用者因为创建逻辑导致的错误
  • +
+
    +
  1. 简单工厂模式(Simple Factory)
    简单工厂模式也叫静态工厂模式,用一个工厂对象创建同一类对象类的实例

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    function Factory(career) {
    +  function User(career, work) {
    +    this.career = career;
    +    this.work = work;
    +  }
    +  let work;
    +  switch (career) {
    +    case "coder":
    +      work = ["写代码", "修Bug"];
    +      return new User(career, work);
    +      break;
    +    case "hr":
    +      work = ["招聘", "员工信息管理"];
    +      return new User(career, work);
    +      break;
    +    case "driver":
    +      work = ["开车"];
    +      return new User(career, work);
    +      break;
    +    case "boss":
    +      work = ["喝茶", "开会", "审批文件"];
    +      return new User(career, work);
    +      break;
    +  }
    +}
    +let coder = new Factory("coder");
    +console.log(coder);
    +let boss = new Factory("boss");
    +console.log(boss);
    +
  2. +
  3. 工厂方法模式
    工厂方法模式是一种创建型设计模式,它定义了一个接口或抽象类,用于创建对象,但让子类决定要实例化哪个类。工厂方法让类将实例化的过程延迟到子类。
    工厂方法模式跟简单工厂模式差不多,但是把具体的产品放到了工厂函数的 prototype 中。这样一来,扩展产品种类就不必修改工厂函数了,核心就变成抽象类,也可以随时重写某种具体的产品,也就是相当于工厂总部不生产产品了,交给下辖分工厂进行生产;但是进入工厂之前,需要有个判断来验证你要生产的东西是否是属于我们工厂所生产范围,如果是,就丢给下辖工厂来进行生产。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    // 工厂方法
    +function Factory(career) {
    +  if (this instanceof Factory) {
    +    var a = new this[career]();
    +    return a;
    +  } else {
    +    return new Factory(career);
    +  }
    +}
    +// 工厂方法函数的原型中设置所有对象的构造函数
    +Factory.prototype = {
    +  coder: function () {
    +    this.careerName = "程序员";
    +    this.work = ["写代码", "修Bug"];
    +  },
    +  hr: function () {
    +    this.careerName = "HR";
    +    this.work = ["招聘", "员工信息管理"];
    +  },
    +  driver: function () {
    +    this.careerName = "司机";
    +    this.work = ["开车"];
    +  },
    +  boss: function () {
    +    this.careerName = "老板";
    +    this.work = ["喝茶", "开会", "审批文件"];
    +  },
    +};
    +let coder = new Factory("coder");
    +console.log(coder);
    +let hr = new Factory("hr");
    +console.log(hr);
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    // 抽象的Animal类
    +class Animal {
    +  constructor(name, sound, food) {
    +    this.name = name;
    +    this.sound = sound;
    +    this.food = food;
    +  }
    +
    +  // 抽象的create方法,由子类实现
    +  create() {
    +    throw new Error("Abstract method!");
    +  }
    +
    +  // 打印动物的信息
    +  printInfo() {
    +    console.log(
    +      `This is a ${this.name}, it says ${this.sound}, and it eats ${this.food}.`
    +    );
    +  }
    +}
    +
    +// Lion类,继承自Animal
    +class Lion extends Animal {
    +  constructor() {
    +    super("lion", "roar", "meat");
    +  }
    +
    +  // 实现create方法,返回自己的实例
    +  create() {
    +    return new Lion();
    +  }
    +}
    +
    +// Tiger类,继承自Animal
    +class Tiger extends Animal {
    +  constructor() {
    +    super("tiger", "growl", "meat");
    +  }
    +
    +  // 实现create方法,返回自己的实例
    +  create() {
    +    return new Tiger();
    +  }
    +}
    +
    +// Panda类,继承自Animal
    +class Panda extends Animal {
    +  constructor() {
    +    super("panda", "bleat", "bamboo");
    +  }
    +
    +  // 实现create方法,返回自己的实例
    +  create() {
    +    return new Panda();
    +  }
    +}
    +
    +// Zoo类,用于管理动物
    +class Zoo {
    +  constructor() {
    +    this.animals = []; // 存储动物的数组
    +  }
    +
    +  // 添加动物的方法,接收一个Animal类型的参数,并调用其create方法,将创建的动物实例添加到数组中
    +  addAnimal(animal) {
    +    if (animal instanceof Animal) {
    +      this.animals.push(animal.create());
    +    } else {
    +      throw new Error("Invalid animal!");
    +    }
    +  }
    +}
    +
    +// 创建一个Zoo的实例
    +let zoo = new Zoo();
    +
    +// 向Zoo中添加不同种类的动物
    +zoo.addAnimal(new Lion());
    +zoo.addAnimal(new Tiger());
    +zoo.addAnimal(new Panda());
    +
    +// 遍历Zoo的数组,打印每个动物的信息
    +for (let animal of zoo.animals) {
    +  animal.printInfo();
    +}
    +
  4. +
  5. 抽象工厂模式
    抽象工厂模式是一种创建型设计模式,它可以让我们创建一系列相关或相互依赖的对象,而无需指定它们具体的类。抽象工厂模式可以提高代码的可扩展性和可维护性,因为它可以将对象的创建和使用分离,降低了类之间的耦合度。

    +

    抽象工厂模式的主要角色有以下四个:

    +
      +
    • 抽象工厂(Abstract Factory):它是一个接口或抽象类,用于声明一组创建不同类型对象的方法。
    • +
    • 具体工厂(Concrete Factory):它是抽象工厂的子类,用于实现抽象工厂中声明的方法,创建具体的对象。
    • +
    • 抽象产品(Abstract Product):它是一个接口或抽象类,用于定义一类产品的公共属性和方法。
    • +
    • 具体产品(Concrete Product):它是抽象产品的子类,用于实现抽象产品中定义的属性和方法,表示具体的产品实例。
      JavaScript中,我们可以使用函数来实现抽象工厂模式。具体实现步骤如下:
    • +
    • 创建一个抽象工厂函数,用于创建一系列相关的对象。
    • +
    • 在抽象工厂函数中,创建一个对象字典,用于存储不同类型的对象。
    • +
    • 在对象字典中,为每种类型的对象创建一个工厂函数,用于创建该类型的对象。
    • +
    • 在抽象工厂函数中,创建一个工厂选择器函数,用于根据传入的参数选择相应的工厂函数。
    • +
    • 在工厂函数中,创建具体的对象,并返回该对象。
    • +
    +

    以下是使用JS实现的抽象工厂模式的一个例子,它模拟了一个电脑商店,有不同品牌和类型的电脑,每种电脑都有自己的价格和性能。我们定义了一个抽象的Computer类,它有一个抽象的create方法,用于创建具体的电脑实例。我们还定义了几个继承自Computer的子类,如Dell, Lenovo, Asus等,它们都实现了自己的create方法,用于返回自己的实例。我们还定义了一个ComputerStore类,它有一个getComputer方法,用于接收一个Computer类型的参数,并调用其create方法,将创建的电脑实例返回给客户。最后,我们创建了一个ComputerStore的实例,并向其请求了不同品牌和类型的电脑,然后打印出每个电脑的信息。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    // 抽象的Computer类
    +class Computer {
    +  constructor(brand, type, price, performance) {
    +    this.brand = brand;
    +    this.type = type;
    +    this.price = price;
    +    this.performance = performance;
    +  }
    +
    +  // 抽象的create方法,由子类实现
    +  create() {
    +    throw new Error("Abstract method!");
    +  }
    +
    +  // 打印电脑的信息
    +  printInfo() {
    +    console.log(
    +      `This is a ${this.brand} ${this.type}, it costs ${this.price}, and it has ${this.performance} performance.`
    +    );
    +  }
    +}
    +
    +// Dell类,继承自Computer
    +class Dell extends Computer {
    +  constructor(type) {
    +    super("Dell", type);
    +  }
    +
    +  // 实现create方法,根据type返回不同的实例
    +  create() {
    +    switch (this.type) {
    +      case "laptop":
    +        return new Dell("laptop", 5000, "high");
    +      case "desktop":
    +        return new Dell("desktop", 3000, "medium");
    +      default:
    +        throw new Error("Invalid type!");
    +    }
    +  }
    +}
    +
    +// Lenovo类,继承自Computer
    +class Lenovo extends Computer {
    +  constructor(type) {
    +    super("Lenovo", type);
    +  }
    +
    +  // 实现create方法,根据type返回不同的实例
    +  create() {
    +    switch (this.type) {
    +      case "laptop":
    +        return new Lenovo("laptop", 4000, "medium");
    +      case "desktop":
    +        return new Lenovo("desktop", 2000, "low");
    +      default:
    +        throw new Error("Invalid type!");
    +    }
    +  }
    +}
    +
    +// Asus类,继承自Computer
    +class Asus extends Computer {
    +  constructor(type) {
    +    super("Asus", type);
    +  }
    +
    +  // 实现create方法,根据type返回不同的实例
    +  create() {
    +    switch (this.type) {
    +      case "laptop":
    +        return new Asus("laptop", 6000, "high");
    +      case "desktop":
    +        return new Asus("desktop", 4000, "high");
    +      default:
    +        throw new Error("Invalid type!");
    +    }
    +  }
    +}
    +
    +// ComputerStore类,用于管理电脑
    +class ComputerStore {
    +  constructor() {
    +    this.computers = {}; // 存储电脑的对象字典
    +  }
    +
    +  // 添加电脑的方法,接收一个Computer类型的参数,并将其添加到对象字典中
    +  addComputer(computer) {
    +    if (computer instanceof Computer) {
    +      this.computers[computer.brand] = computer;
    +    } else {
    +      throw new Error("Invalid computer!");
    +    }
    +  }
    +
    +  // 获取电脑的方法,接收一个brand和type的参数,并根据对象字典中的工厂函数,创建并返回相应的电脑实例
    +  getComputer(brand, type) {
    +    if (this.computers[brand]) {
    +      return this.computers[brand].create(type);
    +    } else {
    +      throw new Error("No such brand!");
    +    }
    +  }
    +}
    +
    +// 创建一个ComputerStore的实例
    +let store = new ComputerStore();
    +
    +// 向ComputerStore中添加不同品牌的电脑
    +store.addComputer(new Dell());
    +store.addComputer(new Lenovo());
    +store.addComputer(new Asus());
    +
    +// 从ComputerStore中获取不同品牌和类型的电脑
    +let dellLaptop = store.getComputer("Dell", "laptop");
    +let lenovoDesktop = store.getComputer("Lenovo", "desktop");
    +let asusLaptop = store.getComputer("Asus", "laptop");
    +
    +// 打印电脑的信息
    +dellLaptop.printInfo();
    +lenovoDesktop.printInfo();
    +asusLaptop.printInfo();
    +

    输出结果为:

    +
    1
    2
    3
    This is a Dell laptop, it costs 5000, and it has high performance.
    +This is a Lenovo desktop, it costs 2000, and it has low performance.
    +This is a Asus laptop, it costs 6000, and it has high performance.
    +

    这个例子展示了抽象工厂模式的优点,它可以让我们在不修改抽象工厂的情况下,增加新的具体工厂和具体产品,实现多态性和灵活性。同时,它也可以让我们将对象的创建和使用分离,降低了代码的耦合度。

    +
  6. +
+

策略模式(Strategy)

策略模式(Strategy Pattern)指的是定义一系列的算法,把它们一个个封装起来,目的就是将算法的使用与算法的实现分离开来

+

一个基于策略模式的程序至少由两部分组成:

+
    +
  • 策略类,策略类封装了具体的算法,并负责具体的计算过程
  • +
  • 环境类ContextContext 接受客户的请求,随后 把请求委托给某一个策略类
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// 抽象的Operator类
+class Operator {
+  constructor(symbol) {
+    this.symbol = symbol;
+  }
+
+  // 抽象的calculate方法,由子类实现
+  calculate(num1, num2) {
+    throw new Error("Abstract method!");
+  }
+}
+
+// Add类,继承自Operator
+class Add extends Operator {
+  constructor() {
+    super("+");
+  }
+
+  // 实现calculate方法,返回两个数的和
+  calculate(num1, num2) {
+    return num1 + num2;
+  }
+}
+
+// Subtract类,继承自Operator
+class Subtract extends Operator {
+  constructor() {
+    super("-");
+  }
+
+  // 实现calculate方法,返回两个数的差
+  calculate(num1, num2) {
+    return num1 - num2;
+  }
+}
+
+// Multiply类,继承自Operator
+class Multiply extends Operator {
+  constructor() {
+    super("*");
+  }
+
+  // 实现calculate方法,返回两个数的积
+  calculate(num1, num2) {
+    return num1 * num2;
+  }
+}
+
+// Divide类,继承自Operator
+class Divide extends Operator {
+  constructor() {
+    super("/");
+  }
+
+  // 实现calculate方法,返回两个数的商
+  calculate(num1, num2) {
+    if (num2 === 0) {
+      throw new Error("Divide by zero!");
+    }
+    return num1 / num2;
+  }
+}
+
+// Calculator类,用于管理运算符
+class Calculator {
+  constructor() {
+    this.operator = null; // 存储当前的运算符
+  }
+
+  // 设置运算符的方法,接收一个Operator类型的参数,并将其保存为当前的运算符
+  setOperator(operator) {
+    if (operator instanceof Operator) {
+      this.operator = operator;
+    } else {
+      throw new Error("Invalid operator!");
+    }
+  }
+
+  // 获取计算结果的方法,接收两个数作为参数,并调用当前运算符的calculate方法,将计算结果返回给用户
+  getResult(num1, num2) {
+    if (this.operator) {
+      return this.operator.calculate(num1, num2);
+    } else {
+      throw new Error("No operator!");
+    }
+  }
+}
+
+// 创建一个Calculator的实例
+let calculator = new Calculator();
+
+// 向Calculator中设置不同的运算符
+calculator.setOperator(new Add());
+calculator.setOperator(new Subtract());
+calculator.setOperator(new Multiply());
+calculator.setOperator(new Divide());
+
+// 调用Calculator的getResult方法,得到不同的计算结果
+console.log(calculator.getResult(10, 5)); // 2
+console.log(calculator.getResult(20, 10)); // 2
+console.log(calculator.getResult(3, 4)); // 0.75
+console.log(calculator.getResult(6, 2)); // 3
+

代理模式(Proxy)

代理模式是一种结构型设计模式,它可以让我们为一个对象创建一个替代对象,用来控制对原对象的访问。代理对象和原对象有相同的接口,这样就可以在不影响原对象功能的情况下,增加一些额外的操作,如验证、缓存、延迟等。代理模式的优点是可以提高代码的可扩展性和可维护性,降低原对象的复杂度和耦合度,遵循单一职责原则。

+

JavaScript中,我们可以使用ES6Proxy类来实现代理模式。Proxy类可以接收两个参数,一个是目标对象,一个是处理器对象。处理器对象可以定义一些拦截器函数,用来拦截目标对象的属性和方法的访问,从而实现代理的功能。

+

以下是一个使用JavaScript实现的代理模式的一个例子,它模拟了一个用户登录的场景,有一个User类,用来表示用户的信息,如用户名和密码。我们定义了一个ProxyUser类,用来作为User类的代理,它接收一个User对象作为参数,并创建一个Proxy对象,用来拦截User对象的login方法,增加一些验证和日志的操作。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// User类,用来表示用户的信息
+class User {
+  constructor(username, password) {
+    this.username = username;
+    this.password = password;
+  }
+
+  // login方法,用来模拟用户登录的逻辑
+  login() {
+    console.log(`${this.username} is logging in...`);
+    // some login logic
+    console.log(`${this.username} logged in successfully!`);
+  }
+}
+
+// ProxyUser类,用来作为User类的代理
+class ProxyUser {
+  constructor(user) {
+    // 创建一个Proxy对象,用来拦截user对象的login方法
+    this.proxy = new Proxy(user, {
+      // 定义一个拦截器函数,用来在调用login方法之前和之后执行一些操作
+      apply: function (target, thisArg, argumentsList) {
+        // 在调用login方法之前,验证用户名和密码是否合法
+        if (target.username && target.password) {
+          // 调用login方法
+          target.login.apply(thisArg, argumentsList);
+          // 在调用login方法之后,记录日志
+          console.log(`Log: ${target.username} logged in at ${new Date()}`);
+        } else {
+          // 如果用户名或密码不合法,抛出错误
+          throw new Error("Invalid username or password!");
+        }
+      },
+    });
+  }
+}
+
+// 创建一个User对象
+let user = new User("Alice", "123456");
+
+// 创建一个ProxyUser对象,传入User对象作为参数
+let proxyUser = new ProxyUser(user);
+
+// 通过ProxyUser对象的proxy属性,调用User对象的login方法
+proxyUser.proxy();
+

输出结果为:

+
1
2
3
Alice is logging in...
+Alice logged in successfully!
+Log: Alice logged in at Fri Nov 26 2021 16:11:23 GMT+0800 (中国标准时间)
+

观察者模式(Observer)

观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Subject {
+  constructor() {
+    this.observerList = [];
+  }
+
+  addObserver(observer) {
+    this.observerList.push(observer);
+  }
+
+  removeObserver(observer) {
+    const index = this.observerList.findIndex((o) => o.name === observer.name);
+    this.observerList.splice(index, 1);
+  }
+
+  notifyObservers(message) {
+    const observers = this.observerList;
+    observers.forEach((observer) => observer.notified(message));
+  }
+}
+
+class Observer {
+  constructor(name, subject) {
+    this.name = name;
+    if (subject) {
+      subject.addObserver(this);
+    }
+  }
+
+  notified(message) {
+    console.log(this.name, "got message", message);
+  }
+}
+
+const subject = new Subject();
+const observerA = new Observer("observerA", subject);
+const observerB = new Observer("observerB");
+subject.addObserver(observerB);
+subject.notifyObservers("Hello from subject");
+subject.removeObserver(observerA);
+subject.notifyObservers("Hello again");
+

发布订阅模式(Pub/Sub)

发布-订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在

+

同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者存在

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
class PubSub {
+  constructor() {
+    this.messages = {};
+    this.listeners = {};
+  }
+  // 添加发布者
+  publish(type, content) {
+    const existContent = this.messages[type];
+    if (!existContent) {
+      this.messages[type] = [];
+    }
+    this.messages[type].push(content);
+  }
+  // 添加订阅者
+  subscribe(type, cb) {
+    const existListener = this.listeners[type];
+    if (!existListener) {
+      this.listeners[type] = [];
+    }
+    this.listeners[type].push(cb);
+  }
+  // 通知
+  notify(type) {
+    const messages = this.messages[type];
+    const subscribers = this.listeners[type] || [];
+    subscribers.forEach((cb, index) => cb(messages[index]));
+  }
+}
+class Publisher {
+  constructor(name, context) {
+    this.name = name;
+    this.context = context;
+  }
+  publish(type, content) {
+    this.context.publish(type, content);
+  }
+}
+class Subscriber {
+  constructor(name, context) {
+    this.name = name;
+    this.context = context;
+  }
+  subscribe(type, cb) {
+    this.context.subscribe(type, cb);
+  }
+}
+
+const TYPE_A = "music";
+const TYPE_B = "movie";
+const TYPE_C = "novel";
+
+const pubsub = new PubSub();
+
+const publisherA = new Publisher("publisherA", pubsub);
+publisherA.publish(TYPE_A, "we are young");
+publisherA.publish(TYPE_B, "the silicon valley");
+const publisherB = new Publisher("publisherB", pubsub);
+publisherB.publish(TYPE_A, "stronger");
+const publisherC = new Publisher("publisherC", pubsub);
+publisherC.publish(TYPE_C, "a brief history of time");
+
+const subscriberA = new Subscriber("subscriberA", pubsub);
+subscriberA.subscribe(TYPE_A, (res) => {
+  console.log("subscriberA received", res);
+});
+const subscriberB = new Subscriber("subscriberB", pubsub);
+subscriberB.subscribe(TYPE_C, (res) => {
+  console.log("subscriberB received", res);
+});
+const subscriberC = new Subscriber("subscriberC", pubsub);
+subscriberC.subscribe(TYPE_B, (res) => {
+  console.log("subscriberC received", res);
+});
+
+pubsub.notify(TYPE_A);
+pubsub.notify(TYPE_B);
+pubsub.notify(TYPE_C);
+
文章作者: 希亚的西红柿
文章链接: http://blog.refrain.site/posts/fccf75e5/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/posts/ffb66302/index.html b/posts/ffb66302/index.html new file mode 100644 index 000000000..8c78f274e --- /dev/null +++ b/posts/ffb66302/index.html @@ -0,0 +1,1801 @@ +LeetCode 算法笔记 Part3 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + + +

LeetCode 算法笔记 Part3

本文主要记录了博主的 Leetcode 算法刷题记录,方便以后查询复习。

+
+

图论

岛屿数量

LeetCode 原题链接:200. 岛屿数量

+
+

深度优先遍历,基本思路为,遇到一个岛就把这个岛给沉没掉,然后统计数加 1。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
+ * @param {character[][]} grid
+ * @return {number}
+ */
+const numIslands = (grid) => {
+  const m = grid.length,
+    n = grid[0].length;
+  const dfs = (i, j) => {
+    if (i < 0 || i > m - 1 || j < 0 || j > n - 1 || grid[i][j] === "0") {
+      return;
+    }
+    grid[i][j] = "0";
+    dfs(i + 1, j);
+    dfs(i - 1, j);
+    dfs(i, j + 1);
+    dfs(i, j - 1);
+  };
+  let count = 0;
+  for (let i = 0; i < m; i++) {
+    for (let j = 0; j < n; j++) {
+      if (grid[i][j] === "1") {
+        count++;
+        dfs(i, j);
+      }
+    }
+  }
+  return count;
+};
+

腐烂的橘子

LeetCode 原题链接:994. 腐烂的橘子

+
+

广度优先遍历,利用循环比较好做,这边需要注意广度优先遍历的一些条件,辅助函数用于推入队列,上下左右扩散的过程还是由循环过程去做。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
+ * @param {number[][]} grid
+ * @return {number}
+ */
+const orangesRotting = (grid) => {
+  const m = grid.length,
+    n = grid[0].length;
+  const queue = [];
+
+  for (let i = 0; i < m; i++) {
+    for (let j = 0; j < n; j++) {
+      if (grid[i][j] === 2) {
+        queue.push([i, j]);
+      }
+    }
+  }
+
+  const step = (i, j, arr) => {
+    if (i < 0 || i > m - 1 || j < 0 || j > n - 1 || grid[i][j] !== 1) {
+      return;
+    }
+    grid[i][j] = 2;
+    arr.push([i, j]);
+  };
+
+  let count = 0;
+  while (queue.length) {
+    const tempArr = [];
+    while (queue.length) {
+      const [i, j] = queue.shift();
+      step(i + 1, j, tempArr);
+      step(i - 1, j, tempArr);
+      step(i, j + 1, tempArr);
+      step(i, j - 1, tempArr);
+    }
+    if (tempArr.length) {
+      count++;
+      queue.push(...tempArr);
+    }
+  }
+
+  for (let i = 0; i < m; i++) {
+    for (let j = 0; j < n; j++) {
+      if (grid[i][j] === 1) {
+        return -1;
+      }
+    }
+  }
+
+  return count;
+};
+
+console.log(orangesRotting([[0, 2]]));
+

课程表

LeetCode 原题链接:207. 课程表

+
+

广度优先遍历,采用图和入度的概念进行求解。

+
    +
  1. 构建入度数组,使用哈希表以该课程为键,依赖该课程的数组为值,建立哈希表。
  2. +
  3. 将入度为 0 的数组推入队列。
  4. +
  5. 迭代,入度为零的课程继续推入队列
  6. +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
+ * @param {number} numCourses
+ * @param {number[][]} prerequisites
+ * @return {boolean}
+ */
+const canFinish = (numCourses, prerequisites) => {
+  const inDegArr = new Array(numCourses).fill(0);
+  const map = new Map();
+  for (let i = 0; i < prerequisites.length; i++) {
+    inDegArr[prerequisites[i][0]]++;
+    if (map.has(prerequisites[i][1])) {
+      map.get(prerequisites[i][1]).push(prerequisites[i][0]);
+    } else {
+      map.set(prerequisites[i][1], [prerequisites[i][0]]);
+    }
+  }
+  const queue = [];
+  for (let i = 0; i < inDegArr.length; i++) {
+    if (inDegArr[i] === 0) {
+      queue.push(i);
+    }
+  }
+  let count = 0;
+  while (queue.length) {
+    const course = queue.shift();
+    count++;
+    const depArr = map.get(course);
+    if (depArr) {
+      for (const dep of depArr) {
+        inDegArr[dep]--;
+        if (inDegArr[dep] === 0) {
+          queue.push(dep);
+        }
+      }
+    }
+  }
+  return count === numCourses;
+};
+

实现 Trie (前缀树)

LeetCode 原题链接:208. 实现 Trie (前缀树)

+
+

采用迭代对象来求解,可以考虑使用数组的reduce方法。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Trie {
+  constructor() {
+    this.children = {};
+  }
+
+  insert(word) {
+    let node = this.children;
+    for (const ch of word) {
+      if (!node[ch]) {
+        node[ch] = {};
+      }
+      node = node[ch];
+    }
+    node.isEnd = true;
+  }
+  searchPrefix(prefix) {
+    let node = this.children;
+    for (const ch of prefix) {
+      if (!node[ch]) {
+        return false;
+      }
+      node = node[ch];
+    }
+    return node;
+  }
+  search(word) {
+    const node = this.searchPrefix(word);
+    return node !== undefined && node.isEnd !== undefined;
+  }
+  startsWith(prefix) {
+    return this.searchPrefix(prefix);
+  }
+}
+

回溯

全排列

LeetCode 原题链接:46. 全排列

+
+

无他,惟手熟尔。😊

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
+ * @param {number[]} nums
+ * @return {number[][]}
+ */
+const permute = (nums) => {
+  const step = (arr) => {
+    if (arr.length <= 1) return [arr];
+    if (arr.length === 2) {
+      return [
+        [arr[0], arr[1]],
+        [arr[1], arr[0]],
+      ];
+    }
+    if (arr.length >= 3) {
+      const res = [];
+      for (let i = 0; i < arr.length; i++) {
+        const tempArr = arr.filter((item) => item !== arr[i]);
+        const tmpRes = step(tempArr);
+        for (const temp of tmpRes) {
+          res.push([arr[i]].concat(temp));
+        }
+      }
+      return res;
+    }
+  };
+  return step(nums);
+};
+
+/**
+ * @param {number[]} nums
+ * @return {number[][]}
+ */
+const permute0 = (nums) => {
+  const res = [];
+  const step = (first) => {
+    if (first === nums.length) {
+      res.push([...nums]);
+    }
+    for (let i = first; i < nums.length; i++) {
+      [nums[first], nums[i]] = [nums[i], nums[first]];
+      step(first + 1);
+      [nums[first], nums[i]] = [nums[i], nums[first]];
+    }
+  };
+  step(0);
+  return res;
+};
+console.log(permute0([1, 2, 3]));
+

字集

LeetCode 原题链接:78. 字集

+
+

可采用幂集算法和深度遍历解决。
对于幂集算法

+
    +
  1. 左移n位,循环。
  2. +
  3. 循环数组的长度次,推入所有的有效数字。
  4. +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
+ * @param {number[]} nums
+ * @return {number[][]}
+ */
+const subsets = (nums) => {
+  const res = [];
+  for (let i = 0; i < 1 << nums.length; i++) {
+    const temp = [];
+    for (let j = 0; j < nums.length; j++) {
+      if (i & (1 << j)) {
+        temp.push(nums[j]);
+      }
+    }
+    res.push(temp);
+  }
+  return res;
+};
+
+const subsets = function (nums) {
+  const temp = [];
+  const res = [];
+  const dfs = (cur) => {
+    if (cur === nums.length) {
+      res.push([...temp]);
+      return;
+    }
+    temp.push(nums[cur]);
+    dfs(cur + 1);
+    temp.pop();
+    dfs(cur + 1);
+  };
+  dfs(0);
+  return res;
+};
+

电话号码的数字组合

LeetCode 原题链接:17. 电话号码的数字组合

+
+

采用回溯算法,从前面回溯到后面

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
+ * @param {string} digits
+ * @return {string[]}
+ */
+const letterCombinations = (digits) => {
+  if (digits.length === 0) {
+    return [];
+  }
+  const map = new Map([
+    ["2", "abc"],
+    ["3", "def"],
+    ["4", "ghi"],
+    ["5", "jkl"],
+    ["6", "mno"],
+    ["7", "pqrs"],
+    ["8", "tuv"],
+    ["9", "wxyz"],
+  ]);
+  const res = [];
+  const step = (str, index) => {
+    if (index === digits.length) {
+      res.push(str);
+      return;
+    }
+    const charSet = map.get(digits[index]);
+    for (const char of charSet) {
+      step(str + char, index + 1);
+    }
+  };
+  step("", 0);
+  return res;
+};
+

组合总和

LeetCode 原题链接:39. 组合总和

+
+

排序candidates数组,递归回溯,选择当前数字或者不选择当前数字然后跳过。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
+ * @param {number[]} candidates
+ * @param {number} target
+ * @return {number[][]}
+ */
+const combinationSum = (candidates, target) => {
+  const res = [];
+  candidates.sort((a, b) => a - b);
+  const step = (curVal, index, arr) => {
+    if (index === candidates.length) {
+      return;
+    }
+    if (curVal === 0) {
+      res.push(arr);
+      return;
+    }
+    if (candidates[index] < curVal) {
+      step(curVal, index + 1, arr);
+    }
+    if (curVal - candidates[index] >= 0) {
+      step(curVal - candidates[index], index, [...arr, candidates[index]]);
+    }
+  };
+  step(target, 0, []);
+  return res;
+};
+

括号总和

LeetCode 原题链接:22. 括号生成

+
+

采用深度遍历的方式进行求解,左侧括号在小于num的情况下可以一直推入,右侧括号只有在左侧括号数量大于右侧括号才能推入。非常经典的回溯加剪枝。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
+ * @param {number} n
+ * @return {string[]}
+ */
+const generateParenthesis = (n) => {
+  const res = [];
+  const dfs = (str, left, right) => {
+    if (left === n && right === n) {
+      res.push(str);
+    }
+    if (left > right) {
+      dfs(str + ")", left, right + 1);
+    }
+    if (left < n) {
+      dfs(str + "(", left + 1, right);
+    }
+  };
+  dfs("", 0, 0);
+  return res;
+};
+

单词搜索

LeetCode 原题链接:79. 单词搜索

+
+

谨慎使用fill方法,主要利用深度遍历与标记数组。通过每个数组都尝试遍历。注意空数组不会被Map等方法遍历,需要先fill值再遍历。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
+ * @param {character[][]} board
+ * @param {string} word
+ * @return {boolean}
+ */
+const exist = (board, word) => {
+  const rowLen = board.length;
+  const colLen = board[0].length;
+  const visited = new Array(rowLen)
+    .fill(0)
+    .map(() => new Array(colLen).fill(false));
+  const dfs = (i, j, k) => {
+    if (k === word.length) {
+      return true;
+    }
+    if (i < 0 || i > rowLen - 1 || j < 0 || j > colLen - 1 || visited[i][j]) {
+      return;
+    }
+    if (board[i][j] === word[k]) {
+      visited[i][j] = true;
+      const flag =
+        dfs(i - 1, j, k + 1) ||
+        dfs(i + 1, j, k + 1) ||
+        dfs(i, j - 1, k + 1) ||
+        dfs(i, j + 1, k + 1);
+      visited[i][j] = false;
+      return flag;
+    }
+  };
+  for (let i = 0; i < rowLen; i++) {
+    for (let j = 0; j < colLen; j++) {
+      if (dfs(i, j, 0)) {
+        return true;
+      }
+    }
+  }
+  return false;
+};
+

分割回文串

LeetCode 原题链接: 131. 分割回文串

+
+

深度遍历加上动态规划。我能说我其实是背下来的吗?🤧

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
+ * @param {string} s
+ * @return {string[][]}
+ */
+const partition = (s) => {
+  const n = s.length,
+    ans = [],
+    res = [];
+  const f = new Array(n).fill(0).map(() => new Array(n).fill(true));
+  for (let i = n - 1; i >= 0; i--) {
+    for (let j = i + 1; j < n; j++) {
+      f[i][j] = s[i] === s[j] && f[i + 1][j - 1];
+    }
+  }
+
+  const step = (i) => {
+    if (i === n) {
+      res.push([...ans]);
+      return;
+    }
+    for (let j = i; j < n; j++) {
+      if (f[i][j]) {
+        ans.push(s.slice(i, j + 1));
+        step(j + 1);
+        ans.pop();
+      }
+    }
+  };
+
+  step(0);
+  return res;
+};
+

N 皇后

LeetCode 原题链接:51. N 皇后

+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/**
+ * @param {number} n
+ * @return {string[][]}
+ */
+const solveNQueens = (n) => {
+  const ans = [];
+  const col = new Array(n).fill(0);
+  const onPath = new Array(n).fill(false);
+  const diag1 = new Array(n * 2 - 1).fill(false);
+  const diag2 = new Array(n * 2 - 1).fill(false);
+  const dfs = (r) => {
+    if (r === n) {
+      ans.push(col.map((c) => ".".repeat(c) + "Q" + ".".repeat(n - 1 - c)));
+      return;
+    }
+    for (let c = 0; c < n; c++) {
+      if (!onPath[c] && !diag1[r + c] && !diag2[r - c]) {
+        col[r] = c;
+        onPath[c] = diag1[r + c] = diag2[r - c] = true;
+        dfs(r + 1);
+        onPath[c] = diag1[r + c] = diag2[r - c] = false; // 恢复现场
+      }
+    }
+  };
+  dfs(0);
+  return ans;
+};
+
+/**
+ * @param {number} n
+ * @return {string[][]}
+ */
+const solveNQueens = (n) => {
+  const arr = new Array(n);
+  const res = [];
+  const judge = (row) => {
+    for (let i = 0; i < row; i++) {
+      if (arr[i] === arr[row] || row - i === Math.abs(arr[row] - arr[i]))
+        return false;
+    }
+    return true;
+  };
+  const step = (count) => {
+    if (count === n) {
+      const temp = [];
+      for (const item of arr) {
+        const strArr = new Array(n).fill(".");
+        strArr[item] = "Q";
+        temp.push(strArr.join(""));
+      }
+      res.push(temp);
+      return;
+    }
+    for (let i = 0; i < n; i++) {
+      arr[count] = i;
+      if (judge(count)) {
+        step(count + 1);
+      }
+    }
+  };
+  step(0);
+  return res;
+};
+
+console.log(solveNQueens(4));
+

二分查找

搜索插入位置

LeetCode 原题链接:35. 搜索插入位置

+
+

二分查找的变式,稍作修改,注意ans的初始值需要设置为num.length来处理越界情况。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
+ * @param {number[]} nums
+ * @param {number} target
+ * @return {number}
+ */
+const searchInsert = (nums, target) => {
+  let low = 0,
+    high = nums.length - 1,
+    ans = nums.length;
+  while (low <= high) {
+    const mid = Math.floor((low + high) / 2);
+    if (target <= nums[mid]) {
+      ans = mid;
+      high = mid - 1;
+    } else {
+      low = mid + 1;
+    }
+  }
+  return ans;
+};
+

搜索二维矩阵

LeetCode 原题链接:74. 搜索二维矩阵

+
+

依旧是二分查找的变式,查找到大于当前数字的一行,然后对当前这一行进行二分查找。另外这里例子是Z字型查找的一个特例,也可使用Z字型查找的方式,更简单。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
+ * @param {number[][]} matrix
+ * @param {number} target
+ * @return {boolean}
+ */
+const searchMatrix = (matrix, target) => {
+  const binarySearchRow = () => {
+    let low = 0,
+      high = matrix.length - 1,
+      ans = 0;
+    while (low <= high) {
+      const mid = Math.floor((low + high) / 2);
+      if (target < matrix[mid][0]) {
+        high = mid - 1;
+      } else if (target >= matrix[mid][0]) {
+        ans = mid;
+        low = mid + 1;
+      }
+    }
+    return ans;
+  };
+  const binarySearchCol = (index) => {
+    let low = 0,
+      high = matrix[0].length - 1;
+    while (low <= high) {
+      const mid = Math.floor((low + high) / 2);
+      if (target < matrix[index][mid]) {
+        high = mid - 1;
+      } else if (target > matrix[index][mid]) {
+        low = mid + 1;
+      } else {
+        return true;
+      }
+    }
+    return false;
+  };
+  const index = binarySearchRow();
+  return binarySearchCol(index);
+};
+

在排序数组中查找元素的第一个和最后一个位置

+

二分查找的变式,注意如何查找到第一次出现的元素与最后一次出现的元素,这边采取循环的方式更好写一些。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/**
+ * @param {number[]} nums
+ * @param {number} target
+ * @return {number[]}
+ */
+const searchRange = (nums, target) => {
+  const binarySearch = (low, high, flag) => {
+    if (low > high) {
+      return -1;
+    }
+    const mid = Math.floor((low + high) / 2);
+    if (target > nums[mid]) {
+      return binarySearch(mid + 1, high, flag);
+    } else if (target < nums[mid]) {
+      return binarySearch(low, mid - 1, flag);
+    } else {
+      if (flag) {
+        if (mid === 0 || nums[mid - 1] < nums[mid]) {
+          return mid;
+        } else {
+          return binarySearch(low, mid - 1, flag);
+        }
+      } else {
+        if (mid === nums.length - 1 || nums[mid] < nums[mid + 1]) {
+          return mid;
+        } else {
+          return binarySearch(mid + 1, high, flag);
+        }
+      }
+    }
+  };
+  const left = binarySearch(0, nums.length - 1, true);
+  const right = binarySearch(0, nums.length - 1, false);
+  return [left, right];
+};
+
+/**
+ * @param {number[]} nums
+ * @param {number} target
+ * @return {number[]}
+ */
+const searchRange = (nums, target) => {
+  const binarySearch = (flag) => {
+    let low = 0,
+      high = nums.length - 1;
+    while (low <= high) {
+      const mid = Math.floor((low + high) / 2);
+      if (target > nums[mid]) {
+        low = mid + 1;
+      } else if (target < nums[mid]) {
+        high = mid - 1;
+      } else {
+        if (flag) {
+          if (mid !== 0 && nums[mid - 1] === nums[mid]) {
+            high = mid - 1;
+          } else {
+            return mid;
+          }
+        } else {
+          if (mid !== nums.length - 1 && nums[mid] === nums[mid + 1]) {
+            low = mid + 1;
+          } else {
+            return mid;
+          }
+        }
+      }
+    }
+    return -1;
+  };
+  return [binarySearch(true), binarySearch(false)];
+};
+

搜索旋转排序数组

LeetCode 原题链接:33. 搜索旋转排序数组

+
+

来详细讲解一下这道题目的思路

+

考虑mid在前区间与后区间的两种情况

+
    +
  1. 在前区间:只有满足target >= nums[0] && target < nums[mid]的情况才有high = mid - 1其他情况都是low = mid + 1
  2. +
  3. 在后区间:只有满足target > nums[mid] && target <= nums[n - 1]才有low = mid + 1其他情况都是high = mid - 1
  4. +
  5. 注意前区间必须是满足>=以确定边缘情况。
  6. +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
+ * @param {number[]} nums
+ * @param {number} target
+ * @return {number}
+ */
+const search = (nums, target) => {
+  const n = nums.length;
+  if (n === 0) return -1;
+  if (n === 1) return nums[0] === target ? 0 : -1;
+  let low = 0,
+    high = n - 1;
+  while (low <= high) {
+    const mid = Math.floor((low + high) / 2);
+    if (target === nums[mid]) return mid;
+    if (nums[mid] >= nums[0]) {
+      if (target >= nums[0] && target < nums[mid]) {
+        high = mid - 1;
+      } else {
+        low = mid + 1;
+      }
+    } else {
+      if (target > nums[mid] && target <= nums[n - 1]) {
+        low = mid + 1;
+      } else {
+        high = mid - 1;
+      }
+    }
+  }
+  return -1;
+};
+

寻找旋转排序数组中的最小值

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
+ * @param {number[]} nums
+ * @return {number}
+ */
+const findMin = (nums) => {
+  const n = nums.length;
+  if (n <= 1) return nums[0];
+  if (nums[0] <= nums[n - 1]) return nums[0];
+  let low = 0,
+    high = n - 1;
+  while (low <= high) {
+    const mid = Math.floor((low + high) / 2);
+    if (nums[mid] < nums[mid - 1]) {
+      return nums[mid];
+    }
+    if (nums[mid] >= nums[0]) {
+      low = mid + 1;
+    }
+    if (nums[mid] < nums[n - 1]) {
+      high = mid - 1;
+    }
+  }
+};
+
+console.log(findMin([2, 1]));
+

寻找两个正序数组的中位数

LeetCode 原题链接:4. 寻找两个正序数组的中位数

+
+

技巧性较强的题目,菜就多练。🦀

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
+ * @param {number[]} nums1
+ * @param {number[]} nums2
+ * @return {number}
+ */
+const findMedianSortedArrays = (nums1, nums2) => {
+  const getKthEl = (nums1, nums2, k) => {
+    let index1 = 0,
+      index2 = 0;
+    while (true) {
+      if (index1 === nums1.length) {
+        return nums2[index2 + k - 1];
+      }
+      if (index2 === nums2.length) {
+        return nums1[index1 + k - 1];
+      }
+      if (k === 1) {
+        return Math.min(nums1[index1], nums2[index2]);
+      }
+      const half = Math.floor(k / 2);
+      const newIndex1 = Math.min(index1 + half, nums1.length) - 1;
+      const newIndex2 = Math.min(index2 + half, nums2.length) - 1;
+      if (nums1[newIndex1] <= nums2[newIndex2]) {
+        k -= newIndex1 - index1 + 1;
+        index1 = newIndex1 + 1;
+      } else {
+        k -= newIndex2 - index2 + 1;
+        index2 = newIndex2 + 1;
+      }
+    }
+  };
+  const totalLength = nums1.length + nums2.length;
+  if (totalLength % 2 === 1) {
+    const midIndex = Math.floor(totalLength / 2);
+    return getKthEl(nums1, nums2, midIndex + 1);
+  } else {
+    const midIndex = totalLength / 2;
+    return (
+      (getKthEl(nums1, nums2, midIndex) +
+        getKthEl(nums1, nums2, midIndex + 1)) /
+      2
+    );
+  }
+};
+

有效括号

LeetCode 原题链接:20. 有效括号

+
+

非常经典的题目,细心点就可以做出来。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
+ * @param {string} s
+ * @return {boolean}
+ */
+const isValid = (s) => {
+  const stack = [];
+  const map = new Map([
+    ["(", ")"],
+    ["{", "}"],
+    ["[", "]"],
+  ]);
+  for (const char of s) {
+    if (stack.length && map.get(stack[stack.length - 1]) === char) {
+      stack.pop();
+      continue;
+    }
+    stack.push(char);
+  }
+  return stack.length ? false : true;
+};
+

最小栈

LeetCode 原题链接:155. 最小栈

+
+

采用辅助栈进行求解,注意考虑栈为空的边缘情况。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class MinStack {
+  constructor() {
+    this.arr = [];
+    this.MinStack = [];
+  }
+  push(el) {
+    this.arr.push(el);
+    if (
+      !this.MinStack.length ||
+      el <= this.MinStack[this.MinStack.length - 1]
+    ) {
+      this.MinStack.push(el);
+    }
+  }
+
+  pop() {
+    const el = this.arr.pop();
+    if (
+      this.MinStack.length &&
+      el === this.MinStack[this.MinStack.length - 1]
+    ) {
+      this.MinStack.pop();
+    }
+    return el;
+  }
+
+  top() {
+    return this.arr[this.arr.length - 1];
+  }
+
+  getMin() {
+    return this.MinStack[this.MinStack.length - 1];
+  }
+}
+

字符串解码

LeetCode 原题链接:394. 字符串解码

+
+

可采用正则表达式的解法与数字栈与字符栈的解法来计算,类似逆波兰表达式的计算。注意对多位数字的置零处理,和字符串栈的转换。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const decodeString = (s) => {
+  while (s.includes("[")) {
+    // 正则匹配每一组编码,其中n为数字,s为字符
+    s = s.replace(/(\d+)\[([a-z]+)\]/g, (_, n, s) => s.repeat(n));
+  }
+  return s;
+};
+
+const decodeString = (s) => {
+  let numStack = []; // 存倍数的栈
+  let strStack = []; // 存 待拼接的str 的栈
+  let num = 0; // 倍数的“搬运工”
+  let result = ""; // 字符串的“搬运工”
+  for (const char of s) {
+    // 逐字符扫描
+    if (!isNaN(char)) {
+      // 遇到数字
+      num = num * 10 + Number(char); // 算出倍数
+    } else if (char == "[") {
+      // 遇到 [
+      strStack.push(result); // result串入栈
+      result = ""; // 入栈后清零
+      numStack.push(num); // 倍数num进入栈等待
+      num = 0; // 入栈后清零
+    } else if (char == "]") {
+      // 遇到 ],两个栈的栈顶出栈
+      let repeatTimes = numStack.pop(); // 获取拷贝次数
+      result = strStack.pop() + result.repeat(repeatTimes); // 构建子串
+    } else {
+      result += char; // 遇到字母,追加给result串
+    }
+  }
+  return result;
+};
+

每日温度

LeetCode 原题链接:739. 每日温度

+
+

注意栈中存储的是数组的下标,单调栈一般是遇到大的元素开始做相关的操作。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
+ * @param {number[]} temperatures
+ * @return {number[]}
+ */
+const dailyTemperatures = (temperatures) => {
+  const stack = [];
+  const arr = new Array(temperatures.length).fill(0);
+  for (let i = 0; i < temperatures.length; i++) {
+    while (
+      stack.length &&
+      temperatures[i] > temperatures[stack[stack.length - 1]]
+    ) {
+      const j = stack.pop();
+      arr[j] = i - j;
+    }
+    stack.push(i);
+  }
+  return arr;
+};
+

数组中的第 K 个最大元素

LeetCode 原题链接:215. 数组中的第 K 个最大元素

+
+

存在快排和堆两种排序算法,熟能生巧,注意快排的边界判断,菜就多练。🦀

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/**
+ * @param {number[]} nums
+ * @param {number} k
+ * @return {number}
+ */
+const findKthLargest = (nums, k) => {
+  const quickSelect = (l, r) => {
+    if (l === r) {
+      return nums[nums.length - k];
+    }
+    let i = l - 1,
+      j = r + 1;
+    const x = nums[l];
+    while (i < j) {
+      do {
+        i++;
+      } while (nums[i] < x);
+      do {
+        j--;
+      } while (nums[j] > x);
+      if (i < j) {
+        [nums[i], nums[j]] = [nums[j], nums[i]];
+      }
+    }
+    if (nums.length - k <= j) {
+      return quickSelect(l, j);
+    } else {
+      return quickSelect(j + 1, r);
+    }
+  };
+  return quickSelect(0, nums.length - 1);
+};
+
+/**
+ * @param {number[]} nums
+ * @param {number} k
+ * @return {number}
+ */
+const findKthLargest = (nums, k) => {
+  const buildMaxHeap = (heapSize) => {
+    for (let i = Math.floor(heapSize / 2) - 1; i >= 0; i--) {
+      maxHeapify(i, heapSize);
+    }
+  };
+  const maxHeapify = (i, heapSize) => {
+    const l = i * 2 + 1;
+    const r = i * 2 + 2;
+    let largest = i;
+    if (l < heapSize && nums[l] > nums[largest]) {
+      largest = l;
+    }
+    if (r < heapSize && nums[r] > nums[largest]) {
+      largest = r;
+    }
+    if (i !== largest) {
+      swap(i, largest);
+      maxHeapify(largest, heapSize);
+    }
+  };
+  const swap = (i, j) => {
+    const temp = nums[i];
+    nums[i] = nums[j];
+    nums[j] = temp;
+  };
+  let heapSize = nums.length;
+  buildMaxHeap(heapSize);
+  for (let i = nums.length - 1; i >= nums.length - k + 1; i--) {
+    swap(0, i);
+    heapSize--;
+    maxHeapify(0, heapSize);
+  }
+  return nums[0];
+};
+

贪心

买卖股票的最佳时机

LeetCode 原题链接:121. 买卖股票的最佳时机

+
+

贪心算法,每次都找到前一个的最优条件

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
+ * @param {number[]} prices
+ * @return {number}
+ */
+const maxProfit = (prices) => {
+  let maxIn = 0,
+    minPrice = prices[0];
+  for (let i = 1; i < prices.length; i++) {
+    if (minPrice < prices[i]) {
+      maxIn = Math.max(maxIn, prices[i] - minPrice);
+    } else {
+      minPrice = prices[i];
+    }
+  }
+  return maxIn;
+};
+

跳跃游戏

LeetCode 原题链接:55. 跳跃游戏

+
+

贪心算法,每次在符合条件情况下更新最长的长度。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
+ * @param {number[]} nums
+ * @return {boolean}
+ */
+const canJump = (nums) => {
+  let head = nums[0];
+  for (let i = 1; i < nums.length; i++) {
+    if (i <= head) {
+      head = Math.max(head, i + nums[i]);
+    }
+    if (head >= nums.length - 1) {
+      return true;
+    }
+  }
+  return head >= nums.length - 1;
+};
+

动态规划

爬楼梯

LeetCode 原题链接:70. 爬楼梯

+
+

经典问题,甚至让我觉得有点像斐波那契数列。🤔

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
+ * @param {number} n
+ * @return {number}
+ */
+const climbStairs = (n) => {
+  let pre = 1,
+    cur = 1;
+  for (let i = 1; i < n; i++) {
+    const temp = pre + cur;
+    pre = cur;
+    cur = temp;
+  }
+  return cur;
+};
+

打家劫舍

LeetCode 原题链接:70. 爬楼梯

+
+

常规的动态规划题目,注意状态转移方程的书写,注意初始条件的设置,并不影响整体的判断。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
+ * @param {number[]} nums
+ * @return {number}
+ */
+const rob = (nums) => {
+  if (!nums || nums.length === 0) {
+    return 0;
+  }
+  if (nums.length === 1) {
+    return nums[0];
+  }
+  let first = nums[0],
+    second = Math.max(nums[0], nums[1]);
+  for (let i = 2; i < nums.length; i++) {
+    const temp = second;
+    second = Math.max(second, first + nums[i]);
+    first = temp;
+  }
+  return second;
+};
+
+/**
+ * @param {number[]} nums
+ * @return {boolean}
+ */
+const rob = (nums) => {
+  if (!nums || nums.length === 0) {
+    return 0;
+  }
+  if (nums.length === 1) {
+    return nums[0];
+  }
+  const sumArr = new Array(nums.length);
+  sumArr[0] = nums[0];
+  sumArr[1] = Math.max(nums[0], nums[1]);
+  for (let i = 2; i < nums.length; i++) {
+    sumArr[i] = Math.max(sumArr[i - 1], sumArr[i - 2] + nums[i]);
+  }
+  return sumArr[sumArr.length - 1];
+};
+

单词拆分

LeetCode 原题链接:139. 单词拆分

+
+

这边还是有些技巧,首先dp数组的长度为n + 1并且第一项为true,然后状态转移方程如下,值得仔细理解。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
+ * @param {string} s
+ * @param {string[]} wordDict
+ * @return {boolean}
+ */
+const wordBreak = (s, wordDict) => {
+  const set = new Set(wordDict);
+  const dp = new Array(s.length + 1).fill(false);
+  dp[0] = true;
+  for (let i = 1; i < s.length + 1; i++) {
+    for (let j = 0; j < i; j++) {
+      if (dp[j] && set.has(s.slice(j, i))) {
+        dp[i] = true;
+        break;
+      }
+    }
+  }
+  return dp[dp.length - 1];
+};
+

最长递增子序列

LeetCode 原题链接:300. 最长递增子序列

+
+

维护一个dp数组,数组中的每个值为当前索引的最长子序列长度,然后动态规划求解。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
+ * @param {number[]} nums
+ * @return {number}
+ */
+const lengthOfLIS = (nums) => {
+  const dp = new Array(nums.length).fill(1);
+  let maxLen = 1;
+  for (let i = 1; i < nums.length; i++) {
+    let max = 1;
+    for (let j = 0; j < i; j++) {
+      if (nums[i] > nums[j]) {
+        max = Math.max(max, dp[j] + 1);
+      }
+    }
+    dp[i] = max;
+    maxLen = Math.max(maxLen, max);
+  }
+  return maxLen;
+};
+

多维动态规划

不同路径

LeetCode 原题链接:62. 不同路径

+
+

二维动态规划,也可直接使用一维数组来模仿,重点在于当前节点的次数只会来源于左上。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
+ * @param {number} m
+ * @param {number} n
+ * @return {number}
+ */
+const uniquePaths = (m, n) => {
+  const f = new Array(m).fill(0).map(() => new Array(n).fill(0));
+  for (let i = 0; i < m; i++) {
+    f[i][0] = 1;
+  }
+  for (let j = 0; j < n; j++) {
+    f[0][j] = 1;
+  }
+  for (let i = 1; i < m; i++) {
+    for (let j = 1; j < n; j++) {
+      f[i][j] = f[i - 1][j] + f[i][j - 1];
+    }
+  }
+  return f[m - 1][n - 1];
+};
+
+/**
+ * @param {number} m
+ * @param {number} n
+ * @return {number}
+ */
+const uniquePaths = (m, n) => {
+  const newArr = new Array(n).fill(1);
+  for (let i = 1; i < m; i++) {
+    for (let j = 1; j < n; j++) {
+      newArr[j] += newArr[j - 1];
+    }
+  }
+  return newArr[n - 1];
+};
+

最小路径和

LeetCode 原题链接:64. 最小路径和

+
+

相同的动态规划思路,左边和上面哪个更小取哪个,注意需要初始化边缘的行与列。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
+ * @param {number[][]} grid
+ * @return {number}
+ */
+const minPathSum = (grid) => {
+  const rowLen = grid.length,
+    colLen = grid[0].length;
+  for (let i = 1; i < colLen; i++) {
+    grid[0][i] += grid[0][i - 1];
+  }
+  for (let i = 1; i < rowLen; i++) {
+    grid[i][0] += grid[i - 1][0];
+  }
+  for (let i = 1; i < rowLen; i++) {
+    for (let j = 1; j < colLen; j++) {
+      grid[i][j] += Math.min(grid[i - 1][j], grid[i][j - 1]);
+    }
+  }
+  return grid[rowLen - 1][colLen - 1];
+};
+

最长回文子串

LeetCode 原题链接:5. 最长回文子串

+
+

中心扩散法直接秒,经典无需多言。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
+ * @param {string} s
+ * @return {string}
+ */
+const longestPalindrome = (s) => {
+  if (s.length <= 1) {
+    return s;
+  }
+  let res = "";
+  const helper = (m, n) => {
+    while (m >= 0 && n < s.length && s[m] === s[n]) {
+      m--;
+      n++;
+    }
+    const temp = s.slice(m + 1, n);
+    if (temp.length > res.length) {
+      res = temp;
+    }
+  };
+  for (let i = 0; i < s.length; i++) {
+    helper(i, i);
+    helper(i, i + 1);
+  }
+  return res;
+};
+

技巧

多数元素

LeetCode 原题链接:169. 多数元素

+
+

哈希表求解

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
+ * @param {number[]} nums
+ * @return {number}
+ */
+const majorityElement = (nums) => {
+  if (nums.length <= 1) return nums;
+  const map = new Map();
+  for (const num of nums) {
+    if (map.has(num)) {
+      const count = map.get(num);
+      if (count === Math.floor(nums.length / 2)) {
+        return num;
+      }
+      map.set(num, count + 1);
+    } else {
+      map.set(num, 1);
+    }
+  }
+};
+

下一个排列

LeetCode 原题链接:31. 下一个排列

+
+

从右往左找到较小的一个数,然后找到较大的一个数然后需要尽量靠右,交换后然后反转。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
+ * @param {number[]} nums
+ * @return {void} Do not return anything, modify nums in-place instead.
+ */
+const nextPermutation = (nums) => {
+  const swap = (i, j) => {
+    const temp = nums[i];
+    nums[i] = nums[j];
+    nums[j] = temp;
+  };
+
+  const reverse = (l, r) => {
+    while (l < r) {
+      swap(l++, r--);
+    }
+  };
+
+  let i = nums.length - 2;
+  while (i >= 0 && nums[i] >= nums[i + 1]) {
+    i--;
+  }
+  if (i >= 0) {
+    let j = nums.length - 1;
+    while (j > i && nums[j] <= nums[i]) {
+      j--;
+    }
+    swap(i, j);
+  }
+  reverse(i + 1, nums.length - 1);
+};
+
文章作者: 希亚的西红柿
文章链接: http://blog.refrain.site/posts/ffb66302/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
+ + + + \ No newline at end of file diff --git a/pwa/images/16.png b/pwa/images/16.png new file mode 100644 index 000000000..19e0217f9 Binary files /dev/null and b/pwa/images/16.png differ diff --git a/pwa/images/32.png b/pwa/images/32.png new file mode 100644 index 000000000..db2b03dcb Binary files /dev/null and b/pwa/images/32.png differ diff --git a/pwa/images/apple-touch-icon.png b/pwa/images/apple-touch-icon.png new file mode 100644 index 000000000..c970286de Binary files /dev/null and b/pwa/images/apple-touch-icon.png differ diff --git a/pwa/images/icons/icon-128x128.png b/pwa/images/icons/icon-128x128.png new file mode 100644 index 000000000..34d8c86c4 Binary files /dev/null and b/pwa/images/icons/icon-128x128.png differ diff --git a/pwa/images/icons/icon-144x144.png b/pwa/images/icons/icon-144x144.png new file mode 100644 index 000000000..fb212f7d0 Binary files /dev/null and b/pwa/images/icons/icon-144x144.png differ diff --git a/pwa/images/icons/icon-152x152.png b/pwa/images/icons/icon-152x152.png new file mode 100644 index 000000000..f9dbb83c2 Binary files /dev/null and b/pwa/images/icons/icon-152x152.png differ diff --git a/pwa/images/icons/icon-192x192.png b/pwa/images/icons/icon-192x192.png new file mode 100644 index 000000000..58ae13f4c Binary files /dev/null and b/pwa/images/icons/icon-192x192.png differ diff --git a/pwa/images/icons/icon-384x384.png b/pwa/images/icons/icon-384x384.png new file mode 100644 index 000000000..c5cd2de12 Binary files /dev/null and b/pwa/images/icons/icon-384x384.png differ diff --git a/pwa/images/icons/icon-512x512.png b/pwa/images/icons/icon-512x512.png new file mode 100644 index 000000000..4e0fd0dde Binary files /dev/null and b/pwa/images/icons/icon-512x512.png differ diff --git a/pwa/images/icons/icon-72x72.png b/pwa/images/icons/icon-72x72.png new file mode 100644 index 000000000..8994342b3 Binary files /dev/null and b/pwa/images/icons/icon-72x72.png differ diff --git a/pwa/images/icons/icon-96x96.png b/pwa/images/icons/icon-96x96.png new file mode 100644 index 000000000..7ddfd0ca4 Binary files /dev/null and b/pwa/images/icons/icon-96x96.png differ diff --git a/pwa/images/safari-pinned-tab.svg b/pwa/images/safari-pinned-tab.svg new file mode 100644 index 000000000..f129d6998 --- /dev/null +++ b/pwa/images/safari-pinned-tab.svg @@ -0,0 +1,669 @@ + + + + +Created by potrace 1.14, written by Peter Selinger 2001-2017 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pwa/manifest.json b/pwa/manifest.json new file mode 100644 index 000000000..13073e9ec --- /dev/null +++ b/pwa/manifest.json @@ -0,0 +1 @@ +{"name":"希亚的西红柿のBlog","short_name":"Nova's Blog","theme_color":"#6dc1f7","background_color":"#6dc1f7","display":"standalone","scope":"/","start_url":"/","icons":[{"src":"images/icons/icon-72x72.png","sizes":"72x72","type":"image/png"},{"src":"images/icons/icon-96x96.png","sizes":"96x96","type":"image/png"},{"src":"images/icons/icon-128x128.png","sizes":"128x128","type":"image/png"},{"src":"images/icons/icon-144x144.png","sizes":"144x144","type":"image/png"},{"src":"images/icons/icon-152x152.png","sizes":"152x152","type":"image/png"},{"src":"images/icons/icon-192x192.png","sizes":"192x192","type":"image/png"},{"src":"images/icons/icon-384x384.png","sizes":"384x384","type":"image/png"},{"src":"images/icons/icon-512x512.png","sizes":"512x512","type":"image/png"}]} \ No newline at end of file diff --git a/service-worker.js b/service-worker.js new file mode 100644 index 000000000..70d6cd087 --- /dev/null +++ b/service-worker.js @@ -0,0 +1 @@ +if(!self.define){let e,i={};const a=(a,d)=>(a=new URL(a+".js",d).href,i[a]||new Promise((i=>{if("document"in self){const e=document.createElement("script");e.src=a,e.onload=i,document.head.appendChild(e)}else e=a,importScripts(a),i()})).then((()=>{let e=i[a];if(!e)throw new Error(`Module ${a} didn’t register its module`);return e})));self.define=(d,c)=>{const r=e||("document"in self?document.currentScript.src:"")||location.href;if(i[r])return;let s={};const n=e=>a(e,r),l={module:{uri:r},exports:s,require:n};i[r]=Promise.all(d.map((e=>l[e]||n(e)))).then((e=>(c(...e),s)))}}define(["./workbox-f2630e7e"],(function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"404.html",revision:"abc0d4f10e11a8d01cb98b1381ef6049"},{url:"about/index.html",revision:"4131d7284949791a75824cbc2147cce0"},{url:"archives/2022/07/index.html",revision:"5f700edc7e13b8987afcad3820d19702"},{url:"archives/2022/08/index.html",revision:"781041db929ddd8aa9f346f2b4d56e7c"},{url:"archives/2022/09/index.html",revision:"8da88231e806b040f674a40f29e61795"},{url:"archives/2022/12/index.html",revision:"305731aaec1029a4cd22020632014e86"},{url:"archives/2022/index.html",revision:"569eae688ce62a39632b5639120178a1"},{url:"archives/2022/page/2/index.html",revision:"f37caaf1ecda24ba4ab75f94d61f182f"},{url:"archives/2023/01/index.html",revision:"a37f2e0ce1d63693916e95ce3a3fd7f3"},{url:"archives/2023/03/index.html",revision:"5fa2f515e86457893197df0da84a006a"},{url:"archives/2023/10/index.html",revision:"3dc1bf4825021a440a64a72a6e9c719e"},{url:"archives/2023/11/index.html",revision:"f75df1a6558414428f1a8fa136278d21"},{url:"archives/2023/12/index.html",revision:"c8acc6a4e7cc8153f36e010964525bbb"},{url:"archives/2023/index.html",revision:"d796f796ca4b8f9724d4168d8796fd41"},{url:"archives/2023/page/2/index.html",revision:"4ae476db97819e30bc34a23a1ded9ab3"},{url:"archives/2024/01/index.html",revision:"46891a716b2b14ce8f200dac29c826c6"},{url:"archives/2024/02/index.html",revision:"fb9a7f33b4a2a121869281127d7614b6"},{url:"archives/2024/03/index.html",revision:"75cc727e5c75641c060cedd70874169b"},{url:"archives/2024/08/index.html",revision:"dc63a2eda86aa1dc51d720195da91b1e"},{url:"archives/2024/index.html",revision:"13f4d78ed9ca6f1780af5677374e5479"},{url:"archives/index.html",revision:"54089a2ca83b6c476125a7204807fbe3"},{url:"archives/page/2/index.html",revision:"3f5699659ecdb7af0684f2d2da4adde5"},{url:"archives/page/3/index.html",revision:"aba67813870f633374e9e6945d115f14"},{url:"archives/page/4/index.html",revision:"8d8f3124bad16f538e0aeb9a8787a36a"},{url:"categories/DNS/index.html",revision:"b8c5c036477a0b781bb03748a777d536"},{url:"categories/Front-End/index.html",revision:"b143cdd1b297dc18c592e3b22b99ba4a"},{url:"categories/FrontEnd/index.html",revision:"149c9a48aab69a515032b7c0c9d7a20a"},{url:"categories/index.html",revision:"758281d2309323981dc8974b6e068480"},{url:"categories/Java/index.html",revision:"839ff4b1188d36fe30acd60c99abb8d7"},{url:"categories/JavaScript/index.html",revision:"2ea57203ead2456edfdd264c8cae4eef"},{url:"categories/LeetCode/index.html",revision:"7372b7649429bcf23765a304b337aa04"},{url:"categories/React/index.html",revision:"6e32185033dc5ddbbf14ae00baa68d50"},{url:"categories/Vue/index.html",revision:"82694ae5a386689c953dabeb129a7cd5"},{url:"categories/数据结构/index.html",revision:"af7f49d719a14ce715bf1cf62960fcee"},{url:"categories/机器学习/index.html",revision:"e2085a5a1a2b3c433618cee35cbf9fe3"},{url:"categories/百宝箱/index.html",revision:"c8c2fe258fb7a6ac5a2de36c34101a20"},{url:"comments/index.html",revision:"151b537aa40bd8424b2dd4a42450df53"},{url:"css/custom.css",revision:"583c638f567c737dbf72d3744941c660"},{url:"css/index.css",revision:"dfa0552e119c6ee50b8a27440702109b"},{url:"css/var.css",revision:"d41d8cd98f00b204e9800998ecf8427e"},{url:"drive/index.html",revision:"69cf00d9b87697514ace791a974456fd"},{url:"img/404.jpg",revision:"4ef3cfb882b6dd4128da4c8745e9a507"},{url:"img/favicon.png",revision:"7a8c47cb5a2149c1a1af21e90ecd9ca7"},{url:"img/friend_404.gif",revision:"68af0be9d22722e74665ef44dd532ba8"},{url:"index.html",revision:"b312e7be31106843b535a09721e835d7"},{url:"js/hideLrc.js",revision:"c6a7b164911292b0b38f798174447fa0"},{url:"js/main.js",revision:"c1abc98ff6aa69f598f43b8604fb7b3e"},{url:"js/search/algolia.js",revision:"5e2a2c65f28bddbb3d94529453e91716"},{url:"js/search/local-search.js",revision:"2e3ff3d156bb208f752d95375ebca557"},{url:"js/tw_cn.js",revision:"fd395fc3b4df9c7da17e730d173cfbea"},{url:"js/utils.js",revision:"2fd35bd141fd541a188ef52dd30108d5"},{url:"link/index.html",revision:"22e35630e7e26afaaa6dc11dd0be369e"},{url:"music/index.html",revision:"da3aae4c2c0288cdeb5a30599e6cf4e8"},{url:"page/2/index.html",revision:"ab1126531068693c8994d4cf303a06ad"},{url:"page/3/index.html",revision:"a9745d9726ceb709aa1d06c511ae54e7"},{url:"posts/170bc017/index.html",revision:"a64c722407707ed430e759b480aa6f6a"},{url:"posts/1afd9955/index.html",revision:"1ae0e4460f7131a684ddd1dabeda0522"},{url:"posts/1be7bb2b/index.html",revision:"1b88321788f72a5d2857afc542b3a0a3"},{url:"posts/20642/index.html",revision:"2d7a7764b2c343472a8f5b21399281f2"},{url:"posts/2275c8/index.html",revision:"21d28e4b8e5d9b67eb61cf64e28a6fc3"},{url:"posts/2afbd983/index.html",revision:"2ae18e0397c26333021b9de576ce2c88"},{url:"posts/300a8ca5/index.html",revision:"644ff01d034309dc60e587f749d7f85f"},{url:"posts/355699d5/index.html",revision:"3c462e0398c37740f00e9b0af42334c3"},{url:"posts/35a88b44/index.html",revision:"48227c1d6d78495dc3c0ecf89ce722d4"},{url:"posts/3c94855e/index.html",revision:"4d0ebc89d979b683d22241fd8b0ac40c"},{url:"posts/40f24371/index.html",revision:"4cb01f4f84633026275a10c05ef9493e"},{url:"posts/47341/index.html",revision:"12ff6cd40d6dbd6151cc3fa033427d72"},{url:"posts/54c08517/index.html",revision:"d1a07ba96f12fa23f5ca167a259d7cba"},{url:"posts/582b690b/index.html",revision:"98bc88f8d79f27f5b39348284b6f65fc"},{url:"posts/6534ce06/index.html",revision:"848a2908dfef555ff740254c4b65661b"},{url:"posts/75378e04/index.html",revision:"202d0a66a4d3b0a0b4c209864559a346"},{url:"posts/775eb342/index.html",revision:"18487395272bbdc79380735f2897f747"},{url:"posts/8d55d49a/index.html",revision:"3a5e83a7ecdf38c47201d9debba557a9"},{url:"posts/9614c7d1/index.html",revision:"b9d96ad932372029378723a3451b3cc6"},{url:"posts/a1605017/index.html",revision:"91d6e6c4775eabc43e32a3dc042f1382"},{url:"posts/aac5e919/index.html",revision:"fd65a042b74c0298c8f117f345de87e4"},{url:"posts/acc0017d/index.html",revision:"698e0dfcbf601791c2b1454c26ae3ab5"},{url:"posts/adf5c49/index.html",revision:"bfa9ae2d70939970be854a54f15643ec"},{url:"posts/b06cc6ec/index.html",revision:"8d6ae2bb184df30b74a0428c361e5325"},{url:"posts/b2b6ba06/index.html",revision:"57ce47730b980d7277b5362d25909024"},{url:"posts/b672a0f6/index.html",revision:"334a1bc471ce6055a32ef1e68f853198"},{url:"posts/b67f488/index.html",revision:"4d627cd541a847afc339d68dbc79b28a"},{url:"posts/d3748e5f/index.html",revision:"04c4754b0d5a9ab0d97e46639239a4d2"},{url:"posts/e89bd903/index.html",revision:"e7f2a7e9dbe1547917c6330c1fee09d2"},{url:"posts/fccf75e5/index.html",revision:"90c242833ff20bc1d692554e9ab97ab9"},{url:"posts/ffb66302/index.html",revision:"2e692920009bea9a22259d8746bb5923"},{url:"pwa/images/16.png",revision:"a371ff1dcf1e58c4b363b4d15a2c4b32"},{url:"pwa/images/32.png",revision:"eca9d7c23cc8aac4e5b5bef7b3040909"},{url:"pwa/images/apple-touch-icon.png",revision:"97191e0537f84803438df9c37b031107"},{url:"pwa/images/icons/icon-128x128.png",revision:"5132c926d5b019070f0228c53b69d5bf"},{url:"pwa/images/icons/icon-144x144.png",revision:"713d19f7487337b6d78b932f43082ea1"},{url:"pwa/images/icons/icon-152x152.png",revision:"24a0aab809179697db91023821046a16"},{url:"pwa/images/icons/icon-192x192.png",revision:"eea0e91aec11d2e735bd688bb55757e3"},{url:"pwa/images/icons/icon-384x384.png",revision:"9ab4db6f3c16121ee01506cadc687c95"},{url:"pwa/images/icons/icon-512x512.png",revision:"e5b5cde78f88b6ff2215275a7a4eebea"},{url:"pwa/images/icons/icon-72x72.png",revision:"1db80c3d1043caecc6f56a6f0f3aa0b1"},{url:"pwa/images/icons/icon-96x96.png",revision:"f69aa52fa6e21c6cfb9fa2e06d9079e3"},{url:"pwa/images/safari-pinned-tab.svg",revision:"c3fe0ab7adb65a2a631d99aced3894ca"},{url:"tags/Build-Tool/index.html",revision:"5d6fbdf10c82bb74618e7f18573aa9f7"},{url:"tags/cloudflare/index.html",revision:"a35df118cc3ad1a4fb8d945d884f85c7"},{url:"tags/Framework/index.html",revision:"097e28eea53c57558902030000366570"},{url:"tags/Front-End/index.html",revision:"68f47498cc74a45ebe06f4bc47becbd0"},{url:"tags/FrontEnd/index.html",revision:"f755531af79f6d9737a8ef4f0dbd05fa"},{url:"tags/Git/index.html",revision:"9793966e04c2b03fd342bc91369f26e0"},{url:"tags/GitHub/index.html",revision:"4d229fd7531ec7b9a69c8432d19c2f1a"},{url:"tags/index.html",revision:"4b88bc848abe94edf52ffae09efa1203"},{url:"tags/Java/index.html",revision:"ba833679afb9a6b3df88a63da8038264"},{url:"tags/JavaScript/index.html",revision:"3f8cc89069665625fc922f9b30394e6e"},{url:"tags/JavaScript/page/2/index.html",revision:"db80a87f1be89422977c2aac7cf34831"},{url:"tags/JS-Basic/index.html",revision:"9e5a28571571931d1c6bc851b3c4ba7a"},{url:"tags/k-means/index.html",revision:"99fd1380b99584f7f54a81100a7c8cb4"},{url:"tags/kNN算法/index.html",revision:"1b3e8fd29f51941e9d58319985d03ac4"},{url:"tags/Numpy/index.html",revision:"31a0b19349cd46918adcbdb9247973b6"},{url:"tags/python/index.html",revision:"83d82dfdb504ceb8731c4bff35771b48"},{url:"tags/Python/index.html",revision:"770015044455172996221a1b84c02bee"},{url:"tags/React/index.html",revision:"f311538158dc018ba3f175dcccdb27c9"},{url:"tags/Regex/index.html",revision:"276309fab33e9148dfb952138fbb9851"},{url:"tags/TypeScript/index.html",revision:"0aa9d3596b45e0ea1d4c908b97d19417"},{url:"tags/Ubuntu/index.html",revision:"da721add0780d8eec8057d667f0d19af"},{url:"tags/Vite/index.html",revision:"478978b5a21a54a25b2785a49b2f972a"},{url:"tags/Vue/index.html",revision:"f1399e08770bcacad802cf7baae9545c"},{url:"tags/WebAPI/index.html",revision:"4341c175f0531fa062970bf8c89ab607"},{url:"tags/Windows/index.html",revision:"54d80d72de17259881b9077e748dd673"},{url:"tags/任务栏/index.html",revision:"1a25cdd2d4499dc8baf34c8b11811113"},{url:"tags/决策树/index.html",revision:"25e75f6cad5c1d025e7097fd5459d780"},{url:"tags/分布式管理系统/index.html",revision:"0bbca33bd8bc5e0d1fa142186ffc95d2"},{url:"tags/域名/index.html",revision:"334fa3363e3e9fc47cae135c80dd3640"},{url:"tags/数据结构/index.html",revision:"2a06d84dc8441473ea3fe7c151ebe894"},{url:"tags/机器学习/index.html",revision:"813a2ee4a39fdf074023012ef48f604f"},{url:"tags/栈/index.html",revision:"b776a1356e9993dd29524d8139f8ca16"},{url:"tags/算法/index.html",revision:"45ff1ae87626cd69d1caaed45424bd38"},{url:"tags/线性回归/index.html",revision:"757c5a789c9e66872a277a59cc3cbefd"},{url:"tags/线性表/index.html",revision:"2897825c8952f89f41c9c107b4ecf3b6"},{url:"tags/绪论/index.html",revision:"9600c0d2d0d9d898af489f0925d46df4"},{url:"tags/编程学习/index.html",revision:"58c50a462467da7f2337e8788df1ef14"},{url:"tags/美化/index.html",revision:"51c1ccf45774eeb73810a1007859ae6d"},{url:"tags/虚拟机/index.html",revision:"ddd6759bfe55e71c0095e9a86e678a13"},{url:"tags/逻辑回归/index.html",revision:"cd1d9dcb691f850b6281bf0577d52b22"},{url:"tags/队列/index.html",revision:"4de09531dab7581fee1a1865ca6e1cee"}],{}),e.registerRoute(/^https:\/\/npm\.elemecdn\.com\/.*/,new e.CacheFirst,"GET"),e.registerRoute(/^https:\/\/cdn\.jsdelivr\.com\/.*/,new e.CacheFirst,"GET"),e.registerRoute(/^https:\/\/jsd\.cdn\.zzko\.cn\/.*/,new e.CacheFirst,"GET"),e.registerRoute(/^https:\/\/unpkg\.com\/.*/,new e.CacheFirst,"GET")})); \ No newline at end of file diff --git a/service-worker.js.map b/service-worker.js.map new file mode 100644 index 000000000..5deba368b --- /dev/null +++ b/service-worker.js.map @@ -0,0 +1 @@ +{"version":3,"file":"service-worker.js","sources":["../../../../../tmp/17d6161d1ff594ec26410c9b9159b479/service-worker.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/home/runner/work/HEXO/HEXO/node_modules/workbox-routing/registerRoute.mjs';\nimport {CacheFirst as workbox_strategies_CacheFirst} from '/home/runner/work/HEXO/HEXO/node_modules/workbox-strategies/CacheFirst.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/home/runner/work/HEXO/HEXO/node_modules/workbox-core/clientsClaim.mjs';\nimport {precacheAndRoute as workbox_precaching_precacheAndRoute} from '/home/runner/work/HEXO/HEXO/node_modules/workbox-precaching/precacheAndRoute.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\n\n\n\n\n\n\nself.skipWaiting();\n\nworkbox_core_clientsClaim();\n\n\n/**\n * The precacheAndRoute() method efficiently caches and responds to\n * requests for URLs in the manifest.\n * See https://goo.gl/S9QRab\n */\nworkbox_precaching_precacheAndRoute([\n {\n \"url\": \"404.html\",\n \"revision\": \"abc0d4f10e11a8d01cb98b1381ef6049\"\n },\n {\n \"url\": \"about/index.html\",\n \"revision\": \"4131d7284949791a75824cbc2147cce0\"\n },\n {\n \"url\": \"archives/2022/07/index.html\",\n \"revision\": \"5f700edc7e13b8987afcad3820d19702\"\n },\n {\n \"url\": \"archives/2022/08/index.html\",\n \"revision\": \"781041db929ddd8aa9f346f2b4d56e7c\"\n },\n {\n \"url\": \"archives/2022/09/index.html\",\n \"revision\": \"8da88231e806b040f674a40f29e61795\"\n },\n {\n \"url\": \"archives/2022/12/index.html\",\n \"revision\": \"305731aaec1029a4cd22020632014e86\"\n },\n {\n \"url\": \"archives/2022/index.html\",\n \"revision\": \"569eae688ce62a39632b5639120178a1\"\n },\n {\n \"url\": \"archives/2022/page/2/index.html\",\n \"revision\": \"f37caaf1ecda24ba4ab75f94d61f182f\"\n },\n {\n \"url\": \"archives/2023/01/index.html\",\n \"revision\": \"a37f2e0ce1d63693916e95ce3a3fd7f3\"\n },\n {\n \"url\": \"archives/2023/03/index.html\",\n \"revision\": \"5fa2f515e86457893197df0da84a006a\"\n },\n {\n \"url\": \"archives/2023/10/index.html\",\n \"revision\": \"3dc1bf4825021a440a64a72a6e9c719e\"\n },\n {\n \"url\": \"archives/2023/11/index.html\",\n \"revision\": \"f75df1a6558414428f1a8fa136278d21\"\n },\n {\n \"url\": \"archives/2023/12/index.html\",\n \"revision\": \"c8acc6a4e7cc8153f36e010964525bbb\"\n },\n {\n \"url\": \"archives/2023/index.html\",\n \"revision\": \"d796f796ca4b8f9724d4168d8796fd41\"\n },\n {\n \"url\": \"archives/2023/page/2/index.html\",\n \"revision\": \"4ae476db97819e30bc34a23a1ded9ab3\"\n },\n {\n \"url\": \"archives/2024/01/index.html\",\n \"revision\": \"46891a716b2b14ce8f200dac29c826c6\"\n },\n {\n \"url\": \"archives/2024/02/index.html\",\n \"revision\": \"fb9a7f33b4a2a121869281127d7614b6\"\n },\n {\n \"url\": \"archives/2024/03/index.html\",\n \"revision\": \"75cc727e5c75641c060cedd70874169b\"\n },\n {\n \"url\": \"archives/2024/08/index.html\",\n \"revision\": \"dc63a2eda86aa1dc51d720195da91b1e\"\n },\n {\n \"url\": \"archives/2024/index.html\",\n \"revision\": \"13f4d78ed9ca6f1780af5677374e5479\"\n },\n {\n \"url\": \"archives/index.html\",\n \"revision\": \"54089a2ca83b6c476125a7204807fbe3\"\n },\n {\n \"url\": \"archives/page/2/index.html\",\n \"revision\": \"3f5699659ecdb7af0684f2d2da4adde5\"\n },\n {\n \"url\": \"archives/page/3/index.html\",\n \"revision\": \"aba67813870f633374e9e6945d115f14\"\n },\n {\n \"url\": \"archives/page/4/index.html\",\n \"revision\": \"8d8f3124bad16f538e0aeb9a8787a36a\"\n },\n {\n \"url\": \"categories/DNS/index.html\",\n \"revision\": \"b8c5c036477a0b781bb03748a777d536\"\n },\n {\n \"url\": \"categories/Front-End/index.html\",\n \"revision\": \"b143cdd1b297dc18c592e3b22b99ba4a\"\n },\n {\n \"url\": \"categories/FrontEnd/index.html\",\n \"revision\": \"149c9a48aab69a515032b7c0c9d7a20a\"\n },\n {\n \"url\": \"categories/index.html\",\n \"revision\": \"758281d2309323981dc8974b6e068480\"\n },\n {\n \"url\": \"categories/Java/index.html\",\n \"revision\": \"839ff4b1188d36fe30acd60c99abb8d7\"\n },\n {\n \"url\": \"categories/JavaScript/index.html\",\n \"revision\": \"2ea57203ead2456edfdd264c8cae4eef\"\n },\n {\n \"url\": \"categories/LeetCode/index.html\",\n \"revision\": \"7372b7649429bcf23765a304b337aa04\"\n },\n {\n \"url\": \"categories/React/index.html\",\n \"revision\": \"6e32185033dc5ddbbf14ae00baa68d50\"\n },\n {\n \"url\": \"categories/Vue/index.html\",\n \"revision\": \"82694ae5a386689c953dabeb129a7cd5\"\n },\n {\n \"url\": \"categories/数据结构/index.html\",\n \"revision\": \"af7f49d719a14ce715bf1cf62960fcee\"\n },\n {\n \"url\": \"categories/机器学习/index.html\",\n \"revision\": \"e2085a5a1a2b3c433618cee35cbf9fe3\"\n },\n {\n \"url\": \"categories/百宝箱/index.html\",\n \"revision\": \"c8c2fe258fb7a6ac5a2de36c34101a20\"\n },\n {\n \"url\": \"comments/index.html\",\n \"revision\": \"151b537aa40bd8424b2dd4a42450df53\"\n },\n {\n \"url\": \"css/custom.css\",\n \"revision\": \"583c638f567c737dbf72d3744941c660\"\n },\n {\n \"url\": \"css/index.css\",\n \"revision\": \"dfa0552e119c6ee50b8a27440702109b\"\n },\n {\n \"url\": \"css/var.css\",\n \"revision\": \"d41d8cd98f00b204e9800998ecf8427e\"\n },\n {\n \"url\": \"drive/index.html\",\n \"revision\": \"69cf00d9b87697514ace791a974456fd\"\n },\n {\n \"url\": \"img/404.jpg\",\n \"revision\": \"4ef3cfb882b6dd4128da4c8745e9a507\"\n },\n {\n \"url\": \"img/favicon.png\",\n \"revision\": \"7a8c47cb5a2149c1a1af21e90ecd9ca7\"\n },\n {\n \"url\": \"img/friend_404.gif\",\n \"revision\": \"68af0be9d22722e74665ef44dd532ba8\"\n },\n {\n \"url\": \"index.html\",\n \"revision\": \"b312e7be31106843b535a09721e835d7\"\n },\n {\n \"url\": \"js/hideLrc.js\",\n \"revision\": \"c6a7b164911292b0b38f798174447fa0\"\n },\n {\n \"url\": \"js/main.js\",\n \"revision\": \"c1abc98ff6aa69f598f43b8604fb7b3e\"\n },\n {\n \"url\": \"js/search/algolia.js\",\n \"revision\": \"5e2a2c65f28bddbb3d94529453e91716\"\n },\n {\n \"url\": \"js/search/local-search.js\",\n \"revision\": \"2e3ff3d156bb208f752d95375ebca557\"\n },\n {\n \"url\": \"js/tw_cn.js\",\n \"revision\": \"fd395fc3b4df9c7da17e730d173cfbea\"\n },\n {\n \"url\": \"js/utils.js\",\n \"revision\": \"2fd35bd141fd541a188ef52dd30108d5\"\n },\n {\n \"url\": \"link/index.html\",\n \"revision\": \"22e35630e7e26afaaa6dc11dd0be369e\"\n },\n {\n \"url\": \"music/index.html\",\n \"revision\": \"da3aae4c2c0288cdeb5a30599e6cf4e8\"\n },\n {\n \"url\": \"page/2/index.html\",\n \"revision\": \"ab1126531068693c8994d4cf303a06ad\"\n },\n {\n \"url\": \"page/3/index.html\",\n \"revision\": \"a9745d9726ceb709aa1d06c511ae54e7\"\n },\n {\n \"url\": \"posts/170bc017/index.html\",\n \"revision\": \"a64c722407707ed430e759b480aa6f6a\"\n },\n {\n \"url\": \"posts/1afd9955/index.html\",\n \"revision\": \"1ae0e4460f7131a684ddd1dabeda0522\"\n },\n {\n \"url\": \"posts/1be7bb2b/index.html\",\n \"revision\": \"1b88321788f72a5d2857afc542b3a0a3\"\n },\n {\n \"url\": \"posts/20642/index.html\",\n \"revision\": \"2d7a7764b2c343472a8f5b21399281f2\"\n },\n {\n \"url\": \"posts/2275c8/index.html\",\n \"revision\": \"21d28e4b8e5d9b67eb61cf64e28a6fc3\"\n },\n {\n \"url\": \"posts/2afbd983/index.html\",\n \"revision\": \"2ae18e0397c26333021b9de576ce2c88\"\n },\n {\n \"url\": \"posts/300a8ca5/index.html\",\n \"revision\": \"644ff01d034309dc60e587f749d7f85f\"\n },\n {\n \"url\": \"posts/355699d5/index.html\",\n \"revision\": \"3c462e0398c37740f00e9b0af42334c3\"\n },\n {\n \"url\": \"posts/35a88b44/index.html\",\n \"revision\": \"48227c1d6d78495dc3c0ecf89ce722d4\"\n },\n {\n \"url\": \"posts/3c94855e/index.html\",\n \"revision\": \"4d0ebc89d979b683d22241fd8b0ac40c\"\n },\n {\n \"url\": \"posts/40f24371/index.html\",\n \"revision\": \"4cb01f4f84633026275a10c05ef9493e\"\n },\n {\n \"url\": \"posts/47341/index.html\",\n \"revision\": \"12ff6cd40d6dbd6151cc3fa033427d72\"\n },\n {\n \"url\": \"posts/54c08517/index.html\",\n \"revision\": \"d1a07ba96f12fa23f5ca167a259d7cba\"\n },\n {\n \"url\": \"posts/582b690b/index.html\",\n \"revision\": \"98bc88f8d79f27f5b39348284b6f65fc\"\n },\n {\n \"url\": \"posts/6534ce06/index.html\",\n \"revision\": \"848a2908dfef555ff740254c4b65661b\"\n },\n {\n \"url\": \"posts/75378e04/index.html\",\n \"revision\": \"202d0a66a4d3b0a0b4c209864559a346\"\n },\n {\n \"url\": \"posts/775eb342/index.html\",\n \"revision\": \"18487395272bbdc79380735f2897f747\"\n },\n {\n \"url\": \"posts/8d55d49a/index.html\",\n \"revision\": \"3a5e83a7ecdf38c47201d9debba557a9\"\n },\n {\n \"url\": \"posts/9614c7d1/index.html\",\n \"revision\": \"b9d96ad932372029378723a3451b3cc6\"\n },\n {\n \"url\": \"posts/a1605017/index.html\",\n \"revision\": \"91d6e6c4775eabc43e32a3dc042f1382\"\n },\n {\n \"url\": \"posts/aac5e919/index.html\",\n \"revision\": \"fd65a042b74c0298c8f117f345de87e4\"\n },\n {\n \"url\": \"posts/acc0017d/index.html\",\n \"revision\": \"698e0dfcbf601791c2b1454c26ae3ab5\"\n },\n {\n \"url\": \"posts/adf5c49/index.html\",\n \"revision\": \"bfa9ae2d70939970be854a54f15643ec\"\n },\n {\n \"url\": \"posts/b06cc6ec/index.html\",\n \"revision\": \"8d6ae2bb184df30b74a0428c361e5325\"\n },\n {\n \"url\": \"posts/b2b6ba06/index.html\",\n \"revision\": \"57ce47730b980d7277b5362d25909024\"\n },\n {\n \"url\": \"posts/b672a0f6/index.html\",\n \"revision\": \"334a1bc471ce6055a32ef1e68f853198\"\n },\n {\n \"url\": \"posts/b67f488/index.html\",\n \"revision\": \"4d627cd541a847afc339d68dbc79b28a\"\n },\n {\n \"url\": \"posts/d3748e5f/index.html\",\n \"revision\": \"04c4754b0d5a9ab0d97e46639239a4d2\"\n },\n {\n \"url\": \"posts/e89bd903/index.html\",\n \"revision\": \"e7f2a7e9dbe1547917c6330c1fee09d2\"\n },\n {\n \"url\": \"posts/fccf75e5/index.html\",\n \"revision\": \"90c242833ff20bc1d692554e9ab97ab9\"\n },\n {\n \"url\": \"posts/ffb66302/index.html\",\n \"revision\": \"2e692920009bea9a22259d8746bb5923\"\n },\n {\n \"url\": \"pwa/images/16.png\",\n \"revision\": \"a371ff1dcf1e58c4b363b4d15a2c4b32\"\n },\n {\n \"url\": \"pwa/images/32.png\",\n \"revision\": \"eca9d7c23cc8aac4e5b5bef7b3040909\"\n },\n {\n \"url\": \"pwa/images/apple-touch-icon.png\",\n \"revision\": \"97191e0537f84803438df9c37b031107\"\n },\n {\n \"url\": \"pwa/images/icons/icon-128x128.png\",\n \"revision\": \"5132c926d5b019070f0228c53b69d5bf\"\n },\n {\n \"url\": \"pwa/images/icons/icon-144x144.png\",\n \"revision\": \"713d19f7487337b6d78b932f43082ea1\"\n },\n {\n \"url\": \"pwa/images/icons/icon-152x152.png\",\n \"revision\": \"24a0aab809179697db91023821046a16\"\n },\n {\n \"url\": \"pwa/images/icons/icon-192x192.png\",\n \"revision\": \"eea0e91aec11d2e735bd688bb55757e3\"\n },\n {\n \"url\": \"pwa/images/icons/icon-384x384.png\",\n \"revision\": \"9ab4db6f3c16121ee01506cadc687c95\"\n },\n {\n \"url\": \"pwa/images/icons/icon-512x512.png\",\n \"revision\": \"e5b5cde78f88b6ff2215275a7a4eebea\"\n },\n {\n \"url\": \"pwa/images/icons/icon-72x72.png\",\n \"revision\": \"1db80c3d1043caecc6f56a6f0f3aa0b1\"\n },\n {\n \"url\": \"pwa/images/icons/icon-96x96.png\",\n \"revision\": \"f69aa52fa6e21c6cfb9fa2e06d9079e3\"\n },\n {\n \"url\": \"pwa/images/safari-pinned-tab.svg\",\n \"revision\": \"c3fe0ab7adb65a2a631d99aced3894ca\"\n },\n {\n \"url\": \"tags/Build-Tool/index.html\",\n \"revision\": \"5d6fbdf10c82bb74618e7f18573aa9f7\"\n },\n {\n \"url\": \"tags/cloudflare/index.html\",\n \"revision\": \"a35df118cc3ad1a4fb8d945d884f85c7\"\n },\n {\n \"url\": \"tags/Framework/index.html\",\n \"revision\": \"097e28eea53c57558902030000366570\"\n },\n {\n \"url\": \"tags/Front-End/index.html\",\n \"revision\": \"68f47498cc74a45ebe06f4bc47becbd0\"\n },\n {\n \"url\": \"tags/FrontEnd/index.html\",\n \"revision\": \"f755531af79f6d9737a8ef4f0dbd05fa\"\n },\n {\n \"url\": \"tags/Git/index.html\",\n \"revision\": \"9793966e04c2b03fd342bc91369f26e0\"\n },\n {\n \"url\": \"tags/GitHub/index.html\",\n \"revision\": \"4d229fd7531ec7b9a69c8432d19c2f1a\"\n },\n {\n \"url\": \"tags/index.html\",\n \"revision\": \"4b88bc848abe94edf52ffae09efa1203\"\n },\n {\n \"url\": \"tags/Java/index.html\",\n \"revision\": \"ba833679afb9a6b3df88a63da8038264\"\n },\n {\n \"url\": \"tags/JavaScript/index.html\",\n \"revision\": \"3f8cc89069665625fc922f9b30394e6e\"\n },\n {\n \"url\": \"tags/JavaScript/page/2/index.html\",\n \"revision\": \"db80a87f1be89422977c2aac7cf34831\"\n },\n {\n \"url\": \"tags/JS-Basic/index.html\",\n \"revision\": \"9e5a28571571931d1c6bc851b3c4ba7a\"\n },\n {\n \"url\": \"tags/k-means/index.html\",\n \"revision\": \"99fd1380b99584f7f54a81100a7c8cb4\"\n },\n {\n \"url\": \"tags/kNN算法/index.html\",\n \"revision\": \"1b3e8fd29f51941e9d58319985d03ac4\"\n },\n {\n \"url\": \"tags/Numpy/index.html\",\n \"revision\": \"31a0b19349cd46918adcbdb9247973b6\"\n },\n {\n \"url\": \"tags/python/index.html\",\n \"revision\": \"83d82dfdb504ceb8731c4bff35771b48\"\n },\n {\n \"url\": \"tags/Python/index.html\",\n \"revision\": \"770015044455172996221a1b84c02bee\"\n },\n {\n \"url\": \"tags/React/index.html\",\n \"revision\": \"f311538158dc018ba3f175dcccdb27c9\"\n },\n {\n \"url\": \"tags/Regex/index.html\",\n \"revision\": \"276309fab33e9148dfb952138fbb9851\"\n },\n {\n \"url\": \"tags/TypeScript/index.html\",\n \"revision\": \"0aa9d3596b45e0ea1d4c908b97d19417\"\n },\n {\n \"url\": \"tags/Ubuntu/index.html\",\n \"revision\": \"da721add0780d8eec8057d667f0d19af\"\n },\n {\n \"url\": \"tags/Vite/index.html\",\n \"revision\": \"478978b5a21a54a25b2785a49b2f972a\"\n },\n {\n \"url\": \"tags/Vue/index.html\",\n \"revision\": \"f1399e08770bcacad802cf7baae9545c\"\n },\n {\n \"url\": \"tags/WebAPI/index.html\",\n \"revision\": \"4341c175f0531fa062970bf8c89ab607\"\n },\n {\n \"url\": \"tags/Windows/index.html\",\n \"revision\": \"54d80d72de17259881b9077e748dd673\"\n },\n {\n \"url\": \"tags/任务栏/index.html\",\n \"revision\": \"1a25cdd2d4499dc8baf34c8b11811113\"\n },\n {\n \"url\": \"tags/决策树/index.html\",\n \"revision\": \"25e75f6cad5c1d025e7097fd5459d780\"\n },\n {\n \"url\": \"tags/分布式管理系统/index.html\",\n \"revision\": \"0bbca33bd8bc5e0d1fa142186ffc95d2\"\n },\n {\n \"url\": \"tags/域名/index.html\",\n \"revision\": \"334fa3363e3e9fc47cae135c80dd3640\"\n },\n {\n \"url\": \"tags/数据结构/index.html\",\n \"revision\": \"2a06d84dc8441473ea3fe7c151ebe894\"\n },\n {\n \"url\": \"tags/机器学习/index.html\",\n \"revision\": \"813a2ee4a39fdf074023012ef48f604f\"\n },\n {\n \"url\": \"tags/栈/index.html\",\n \"revision\": \"b776a1356e9993dd29524d8139f8ca16\"\n },\n {\n \"url\": \"tags/算法/index.html\",\n \"revision\": \"45ff1ae87626cd69d1caaed45424bd38\"\n },\n {\n \"url\": \"tags/线性回归/index.html\",\n \"revision\": \"757c5a789c9e66872a277a59cc3cbefd\"\n },\n {\n \"url\": \"tags/线性表/index.html\",\n \"revision\": \"2897825c8952f89f41c9c107b4ecf3b6\"\n },\n {\n \"url\": \"tags/绪论/index.html\",\n \"revision\": \"9600c0d2d0d9d898af489f0925d46df4\"\n },\n {\n \"url\": \"tags/编程学习/index.html\",\n \"revision\": \"58c50a462467da7f2337e8788df1ef14\"\n },\n {\n \"url\": \"tags/美化/index.html\",\n \"revision\": \"51c1ccf45774eeb73810a1007859ae6d\"\n },\n {\n \"url\": \"tags/虚拟机/index.html\",\n \"revision\": \"ddd6759bfe55e71c0095e9a86e678a13\"\n },\n {\n \"url\": \"tags/逻辑回归/index.html\",\n \"revision\": \"cd1d9dcb691f850b6281bf0577d52b22\"\n },\n {\n \"url\": \"tags/队列/index.html\",\n \"revision\": \"4de09531dab7581fee1a1865ca6e1cee\"\n }\n], {});\n\n\n\n\nworkbox_routing_registerRoute(/^https:\\/\\/npm\\.elemecdn\\.com\\/.*/, new workbox_strategies_CacheFirst(), 'GET');\nworkbox_routing_registerRoute(/^https:\\/\\/cdn\\.jsdelivr\\.com\\/.*/, new workbox_strategies_CacheFirst(), 'GET');\nworkbox_routing_registerRoute(/^https:\\/\\/jsd\\.cdn\\.zzko\\.cn\\/.*/, new workbox_strategies_CacheFirst(), 'GET');\nworkbox_routing_registerRoute(/^https:\\/\\/unpkg\\.com\\/.*/, new workbox_strategies_CacheFirst(), 'GET');\n\n\n\n\n"],"names":["self","skipWaiting","workbox_core_clientsClaim","workbox_precaching_precacheAndRoute","url","revision","workbox","registerRoute","workbox_strategies_CacheFirst"],"mappings":"0nBAsBAA,KAAKC,cAELC,EAAAA,eAQAC,EAAAA,iBAAoC,CAClC,CACEC,IAAO,WACPC,SAAY,oCAEd,CACED,IAAO,mBACPC,SAAY,oCAEd,CACED,IAAO,8BACPC,SAAY,oCAEd,CACED,IAAO,8BACPC,SAAY,oCAEd,CACED,IAAO,8BACPC,SAAY,oCAEd,CACED,IAAO,8BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,kCACPC,SAAY,oCAEd,CACED,IAAO,8BACPC,SAAY,oCAEd,CACED,IAAO,8BACPC,SAAY,oCAEd,CACED,IAAO,8BACPC,SAAY,oCAEd,CACED,IAAO,8BACPC,SAAY,oCAEd,CACED,IAAO,8BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,kCACPC,SAAY,oCAEd,CACED,IAAO,8BACPC,SAAY,oCAEd,CACED,IAAO,8BACPC,SAAY,oCAEd,CACED,IAAO,8BACPC,SAAY,oCAEd,CACED,IAAO,8BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,sBACPC,SAAY,oCAEd,CACED,IAAO,6BACPC,SAAY,oCAEd,CACED,IAAO,6BACPC,SAAY,oCAEd,CACED,IAAO,6BACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,kCACPC,SAAY,oCAEd,CACED,IAAO,iCACPC,SAAY,oCAEd,CACED,IAAO,wBACPC,SAAY,oCAEd,CACED,IAAO,6BACPC,SAAY,oCAEd,CACED,IAAO,mCACPC,SAAY,oCAEd,CACED,IAAO,iCACPC,SAAY,oCAEd,CACED,IAAO,8BACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,6BACPC,SAAY,oCAEd,CACED,IAAO,6BACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,sBACPC,SAAY,oCAEd,CACED,IAAO,iBACPC,SAAY,oCAEd,CACED,IAAO,gBACPC,SAAY,oCAEd,CACED,IAAO,cACPC,SAAY,oCAEd,CACED,IAAO,mBACPC,SAAY,oCAEd,CACED,IAAO,cACPC,SAAY,oCAEd,CACED,IAAO,kBACPC,SAAY,oCAEd,CACED,IAAO,qBACPC,SAAY,oCAEd,CACED,IAAO,aACPC,SAAY,oCAEd,CACED,IAAO,gBACPC,SAAY,oCAEd,CACED,IAAO,aACPC,SAAY,oCAEd,CACED,IAAO,uBACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,cACPC,SAAY,oCAEd,CACED,IAAO,cACPC,SAAY,oCAEd,CACED,IAAO,kBACPC,SAAY,oCAEd,CACED,IAAO,mBACPC,SAAY,oCAEd,CACED,IAAO,oBACPC,SAAY,oCAEd,CACED,IAAO,oBACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,yBACPC,SAAY,oCAEd,CACED,IAAO,0BACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,yBACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,oBACPC,SAAY,oCAEd,CACED,IAAO,oBACPC,SAAY,oCAEd,CACED,IAAO,kCACPC,SAAY,oCAEd,CACED,IAAO,oCACPC,SAAY,oCAEd,CACED,IAAO,oCACPC,SAAY,oCAEd,CACED,IAAO,oCACPC,SAAY,oCAEd,CACED,IAAO,oCACPC,SAAY,oCAEd,CACED,IAAO,oCACPC,SAAY,oCAEd,CACED,IAAO,oCACPC,SAAY,oCAEd,CACED,IAAO,kCACPC,SAAY,oCAEd,CACED,IAAO,kCACPC,SAAY,oCAEd,CACED,IAAO,mCACPC,SAAY,oCAEd,CACED,IAAO,6BACPC,SAAY,oCAEd,CACED,IAAO,6BACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,sBACPC,SAAY,oCAEd,CACED,IAAO,yBACPC,SAAY,oCAEd,CACED,IAAO,kBACPC,SAAY,oCAEd,CACED,IAAO,uBACPC,SAAY,oCAEd,CACED,IAAO,6BACPC,SAAY,oCAEd,CACED,IAAO,oCACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,0BACPC,SAAY,oCAEd,CACED,IAAO,wBACPC,SAAY,oCAEd,CACED,IAAO,wBACPC,SAAY,oCAEd,CACED,IAAO,yBACPC,SAAY,oCAEd,CACED,IAAO,yBACPC,SAAY,oCAEd,CACED,IAAO,wBACPC,SAAY,oCAEd,CACED,IAAO,wBACPC,SAAY,oCAEd,CACED,IAAO,6BACPC,SAAY,oCAEd,CACED,IAAO,yBACPC,SAAY,oCAEd,CACED,IAAO,uBACPC,SAAY,oCAEd,CACED,IAAO,sBACPC,SAAY,oCAEd,CACED,IAAO,yBACPC,SAAY,oCAEd,CACED,IAAO,0BACPC,SAAY,oCAEd,CACED,IAAO,sBACPC,SAAY,oCAEd,CACED,IAAO,sBACPC,SAAY,oCAEd,CACED,IAAO,0BACPC,SAAY,oCAEd,CACED,IAAO,qBACPC,SAAY,oCAEd,CACED,IAAO,uBACPC,SAAY,oCAEd,CACED,IAAO,uBACPC,SAAY,oCAEd,CACED,IAAO,oBACPC,SAAY,oCAEd,CACED,IAAO,qBACPC,SAAY,oCAEd,CACED,IAAO,uBACPC,SAAY,oCAEd,CACED,IAAO,sBACPC,SAAY,oCAEd,CACED,IAAO,qBACPC,SAAY,oCAEd,CACED,IAAO,uBACPC,SAAY,oCAEd,CACED,IAAO,qBACPC,SAAY,oCAEd,CACED,IAAO,sBACPC,SAAY,oCAEd,CACED,IAAO,uBACPC,SAAY,oCAEd,CACED,IAAO,qBACPC,SAAY,qCAEb,CAAE,GAKwBC,EAAAC,cAAC,oCAAqC,IAAIC,aAAiC,OAC3EF,EAAAC,cAAC,oCAAqC,IAAIC,aAAiC,OAC3EF,EAAAC,cAAC,oCAAqC,IAAIC,aAAiC,OAC3EF,EAAAC,cAAC,4BAA6B,IAAIC,aAAiC"} \ No newline at end of file diff --git a/sitemap.txt b/sitemap.txt new file mode 100644 index 000000000..ae2bd175b --- /dev/null +++ b/sitemap.txt @@ -0,0 +1,90 @@ +http://blog.refrain.site/pwa/manifest.json +http://blog.refrain.site/comments/index.html +http://blog.refrain.site/posts/300a8ca5/ +http://blog.refrain.site/posts/aac5e919/ +http://blog.refrain.site/posts/adf5c49/ +http://blog.refrain.site/posts/b672a0f6/ +http://blog.refrain.site/posts/1be7bb2b/ +http://blog.refrain.site/posts/ffb66302/ +http://blog.refrain.site/posts/582b690b/ +http://blog.refrain.site/posts/a1605017/ +http://blog.refrain.site/posts/fccf75e5/ +http://blog.refrain.site/posts/1afd9955/ +http://blog.refrain.site/posts/9614c7d1/ +http://blog.refrain.site/posts/2afbd983/ +http://blog.refrain.site/posts/3c94855e/ +http://blog.refrain.site/posts/2275c8/ +http://blog.refrain.site/posts/acc0017d/ +http://blog.refrain.site/posts/b2b6ba06/ +http://blog.refrain.site/posts/35a88b44/ +http://blog.refrain.site/posts/e89bd903/ +http://blog.refrain.site/posts/d3748e5f/ +http://blog.refrain.site/music/index.html +http://blog.refrain.site/drive/index.html +http://blog.refrain.site/about/index.html +http://blog.refrain.site/link/index.html +http://blog.refrain.site/categories/index.html +http://blog.refrain.site/tags/index.html +http://blog.refrain.site/posts/8d55d49a/ +http://blog.refrain.site/posts/6534ce06/ +http://blog.refrain.site/posts/40f24371/ +http://blog.refrain.site/posts/b06cc6ec/ +http://blog.refrain.site/posts/b67f488/ +http://blog.refrain.site/posts/75378e04/ +http://blog.refrain.site/posts/170bc017/ +http://blog.refrain.site/posts/775eb342/ +http://blog.refrain.site/posts/54c08517/ +http://blog.refrain.site/posts/355699d5/ +http://blog.refrain.site/posts/20642/ +http://blog.refrain.site/posts/47341/ +http://blog.refrain.site/ +http://blog.refrain.site/tags/JavaScript/ +http://blog.refrain.site/tags/WebAPI/ +http://blog.refrain.site/tags/Front-End/ +http://blog.refrain.site/tags/React/ +http://blog.refrain.site/tags/Framework/ +http://blog.refrain.site/tags/TypeScript/ +http://blog.refrain.site/tags/FrontEnd/ +http://blog.refrain.site/tags/Vue/ +http://blog.refrain.site/tags/JS-Basic/ +http://blog.refrain.site/tags/Regex/ +http://blog.refrain.site/tags/Vite/ +http://blog.refrain.site/tags/Build-Tool/ +http://blog.refrain.site/tags/cloudflare/ +http://blog.refrain.site/tags/%E5%9F%9F%E5%90%8D/ +http://blog.refrain.site/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/ +http://blog.refrain.site/tags/%E7%AE%97%E6%B3%95/ +http://blog.refrain.site/tags/Git/ +http://blog.refrain.site/tags/GitHub/ +http://blog.refrain.site/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/ +http://blog.refrain.site/tags/Windows/ +http://blog.refrain.site/tags/%E4%BB%BB%E5%8A%A1%E6%A0%8F/ +http://blog.refrain.site/tags/%E7%BE%8E%E5%8C%96/ +http://blog.refrain.site/tags/Ubuntu/ +http://blog.refrain.site/tags/%E8%99%9A%E6%8B%9F%E6%9C%BA/ +http://blog.refrain.site/tags/%E7%BA%BF%E6%80%A7%E8%A1%A8/ +http://blog.refrain.site/tags/%E7%BB%AA%E8%AE%BA/ +http://blog.refrain.site/tags/%E6%A0%88/ +http://blog.refrain.site/tags/%E9%98%9F%E5%88%97/ +http://blog.refrain.site/tags/Java/ +http://blog.refrain.site/tags/%E7%BC%96%E7%A8%8B%E5%AD%A6%E4%B9%A0/ +http://blog.refrain.site/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/ +http://blog.refrain.site/tags/python/ +http://blog.refrain.site/tags/%E5%86%B3%E7%AD%96%E6%A0%91/ +http://blog.refrain.site/tags/Numpy/ +http://blog.refrain.site/tags/Python/ +http://blog.refrain.site/tags/k-means/ +http://blog.refrain.site/tags/kNN%E7%AE%97%E6%B3%95/ +http://blog.refrain.site/tags/%E7%BA%BF%E6%80%A7%E5%9B%9E%E5%BD%92/ +http://blog.refrain.site/tags/%E9%80%BB%E8%BE%91%E5%9B%9E%E5%BD%92/ +http://blog.refrain.site/categories/JavaScript/ +http://blog.refrain.site/categories/Front-End/ +http://blog.refrain.site/categories/React/ +http://blog.refrain.site/categories/FrontEnd/ +http://blog.refrain.site/categories/Vue/ +http://blog.refrain.site/categories/DNS/ +http://blog.refrain.site/categories/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/ +http://blog.refrain.site/categories/%E7%99%BE%E5%AE%9D%E7%AE%B1/ +http://blog.refrain.site/categories/LeetCode/ +http://blog.refrain.site/categories/Java/ +http://blog.refrain.site/categories/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/ diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 000000000..45185a6ec --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,716 @@ + + + + + http://blog.refrain.site/pwa/manifest.json + + 2024-08-06 + + monthly + 0.6 + + + + http://blog.refrain.site/comments/index.html + + 2024-08-06 + + monthly + 0.6 + + + + http://blog.refrain.site/posts/300a8ca5/ + + 2024-08-06 + + monthly + 0.6 + + + + http://blog.refrain.site/posts/aac5e919/ + + 2024-05-30 + + monthly + 0.6 + + + + http://blog.refrain.site/posts/adf5c49/ + + 2024-05-10 + + monthly + 0.6 + + + + http://blog.refrain.site/posts/b672a0f6/ + + 2024-04-15 + + monthly + 0.6 + + + + http://blog.refrain.site/posts/1be7bb2b/ + + 2024-03-29 + + monthly + 0.6 + + + + http://blog.refrain.site/posts/ffb66302/ + + 2024-03-05 + + monthly + 0.6 + + + + http://blog.refrain.site/posts/582b690b/ + + 2024-03-02 + + monthly + 0.6 + + + + http://blog.refrain.site/posts/a1605017/ + + 2024-01-01 + + monthly + 0.6 + + + + http://blog.refrain.site/posts/fccf75e5/ + + 2024-01-01 + + monthly + 0.6 + + + + http://blog.refrain.site/posts/1afd9955/ + + 2024-01-01 + + monthly + 0.6 + + + + http://blog.refrain.site/posts/9614c7d1/ + + 2024-01-01 + + monthly + 0.6 + + + + http://blog.refrain.site/posts/2afbd983/ + + 2023-12-31 + + monthly + 0.6 + + + + http://blog.refrain.site/posts/3c94855e/ + + 2023-12-31 + + monthly + 0.6 + + + + http://blog.refrain.site/posts/2275c8/ + + 2023-12-24 + + monthly + 0.6 + + + + http://blog.refrain.site/posts/acc0017d/ + + 2023-12-12 + + monthly + 0.6 + + + + http://blog.refrain.site/posts/b2b6ba06/ + + 2023-11-24 + + monthly + 0.6 + + + + http://blog.refrain.site/posts/35a88b44/ + + 2023-11-21 + + monthly + 0.6 + + + + http://blog.refrain.site/posts/e89bd903/ + + 2023-11-21 + + monthly + 0.6 + + + + http://blog.refrain.site/posts/d3748e5f/ + + 2023-09-26 + + monthly + 0.6 + + + + http://blog.refrain.site/music/index.html + + 2023-09-22 + + monthly + 0.6 + + + + http://blog.refrain.site/drive/index.html + + 2023-09-22 + + monthly + 0.6 + + + + http://blog.refrain.site/about/index.html + + 2023-09-11 + + monthly + 0.6 + + + + http://blog.refrain.site/link/index.html + + 2023-05-01 + + monthly + 0.6 + + + + http://blog.refrain.site/categories/index.html + + 2023-05-01 + + monthly + 0.6 + + + + http://blog.refrain.site/tags/index.html + + 2023-05-01 + + monthly + 0.6 + + + + http://blog.refrain.site/posts/8d55d49a/ + + 2023-03-07 + + monthly + 0.6 + + + + http://blog.refrain.site/posts/6534ce06/ + + 2023-01-09 + + monthly + 0.6 + + + + http://blog.refrain.site/posts/40f24371/ + + 2022-12-31 + + monthly + 0.6 + + + + http://blog.refrain.site/posts/b06cc6ec/ + + 2022-12-12 + + monthly + 0.6 + + + + http://blog.refrain.site/posts/b67f488/ + + 2022-12-02 + + monthly + 0.6 + + + + http://blog.refrain.site/posts/75378e04/ + + 2022-08-20 + + monthly + 0.6 + + + + http://blog.refrain.site/posts/170bc017/ + + 2022-08-17 + + monthly + 0.6 + + + + http://blog.refrain.site/posts/775eb342/ + + 2022-08-14 + + monthly + 0.6 + + + + http://blog.refrain.site/posts/54c08517/ + + 2022-08-07 + + monthly + 0.6 + + + + http://blog.refrain.site/posts/355699d5/ + + 2022-08-06 + + monthly + 0.6 + + + + http://blog.refrain.site/posts/20642/ + + 2022-07-22 + + monthly + 0.6 + + + + http://blog.refrain.site/posts/47341/ + + 2022-07-18 + + monthly + 0.6 + + + + + http://blog.refrain.site/ + 2024-08-06 + daily + 1.0 + + + + + http://blog.refrain.site/tags/JavaScript/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/WebAPI/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/Front-End/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/React/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/Framework/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/TypeScript/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/FrontEnd/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/Vue/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/JS-Basic/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/Regex/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/Vite/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/Build-Tool/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/cloudflare/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/%E5%9F%9F%E5%90%8D/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/%E7%AE%97%E6%B3%95/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/Git/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/GitHub/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/Windows/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/%E4%BB%BB%E5%8A%A1%E6%A0%8F/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/%E7%BE%8E%E5%8C%96/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/Ubuntu/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/%E8%99%9A%E6%8B%9F%E6%9C%BA/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/%E7%BA%BF%E6%80%A7%E8%A1%A8/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/%E7%BB%AA%E8%AE%BA/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/%E6%A0%88/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/%E9%98%9F%E5%88%97/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/Java/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/%E7%BC%96%E7%A8%8B%E5%AD%A6%E4%B9%A0/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/python/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/%E5%86%B3%E7%AD%96%E6%A0%91/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/Numpy/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/Python/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/k-means/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/kNN%E7%AE%97%E6%B3%95/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/%E7%BA%BF%E6%80%A7%E5%9B%9E%E5%BD%92/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/tags/%E9%80%BB%E8%BE%91%E5%9B%9E%E5%BD%92/ + 2024-08-06 + weekly + 0.2 + + + + + + http://blog.refrain.site/categories/JavaScript/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/categories/Front-End/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/categories/React/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/categories/FrontEnd/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/categories/Vue/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/categories/DNS/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/categories/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/categories/%E7%99%BE%E5%AE%9D%E7%AE%B1/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/categories/LeetCode/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/categories/Java/ + 2024-08-06 + weekly + 0.2 + + + + http://blog.refrain.site/categories/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/ + 2024-08-06 + weekly + 0.2 + + + diff --git a/tags/Build-Tool/index.html b/tags/Build-Tool/index.html new file mode 100644 index 000000000..6e30adb47 --- /dev/null +++ b/tags/Build-Tool/index.html @@ -0,0 +1,498 @@ +标签: Build Tool | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - Build Tool
2023
Vite基础知识总结
Vite基础知识总结
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/tags/Framework/index.html b/tags/Framework/index.html new file mode 100644 index 000000000..926c78380 --- /dev/null +++ b/tags/Framework/index.html @@ -0,0 +1,498 @@ +标签: Framework | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - Framework
2023
React笔记
React笔记
Vue3笔记
Vue3笔记
Vue2笔记
Vue2笔记
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/tags/Front-End/index.html b/tags/Front-End/index.html new file mode 100644 index 000000000..65304bf67 --- /dev/null +++ b/tags/Front-End/index.html @@ -0,0 +1,498 @@ +标签: Front End | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - Front End
2023
Intersection Observer
Intersection Observer
React笔记
React笔记
Vue3笔记
Vue3笔记
Vue2笔记
Vue2笔记
前端知识笔记
前端知识笔记
JS拾遗笔记
JS拾遗笔记
JS事件循环
JS事件循环
深入了解Promise
深入了解Promise
Vite基础知识总结
Vite基础知识总结
REGEX in JavaScript
REGEX in JavaScript
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/tags/FrontEnd/index.html b/tags/FrontEnd/index.html new file mode 100644 index 000000000..1ba16dfc8 --- /dev/null +++ b/tags/FrontEnd/index.html @@ -0,0 +1,498 @@ +标签: FrontEnd | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - FrontEnd
2024
TypeSript笔记
TypeSript笔记
2023
数据结构与算法笔记
数据结构与算法笔记
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/tags/Git/index.html b/tags/Git/index.html new file mode 100644 index 000000000..fb956aff2 --- /dev/null +++ b/tags/Git/index.html @@ -0,0 +1,498 @@ +标签: Git | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - Git
2023
Git 笔记
Git 笔记
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/tags/GitHub/index.html b/tags/GitHub/index.html new file mode 100644 index 000000000..c11e0aab7 --- /dev/null +++ b/tags/GitHub/index.html @@ -0,0 +1,498 @@ +标签: GitHub | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - GitHub
2023
Git 笔记
Git 笔记
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/tags/JS-Basic/index.html b/tags/JS-Basic/index.html new file mode 100644 index 000000000..f610010ad --- /dev/null +++ b/tags/JS-Basic/index.html @@ -0,0 +1,498 @@ +标签: JS Basic | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - JS Basic
2023
JS拾遗笔记
JS拾遗笔记
JS事件循环
JS事件循环
深入了解Promise
深入了解Promise
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/tags/Java/index.html b/tags/Java/index.html new file mode 100644 index 000000000..ff0618815 --- /dev/null +++ b/tags/Java/index.html @@ -0,0 +1,498 @@ +标签: Java | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - Java
2023
JAVA学习笔记
JAVA学习笔记
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/tags/JavaScript/index.html b/tags/JavaScript/index.html new file mode 100644 index 000000000..b25595fb9 --- /dev/null +++ b/tags/JavaScript/index.html @@ -0,0 +1,498 @@ +标签: JavaScript | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - JavaScript
2024
虚拟列表
虚拟列表
JS手写题大汇总
JS手写题大汇总
TypeSript笔记
TypeSript笔记
2023
Intersection Observer
Intersection Observer
数据结构与算法笔记
数据结构与算法笔记
React笔记
React笔记
Vue3笔记
Vue3笔记
Vue2笔记
Vue2笔记
JS拾遗笔记
JS拾遗笔记
JS事件循环
JS事件循环
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/tags/JavaScript/page/2/index.html b/tags/JavaScript/page/2/index.html new file mode 100644 index 000000000..d32e24531 --- /dev/null +++ b/tags/JavaScript/page/2/index.html @@ -0,0 +1,498 @@ +标签: JavaScript | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - JavaScript
2023
深入了解Promise
深入了解Promise
Vite基础知识总结
Vite基础知识总结
REGEX in JavaScript
REGEX in JavaScript
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/tags/Numpy/index.html b/tags/Numpy/index.html new file mode 100644 index 000000000..acbfc5364 --- /dev/null +++ b/tags/Numpy/index.html @@ -0,0 +1,498 @@ +标签: Numpy | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/tags/Python/index.html b/tags/Python/index.html new file mode 100644 index 000000000..7945d7f02 --- /dev/null +++ b/tags/Python/index.html @@ -0,0 +1,498 @@ +标签: Python | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - Python
2022
使用numpy实现k-means聚类算法
使用numpy实现k-means聚类算法
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/tags/React/index.html b/tags/React/index.html new file mode 100644 index 000000000..1ac1a246b --- /dev/null +++ b/tags/React/index.html @@ -0,0 +1,498 @@ +标签: React | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - React
2024
虚拟列表
虚拟列表
2023
React笔记
React笔记
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/tags/Regex/index.html b/tags/Regex/index.html new file mode 100644 index 000000000..c38156b2d --- /dev/null +++ b/tags/Regex/index.html @@ -0,0 +1,498 @@ +标签: Regex | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - Regex
2023
REGEX in JavaScript
REGEX in JavaScript
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/tags/TypeScript/index.html b/tags/TypeScript/index.html new file mode 100644 index 000000000..07e0fb680 --- /dev/null +++ b/tags/TypeScript/index.html @@ -0,0 +1,498 @@ +标签: TypeScript | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - TypeScript
2024
TypeSript笔记
TypeSript笔记
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/tags/Ubuntu/index.html b/tags/Ubuntu/index.html new file mode 100644 index 000000000..6de5d13b3 --- /dev/null +++ b/tags/Ubuntu/index.html @@ -0,0 +1,498 @@ +标签: Ubuntu | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - Ubuntu
2023
Windows/Ubuntu双系统安装教程
Windows/Ubuntu双系统安装教程
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/tags/Vite/index.html b/tags/Vite/index.html new file mode 100644 index 000000000..572f4e4c7 --- /dev/null +++ b/tags/Vite/index.html @@ -0,0 +1,498 @@ +标签: Vite | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - Vite
2023
Vite基础知识总结
Vite基础知识总结
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/tags/Vue/index.html b/tags/Vue/index.html new file mode 100644 index 000000000..e4783a414 --- /dev/null +++ b/tags/Vue/index.html @@ -0,0 +1,498 @@ +标签: Vue | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - Vue
2023
Vue3笔记
Vue3笔记
Vue2笔记
Vue2笔记
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/tags/WebAPI/index.html b/tags/WebAPI/index.html new file mode 100644 index 000000000..7dd2ae9bc --- /dev/null +++ b/tags/WebAPI/index.html @@ -0,0 +1,498 @@ +标签: WebAPI | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - WebAPI
2023
Intersection Observer
Intersection Observer
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/tags/Windows/index.html b/tags/Windows/index.html new file mode 100644 index 000000000..4603f0693 --- /dev/null +++ b/tags/Windows/index.html @@ -0,0 +1,498 @@ +标签: Windows | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - Windows
2023
Windows/Ubuntu双系统安装教程
Windows/Ubuntu双系统安装教程
2022
Win10/11任务栏透明美化
Win10/11任务栏透明美化
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/tags/cloudflare/index.html b/tags/cloudflare/index.html new file mode 100644 index 000000000..698e4db09 --- /dev/null +++ b/tags/cloudflare/index.html @@ -0,0 +1,498 @@ +标签: cloudflare | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - cloudflare
2022
关于cloudflare对网站搭建的使用
关于cloudflare对网站搭建的使用
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/tags/index.html b/tags/index.html new file mode 100644 index 000000000..67893f3df --- /dev/null +++ b/tags/index.html @@ -0,0 +1,583 @@ +tags | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/tags/k-means/index.html b/tags/k-means/index.html new file mode 100644 index 000000000..51731a946 --- /dev/null +++ b/tags/k-means/index.html @@ -0,0 +1,498 @@ +标签: k-means | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - k-means
2022
使用numpy实现k-means聚类算法
使用numpy实现k-means聚类算法
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git "a/tags/kNN\347\256\227\346\263\225/index.html" "b/tags/kNN\347\256\227\346\263\225/index.html" new file mode 100644 index 000000000..377114ae5 --- /dev/null +++ "b/tags/kNN\347\256\227\346\263\225/index.html" @@ -0,0 +1,498 @@ +标签: kNN算法 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - kNN算法
2022
使用Numpy实现k-Nearest-Neighbor算法
使用Numpy实现k-Nearest-Neighbor算法
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/tags/python/index.html b/tags/python/index.html new file mode 100644 index 000000000..3ac1d05bc --- /dev/null +++ b/tags/python/index.html @@ -0,0 +1,498 @@ +标签: python | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
+ + + + \ No newline at end of file diff --git "a/tags/\344\273\273\345\212\241\346\240\217/index.html" "b/tags/\344\273\273\345\212\241\346\240\217/index.html" new file mode 100644 index 000000000..e2b993d5f --- /dev/null +++ "b/tags/\344\273\273\345\212\241\346\240\217/index.html" @@ -0,0 +1,498 @@ +标签: 任务栏 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - 任务栏
2022
Win10/11任务栏透明美化
Win10/11任务栏透明美化
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git "a/tags/\345\206\263\347\255\226\346\240\221/index.html" "b/tags/\345\206\263\347\255\226\346\240\221/index.html" new file mode 100644 index 000000000..9855c6a63 --- /dev/null +++ "b/tags/\345\206\263\347\255\226\346\240\221/index.html" @@ -0,0 +1,498 @@ +标签: 决策树 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - 决策树
2022
Python实现决策树(Decision Tree)算法
Python实现决策树(Decision Tree)算法
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git "a/tags/\345\210\206\345\270\203\345\274\217\347\256\241\347\220\206\347\263\273\347\273\237/index.html" "b/tags/\345\210\206\345\270\203\345\274\217\347\256\241\347\220\206\347\263\273\347\273\237/index.html" new file mode 100644 index 000000000..99a321a81 --- /dev/null +++ "b/tags/\345\210\206\345\270\203\345\274\217\347\256\241\347\220\206\347\263\273\347\273\237/index.html" @@ -0,0 +1,498 @@ +标签: 分布式管理系统 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - 分布式管理系统
2023
Git 笔记
Git 笔记
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git "a/tags/\345\237\237\345\220\215/index.html" "b/tags/\345\237\237\345\220\215/index.html" new file mode 100644 index 000000000..38e67048a --- /dev/null +++ "b/tags/\345\237\237\345\220\215/index.html" @@ -0,0 +1,498 @@ +标签: 域名 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - 域名
2022
关于cloudflare对网站搭建的使用
关于cloudflare对网站搭建的使用
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git "a/tags/\346\225\260\346\215\256\347\273\223\346\236\204/index.html" "b/tags/\346\225\260\346\215\256\347\273\223\346\236\204/index.html" new file mode 100644 index 000000000..bf6567d24 --- /dev/null +++ "b/tags/\346\225\260\346\215\256\347\273\223\346\236\204/index.html" @@ -0,0 +1,498 @@ +标签: 数据结构 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - 数据结构
2024
LeetCode 算法笔记 Part3
LeetCode 算法笔记 Part3
LeetCode 算法笔记 Part2
LeetCode 算法笔记 Part2
LeetCode 算法笔记 Part1
LeetCode 算法笔记 Part1
2023
数据结构与算法笔记
数据结构与算法笔记
2022
栈与队列
栈与队列
线性表
线性表
数据结构绪论
数据结构绪论
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git "a/tags/\346\234\272\345\231\250\345\255\246\344\271\240/index.html" "b/tags/\346\234\272\345\231\250\345\255\246\344\271\240/index.html" new file mode 100644 index 000000000..9eebb7d91 --- /dev/null +++ "b/tags/\346\234\272\345\231\250\345\255\246\344\271\240/index.html" @@ -0,0 +1,498 @@ +标签: 机器学习 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
+ + + + \ No newline at end of file diff --git "a/tags/\346\240\210/index.html" "b/tags/\346\240\210/index.html" new file mode 100644 index 000000000..3041c4dfe --- /dev/null +++ "b/tags/\346\240\210/index.html" @@ -0,0 +1,498 @@ +标签: 栈 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - 栈
2022
栈与队列
栈与队列
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git "a/tags/\347\256\227\346\263\225/index.html" "b/tags/\347\256\227\346\263\225/index.html" new file mode 100644 index 000000000..54eca2512 --- /dev/null +++ "b/tags/\347\256\227\346\263\225/index.html" @@ -0,0 +1,498 @@ +标签: 算法 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git "a/tags/\347\272\277\346\200\247\345\233\236\345\275\222/index.html" "b/tags/\347\272\277\346\200\247\345\233\236\345\275\222/index.html" new file mode 100644 index 000000000..7a0a27f13 --- /dev/null +++ "b/tags/\347\272\277\346\200\247\345\233\236\345\275\222/index.html" @@ -0,0 +1,498 @@ +标签: 线性回归 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - 线性回归
2022
线性回归 (Linear Regression)
线性回归 (Linear Regression)
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git "a/tags/\347\272\277\346\200\247\350\241\250/index.html" "b/tags/\347\272\277\346\200\247\350\241\250/index.html" new file mode 100644 index 000000000..d527e02a9 --- /dev/null +++ "b/tags/\347\272\277\346\200\247\350\241\250/index.html" @@ -0,0 +1,498 @@ +标签: 线性表 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - 线性表
2022
线性表
线性表
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git "a/tags/\347\273\252\350\256\272/index.html" "b/tags/\347\273\252\350\256\272/index.html" new file mode 100644 index 000000000..b0c84c95e --- /dev/null +++ "b/tags/\347\273\252\350\256\272/index.html" @@ -0,0 +1,498 @@ +标签: 绪论 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - 绪论
2022
数据结构绪论
数据结构绪论
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git "a/tags/\347\274\226\347\250\213\345\255\246\344\271\240/index.html" "b/tags/\347\274\226\347\250\213\345\255\246\344\271\240/index.html" new file mode 100644 index 000000000..c20c5cb0f --- /dev/null +++ "b/tags/\347\274\226\347\250\213\345\255\246\344\271\240/index.html" @@ -0,0 +1,498 @@ +标签: 编程学习 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - 编程学习
2023
JAVA学习笔记
JAVA学习笔记
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git "a/tags/\347\276\216\345\214\226/index.html" "b/tags/\347\276\216\345\214\226/index.html" new file mode 100644 index 000000000..287ae0508 --- /dev/null +++ "b/tags/\347\276\216\345\214\226/index.html" @@ -0,0 +1,498 @@ +标签: 美化 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - 美化
2022
Win10/11任务栏透明美化
Win10/11任务栏透明美化
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git "a/tags/\350\231\232\346\213\237\346\234\272/index.html" "b/tags/\350\231\232\346\213\237\346\234\272/index.html" new file mode 100644 index 000000000..efe18c298 --- /dev/null +++ "b/tags/\350\231\232\346\213\237\346\234\272/index.html" @@ -0,0 +1,498 @@ +标签: 虚拟机 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - 虚拟机
2023
Windows/Ubuntu双系统安装教程
Windows/Ubuntu双系统安装教程
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git "a/tags/\351\200\273\350\276\221\345\233\236\345\275\222/index.html" "b/tags/\351\200\273\350\276\221\345\233\236\345\275\222/index.html" new file mode 100644 index 000000000..e2ca0ea2f --- /dev/null +++ "b/tags/\351\200\273\350\276\221\345\233\236\345\275\222/index.html" @@ -0,0 +1,498 @@ +标签: 逻辑回归 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - 逻辑回归
2022
Numpy实现逻辑回归(Logistic Regression)算法
Numpy实现逻辑回归(Logistic Regression)算法
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git "a/tags/\351\230\237\345\210\227/index.html" "b/tags/\351\230\237\345\210\227/index.html" new file mode 100644 index 000000000..dda7c4019 --- /dev/null +++ "b/tags/\351\230\237\345\210\227/index.html" @@ -0,0 +1,498 @@ +标签: 队列 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - 队列
2022
栈与队列
栈与队列
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/workbox-f2630e7e.js b/workbox-f2630e7e.js new file mode 100644 index 000000000..78f7b95c0 --- /dev/null +++ b/workbox-f2630e7e.js @@ -0,0 +1 @@ +define(["exports"],(function(e){"use strict";try{self["workbox:core:6.6.0"]&&_()}catch(e){}class t extends Error{constructor(e,t){super(((e,...t)=>{let s=e;return t.length>0&&(s+=` :: ${JSON.stringify(t)}`),s})(e,t)),this.name=e,this.details=t}}try{self["workbox:routing:6.6.0"]&&_()}catch(e){}const s=e=>e&&"object"==typeof e?e:{handle:e};class n{constructor(e,t,n="GET"){this.handler=s(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=s(e)}}class r extends n{constructor(e,t,s){super((({url:t})=>{const s=e.exec(t.href);if(s&&(t.origin===location.origin||0===s.index))return s.slice(1)}),t,s)}}class a{constructor(){this.t=new Map,this.i=new Map}get routes(){return this.t}addFetchListener(){self.addEventListener("fetch",(e=>{const{request:t}=e,s=this.handleRequest({request:t,event:e});s&&e.respondWith(s)}))}addCacheListener(){self.addEventListener("message",(e=>{if(e.data&&"CACHE_URLS"===e.data.type){const{payload:t}=e.data,s=Promise.all(t.urlsToCache.map((t=>{"string"==typeof t&&(t=[t]);const s=new Request(...t);return this.handleRequest({request:s,event:e})})));e.waitUntil(s),e.ports&&e.ports[0]&&s.then((()=>e.ports[0].postMessage(!0)))}}))}handleRequest({request:e,event:t}){const s=new URL(e.url,location.href);if(!s.protocol.startsWith("http"))return;const n=s.origin===location.origin,{params:r,route:a}=this.findMatchingRoute({event:t,request:e,sameOrigin:n,url:s});let i=a&&a.handler;const c=e.method;if(!i&&this.i.has(c)&&(i=this.i.get(c)),!i)return;let o;try{o=i.handle({url:s,request:e,event:t,params:r})}catch(e){o=Promise.reject(e)}const h=a&&a.catchHandler;return o instanceof Promise&&(this.o||h)&&(o=o.catch((async n=>{if(h)try{return await h.handle({url:s,request:e,event:t,params:r})}catch(e){e instanceof Error&&(n=e)}if(this.o)return this.o.handle({url:s,request:e,event:t});throw n}))),o}findMatchingRoute({url:e,sameOrigin:t,request:s,event:n}){const r=this.t.get(s.method)||[];for(const a of r){let r;const i=a.match({url:e,sameOrigin:t,request:s,event:n});if(i)return r=i,(Array.isArray(r)&&0===r.length||i.constructor===Object&&0===Object.keys(i).length||"boolean"==typeof i)&&(r=void 0),{route:a,params:r}}return{}}setDefaultHandler(e,t="GET"){this.i.set(t,s(e))}setCatchHandler(e){this.o=s(e)}registerRoute(e){this.t.has(e.method)||this.t.set(e.method,[]),this.t.get(e.method).push(e)}unregisterRoute(e){if(!this.t.has(e.method))throw new t("unregister-route-but-not-found-with-method",{method:e.method});const s=this.t.get(e.method).indexOf(e);if(!(s>-1))throw new t("unregister-route-route-not-registered");this.t.get(e.method).splice(s,1)}}let i;const c=()=>(i||(i=new a,i.addFetchListener(),i.addCacheListener()),i);function o(e,s,a){let i;if("string"==typeof e){const t=new URL(e,location.href);i=new n((({url:e})=>e.href===t.href),s,a)}else if(e instanceof RegExp)i=new r(e,s,a);else if("function"==typeof e)i=new n(e,s,a);else{if(!(e instanceof n))throw new t("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});i=e}return c().registerRoute(i),i}const h={googleAnalytics:"googleAnalytics",precache:"precache-v2",prefix:"workbox",runtime:"runtime",suffix:"undefined"!=typeof registration?registration.scope:""},l=e=>[h.prefix,e,h.suffix].filter((e=>e&&e.length>0)).join("-"),u=e=>e||l(h.precache);function f(e,t){const s=new URL(e);for(const e of t)s.searchParams.delete(e);return s.href}class d{constructor(){this.promise=new Promise(((e,t)=>{this.resolve=e,this.reject=t}))}}const p=new Set;try{self["workbox:strategies:6.6.0"]&&_()}catch(e){}function w(e){return"string"==typeof e?new Request(e):e}class g{constructor(e,t){this.h={},Object.assign(this,t),this.event=t.event,this.u=e,this.l=new d,this.p=[],this.R=[...e.plugins],this.m=new Map;for(const e of this.R)this.m.set(e,{});this.event.waitUntil(this.l.promise)}async fetch(e){const{event:s}=this;let n=w(e);if("navigate"===n.mode&&s instanceof FetchEvent&&s.preloadResponse){const e=await s.preloadResponse;if(e)return e}const r=this.hasCallback("fetchDidFail")?n.clone():null;try{for(const e of this.iterateCallbacks("requestWillFetch"))n=await e({request:n.clone(),event:s})}catch(e){if(e instanceof Error)throw new t("plugin-error-request-will-fetch",{thrownErrorMessage:e.message})}const a=n.clone();try{let e;e=await fetch(n,"navigate"===n.mode?void 0:this.u.fetchOptions);for(const t of this.iterateCallbacks("fetchDidSucceed"))e=await t({event:s,request:a,response:e});return e}catch(e){throw r&&await this.runCallbacks("fetchDidFail",{error:e,event:s,originalRequest:r.clone(),request:a.clone()}),e}}async fetchAndCachePut(e){const t=await this.fetch(e),s=t.clone();return this.waitUntil(this.cachePut(e,s)),t}async cacheMatch(e){const t=w(e);let s;const{cacheName:n,matchOptions:r}=this.u,a=await this.getCacheKey(t,"read"),i=Object.assign(Object.assign({},r),{cacheName:n});s=await caches.match(a,i);for(const e of this.iterateCallbacks("cachedResponseWillBeUsed"))s=await e({cacheName:n,matchOptions:r,cachedResponse:s,request:a,event:this.event})||void 0;return s}async cachePut(e,s){const n=w(e);await(0,new Promise((e=>setTimeout(e,0))));const r=await this.getCacheKey(n,"write");if(!s)throw new t("cache-put-with-no-response",{url:(a=r.url,new URL(String(a),location.href).href.replace(new RegExp(`^${location.origin}`),""))});var a;const i=await this.v(s);if(!i)return!1;const{cacheName:c,matchOptions:o}=this.u,h=await self.caches.open(c),l=this.hasCallback("cacheDidUpdate"),u=l?await async function(e,t,s,n){const r=f(t.url,s);if(t.url===r)return e.match(t,n);const a=Object.assign(Object.assign({},n),{ignoreSearch:!0}),i=await e.keys(t,a);for(const t of i)if(r===f(t.url,s))return e.match(t,n)}(h,r.clone(),["__WB_REVISION__"],o):null;try{await h.put(r,l?i.clone():i)}catch(e){if(e instanceof Error)throw"QuotaExceededError"===e.name&&await async function(){for(const e of p)await e()}(),e}for(const e of this.iterateCallbacks("cacheDidUpdate"))await e({cacheName:c,oldResponse:u,newResponse:i.clone(),request:r,event:this.event});return!0}async getCacheKey(e,t){const s=`${e.url} | ${t}`;if(!this.h[s]){let n=e;for(const e of this.iterateCallbacks("cacheKeyWillBeUsed"))n=w(await e({mode:t,request:n,event:this.event,params:this.params}));this.h[s]=n}return this.h[s]}hasCallback(e){for(const t of this.u.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(const s of this.iterateCallbacks(e))await s(t)}*iterateCallbacks(e){for(const t of this.u.plugins)if("function"==typeof t[e]){const s=this.m.get(t),n=n=>{const r=Object.assign(Object.assign({},n),{state:s});return t[e](r)};yield n}}waitUntil(e){return this.p.push(e),e}async doneWaiting(){let e;for(;e=this.p.shift();)await e}destroy(){this.l.resolve(null)}async v(e){let t=e,s=!1;for(const e of this.iterateCallbacks("cacheWillUpdate"))if(t=await e({request:this.request,response:t,event:this.event})||void 0,s=!0,!t)break;return s||t&&200!==t.status&&(t=void 0),t}}class y{constructor(e={}){this.cacheName=(e=>e||l(h.runtime))(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){const[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});const t=e.event,s="string"==typeof e.request?new Request(e.request):e.request,n="params"in e?e.params:void 0,r=new g(this,{event:t,request:s,params:n}),a=this.q(r,s,t);return[a,this.U(a,r,s,t)]}async q(e,s,n){let r;await e.runCallbacks("handlerWillStart",{event:n,request:s});try{if(r=await this.L(s,e),!r||"error"===r.type)throw new t("no-response",{url:s.url})}catch(t){if(t instanceof Error)for(const a of e.iterateCallbacks("handlerDidError"))if(r=await a({error:t,event:n,request:s}),r)break;if(!r)throw t}for(const t of e.iterateCallbacks("handlerWillRespond"))r=await t({event:n,request:s,response:r});return r}async U(e,t,s,n){let r,a;try{r=await e}catch(a){}try{await t.runCallbacks("handlerDidRespond",{event:n,request:s,response:r}),await t.doneWaiting()}catch(e){e instanceof Error&&(a=e)}if(await t.runCallbacks("handlerDidComplete",{event:n,request:s,response:r,error:a}),t.destroy(),a)throw a}}function m(e,t){const s=t();return e.waitUntil(s),s}try{self["workbox:precaching:6.6.0"]&&_()}catch(e){}function R(e){if(!e)throw new t("add-to-cache-list-unexpected-type",{entry:e});if("string"==typeof e){const t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}const{revision:s,url:n}=e;if(!n)throw new t("add-to-cache-list-unexpected-type",{entry:e});if(!s){const e=new URL(n,location.href);return{cacheKey:e.href,url:e.href}}const r=new URL(n,location.href),a=new URL(n,location.href);return r.searchParams.set("__WB_REVISION__",s),{cacheKey:r.href,url:a.href}}class v{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:s})=>{if("install"===e.type&&t&&t.originalRequest&&t.originalRequest instanceof Request){const e=t.originalRequest.url;s?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return s}}}class b{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:e,params:t})=>{const s=(null==t?void 0:t.cacheKey)||this._.getCacheKeyForURL(e.url);return s?new Request(s,{headers:e.headers}):e},this._=e}}let q,U;class C extends y{constructor(e={}){e.cacheName=u(e.cacheName),super(e),this.C=!1!==e.fallbackToNetwork,this.plugins.push(C.copyRedirectedCacheableResponsesPlugin)}async L(e,t){return await t.cacheMatch(e)||(t.event&&"install"===t.event.type?await this.O(e,t):await this.N(e,t))}async N(e,s){let n;const r=s.params||{};if(!this.C)throw new t("missing-precache-entry",{cacheName:this.cacheName,url:e.url});{const t=r.integrity,a=e.integrity,i=!a||a===t;n=await s.fetch(new Request(e,{integrity:"no-cors"!==e.mode?a||t:void 0})),t&&i&&"no-cors"!==e.mode&&(this.k(),await s.cachePut(e,n.clone()))}return n}async O(e,s){this.k();const n=await s.fetch(e);if(!await s.cachePut(e,n.clone()))throw new t("bad-precaching-response",{url:e.url,status:n.status});return n}k(){let e=null,t=0;for(const[s,n]of this.plugins.entries())n!==C.copyRedirectedCacheableResponsesPlugin&&(n===C.defaultPrecacheCacheabilityPlugin&&(e=s),n.cacheWillUpdate&&t++);0===t?this.plugins.push(C.defaultPrecacheCacheabilityPlugin):t>1&&null!==e&&this.plugins.splice(e,1)}}C.defaultPrecacheCacheabilityPlugin={cacheWillUpdate:async({response:e})=>!e||e.status>=400?null:e},C.copyRedirectedCacheableResponsesPlugin={cacheWillUpdate:async({response:e})=>e.redirected?await async function(e,s){let n=null;if(e.url&&(n=new URL(e.url).origin),n!==self.location.origin)throw new t("cross-origin-copy-response",{origin:n});const r=e.clone(),a={headers:new Headers(r.headers),status:r.status,statusText:r.statusText},i=s?s(a):a,c=function(){if(void 0===q){const e=new Response("");if("body"in e)try{new Response(e.body),q=!0}catch(e){q=!1}q=!1}return q}()?r.body:await r.blob();return new Response(c,i)}(e):e};class L{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:s=!0}={}){this.K=new Map,this.T=new Map,this.W=new Map,this.u=new C({cacheName:u(e),plugins:[...t,new b({precacheController:this})],fallbackToNetwork:s}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this.u}precache(e){this.addToCacheList(e),this.j||(self.addEventListener("install",this.install),self.addEventListener("activate",this.activate),this.j=!0)}addToCacheList(e){const s=[];for(const n of e){"string"==typeof n?s.push(n):n&&void 0===n.revision&&s.push(n.url);const{cacheKey:e,url:r}=R(n),a="string"!=typeof n&&n.revision?"reload":"default";if(this.K.has(r)&&this.K.get(r)!==e)throw new t("add-to-cache-list-conflicting-entries",{firstEntry:this.K.get(r),secondEntry:e});if("string"!=typeof n&&n.integrity){if(this.W.has(e)&&this.W.get(e)!==n.integrity)throw new t("add-to-cache-list-conflicting-integrities",{url:r});this.W.set(e,n.integrity)}if(this.K.set(r,e),this.T.set(r,a),s.length>0){const e=`Workbox is precaching URLs without revision info: ${s.join(", ")}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(e)}}}install(e){return m(e,(async()=>{const t=new v;this.strategy.plugins.push(t);for(const[t,s]of this.K){const n=this.W.get(s),r=this.T.get(t),a=new Request(t,{integrity:n,cache:r,credentials:"same-origin"});await Promise.all(this.strategy.handleAll({params:{cacheKey:s},request:a,event:e}))}const{updatedURLs:s,notUpdatedURLs:n}=t;return{updatedURLs:s,notUpdatedURLs:n}}))}activate(e){return m(e,(async()=>{const e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),s=new Set(this.K.values()),n=[];for(const r of t)s.has(r.url)||(await e.delete(r),n.push(r.url));return{deletedURLs:n}}))}getURLsToCacheKeys(){return this.K}getCachedURLs(){return[...this.K.keys()]}getCacheKeyForURL(e){const t=new URL(e,location.href);return this.K.get(t.href)}getIntegrityForCacheKey(e){return this.W.get(e)}async matchPrecache(e){const t=e instanceof Request?e.url:e,s=this.getCacheKeyForURL(t);if(s)return(await self.caches.open(this.strategy.cacheName)).match(s)}createHandlerBoundToURL(e){const s=this.getCacheKeyForURL(e);if(!s)throw new t("non-precached-url",{url:e});return t=>(t.request=new Request(e),t.params=Object.assign({cacheKey:s},t.params),this.strategy.handle(t))}}const k=()=>(U||(U=new L),U);class K extends n{constructor(e,t){super((({request:s})=>{const n=e.getURLsToCacheKeys();for(const r of function*(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:s="index.html",cleanURLs:n=!0,urlManipulation:r}={}){const a=new URL(e,location.href);a.hash="",yield a.href;const i=function(e,t=[]){for(const s of[...e.searchParams.keys()])t.some((e=>e.test(s)))&&e.searchParams.delete(s);return e}(a,t);if(yield i.href,s&&i.pathname.endsWith("/")){const e=new URL(i.href);e.pathname+=s,yield e.href}if(n){const e=new URL(i.href);e.pathname+=".html",yield e.href}if(r){const e=r({url:a});for(const t of e)yield t.href}}(s.url,t)){const t=n.get(r);if(t)return{cacheKey:t,integrity:e.getIntegrityForCacheKey(t)}}}),e.strategy)}}e.CacheFirst=class extends y{async L(e,s){let n,r=await s.cacheMatch(e);if(!r)try{r=await s.fetchAndCachePut(e)}catch(e){e instanceof Error&&(n=e)}if(!r)throw new t("no-response",{url:e.url,error:n});return r}},e.clientsClaim=function(){self.addEventListener("activate",(()=>self.clients.claim()))},e.precacheAndRoute=function(e,t){!function(e){k().precache(e)}(e),function(e){const t=k();o(new K(t,e))}(t)},e.registerRoute=o})); \ No newline at end of file diff --git a/workbox-f2630e7e.js.map b/workbox-f2630e7e.js.map new file mode 100644 index 000000000..9cccf173e --- /dev/null +++ b/workbox-f2630e7e.js.map @@ -0,0 +1 @@ +{"version":3,"file":"workbox-f2630e7e.js","sources":["node_modules/workbox-core/_version.js","node_modules/workbox-core/_private/logger.js","node_modules/workbox-core/models/messages/messageGenerator.js","node_modules/workbox-core/_private/WorkboxError.js","node_modules/workbox-routing/_version.js","node_modules/workbox-routing/utils/constants.js","node_modules/workbox-routing/utils/normalizeHandler.js","node_modules/workbox-routing/Route.js","node_modules/workbox-routing/RegExpRoute.js","node_modules/workbox-routing/Router.js","node_modules/workbox-routing/utils/getOrCreateDefaultRouter.js","node_modules/workbox-routing/registerRoute.js","node_modules/workbox-core/_private/cacheNames.js","node_modules/workbox-core/_private/cacheMatchIgnoreParams.js","node_modules/workbox-core/_private/Deferred.js","node_modules/workbox-core/models/quotaErrorCallbacks.js","node_modules/workbox-strategies/_version.js","node_modules/workbox-strategies/StrategyHandler.js","node_modules/workbox-core/_private/timeout.js","node_modules/workbox-core/_private/getFriendlyURL.js","node_modules/workbox-core/_private/executeQuotaErrorCallbacks.js","node_modules/workbox-strategies/Strategy.js","node_modules/workbox-core/_private/waitUntil.js","node_modules/workbox-precaching/_version.js","node_modules/workbox-precaching/utils/createCacheKey.js","node_modules/workbox-precaching/utils/PrecacheInstallReportPlugin.js","node_modules/workbox-precaching/utils/PrecacheCacheKeyPlugin.js","node_modules/workbox-core/_private/canConstructResponseFromBodyStream.js","node_modules/workbox-precaching/utils/getOrCreatePrecacheController.js","node_modules/workbox-core/copyResponse.js","node_modules/workbox-precaching/PrecacheStrategy.js","node_modules/workbox-precaching/PrecacheController.js","node_modules/workbox-precaching/PrecacheRoute.js","node_modules/workbox-precaching/utils/generateURLVariations.js","node_modules/workbox-precaching/utils/removeIgnoredSearchParams.js","node_modules/workbox-strategies/CacheFirst.js","node_modules/workbox-core/clientsClaim.js","node_modules/workbox-precaching/precacheAndRoute.js","node_modules/workbox-precaching/precache.js","node_modules/workbox-precaching/addRoute.js"],"sourcesContent":["\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:core:6.6.0'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2019 Google LLC\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\nconst logger = (process.env.NODE_ENV === 'production'\n ? null\n : (() => {\n // Don't overwrite this value if it's already set.\n // See https://github.com/GoogleChrome/workbox/pull/2284#issuecomment-560470923\n if (!('__WB_DISABLE_DEV_LOGS' in globalThis)) {\n self.__WB_DISABLE_DEV_LOGS = false;\n }\n let inGroup = false;\n const methodToColorMap = {\n debug: `#7f8c8d`,\n log: `#2ecc71`,\n warn: `#f39c12`,\n error: `#c0392b`,\n groupCollapsed: `#3498db`,\n groupEnd: null, // No colored prefix on groupEnd\n };\n const print = function (method, args) {\n if (self.__WB_DISABLE_DEV_LOGS) {\n return;\n }\n if (method === 'groupCollapsed') {\n // Safari doesn't print all console.groupCollapsed() arguments:\n // https://bugs.webkit.org/show_bug.cgi?id=182754\n if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) {\n console[method](...args);\n return;\n }\n }\n const styles = [\n `background: ${methodToColorMap[method]}`,\n `border-radius: 0.5em`,\n `color: white`,\n `font-weight: bold`,\n `padding: 2px 0.5em`,\n ];\n // When in a group, the workbox prefix is not displayed.\n const logPrefix = inGroup ? [] : ['%cworkbox', styles.join(';')];\n console[method](...logPrefix, ...args);\n if (method === 'groupCollapsed') {\n inGroup = true;\n }\n if (method === 'groupEnd') {\n inGroup = false;\n }\n };\n // eslint-disable-next-line @typescript-eslint/ban-types\n const api = {};\n const loggerMethods = Object.keys(methodToColorMap);\n for (const key of loggerMethods) {\n const method = key;\n api[method] = (...args) => {\n print(method, args);\n };\n }\n return api;\n })());\nexport { logger };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { messages } from './messages.js';\nimport '../../_version.js';\nconst fallback = (code, ...args) => {\n let msg = code;\n if (args.length > 0) {\n msg += ` :: ${JSON.stringify(args)}`;\n }\n return msg;\n};\nconst generatorFunction = (code, details = {}) => {\n const message = messages[code];\n if (!message) {\n throw new Error(`Unable to find message for code '${code}'.`);\n }\n return message(details);\n};\nexport const messageGenerator = process.env.NODE_ENV === 'production' ? fallback : generatorFunction;\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { messageGenerator } from '../models/messages/messageGenerator.js';\nimport '../_version.js';\n/**\n * Workbox errors should be thrown with this class.\n * This allows use to ensure the type easily in tests,\n * helps developers identify errors from workbox\n * easily and allows use to optimise error\n * messages correctly.\n *\n * @private\n */\nclass WorkboxError extends Error {\n /**\n *\n * @param {string} errorCode The error code that\n * identifies this particular error.\n * @param {Object=} details Any relevant arguments\n * that will help developers identify issues should\n * be added as a key on the context object.\n */\n constructor(errorCode, details) {\n const message = messageGenerator(errorCode, details);\n super(message);\n this.name = errorCode;\n this.details = details;\n }\n}\nexport { WorkboxError };\n","\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:routing:6.6.0'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\n/**\n * The default HTTP method, 'GET', used when there's no specific method\n * configured for a route.\n *\n * @type {string}\n *\n * @private\n */\nexport const defaultMethod = 'GET';\n/**\n * The list of valid HTTP methods associated with requests that could be routed.\n *\n * @type {Array}\n *\n * @private\n */\nexport const validMethods = [\n 'DELETE',\n 'GET',\n 'HEAD',\n 'PATCH',\n 'POST',\n 'PUT',\n];\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport '../_version.js';\n/**\n * @param {function()|Object} handler Either a function, or an object with a\n * 'handle' method.\n * @return {Object} An object with a handle method.\n *\n * @private\n */\nexport const normalizeHandler = (handler) => {\n if (handler && typeof handler === 'object') {\n if (process.env.NODE_ENV !== 'production') {\n assert.hasMethod(handler, 'handle', {\n moduleName: 'workbox-routing',\n className: 'Route',\n funcName: 'constructor',\n paramName: 'handler',\n });\n }\n return handler;\n }\n else {\n if (process.env.NODE_ENV !== 'production') {\n assert.isType(handler, 'function', {\n moduleName: 'workbox-routing',\n className: 'Route',\n funcName: 'constructor',\n paramName: 'handler',\n });\n }\n return { handle: handler };\n }\n};\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { defaultMethod, validMethods } from './utils/constants.js';\nimport { normalizeHandler } from './utils/normalizeHandler.js';\nimport './_version.js';\n/**\n * A `Route` consists of a pair of callback functions, \"match\" and \"handler\".\n * The \"match\" callback determine if a route should be used to \"handle\" a\n * request by returning a non-falsy value if it can. The \"handler\" callback\n * is called when there is a match and should return a Promise that resolves\n * to a `Response`.\n *\n * @memberof workbox-routing\n */\nclass Route {\n /**\n * Constructor for Route class.\n *\n * @param {workbox-routing~matchCallback} match\n * A callback function that determines whether the route matches a given\n * `fetch` event by returning a non-falsy value.\n * @param {workbox-routing~handlerCallback} handler A callback\n * function that returns a Promise resolving to a Response.\n * @param {string} [method='GET'] The HTTP method to match the Route\n * against.\n */\n constructor(match, handler, method = defaultMethod) {\n if (process.env.NODE_ENV !== 'production') {\n assert.isType(match, 'function', {\n moduleName: 'workbox-routing',\n className: 'Route',\n funcName: 'constructor',\n paramName: 'match',\n });\n if (method) {\n assert.isOneOf(method, validMethods, { paramName: 'method' });\n }\n }\n // These values are referenced directly by Router so cannot be\n // altered by minificaton.\n this.handler = normalizeHandler(handler);\n this.match = match;\n this.method = method;\n }\n /**\n *\n * @param {workbox-routing-handlerCallback} handler A callback\n * function that returns a Promise resolving to a Response\n */\n setCatchHandler(handler) {\n this.catchHandler = normalizeHandler(handler);\n }\n}\nexport { Route };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { Route } from './Route.js';\nimport './_version.js';\n/**\n * RegExpRoute makes it easy to create a regular expression based\n * {@link workbox-routing.Route}.\n *\n * For same-origin requests the RegExp only needs to match part of the URL. For\n * requests against third-party servers, you must define a RegExp that matches\n * the start of the URL.\n *\n * @memberof workbox-routing\n * @extends workbox-routing.Route\n */\nclass RegExpRoute extends Route {\n /**\n * If the regular expression contains\n * [capture groups]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#grouping-back-references},\n * the captured values will be passed to the\n * {@link workbox-routing~handlerCallback} `params`\n * argument.\n *\n * @param {RegExp} regExp The regular expression to match against URLs.\n * @param {workbox-routing~handlerCallback} handler A callback\n * function that returns a Promise resulting in a Response.\n * @param {string} [method='GET'] The HTTP method to match the Route\n * against.\n */\n constructor(regExp, handler, method) {\n if (process.env.NODE_ENV !== 'production') {\n assert.isInstance(regExp, RegExp, {\n moduleName: 'workbox-routing',\n className: 'RegExpRoute',\n funcName: 'constructor',\n paramName: 'pattern',\n });\n }\n const match = ({ url }) => {\n const result = regExp.exec(url.href);\n // Return immediately if there's no match.\n if (!result) {\n return;\n }\n // Require that the match start at the first character in the URL string\n // if it's a cross-origin request.\n // See https://github.com/GoogleChrome/workbox/issues/281 for the context\n // behind this behavior.\n if (url.origin !== location.origin && result.index !== 0) {\n if (process.env.NODE_ENV !== 'production') {\n logger.debug(`The regular expression '${regExp.toString()}' only partially matched ` +\n `against the cross-origin URL '${url.toString()}'. RegExpRoute's will only ` +\n `handle cross-origin requests if they match the entire URL.`);\n }\n return;\n }\n // If the route matches, but there aren't any capture groups defined, then\n // this will return [], which is truthy and therefore sufficient to\n // indicate a match.\n // If there are capture groups, then it will return their values.\n return result.slice(1);\n };\n super(match, handler, method);\n }\n}\nexport { RegExpRoute };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { getFriendlyURL } from 'workbox-core/_private/getFriendlyURL.js';\nimport { defaultMethod } from './utils/constants.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { normalizeHandler } from './utils/normalizeHandler.js';\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport './_version.js';\n/**\n * The Router can be used to process a `FetchEvent` using one or more\n * {@link workbox-routing.Route}, responding with a `Response` if\n * a matching route exists.\n *\n * If no route matches a given a request, the Router will use a \"default\"\n * handler if one is defined.\n *\n * Should the matching Route throw an error, the Router will use a \"catch\"\n * handler if one is defined to gracefully deal with issues and respond with a\n * Request.\n *\n * If a request matches multiple routes, the **earliest** registered route will\n * be used to respond to the request.\n *\n * @memberof workbox-routing\n */\nclass Router {\n /**\n * Initializes a new Router.\n */\n constructor() {\n this._routes = new Map();\n this._defaultHandlerMap = new Map();\n }\n /**\n * @return {Map>} routes A `Map` of HTTP\n * method name ('GET', etc.) to an array of all the corresponding `Route`\n * instances that are registered.\n */\n get routes() {\n return this._routes;\n }\n /**\n * Adds a fetch event listener to respond to events when a route matches\n * the event's request.\n */\n addFetchListener() {\n // See https://github.com/Microsoft/TypeScript/issues/28357#issuecomment-436484705\n self.addEventListener('fetch', ((event) => {\n const { request } = event;\n const responsePromise = this.handleRequest({ request, event });\n if (responsePromise) {\n event.respondWith(responsePromise);\n }\n }));\n }\n /**\n * Adds a message event listener for URLs to cache from the window.\n * This is useful to cache resources loaded on the page prior to when the\n * service worker started controlling it.\n *\n * The format of the message data sent from the window should be as follows.\n * Where the `urlsToCache` array may consist of URL strings or an array of\n * URL string + `requestInit` object (the same as you'd pass to `fetch()`).\n *\n * ```\n * {\n * type: 'CACHE_URLS',\n * payload: {\n * urlsToCache: [\n * './script1.js',\n * './script2.js',\n * ['./script3.js', {mode: 'no-cors'}],\n * ],\n * },\n * }\n * ```\n */\n addCacheListener() {\n // See https://github.com/Microsoft/TypeScript/issues/28357#issuecomment-436484705\n self.addEventListener('message', ((event) => {\n // event.data is type 'any'\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n if (event.data && event.data.type === 'CACHE_URLS') {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const { payload } = event.data;\n if (process.env.NODE_ENV !== 'production') {\n logger.debug(`Caching URLs from the window`, payload.urlsToCache);\n }\n const requestPromises = Promise.all(payload.urlsToCache.map((entry) => {\n if (typeof entry === 'string') {\n entry = [entry];\n }\n const request = new Request(...entry);\n return this.handleRequest({ request, event });\n // TODO(philipwalton): TypeScript errors without this typecast for\n // some reason (probably a bug). The real type here should work but\n // doesn't: `Array | undefined>`.\n })); // TypeScript\n event.waitUntil(requestPromises);\n // If a MessageChannel was used, reply to the message on success.\n if (event.ports && event.ports[0]) {\n void requestPromises.then(() => event.ports[0].postMessage(true));\n }\n }\n }));\n }\n /**\n * Apply the routing rules to a FetchEvent object to get a Response from an\n * appropriate Route's handler.\n *\n * @param {Object} options\n * @param {Request} options.request The request to handle.\n * @param {ExtendableEvent} options.event The event that triggered the\n * request.\n * @return {Promise|undefined} A promise is returned if a\n * registered route can handle the request. If there is no matching\n * route and there's no `defaultHandler`, `undefined` is returned.\n */\n handleRequest({ request, event, }) {\n if (process.env.NODE_ENV !== 'production') {\n assert.isInstance(request, Request, {\n moduleName: 'workbox-routing',\n className: 'Router',\n funcName: 'handleRequest',\n paramName: 'options.request',\n });\n }\n const url = new URL(request.url, location.href);\n if (!url.protocol.startsWith('http')) {\n if (process.env.NODE_ENV !== 'production') {\n logger.debug(`Workbox Router only supports URLs that start with 'http'.`);\n }\n return;\n }\n const sameOrigin = url.origin === location.origin;\n const { params, route } = this.findMatchingRoute({\n event,\n request,\n sameOrigin,\n url,\n });\n let handler = route && route.handler;\n const debugMessages = [];\n if (process.env.NODE_ENV !== 'production') {\n if (handler) {\n debugMessages.push([`Found a route to handle this request:`, route]);\n if (params) {\n debugMessages.push([\n `Passing the following params to the route's handler:`,\n params,\n ]);\n }\n }\n }\n // If we don't have a handler because there was no matching route, then\n // fall back to defaultHandler if that's defined.\n const method = request.method;\n if (!handler && this._defaultHandlerMap.has(method)) {\n if (process.env.NODE_ENV !== 'production') {\n debugMessages.push(`Failed to find a matching route. Falling ` +\n `back to the default handler for ${method}.`);\n }\n handler = this._defaultHandlerMap.get(method);\n }\n if (!handler) {\n if (process.env.NODE_ENV !== 'production') {\n // No handler so Workbox will do nothing. If logs is set of debug\n // i.e. verbose, we should print out this information.\n logger.debug(`No route found for: ${getFriendlyURL(url)}`);\n }\n return;\n }\n if (process.env.NODE_ENV !== 'production') {\n // We have a handler, meaning Workbox is going to handle the route.\n // print the routing details to the console.\n logger.groupCollapsed(`Router is responding to: ${getFriendlyURL(url)}`);\n debugMessages.forEach((msg) => {\n if (Array.isArray(msg)) {\n logger.log(...msg);\n }\n else {\n logger.log(msg);\n }\n });\n logger.groupEnd();\n }\n // Wrap in try and catch in case the handle method throws a synchronous\n // error. It should still callback to the catch handler.\n let responsePromise;\n try {\n responsePromise = handler.handle({ url, request, event, params });\n }\n catch (err) {\n responsePromise = Promise.reject(err);\n }\n // Get route's catch handler, if it exists\n const catchHandler = route && route.catchHandler;\n if (responsePromise instanceof Promise &&\n (this._catchHandler || catchHandler)) {\n responsePromise = responsePromise.catch(async (err) => {\n // If there's a route catch handler, process that first\n if (catchHandler) {\n if (process.env.NODE_ENV !== 'production') {\n // Still include URL here as it will be async from the console group\n // and may not make sense without the URL\n logger.groupCollapsed(`Error thrown when responding to: ` +\n ` ${getFriendlyURL(url)}. Falling back to route's Catch Handler.`);\n logger.error(`Error thrown by:`, route);\n logger.error(err);\n logger.groupEnd();\n }\n try {\n return await catchHandler.handle({ url, request, event, params });\n }\n catch (catchErr) {\n if (catchErr instanceof Error) {\n err = catchErr;\n }\n }\n }\n if (this._catchHandler) {\n if (process.env.NODE_ENV !== 'production') {\n // Still include URL here as it will be async from the console group\n // and may not make sense without the URL\n logger.groupCollapsed(`Error thrown when responding to: ` +\n ` ${getFriendlyURL(url)}. Falling back to global Catch Handler.`);\n logger.error(`Error thrown by:`, route);\n logger.error(err);\n logger.groupEnd();\n }\n return this._catchHandler.handle({ url, request, event });\n }\n throw err;\n });\n }\n return responsePromise;\n }\n /**\n * Checks a request and URL (and optionally an event) against the list of\n * registered routes, and if there's a match, returns the corresponding\n * route along with any params generated by the match.\n *\n * @param {Object} options\n * @param {URL} options.url\n * @param {boolean} options.sameOrigin The result of comparing `url.origin`\n * against the current origin.\n * @param {Request} options.request The request to match.\n * @param {Event} options.event The corresponding event.\n * @return {Object} An object with `route` and `params` properties.\n * They are populated if a matching route was found or `undefined`\n * otherwise.\n */\n findMatchingRoute({ url, sameOrigin, request, event, }) {\n const routes = this._routes.get(request.method) || [];\n for (const route of routes) {\n let params;\n // route.match returns type any, not possible to change right now.\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const matchResult = route.match({ url, sameOrigin, request, event });\n if (matchResult) {\n if (process.env.NODE_ENV !== 'production') {\n // Warn developers that using an async matchCallback is almost always\n // not the right thing to do.\n if (matchResult instanceof Promise) {\n logger.warn(`While routing ${getFriendlyURL(url)}, an async ` +\n `matchCallback function was used. Please convert the ` +\n `following route to use a synchronous matchCallback function:`, route);\n }\n }\n // See https://github.com/GoogleChrome/workbox/issues/2079\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n params = matchResult;\n if (Array.isArray(params) && params.length === 0) {\n // Instead of passing an empty array in as params, use undefined.\n params = undefined;\n }\n else if (matchResult.constructor === Object && // eslint-disable-line\n Object.keys(matchResult).length === 0) {\n // Instead of passing an empty object in as params, use undefined.\n params = undefined;\n }\n else if (typeof matchResult === 'boolean') {\n // For the boolean value true (rather than just something truth-y),\n // don't set params.\n // See https://github.com/GoogleChrome/workbox/pull/2134#issuecomment-513924353\n params = undefined;\n }\n // Return early if have a match.\n return { route, params };\n }\n }\n // If no match was found above, return and empty object.\n return {};\n }\n /**\n * Define a default `handler` that's called when no routes explicitly\n * match the incoming request.\n *\n * Each HTTP method ('GET', 'POST', etc.) gets its own default handler.\n *\n * Without a default handler, unmatched requests will go against the\n * network as if there were no service worker present.\n *\n * @param {workbox-routing~handlerCallback} handler A callback\n * function that returns a Promise resulting in a Response.\n * @param {string} [method='GET'] The HTTP method to associate with this\n * default handler. Each method has its own default.\n */\n setDefaultHandler(handler, method = defaultMethod) {\n this._defaultHandlerMap.set(method, normalizeHandler(handler));\n }\n /**\n * If a Route throws an error while handling a request, this `handler`\n * will be called and given a chance to provide a response.\n *\n * @param {workbox-routing~handlerCallback} handler A callback\n * function that returns a Promise resulting in a Response.\n */\n setCatchHandler(handler) {\n this._catchHandler = normalizeHandler(handler);\n }\n /**\n * Registers a route with the router.\n *\n * @param {workbox-routing.Route} route The route to register.\n */\n registerRoute(route) {\n if (process.env.NODE_ENV !== 'production') {\n assert.isType(route, 'object', {\n moduleName: 'workbox-routing',\n className: 'Router',\n funcName: 'registerRoute',\n paramName: 'route',\n });\n assert.hasMethod(route, 'match', {\n moduleName: 'workbox-routing',\n className: 'Router',\n funcName: 'registerRoute',\n paramName: 'route',\n });\n assert.isType(route.handler, 'object', {\n moduleName: 'workbox-routing',\n className: 'Router',\n funcName: 'registerRoute',\n paramName: 'route',\n });\n assert.hasMethod(route.handler, 'handle', {\n moduleName: 'workbox-routing',\n className: 'Router',\n funcName: 'registerRoute',\n paramName: 'route.handler',\n });\n assert.isType(route.method, 'string', {\n moduleName: 'workbox-routing',\n className: 'Router',\n funcName: 'registerRoute',\n paramName: 'route.method',\n });\n }\n if (!this._routes.has(route.method)) {\n this._routes.set(route.method, []);\n }\n // Give precedence to all of the earlier routes by adding this additional\n // route to the end of the array.\n this._routes.get(route.method).push(route);\n }\n /**\n * Unregisters a route with the router.\n *\n * @param {workbox-routing.Route} route The route to unregister.\n */\n unregisterRoute(route) {\n if (!this._routes.has(route.method)) {\n throw new WorkboxError('unregister-route-but-not-found-with-method', {\n method: route.method,\n });\n }\n const routeIndex = this._routes.get(route.method).indexOf(route);\n if (routeIndex > -1) {\n this._routes.get(route.method).splice(routeIndex, 1);\n }\n else {\n throw new WorkboxError('unregister-route-route-not-registered');\n }\n }\n}\nexport { Router };\n","/*\n Copyright 2019 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { Router } from '../Router.js';\nimport '../_version.js';\nlet defaultRouter;\n/**\n * Creates a new, singleton Router instance if one does not exist. If one\n * does already exist, that instance is returned.\n *\n * @private\n * @return {Router}\n */\nexport const getOrCreateDefaultRouter = () => {\n if (!defaultRouter) {\n defaultRouter = new Router();\n // The helpers that use the default Router assume these listeners exist.\n defaultRouter.addFetchListener();\n defaultRouter.addCacheListener();\n }\n return defaultRouter;\n};\n","/*\n Copyright 2019 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport { Route } from './Route.js';\nimport { RegExpRoute } from './RegExpRoute.js';\nimport { getOrCreateDefaultRouter } from './utils/getOrCreateDefaultRouter.js';\nimport './_version.js';\n/**\n * Easily register a RegExp, string, or function with a caching\n * strategy to a singleton Router instance.\n *\n * This method will generate a Route for you if needed and\n * call {@link workbox-routing.Router#registerRoute}.\n *\n * @param {RegExp|string|workbox-routing.Route~matchCallback|workbox-routing.Route} capture\n * If the capture param is a `Route`, all other arguments will be ignored.\n * @param {workbox-routing~handlerCallback} [handler] A callback\n * function that returns a Promise resulting in a Response. This parameter\n * is required if `capture` is not a `Route` object.\n * @param {string} [method='GET'] The HTTP method to match the Route\n * against.\n * @return {workbox-routing.Route} The generated `Route`.\n *\n * @memberof workbox-routing\n */\nfunction registerRoute(capture, handler, method) {\n let route;\n if (typeof capture === 'string') {\n const captureUrl = new URL(capture, location.href);\n if (process.env.NODE_ENV !== 'production') {\n if (!(capture.startsWith('/') || capture.startsWith('http'))) {\n throw new WorkboxError('invalid-string', {\n moduleName: 'workbox-routing',\n funcName: 'registerRoute',\n paramName: 'capture',\n });\n }\n // We want to check if Express-style wildcards are in the pathname only.\n // TODO: Remove this log message in v4.\n const valueToCheck = capture.startsWith('http')\n ? captureUrl.pathname\n : capture;\n // See https://github.com/pillarjs/path-to-regexp#parameters\n const wildcards = '[*:?+]';\n if (new RegExp(`${wildcards}`).exec(valueToCheck)) {\n logger.debug(`The '$capture' parameter contains an Express-style wildcard ` +\n `character (${wildcards}). Strings are now always interpreted as ` +\n `exact matches; use a RegExp for partial or wildcard matches.`);\n }\n }\n const matchCallback = ({ url }) => {\n if (process.env.NODE_ENV !== 'production') {\n if (url.pathname === captureUrl.pathname &&\n url.origin !== captureUrl.origin) {\n logger.debug(`${capture} only partially matches the cross-origin URL ` +\n `${url.toString()}. This route will only handle cross-origin requests ` +\n `if they match the entire URL.`);\n }\n }\n return url.href === captureUrl.href;\n };\n // If `capture` is a string then `handler` and `method` must be present.\n route = new Route(matchCallback, handler, method);\n }\n else if (capture instanceof RegExp) {\n // If `capture` is a `RegExp` then `handler` and `method` must be present.\n route = new RegExpRoute(capture, handler, method);\n }\n else if (typeof capture === 'function') {\n // If `capture` is a function then `handler` and `method` must be present.\n route = new Route(capture, handler, method);\n }\n else if (capture instanceof Route) {\n route = capture;\n }\n else {\n throw new WorkboxError('unsupported-route-type', {\n moduleName: 'workbox-routing',\n funcName: 'registerRoute',\n paramName: 'capture',\n });\n }\n const defaultRouter = getOrCreateDefaultRouter();\n defaultRouter.registerRoute(route);\n return route;\n}\nexport { registerRoute };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\nconst _cacheNameDetails = {\n googleAnalytics: 'googleAnalytics',\n precache: 'precache-v2',\n prefix: 'workbox',\n runtime: 'runtime',\n suffix: typeof registration !== 'undefined' ? registration.scope : '',\n};\nconst _createCacheName = (cacheName) => {\n return [_cacheNameDetails.prefix, cacheName, _cacheNameDetails.suffix]\n .filter((value) => value && value.length > 0)\n .join('-');\n};\nconst eachCacheNameDetail = (fn) => {\n for (const key of Object.keys(_cacheNameDetails)) {\n fn(key);\n }\n};\nexport const cacheNames = {\n updateDetails: (details) => {\n eachCacheNameDetail((key) => {\n if (typeof details[key] === 'string') {\n _cacheNameDetails[key] = details[key];\n }\n });\n },\n getGoogleAnalyticsName: (userCacheName) => {\n return userCacheName || _createCacheName(_cacheNameDetails.googleAnalytics);\n },\n getPrecacheName: (userCacheName) => {\n return userCacheName || _createCacheName(_cacheNameDetails.precache);\n },\n getPrefix: () => {\n return _cacheNameDetails.prefix;\n },\n getRuntimeName: (userCacheName) => {\n return userCacheName || _createCacheName(_cacheNameDetails.runtime);\n },\n getSuffix: () => {\n return _cacheNameDetails.suffix;\n },\n};\n","/*\n Copyright 2020 Google LLC\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\nfunction stripParams(fullURL, ignoreParams) {\n const strippedURL = new URL(fullURL);\n for (const param of ignoreParams) {\n strippedURL.searchParams.delete(param);\n }\n return strippedURL.href;\n}\n/**\n * Matches an item in the cache, ignoring specific URL params. This is similar\n * to the `ignoreSearch` option, but it allows you to ignore just specific\n * params (while continuing to match on the others).\n *\n * @private\n * @param {Cache} cache\n * @param {Request} request\n * @param {Object} matchOptions\n * @param {Array} ignoreParams\n * @return {Promise}\n */\nasync function cacheMatchIgnoreParams(cache, request, ignoreParams, matchOptions) {\n const strippedRequestURL = stripParams(request.url, ignoreParams);\n // If the request doesn't include any ignored params, match as normal.\n if (request.url === strippedRequestURL) {\n return cache.match(request, matchOptions);\n }\n // Otherwise, match by comparing keys\n const keysOptions = Object.assign(Object.assign({}, matchOptions), { ignoreSearch: true });\n const cacheKeys = await cache.keys(request, keysOptions);\n for (const cacheKey of cacheKeys) {\n const strippedCacheKeyURL = stripParams(cacheKey.url, ignoreParams);\n if (strippedRequestURL === strippedCacheKeyURL) {\n return cache.match(cacheKey, matchOptions);\n }\n }\n return;\n}\nexport { cacheMatchIgnoreParams };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\n/**\n * The Deferred class composes Promises in a way that allows for them to be\n * resolved or rejected from outside the constructor. In most cases promises\n * should be used directly, but Deferreds can be necessary when the logic to\n * resolve a promise must be separate.\n *\n * @private\n */\nclass Deferred {\n /**\n * Creates a promise and exposes its resolve and reject functions as methods.\n */\n constructor() {\n this.promise = new Promise((resolve, reject) => {\n this.resolve = resolve;\n this.reject = reject;\n });\n }\n}\nexport { Deferred };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\n// Callbacks to be executed whenever there's a quota error.\n// Can't change Function type right now.\n// eslint-disable-next-line @typescript-eslint/ban-types\nconst quotaErrorCallbacks = new Set();\nexport { quotaErrorCallbacks };\n","\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:strategies:6.6.0'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2020 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { cacheMatchIgnoreParams } from 'workbox-core/_private/cacheMatchIgnoreParams.js';\nimport { Deferred } from 'workbox-core/_private/Deferred.js';\nimport { executeQuotaErrorCallbacks } from 'workbox-core/_private/executeQuotaErrorCallbacks.js';\nimport { getFriendlyURL } from 'workbox-core/_private/getFriendlyURL.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { timeout } from 'workbox-core/_private/timeout.js';\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport './_version.js';\nfunction toRequest(input) {\n return typeof input === 'string' ? new Request(input) : input;\n}\n/**\n * A class created every time a Strategy instance instance calls\n * {@link workbox-strategies.Strategy~handle} or\n * {@link workbox-strategies.Strategy~handleAll} that wraps all fetch and\n * cache actions around plugin callbacks and keeps track of when the strategy\n * is \"done\" (i.e. all added `event.waitUntil()` promises have resolved).\n *\n * @memberof workbox-strategies\n */\nclass StrategyHandler {\n /**\n * Creates a new instance associated with the passed strategy and event\n * that's handling the request.\n *\n * The constructor also initializes the state that will be passed to each of\n * the plugins handling this request.\n *\n * @param {workbox-strategies.Strategy} strategy\n * @param {Object} options\n * @param {Request|string} options.request A request to run this strategy for.\n * @param {ExtendableEvent} options.event The event associated with the\n * request.\n * @param {URL} [options.url]\n * @param {*} [options.params] The return value from the\n * {@link workbox-routing~matchCallback} (if applicable).\n */\n constructor(strategy, options) {\n this._cacheKeys = {};\n /**\n * The request the strategy is performing (passed to the strategy's\n * `handle()` or `handleAll()` method).\n * @name request\n * @instance\n * @type {Request}\n * @memberof workbox-strategies.StrategyHandler\n */\n /**\n * The event associated with this request.\n * @name event\n * @instance\n * @type {ExtendableEvent}\n * @memberof workbox-strategies.StrategyHandler\n */\n /**\n * A `URL` instance of `request.url` (if passed to the strategy's\n * `handle()` or `handleAll()` method).\n * Note: the `url` param will be present if the strategy was invoked\n * from a workbox `Route` object.\n * @name url\n * @instance\n * @type {URL|undefined}\n * @memberof workbox-strategies.StrategyHandler\n */\n /**\n * A `param` value (if passed to the strategy's\n * `handle()` or `handleAll()` method).\n * Note: the `param` param will be present if the strategy was invoked\n * from a workbox `Route` object and the\n * {@link workbox-routing~matchCallback} returned\n * a truthy value (it will be that value).\n * @name params\n * @instance\n * @type {*|undefined}\n * @memberof workbox-strategies.StrategyHandler\n */\n if (process.env.NODE_ENV !== 'production') {\n assert.isInstance(options.event, ExtendableEvent, {\n moduleName: 'workbox-strategies',\n className: 'StrategyHandler',\n funcName: 'constructor',\n paramName: 'options.event',\n });\n }\n Object.assign(this, options);\n this.event = options.event;\n this._strategy = strategy;\n this._handlerDeferred = new Deferred();\n this._extendLifetimePromises = [];\n // Copy the plugins list (since it's mutable on the strategy),\n // so any mutations don't affect this handler instance.\n this._plugins = [...strategy.plugins];\n this._pluginStateMap = new Map();\n for (const plugin of this._plugins) {\n this._pluginStateMap.set(plugin, {});\n }\n this.event.waitUntil(this._handlerDeferred.promise);\n }\n /**\n * Fetches a given request (and invokes any applicable plugin callback\n * methods) using the `fetchOptions` (for non-navigation requests) and\n * `plugins` defined on the `Strategy` object.\n *\n * The following plugin lifecycle methods are invoked when using this method:\n * - `requestWillFetch()`\n * - `fetchDidSucceed()`\n * - `fetchDidFail()`\n *\n * @param {Request|string} input The URL or request to fetch.\n * @return {Promise}\n */\n async fetch(input) {\n const { event } = this;\n let request = toRequest(input);\n if (request.mode === 'navigate' &&\n event instanceof FetchEvent &&\n event.preloadResponse) {\n const possiblePreloadResponse = (await event.preloadResponse);\n if (possiblePreloadResponse) {\n if (process.env.NODE_ENV !== 'production') {\n logger.log(`Using a preloaded navigation response for ` +\n `'${getFriendlyURL(request.url)}'`);\n }\n return possiblePreloadResponse;\n }\n }\n // If there is a fetchDidFail plugin, we need to save a clone of the\n // original request before it's either modified by a requestWillFetch\n // plugin or before the original request's body is consumed via fetch().\n const originalRequest = this.hasCallback('fetchDidFail')\n ? request.clone()\n : null;\n try {\n for (const cb of this.iterateCallbacks('requestWillFetch')) {\n request = await cb({ request: request.clone(), event });\n }\n }\n catch (err) {\n if (err instanceof Error) {\n throw new WorkboxError('plugin-error-request-will-fetch', {\n thrownErrorMessage: err.message,\n });\n }\n }\n // The request can be altered by plugins with `requestWillFetch` making\n // the original request (most likely from a `fetch` event) different\n // from the Request we make. Pass both to `fetchDidFail` to aid debugging.\n const pluginFilteredRequest = request.clone();\n try {\n let fetchResponse;\n // See https://github.com/GoogleChrome/workbox/issues/1796\n fetchResponse = await fetch(request, request.mode === 'navigate' ? undefined : this._strategy.fetchOptions);\n if (process.env.NODE_ENV !== 'production') {\n logger.debug(`Network request for ` +\n `'${getFriendlyURL(request.url)}' returned a response with ` +\n `status '${fetchResponse.status}'.`);\n }\n for (const callback of this.iterateCallbacks('fetchDidSucceed')) {\n fetchResponse = await callback({\n event,\n request: pluginFilteredRequest,\n response: fetchResponse,\n });\n }\n return fetchResponse;\n }\n catch (error) {\n if (process.env.NODE_ENV !== 'production') {\n logger.log(`Network request for ` +\n `'${getFriendlyURL(request.url)}' threw an error.`, error);\n }\n // `originalRequest` will only exist if a `fetchDidFail` callback\n // is being used (see above).\n if (originalRequest) {\n await this.runCallbacks('fetchDidFail', {\n error: error,\n event,\n originalRequest: originalRequest.clone(),\n request: pluginFilteredRequest.clone(),\n });\n }\n throw error;\n }\n }\n /**\n * Calls `this.fetch()` and (in the background) runs `this.cachePut()` on\n * the response generated by `this.fetch()`.\n *\n * The call to `this.cachePut()` automatically invokes `this.waitUntil()`,\n * so you do not have to manually call `waitUntil()` on the event.\n *\n * @param {Request|string} input The request or URL to fetch and cache.\n * @return {Promise}\n */\n async fetchAndCachePut(input) {\n const response = await this.fetch(input);\n const responseClone = response.clone();\n void this.waitUntil(this.cachePut(input, responseClone));\n return response;\n }\n /**\n * Matches a request from the cache (and invokes any applicable plugin\n * callback methods) using the `cacheName`, `matchOptions`, and `plugins`\n * defined on the strategy object.\n *\n * The following plugin lifecycle methods are invoked when using this method:\n * - cacheKeyWillByUsed()\n * - cachedResponseWillByUsed()\n *\n * @param {Request|string} key The Request or URL to use as the cache key.\n * @return {Promise} A matching response, if found.\n */\n async cacheMatch(key) {\n const request = toRequest(key);\n let cachedResponse;\n const { cacheName, matchOptions } = this._strategy;\n const effectiveRequest = await this.getCacheKey(request, 'read');\n const multiMatchOptions = Object.assign(Object.assign({}, matchOptions), { cacheName });\n cachedResponse = await caches.match(effectiveRequest, multiMatchOptions);\n if (process.env.NODE_ENV !== 'production') {\n if (cachedResponse) {\n logger.debug(`Found a cached response in '${cacheName}'.`);\n }\n else {\n logger.debug(`No cached response found in '${cacheName}'.`);\n }\n }\n for (const callback of this.iterateCallbacks('cachedResponseWillBeUsed')) {\n cachedResponse =\n (await callback({\n cacheName,\n matchOptions,\n cachedResponse,\n request: effectiveRequest,\n event: this.event,\n })) || undefined;\n }\n return cachedResponse;\n }\n /**\n * Puts a request/response pair in the cache (and invokes any applicable\n * plugin callback methods) using the `cacheName` and `plugins` defined on\n * the strategy object.\n *\n * The following plugin lifecycle methods are invoked when using this method:\n * - cacheKeyWillByUsed()\n * - cacheWillUpdate()\n * - cacheDidUpdate()\n *\n * @param {Request|string} key The request or URL to use as the cache key.\n * @param {Response} response The response to cache.\n * @return {Promise} `false` if a cacheWillUpdate caused the response\n * not be cached, and `true` otherwise.\n */\n async cachePut(key, response) {\n const request = toRequest(key);\n // Run in the next task to avoid blocking other cache reads.\n // https://github.com/w3c/ServiceWorker/issues/1397\n await timeout(0);\n const effectiveRequest = await this.getCacheKey(request, 'write');\n if (process.env.NODE_ENV !== 'production') {\n if (effectiveRequest.method && effectiveRequest.method !== 'GET') {\n throw new WorkboxError('attempt-to-cache-non-get-request', {\n url: getFriendlyURL(effectiveRequest.url),\n method: effectiveRequest.method,\n });\n }\n // See https://github.com/GoogleChrome/workbox/issues/2818\n const vary = response.headers.get('Vary');\n if (vary) {\n logger.debug(`The response for ${getFriendlyURL(effectiveRequest.url)} ` +\n `has a 'Vary: ${vary}' header. ` +\n `Consider setting the {ignoreVary: true} option on your strategy ` +\n `to ensure cache matching and deletion works as expected.`);\n }\n }\n if (!response) {\n if (process.env.NODE_ENV !== 'production') {\n logger.error(`Cannot cache non-existent response for ` +\n `'${getFriendlyURL(effectiveRequest.url)}'.`);\n }\n throw new WorkboxError('cache-put-with-no-response', {\n url: getFriendlyURL(effectiveRequest.url),\n });\n }\n const responseToCache = await this._ensureResponseSafeToCache(response);\n if (!responseToCache) {\n if (process.env.NODE_ENV !== 'production') {\n logger.debug(`Response '${getFriendlyURL(effectiveRequest.url)}' ` +\n `will not be cached.`, responseToCache);\n }\n return false;\n }\n const { cacheName, matchOptions } = this._strategy;\n const cache = await self.caches.open(cacheName);\n const hasCacheUpdateCallback = this.hasCallback('cacheDidUpdate');\n const oldResponse = hasCacheUpdateCallback\n ? await cacheMatchIgnoreParams(\n // TODO(philipwalton): the `__WB_REVISION__` param is a precaching\n // feature. Consider into ways to only add this behavior if using\n // precaching.\n cache, effectiveRequest.clone(), ['__WB_REVISION__'], matchOptions)\n : null;\n if (process.env.NODE_ENV !== 'production') {\n logger.debug(`Updating the '${cacheName}' cache with a new Response ` +\n `for ${getFriendlyURL(effectiveRequest.url)}.`);\n }\n try {\n await cache.put(effectiveRequest, hasCacheUpdateCallback ? responseToCache.clone() : responseToCache);\n }\n catch (error) {\n if (error instanceof Error) {\n // See https://developer.mozilla.org/en-US/docs/Web/API/DOMException#exception-QuotaExceededError\n if (error.name === 'QuotaExceededError') {\n await executeQuotaErrorCallbacks();\n }\n throw error;\n }\n }\n for (const callback of this.iterateCallbacks('cacheDidUpdate')) {\n await callback({\n cacheName,\n oldResponse,\n newResponse: responseToCache.clone(),\n request: effectiveRequest,\n event: this.event,\n });\n }\n return true;\n }\n /**\n * Checks the list of plugins for the `cacheKeyWillBeUsed` callback, and\n * executes any of those callbacks found in sequence. The final `Request`\n * object returned by the last plugin is treated as the cache key for cache\n * reads and/or writes. If no `cacheKeyWillBeUsed` plugin callbacks have\n * been registered, the passed request is returned unmodified\n *\n * @param {Request} request\n * @param {string} mode\n * @return {Promise}\n */\n async getCacheKey(request, mode) {\n const key = `${request.url} | ${mode}`;\n if (!this._cacheKeys[key]) {\n let effectiveRequest = request;\n for (const callback of this.iterateCallbacks('cacheKeyWillBeUsed')) {\n effectiveRequest = toRequest(await callback({\n mode,\n request: effectiveRequest,\n event: this.event,\n // params has a type any can't change right now.\n params: this.params, // eslint-disable-line\n }));\n }\n this._cacheKeys[key] = effectiveRequest;\n }\n return this._cacheKeys[key];\n }\n /**\n * Returns true if the strategy has at least one plugin with the given\n * callback.\n *\n * @param {string} name The name of the callback to check for.\n * @return {boolean}\n */\n hasCallback(name) {\n for (const plugin of this._strategy.plugins) {\n if (name in plugin) {\n return true;\n }\n }\n return false;\n }\n /**\n * Runs all plugin callbacks matching the given name, in order, passing the\n * given param object (merged ith the current plugin state) as the only\n * argument.\n *\n * Note: since this method runs all plugins, it's not suitable for cases\n * where the return value of a callback needs to be applied prior to calling\n * the next callback. See\n * {@link workbox-strategies.StrategyHandler#iterateCallbacks}\n * below for how to handle that case.\n *\n * @param {string} name The name of the callback to run within each plugin.\n * @param {Object} param The object to pass as the first (and only) param\n * when executing each callback. This object will be merged with the\n * current plugin state prior to callback execution.\n */\n async runCallbacks(name, param) {\n for (const callback of this.iterateCallbacks(name)) {\n // TODO(philipwalton): not sure why `any` is needed. It seems like\n // this should work with `as WorkboxPluginCallbackParam[C]`.\n await callback(param);\n }\n }\n /**\n * Accepts a callback and returns an iterable of matching plugin callbacks,\n * where each callback is wrapped with the current handler state (i.e. when\n * you call each callback, whatever object parameter you pass it will\n * be merged with the plugin's current state).\n *\n * @param {string} name The name fo the callback to run\n * @return {Array}\n */\n *iterateCallbacks(name) {\n for (const plugin of this._strategy.plugins) {\n if (typeof plugin[name] === 'function') {\n const state = this._pluginStateMap.get(plugin);\n const statefulCallback = (param) => {\n const statefulParam = Object.assign(Object.assign({}, param), { state });\n // TODO(philipwalton): not sure why `any` is needed. It seems like\n // this should work with `as WorkboxPluginCallbackParam[C]`.\n return plugin[name](statefulParam);\n };\n yield statefulCallback;\n }\n }\n }\n /**\n * Adds a promise to the\n * [extend lifetime promises]{@link https://w3c.github.io/ServiceWorker/#extendableevent-extend-lifetime-promises}\n * of the event event associated with the request being handled (usually a\n * `FetchEvent`).\n *\n * Note: you can await\n * {@link workbox-strategies.StrategyHandler~doneWaiting}\n * to know when all added promises have settled.\n *\n * @param {Promise} promise A promise to add to the extend lifetime promises\n * of the event that triggered the request.\n */\n waitUntil(promise) {\n this._extendLifetimePromises.push(promise);\n return promise;\n }\n /**\n * Returns a promise that resolves once all promises passed to\n * {@link workbox-strategies.StrategyHandler~waitUntil}\n * have settled.\n *\n * Note: any work done after `doneWaiting()` settles should be manually\n * passed to an event's `waitUntil()` method (not this handler's\n * `waitUntil()` method), otherwise the service worker thread my be killed\n * prior to your work completing.\n */\n async doneWaiting() {\n let promise;\n while ((promise = this._extendLifetimePromises.shift())) {\n await promise;\n }\n }\n /**\n * Stops running the strategy and immediately resolves any pending\n * `waitUntil()` promises.\n */\n destroy() {\n this._handlerDeferred.resolve(null);\n }\n /**\n * This method will call cacheWillUpdate on the available plugins (or use\n * status === 200) to determine if the Response is safe and valid to cache.\n *\n * @param {Request} options.request\n * @param {Response} options.response\n * @return {Promise}\n *\n * @private\n */\n async _ensureResponseSafeToCache(response) {\n let responseToCache = response;\n let pluginsUsed = false;\n for (const callback of this.iterateCallbacks('cacheWillUpdate')) {\n responseToCache =\n (await callback({\n request: this.request,\n response: responseToCache,\n event: this.event,\n })) || undefined;\n pluginsUsed = true;\n if (!responseToCache) {\n break;\n }\n }\n if (!pluginsUsed) {\n if (responseToCache && responseToCache.status !== 200) {\n responseToCache = undefined;\n }\n if (process.env.NODE_ENV !== 'production') {\n if (responseToCache) {\n if (responseToCache.status !== 200) {\n if (responseToCache.status === 0) {\n logger.warn(`The response for '${this.request.url}' ` +\n `is an opaque response. The caching strategy that you're ` +\n `using will not cache opaque responses by default.`);\n }\n else {\n logger.debug(`The response for '${this.request.url}' ` +\n `returned a status code of '${response.status}' and won't ` +\n `be cached as a result.`);\n }\n }\n }\n }\n }\n return responseToCache;\n }\n}\nexport { StrategyHandler };\n","/*\n Copyright 2019 Google LLC\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\n/**\n * Returns a promise that resolves and the passed number of milliseconds.\n * This utility is an async/await-friendly version of `setTimeout`.\n *\n * @param {number} ms\n * @return {Promise}\n * @private\n */\nexport function timeout(ms) {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\nconst getFriendlyURL = (url) => {\n const urlObj = new URL(String(url), location.href);\n // See https://github.com/GoogleChrome/workbox/issues/2323\n // We want to include everything, except for the origin if it's same-origin.\n return urlObj.href.replace(new RegExp(`^${location.origin}`), '');\n};\nexport { getFriendlyURL };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { logger } from '../_private/logger.js';\nimport { quotaErrorCallbacks } from '../models/quotaErrorCallbacks.js';\nimport '../_version.js';\n/**\n * Runs all of the callback functions, one at a time sequentially, in the order\n * in which they were registered.\n *\n * @memberof workbox-core\n * @private\n */\nasync function executeQuotaErrorCallbacks() {\n if (process.env.NODE_ENV !== 'production') {\n logger.log(`About to run ${quotaErrorCallbacks.size} ` +\n `callbacks to clean up caches.`);\n }\n for (const callback of quotaErrorCallbacks) {\n await callback();\n if (process.env.NODE_ENV !== 'production') {\n logger.log(callback, 'is complete.');\n }\n }\n if (process.env.NODE_ENV !== 'production') {\n logger.log('Finished running callbacks.');\n }\n}\nexport { executeQuotaErrorCallbacks };\n","/*\n Copyright 2020 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { cacheNames } from 'workbox-core/_private/cacheNames.js';\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { getFriendlyURL } from 'workbox-core/_private/getFriendlyURL.js';\nimport { StrategyHandler } from './StrategyHandler.js';\nimport './_version.js';\n/**\n * An abstract base class that all other strategy classes must extend from:\n *\n * @memberof workbox-strategies\n */\nclass Strategy {\n /**\n * Creates a new instance of the strategy and sets all documented option\n * properties as public instance properties.\n *\n * Note: if a custom strategy class extends the base Strategy class and does\n * not need more than these properties, it does not need to define its own\n * constructor.\n *\n * @param {Object} [options]\n * @param {string} [options.cacheName] Cache name to store and retrieve\n * requests. Defaults to the cache names provided by\n * {@link workbox-core.cacheNames}.\n * @param {Array} [options.plugins] [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}\n * to use in conjunction with this caching strategy.\n * @param {Object} [options.fetchOptions] Values passed along to the\n * [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)\n * of [non-navigation](https://github.com/GoogleChrome/workbox/issues/1796)\n * `fetch()` requests made by this strategy.\n * @param {Object} [options.matchOptions] The\n * [`CacheQueryOptions`]{@link https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions}\n * for any `cache.match()` or `cache.put()` calls made by this strategy.\n */\n constructor(options = {}) {\n /**\n * Cache name to store and retrieve\n * requests. Defaults to the cache names provided by\n * {@link workbox-core.cacheNames}.\n *\n * @type {string}\n */\n this.cacheName = cacheNames.getRuntimeName(options.cacheName);\n /**\n * The list\n * [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}\n * used by this strategy.\n *\n * @type {Array}\n */\n this.plugins = options.plugins || [];\n /**\n * Values passed along to the\n * [`init`]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters}\n * of all fetch() requests made by this strategy.\n *\n * @type {Object}\n */\n this.fetchOptions = options.fetchOptions;\n /**\n * The\n * [`CacheQueryOptions`]{@link https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions}\n * for any `cache.match()` or `cache.put()` calls made by this strategy.\n *\n * @type {Object}\n */\n this.matchOptions = options.matchOptions;\n }\n /**\n * Perform a request strategy and returns a `Promise` that will resolve with\n * a `Response`, invoking all relevant plugin callbacks.\n *\n * When a strategy instance is registered with a Workbox\n * {@link workbox-routing.Route}, this method is automatically\n * called when the route matches.\n *\n * Alternatively, this method can be used in a standalone `FetchEvent`\n * listener by passing it to `event.respondWith()`.\n *\n * @param {FetchEvent|Object} options A `FetchEvent` or an object with the\n * properties listed below.\n * @param {Request|string} options.request A request to run this strategy for.\n * @param {ExtendableEvent} options.event The event associated with the\n * request.\n * @param {URL} [options.url]\n * @param {*} [options.params]\n */\n handle(options) {\n const [responseDone] = this.handleAll(options);\n return responseDone;\n }\n /**\n * Similar to {@link workbox-strategies.Strategy~handle}, but\n * instead of just returning a `Promise` that resolves to a `Response` it\n * it will return an tuple of `[response, done]` promises, where the former\n * (`response`) is equivalent to what `handle()` returns, and the latter is a\n * Promise that will resolve once any promises that were added to\n * `event.waitUntil()` as part of performing the strategy have completed.\n *\n * You can await the `done` promise to ensure any extra work performed by\n * the strategy (usually caching responses) completes successfully.\n *\n * @param {FetchEvent|Object} options A `FetchEvent` or an object with the\n * properties listed below.\n * @param {Request|string} options.request A request to run this strategy for.\n * @param {ExtendableEvent} options.event The event associated with the\n * request.\n * @param {URL} [options.url]\n * @param {*} [options.params]\n * @return {Array} A tuple of [response, done]\n * promises that can be used to determine when the response resolves as\n * well as when the handler has completed all its work.\n */\n handleAll(options) {\n // Allow for flexible options to be passed.\n if (options instanceof FetchEvent) {\n options = {\n event: options,\n request: options.request,\n };\n }\n const event = options.event;\n const request = typeof options.request === 'string'\n ? new Request(options.request)\n : options.request;\n const params = 'params' in options ? options.params : undefined;\n const handler = new StrategyHandler(this, { event, request, params });\n const responseDone = this._getResponse(handler, request, event);\n const handlerDone = this._awaitComplete(responseDone, handler, request, event);\n // Return an array of promises, suitable for use with Promise.all().\n return [responseDone, handlerDone];\n }\n async _getResponse(handler, request, event) {\n await handler.runCallbacks('handlerWillStart', { event, request });\n let response = undefined;\n try {\n response = await this._handle(request, handler);\n // The \"official\" Strategy subclasses all throw this error automatically,\n // but in case a third-party Strategy doesn't, ensure that we have a\n // consistent failure when there's no response or an error response.\n if (!response || response.type === 'error') {\n throw new WorkboxError('no-response', { url: request.url });\n }\n }\n catch (error) {\n if (error instanceof Error) {\n for (const callback of handler.iterateCallbacks('handlerDidError')) {\n response = await callback({ error, event, request });\n if (response) {\n break;\n }\n }\n }\n if (!response) {\n throw error;\n }\n else if (process.env.NODE_ENV !== 'production') {\n logger.log(`While responding to '${getFriendlyURL(request.url)}', ` +\n `an ${error instanceof Error ? error.toString() : ''} error occurred. Using a fallback response provided by ` +\n `a handlerDidError plugin.`);\n }\n }\n for (const callback of handler.iterateCallbacks('handlerWillRespond')) {\n response = await callback({ event, request, response });\n }\n return response;\n }\n async _awaitComplete(responseDone, handler, request, event) {\n let response;\n let error;\n try {\n response = await responseDone;\n }\n catch (error) {\n // Ignore errors, as response errors should be caught via the `response`\n // promise above. The `done` promise will only throw for errors in\n // promises passed to `handler.waitUntil()`.\n }\n try {\n await handler.runCallbacks('handlerDidRespond', {\n event,\n request,\n response,\n });\n await handler.doneWaiting();\n }\n catch (waitUntilError) {\n if (waitUntilError instanceof Error) {\n error = waitUntilError;\n }\n }\n await handler.runCallbacks('handlerDidComplete', {\n event,\n request,\n response,\n error: error,\n });\n handler.destroy();\n if (error) {\n throw error;\n }\n }\n}\nexport { Strategy };\n/**\n * Classes extending the `Strategy` based class should implement this method,\n * and leverage the {@link workbox-strategies.StrategyHandler}\n * arg to perform all fetching and cache logic, which will ensure all relevant\n * cache, cache options, fetch options and plugins are used (per the current\n * strategy instance).\n *\n * @name _handle\n * @instance\n * @abstract\n * @function\n * @param {Request} request\n * @param {workbox-strategies.StrategyHandler} handler\n * @return {Promise}\n *\n * @memberof workbox-strategies.Strategy\n */\n","/*\n Copyright 2020 Google LLC\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\n/**\n * A utility method that makes it easier to use `event.waitUntil` with\n * async functions and return the result.\n *\n * @param {ExtendableEvent} event\n * @param {Function} asyncFn\n * @return {Function}\n * @private\n */\nfunction waitUntil(event, asyncFn) {\n const returnPromise = asyncFn();\n event.waitUntil(returnPromise);\n return returnPromise;\n}\nexport { waitUntil };\n","\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:precaching:6.6.0'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport '../_version.js';\n// Name of the search parameter used to store revision info.\nconst REVISION_SEARCH_PARAM = '__WB_REVISION__';\n/**\n * Converts a manifest entry into a versioned URL suitable for precaching.\n *\n * @param {Object|string} entry\n * @return {string} A URL with versioning info.\n *\n * @private\n * @memberof workbox-precaching\n */\nexport function createCacheKey(entry) {\n if (!entry) {\n throw new WorkboxError('add-to-cache-list-unexpected-type', { entry });\n }\n // If a precache manifest entry is a string, it's assumed to be a versioned\n // URL, like '/app.abcd1234.js'. Return as-is.\n if (typeof entry === 'string') {\n const urlObject = new URL(entry, location.href);\n return {\n cacheKey: urlObject.href,\n url: urlObject.href,\n };\n }\n const { revision, url } = entry;\n if (!url) {\n throw new WorkboxError('add-to-cache-list-unexpected-type', { entry });\n }\n // If there's just a URL and no revision, then it's also assumed to be a\n // versioned URL.\n if (!revision) {\n const urlObject = new URL(url, location.href);\n return {\n cacheKey: urlObject.href,\n url: urlObject.href,\n };\n }\n // Otherwise, construct a properly versioned URL using the custom Workbox\n // search parameter along with the revision info.\n const cacheKeyURL = new URL(url, location.href);\n const originalURL = new URL(url, location.href);\n cacheKeyURL.searchParams.set(REVISION_SEARCH_PARAM, revision);\n return {\n cacheKey: cacheKeyURL.href,\n url: originalURL.href,\n };\n}\n","/*\n Copyright 2020 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\n/**\n * A plugin, designed to be used with PrecacheController, to determine the\n * of assets that were updated (or not updated) during the install event.\n *\n * @private\n */\nclass PrecacheInstallReportPlugin {\n constructor() {\n this.updatedURLs = [];\n this.notUpdatedURLs = [];\n this.handlerWillStart = async ({ request, state, }) => {\n // TODO: `state` should never be undefined...\n if (state) {\n state.originalRequest = request;\n }\n };\n this.cachedResponseWillBeUsed = async ({ event, state, cachedResponse, }) => {\n if (event.type === 'install') {\n if (state &&\n state.originalRequest &&\n state.originalRequest instanceof Request) {\n // TODO: `state` should never be undefined...\n const url = state.originalRequest.url;\n if (cachedResponse) {\n this.notUpdatedURLs.push(url);\n }\n else {\n this.updatedURLs.push(url);\n }\n }\n }\n return cachedResponse;\n };\n }\n}\nexport { PrecacheInstallReportPlugin };\n","/*\n Copyright 2020 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\n/**\n * A plugin, designed to be used with PrecacheController, to translate URLs into\n * the corresponding cache key, based on the current revision info.\n *\n * @private\n */\nclass PrecacheCacheKeyPlugin {\n constructor({ precacheController }) {\n this.cacheKeyWillBeUsed = async ({ request, params, }) => {\n // Params is type any, can't change right now.\n /* eslint-disable */\n const cacheKey = (params === null || params === void 0 ? void 0 : params.cacheKey) ||\n this._precacheController.getCacheKeyForURL(request.url);\n /* eslint-enable */\n return cacheKey\n ? new Request(cacheKey, { headers: request.headers })\n : request;\n };\n this._precacheController = precacheController;\n }\n}\nexport { PrecacheCacheKeyPlugin };\n","/*\n Copyright 2019 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\nlet supportStatus;\n/**\n * A utility function that determines whether the current browser supports\n * constructing a new `Response` from a `response.body` stream.\n *\n * @return {boolean} `true`, if the current browser can successfully\n * construct a `Response` from a `response.body` stream, `false` otherwise.\n *\n * @private\n */\nfunction canConstructResponseFromBodyStream() {\n if (supportStatus === undefined) {\n const testResponse = new Response('');\n if ('body' in testResponse) {\n try {\n new Response(testResponse.body);\n supportStatus = true;\n }\n catch (error) {\n supportStatus = false;\n }\n }\n supportStatus = false;\n }\n return supportStatus;\n}\nexport { canConstructResponseFromBodyStream };\n","/*\n Copyright 2019 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { PrecacheController } from '../PrecacheController.js';\nimport '../_version.js';\nlet precacheController;\n/**\n * @return {PrecacheController}\n * @private\n */\nexport const getOrCreatePrecacheController = () => {\n if (!precacheController) {\n precacheController = new PrecacheController();\n }\n return precacheController;\n};\n","/*\n Copyright 2019 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { canConstructResponseFromBodyStream } from './_private/canConstructResponseFromBodyStream.js';\nimport { WorkboxError } from './_private/WorkboxError.js';\nimport './_version.js';\n/**\n * Allows developers to copy a response and modify its `headers`, `status`,\n * or `statusText` values (the values settable via a\n * [`ResponseInit`]{@link https://developer.mozilla.org/en-US/docs/Web/API/Response/Response#Syntax}\n * object in the constructor).\n * To modify these values, pass a function as the second argument. That\n * function will be invoked with a single object with the response properties\n * `{headers, status, statusText}`. The return value of this function will\n * be used as the `ResponseInit` for the new `Response`. To change the values\n * either modify the passed parameter(s) and return it, or return a totally\n * new object.\n *\n * This method is intentionally limited to same-origin responses, regardless of\n * whether CORS was used or not.\n *\n * @param {Response} response\n * @param {Function} modifier\n * @memberof workbox-core\n */\nasync function copyResponse(response, modifier) {\n let origin = null;\n // If response.url isn't set, assume it's cross-origin and keep origin null.\n if (response.url) {\n const responseURL = new URL(response.url);\n origin = responseURL.origin;\n }\n if (origin !== self.location.origin) {\n throw new WorkboxError('cross-origin-copy-response', { origin });\n }\n const clonedResponse = response.clone();\n // Create a fresh `ResponseInit` object by cloning the headers.\n const responseInit = {\n headers: new Headers(clonedResponse.headers),\n status: clonedResponse.status,\n statusText: clonedResponse.statusText,\n };\n // Apply any user modifications.\n const modifiedResponseInit = modifier ? modifier(responseInit) : responseInit;\n // Create the new response from the body stream and `ResponseInit`\n // modifications. Note: not all browsers support the Response.body stream,\n // so fall back to reading the entire body into memory as a blob.\n const body = canConstructResponseFromBodyStream()\n ? clonedResponse.body\n : await clonedResponse.blob();\n return new Response(body, modifiedResponseInit);\n}\nexport { copyResponse };\n","/*\n Copyright 2020 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { copyResponse } from 'workbox-core/copyResponse.js';\nimport { cacheNames } from 'workbox-core/_private/cacheNames.js';\nimport { getFriendlyURL } from 'workbox-core/_private/getFriendlyURL.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport { Strategy } from 'workbox-strategies/Strategy.js';\nimport './_version.js';\n/**\n * A {@link workbox-strategies.Strategy} implementation\n * specifically designed to work with\n * {@link workbox-precaching.PrecacheController}\n * to both cache and fetch precached assets.\n *\n * Note: an instance of this class is created automatically when creating a\n * `PrecacheController`; it's generally not necessary to create this yourself.\n *\n * @extends workbox-strategies.Strategy\n * @memberof workbox-precaching\n */\nclass PrecacheStrategy extends Strategy {\n /**\n *\n * @param {Object} [options]\n * @param {string} [options.cacheName] Cache name to store and retrieve\n * requests. Defaults to the cache names provided by\n * {@link workbox-core.cacheNames}.\n * @param {Array} [options.plugins] {@link https://developers.google.com/web/tools/workbox/guides/using-plugins|Plugins}\n * to use in conjunction with this caching strategy.\n * @param {Object} [options.fetchOptions] Values passed along to the\n * {@link https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters|init}\n * of all fetch() requests made by this strategy.\n * @param {Object} [options.matchOptions] The\n * {@link https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions|CacheQueryOptions}\n * for any `cache.match()` or `cache.put()` calls made by this strategy.\n * @param {boolean} [options.fallbackToNetwork=true] Whether to attempt to\n * get the response from the network if there's a precache miss.\n */\n constructor(options = {}) {\n options.cacheName = cacheNames.getPrecacheName(options.cacheName);\n super(options);\n this._fallbackToNetwork =\n options.fallbackToNetwork === false ? false : true;\n // Redirected responses cannot be used to satisfy a navigation request, so\n // any redirected response must be \"copied\" rather than cloned, so the new\n // response doesn't contain the `redirected` flag. See:\n // https://bugs.chromium.org/p/chromium/issues/detail?id=669363&desc=2#c1\n this.plugins.push(PrecacheStrategy.copyRedirectedCacheableResponsesPlugin);\n }\n /**\n * @private\n * @param {Request|string} request A request to run this strategy for.\n * @param {workbox-strategies.StrategyHandler} handler The event that\n * triggered the request.\n * @return {Promise}\n */\n async _handle(request, handler) {\n const response = await handler.cacheMatch(request);\n if (response) {\n return response;\n }\n // If this is an `install` event for an entry that isn't already cached,\n // then populate the cache.\n if (handler.event && handler.event.type === 'install') {\n return await this._handleInstall(request, handler);\n }\n // Getting here means something went wrong. An entry that should have been\n // precached wasn't found in the cache.\n return await this._handleFetch(request, handler);\n }\n async _handleFetch(request, handler) {\n let response;\n const params = (handler.params || {});\n // Fall back to the network if we're configured to do so.\n if (this._fallbackToNetwork) {\n if (process.env.NODE_ENV !== 'production') {\n logger.warn(`The precached response for ` +\n `${getFriendlyURL(request.url)} in ${this.cacheName} was not ` +\n `found. Falling back to the network.`);\n }\n const integrityInManifest = params.integrity;\n const integrityInRequest = request.integrity;\n const noIntegrityConflict = !integrityInRequest || integrityInRequest === integrityInManifest;\n // Do not add integrity if the original request is no-cors\n // See https://github.com/GoogleChrome/workbox/issues/3096\n response = await handler.fetch(new Request(request, {\n integrity: request.mode !== 'no-cors'\n ? integrityInRequest || integrityInManifest\n : undefined,\n }));\n // It's only \"safe\" to repair the cache if we're using SRI to guarantee\n // that the response matches the precache manifest's expectations,\n // and there's either a) no integrity property in the incoming request\n // or b) there is an integrity, and it matches the precache manifest.\n // See https://github.com/GoogleChrome/workbox/issues/2858\n // Also if the original request users no-cors we don't use integrity.\n // See https://github.com/GoogleChrome/workbox/issues/3096\n if (integrityInManifest &&\n noIntegrityConflict &&\n request.mode !== 'no-cors') {\n this._useDefaultCacheabilityPluginIfNeeded();\n const wasCached = await handler.cachePut(request, response.clone());\n if (process.env.NODE_ENV !== 'production') {\n if (wasCached) {\n logger.log(`A response for ${getFriendlyURL(request.url)} ` +\n `was used to \"repair\" the precache.`);\n }\n }\n }\n }\n else {\n // This shouldn't normally happen, but there are edge cases:\n // https://github.com/GoogleChrome/workbox/issues/1441\n throw new WorkboxError('missing-precache-entry', {\n cacheName: this.cacheName,\n url: request.url,\n });\n }\n if (process.env.NODE_ENV !== 'production') {\n const cacheKey = params.cacheKey || (await handler.getCacheKey(request, 'read'));\n // Workbox is going to handle the route.\n // print the routing details to the console.\n logger.groupCollapsed(`Precaching is responding to: ` + getFriendlyURL(request.url));\n logger.log(`Serving the precached url: ${getFriendlyURL(cacheKey instanceof Request ? cacheKey.url : cacheKey)}`);\n logger.groupCollapsed(`View request details here.`);\n logger.log(request);\n logger.groupEnd();\n logger.groupCollapsed(`View response details here.`);\n logger.log(response);\n logger.groupEnd();\n logger.groupEnd();\n }\n return response;\n }\n async _handleInstall(request, handler) {\n this._useDefaultCacheabilityPluginIfNeeded();\n const response = await handler.fetch(request);\n // Make sure we defer cachePut() until after we know the response\n // should be cached; see https://github.com/GoogleChrome/workbox/issues/2737\n const wasCached = await handler.cachePut(request, response.clone());\n if (!wasCached) {\n // Throwing here will lead to the `install` handler failing, which\n // we want to do if *any* of the responses aren't safe to cache.\n throw new WorkboxError('bad-precaching-response', {\n url: request.url,\n status: response.status,\n });\n }\n return response;\n }\n /**\n * This method is complex, as there a number of things to account for:\n *\n * The `plugins` array can be set at construction, and/or it might be added to\n * to at any time before the strategy is used.\n *\n * At the time the strategy is used (i.e. during an `install` event), there\n * needs to be at least one plugin that implements `cacheWillUpdate` in the\n * array, other than `copyRedirectedCacheableResponsesPlugin`.\n *\n * - If this method is called and there are no suitable `cacheWillUpdate`\n * plugins, we need to add `defaultPrecacheCacheabilityPlugin`.\n *\n * - If this method is called and there is exactly one `cacheWillUpdate`, then\n * we don't have to do anything (this might be a previously added\n * `defaultPrecacheCacheabilityPlugin`, or it might be a custom plugin).\n *\n * - If this method is called and there is more than one `cacheWillUpdate`,\n * then we need to check if one is `defaultPrecacheCacheabilityPlugin`. If so,\n * we need to remove it. (This situation is unlikely, but it could happen if\n * the strategy is used multiple times, the first without a `cacheWillUpdate`,\n * and then later on after manually adding a custom `cacheWillUpdate`.)\n *\n * See https://github.com/GoogleChrome/workbox/issues/2737 for more context.\n *\n * @private\n */\n _useDefaultCacheabilityPluginIfNeeded() {\n let defaultPluginIndex = null;\n let cacheWillUpdatePluginCount = 0;\n for (const [index, plugin] of this.plugins.entries()) {\n // Ignore the copy redirected plugin when determining what to do.\n if (plugin === PrecacheStrategy.copyRedirectedCacheableResponsesPlugin) {\n continue;\n }\n // Save the default plugin's index, in case it needs to be removed.\n if (plugin === PrecacheStrategy.defaultPrecacheCacheabilityPlugin) {\n defaultPluginIndex = index;\n }\n if (plugin.cacheWillUpdate) {\n cacheWillUpdatePluginCount++;\n }\n }\n if (cacheWillUpdatePluginCount === 0) {\n this.plugins.push(PrecacheStrategy.defaultPrecacheCacheabilityPlugin);\n }\n else if (cacheWillUpdatePluginCount > 1 && defaultPluginIndex !== null) {\n // Only remove the default plugin; multiple custom plugins are allowed.\n this.plugins.splice(defaultPluginIndex, 1);\n }\n // Nothing needs to be done if cacheWillUpdatePluginCount is 1\n }\n}\nPrecacheStrategy.defaultPrecacheCacheabilityPlugin = {\n async cacheWillUpdate({ response }) {\n if (!response || response.status >= 400) {\n return null;\n }\n return response;\n },\n};\nPrecacheStrategy.copyRedirectedCacheableResponsesPlugin = {\n async cacheWillUpdate({ response }) {\n return response.redirected ? await copyResponse(response) : response;\n },\n};\nexport { PrecacheStrategy };\n","/*\n Copyright 2019 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { cacheNames } from 'workbox-core/_private/cacheNames.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport { waitUntil } from 'workbox-core/_private/waitUntil.js';\nimport { createCacheKey } from './utils/createCacheKey.js';\nimport { PrecacheInstallReportPlugin } from './utils/PrecacheInstallReportPlugin.js';\nimport { PrecacheCacheKeyPlugin } from './utils/PrecacheCacheKeyPlugin.js';\nimport { printCleanupDetails } from './utils/printCleanupDetails.js';\nimport { printInstallDetails } from './utils/printInstallDetails.js';\nimport { PrecacheStrategy } from './PrecacheStrategy.js';\nimport './_version.js';\n/**\n * Performs efficient precaching of assets.\n *\n * @memberof workbox-precaching\n */\nclass PrecacheController {\n /**\n * Create a new PrecacheController.\n *\n * @param {Object} [options]\n * @param {string} [options.cacheName] The cache to use for precaching.\n * @param {string} [options.plugins] Plugins to use when precaching as well\n * as responding to fetch events for precached assets.\n * @param {boolean} [options.fallbackToNetwork=true] Whether to attempt to\n * get the response from the network if there's a precache miss.\n */\n constructor({ cacheName, plugins = [], fallbackToNetwork = true, } = {}) {\n this._urlsToCacheKeys = new Map();\n this._urlsToCacheModes = new Map();\n this._cacheKeysToIntegrities = new Map();\n this._strategy = new PrecacheStrategy({\n cacheName: cacheNames.getPrecacheName(cacheName),\n plugins: [\n ...plugins,\n new PrecacheCacheKeyPlugin({ precacheController: this }),\n ],\n fallbackToNetwork,\n });\n // Bind the install and activate methods to the instance.\n this.install = this.install.bind(this);\n this.activate = this.activate.bind(this);\n }\n /**\n * @type {workbox-precaching.PrecacheStrategy} The strategy created by this controller and\n * used to cache assets and respond to fetch events.\n */\n get strategy() {\n return this._strategy;\n }\n /**\n * Adds items to the precache list, removing any duplicates and\n * stores the files in the\n * {@link workbox-core.cacheNames|\"precache cache\"} when the service\n * worker installs.\n *\n * This method can be called multiple times.\n *\n * @param {Array} [entries=[]] Array of entries to precache.\n */\n precache(entries) {\n this.addToCacheList(entries);\n if (!this._installAndActiveListenersAdded) {\n self.addEventListener('install', this.install);\n self.addEventListener('activate', this.activate);\n this._installAndActiveListenersAdded = true;\n }\n }\n /**\n * This method will add items to the precache list, removing duplicates\n * and ensuring the information is valid.\n *\n * @param {Array} entries\n * Array of entries to precache.\n */\n addToCacheList(entries) {\n if (process.env.NODE_ENV !== 'production') {\n assert.isArray(entries, {\n moduleName: 'workbox-precaching',\n className: 'PrecacheController',\n funcName: 'addToCacheList',\n paramName: 'entries',\n });\n }\n const urlsToWarnAbout = [];\n for (const entry of entries) {\n // See https://github.com/GoogleChrome/workbox/issues/2259\n if (typeof entry === 'string') {\n urlsToWarnAbout.push(entry);\n }\n else if (entry && entry.revision === undefined) {\n urlsToWarnAbout.push(entry.url);\n }\n const { cacheKey, url } = createCacheKey(entry);\n const cacheMode = typeof entry !== 'string' && entry.revision ? 'reload' : 'default';\n if (this._urlsToCacheKeys.has(url) &&\n this._urlsToCacheKeys.get(url) !== cacheKey) {\n throw new WorkboxError('add-to-cache-list-conflicting-entries', {\n firstEntry: this._urlsToCacheKeys.get(url),\n secondEntry: cacheKey,\n });\n }\n if (typeof entry !== 'string' && entry.integrity) {\n if (this._cacheKeysToIntegrities.has(cacheKey) &&\n this._cacheKeysToIntegrities.get(cacheKey) !== entry.integrity) {\n throw new WorkboxError('add-to-cache-list-conflicting-integrities', {\n url,\n });\n }\n this._cacheKeysToIntegrities.set(cacheKey, entry.integrity);\n }\n this._urlsToCacheKeys.set(url, cacheKey);\n this._urlsToCacheModes.set(url, cacheMode);\n if (urlsToWarnAbout.length > 0) {\n const warningMessage = `Workbox is precaching URLs without revision ` +\n `info: ${urlsToWarnAbout.join(', ')}\\nThis is generally NOT safe. ` +\n `Learn more at https://bit.ly/wb-precache`;\n if (process.env.NODE_ENV === 'production') {\n // Use console directly to display this warning without bloating\n // bundle sizes by pulling in all of the logger codebase in prod.\n console.warn(warningMessage);\n }\n else {\n logger.warn(warningMessage);\n }\n }\n }\n }\n /**\n * Precaches new and updated assets. Call this method from the service worker\n * install event.\n *\n * Note: this method calls `event.waitUntil()` for you, so you do not need\n * to call it yourself in your event handlers.\n *\n * @param {ExtendableEvent} event\n * @return {Promise}\n */\n install(event) {\n // waitUntil returns Promise\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return waitUntil(event, async () => {\n const installReportPlugin = new PrecacheInstallReportPlugin();\n this.strategy.plugins.push(installReportPlugin);\n // Cache entries one at a time.\n // See https://github.com/GoogleChrome/workbox/issues/2528\n for (const [url, cacheKey] of this._urlsToCacheKeys) {\n const integrity = this._cacheKeysToIntegrities.get(cacheKey);\n const cacheMode = this._urlsToCacheModes.get(url);\n const request = new Request(url, {\n integrity,\n cache: cacheMode,\n credentials: 'same-origin',\n });\n await Promise.all(this.strategy.handleAll({\n params: { cacheKey },\n request,\n event,\n }));\n }\n const { updatedURLs, notUpdatedURLs } = installReportPlugin;\n if (process.env.NODE_ENV !== 'production') {\n printInstallDetails(updatedURLs, notUpdatedURLs);\n }\n return { updatedURLs, notUpdatedURLs };\n });\n }\n /**\n * Deletes assets that are no longer present in the current precache manifest.\n * Call this method from the service worker activate event.\n *\n * Note: this method calls `event.waitUntil()` for you, so you do not need\n * to call it yourself in your event handlers.\n *\n * @param {ExtendableEvent} event\n * @return {Promise}\n */\n activate(event) {\n // waitUntil returns Promise\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return waitUntil(event, async () => {\n const cache = await self.caches.open(this.strategy.cacheName);\n const currentlyCachedRequests = await cache.keys();\n const expectedCacheKeys = new Set(this._urlsToCacheKeys.values());\n const deletedURLs = [];\n for (const request of currentlyCachedRequests) {\n if (!expectedCacheKeys.has(request.url)) {\n await cache.delete(request);\n deletedURLs.push(request.url);\n }\n }\n if (process.env.NODE_ENV !== 'production') {\n printCleanupDetails(deletedURLs);\n }\n return { deletedURLs };\n });\n }\n /**\n * Returns a mapping of a precached URL to the corresponding cache key, taking\n * into account the revision information for the URL.\n *\n * @return {Map} A URL to cache key mapping.\n */\n getURLsToCacheKeys() {\n return this._urlsToCacheKeys;\n }\n /**\n * Returns a list of all the URLs that have been precached by the current\n * service worker.\n *\n * @return {Array} The precached URLs.\n */\n getCachedURLs() {\n return [...this._urlsToCacheKeys.keys()];\n }\n /**\n * Returns the cache key used for storing a given URL. If that URL is\n * unversioned, like `/index.html', then the cache key will be the original\n * URL with a search parameter appended to it.\n *\n * @param {string} url A URL whose cache key you want to look up.\n * @return {string} The versioned URL that corresponds to a cache key\n * for the original URL, or undefined if that URL isn't precached.\n */\n getCacheKeyForURL(url) {\n const urlObject = new URL(url, location.href);\n return this._urlsToCacheKeys.get(urlObject.href);\n }\n /**\n * @param {string} url A cache key whose SRI you want to look up.\n * @return {string} The subresource integrity associated with the cache key,\n * or undefined if it's not set.\n */\n getIntegrityForCacheKey(cacheKey) {\n return this._cacheKeysToIntegrities.get(cacheKey);\n }\n /**\n * This acts as a drop-in replacement for\n * [`cache.match()`](https://developer.mozilla.org/en-US/docs/Web/API/Cache/match)\n * with the following differences:\n *\n * - It knows what the name of the precache is, and only checks in that cache.\n * - It allows you to pass in an \"original\" URL without versioning parameters,\n * and it will automatically look up the correct cache key for the currently\n * active revision of that URL.\n *\n * E.g., `matchPrecache('index.html')` will find the correct precached\n * response for the currently active service worker, even if the actual cache\n * key is `'/index.html?__WB_REVISION__=1234abcd'`.\n *\n * @param {string|Request} request The key (without revisioning parameters)\n * to look up in the precache.\n * @return {Promise}\n */\n async matchPrecache(request) {\n const url = request instanceof Request ? request.url : request;\n const cacheKey = this.getCacheKeyForURL(url);\n if (cacheKey) {\n const cache = await self.caches.open(this.strategy.cacheName);\n return cache.match(cacheKey);\n }\n return undefined;\n }\n /**\n * Returns a function that looks up `url` in the precache (taking into\n * account revision information), and returns the corresponding `Response`.\n *\n * @param {string} url The precached URL which will be used to lookup the\n * `Response`.\n * @return {workbox-routing~handlerCallback}\n */\n createHandlerBoundToURL(url) {\n const cacheKey = this.getCacheKeyForURL(url);\n if (!cacheKey) {\n throw new WorkboxError('non-precached-url', { url });\n }\n return (options) => {\n options.request = new Request(url);\n options.params = Object.assign({ cacheKey }, options.params);\n return this.strategy.handle(options);\n };\n }\n}\nexport { PrecacheController };\n","/*\n Copyright 2020 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { getFriendlyURL } from 'workbox-core/_private/getFriendlyURL.js';\nimport { Route } from 'workbox-routing/Route.js';\nimport { generateURLVariations } from './utils/generateURLVariations.js';\nimport './_version.js';\n/**\n * A subclass of {@link workbox-routing.Route} that takes a\n * {@link workbox-precaching.PrecacheController}\n * instance and uses it to match incoming requests and handle fetching\n * responses from the precache.\n *\n * @memberof workbox-precaching\n * @extends workbox-routing.Route\n */\nclass PrecacheRoute extends Route {\n /**\n * @param {PrecacheController} precacheController A `PrecacheController`\n * instance used to both match requests and respond to fetch events.\n * @param {Object} [options] Options to control how requests are matched\n * against the list of precached URLs.\n * @param {string} [options.directoryIndex=index.html] The `directoryIndex` will\n * check cache entries for a URLs ending with '/' to see if there is a hit when\n * appending the `directoryIndex` value.\n * @param {Array} [options.ignoreURLParametersMatching=[/^utm_/, /^fbclid$/]] An\n * array of regex's to remove search params when looking for a cache match.\n * @param {boolean} [options.cleanURLs=true] The `cleanURLs` option will\n * check the cache for the URL with a `.html` added to the end of the end.\n * @param {workbox-precaching~urlManipulation} [options.urlManipulation]\n * This is a function that should take a URL and return an array of\n * alternative URLs that should be checked for precache matches.\n */\n constructor(precacheController, options) {\n const match = ({ request, }) => {\n const urlsToCacheKeys = precacheController.getURLsToCacheKeys();\n for (const possibleURL of generateURLVariations(request.url, options)) {\n const cacheKey = urlsToCacheKeys.get(possibleURL);\n if (cacheKey) {\n const integrity = precacheController.getIntegrityForCacheKey(cacheKey);\n return { cacheKey, integrity };\n }\n }\n if (process.env.NODE_ENV !== 'production') {\n logger.debug(`Precaching did not find a match for ` + getFriendlyURL(request.url));\n }\n return;\n };\n super(match, precacheController.strategy);\n }\n}\nexport { PrecacheRoute };\n","/*\n Copyright 2019 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { removeIgnoredSearchParams } from './removeIgnoredSearchParams.js';\nimport '../_version.js';\n/**\n * Generator function that yields possible variations on the original URL to\n * check, one at a time.\n *\n * @param {string} url\n * @param {Object} options\n *\n * @private\n * @memberof workbox-precaching\n */\nexport function* generateURLVariations(url, { ignoreURLParametersMatching = [/^utm_/, /^fbclid$/], directoryIndex = 'index.html', cleanURLs = true, urlManipulation, } = {}) {\n const urlObject = new URL(url, location.href);\n urlObject.hash = '';\n yield urlObject.href;\n const urlWithoutIgnoredParams = removeIgnoredSearchParams(urlObject, ignoreURLParametersMatching);\n yield urlWithoutIgnoredParams.href;\n if (directoryIndex && urlWithoutIgnoredParams.pathname.endsWith('/')) {\n const directoryURL = new URL(urlWithoutIgnoredParams.href);\n directoryURL.pathname += directoryIndex;\n yield directoryURL.href;\n }\n if (cleanURLs) {\n const cleanURL = new URL(urlWithoutIgnoredParams.href);\n cleanURL.pathname += '.html';\n yield cleanURL.href;\n }\n if (urlManipulation) {\n const additionalURLs = urlManipulation({ url: urlObject });\n for (const urlToAttempt of additionalURLs) {\n yield urlToAttempt.href;\n }\n }\n}\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\n/**\n * Removes any URL search parameters that should be ignored.\n *\n * @param {URL} urlObject The original URL.\n * @param {Array} ignoreURLParametersMatching RegExps to test against\n * each search parameter name. Matches mean that the search parameter should be\n * ignored.\n * @return {URL} The URL with any ignored search parameters removed.\n *\n * @private\n * @memberof workbox-precaching\n */\nexport function removeIgnoredSearchParams(urlObject, ignoreURLParametersMatching = []) {\n // Convert the iterable into an array at the start of the loop to make sure\n // deletion doesn't mess up iteration.\n for (const paramName of [...urlObject.searchParams.keys()]) {\n if (ignoreURLParametersMatching.some((regExp) => regExp.test(paramName))) {\n urlObject.searchParams.delete(paramName);\n }\n }\n return urlObject;\n}\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport { Strategy } from './Strategy.js';\nimport { messages } from './utils/messages.js';\nimport './_version.js';\n/**\n * An implementation of a [cache-first](https://developer.chrome.com/docs/workbox/caching-strategies-overview/#cache-first-falling-back-to-network)\n * request strategy.\n *\n * A cache first strategy is useful for assets that have been revisioned,\n * such as URLs like `/styles/example.a8f5f1.css`, since they\n * can be cached for long periods of time.\n *\n * If the network request fails, and there is no cache match, this will throw\n * a `WorkboxError` exception.\n *\n * @extends workbox-strategies.Strategy\n * @memberof workbox-strategies\n */\nclass CacheFirst extends Strategy {\n /**\n * @private\n * @param {Request|string} request A request to run this strategy for.\n * @param {workbox-strategies.StrategyHandler} handler The event that\n * triggered the request.\n * @return {Promise}\n */\n async _handle(request, handler) {\n const logs = [];\n if (process.env.NODE_ENV !== 'production') {\n assert.isInstance(request, Request, {\n moduleName: 'workbox-strategies',\n className: this.constructor.name,\n funcName: 'makeRequest',\n paramName: 'request',\n });\n }\n let response = await handler.cacheMatch(request);\n let error = undefined;\n if (!response) {\n if (process.env.NODE_ENV !== 'production') {\n logs.push(`No response found in the '${this.cacheName}' cache. ` +\n `Will respond with a network request.`);\n }\n try {\n response = await handler.fetchAndCachePut(request);\n }\n catch (err) {\n if (err instanceof Error) {\n error = err;\n }\n }\n if (process.env.NODE_ENV !== 'production') {\n if (response) {\n logs.push(`Got response from network.`);\n }\n else {\n logs.push(`Unable to get a response from the network.`);\n }\n }\n }\n else {\n if (process.env.NODE_ENV !== 'production') {\n logs.push(`Found a cached response in the '${this.cacheName}' cache.`);\n }\n }\n if (process.env.NODE_ENV !== 'production') {\n logger.groupCollapsed(messages.strategyStart(this.constructor.name, request));\n for (const log of logs) {\n logger.log(log);\n }\n messages.printFinalResponse(response);\n logger.groupEnd();\n }\n if (!response) {\n throw new WorkboxError('no-response', { url: request.url, error });\n }\n return response;\n }\n}\nexport { CacheFirst };\n","/*\n Copyright 2019 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport './_version.js';\n/**\n * Claim any currently available clients once the service worker\n * becomes active. This is normally used in conjunction with `skipWaiting()`.\n *\n * @memberof workbox-core\n */\nfunction clientsClaim() {\n self.addEventListener('activate', () => self.clients.claim());\n}\nexport { clientsClaim };\n","/*\n Copyright 2019 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { addRoute } from './addRoute.js';\nimport { precache } from './precache.js';\nimport './_version.js';\n/**\n * This method will add entries to the precache list and add a route to\n * respond to fetch events.\n *\n * This is a convenience method that will call\n * {@link workbox-precaching.precache} and\n * {@link workbox-precaching.addRoute} in a single call.\n *\n * @param {Array} entries Array of entries to precache.\n * @param {Object} [options] See the\n * {@link workbox-precaching.PrecacheRoute} options.\n *\n * @memberof workbox-precaching\n */\nfunction precacheAndRoute(entries, options) {\n precache(entries);\n addRoute(options);\n}\nexport { precacheAndRoute };\n","/*\n Copyright 2019 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { getOrCreatePrecacheController } from './utils/getOrCreatePrecacheController.js';\nimport './_version.js';\n/**\n * Adds items to the precache list, removing any duplicates and\n * stores the files in the\n * {@link workbox-core.cacheNames|\"precache cache\"} when the service\n * worker installs.\n *\n * This method can be called multiple times.\n *\n * Please note: This method **will not** serve any of the cached files for you.\n * It only precaches files. To respond to a network request you call\n * {@link workbox-precaching.addRoute}.\n *\n * If you have a single array of files to precache, you can just call\n * {@link workbox-precaching.precacheAndRoute}.\n *\n * @param {Array} [entries=[]] Array of entries to precache.\n *\n * @memberof workbox-precaching\n */\nfunction precache(entries) {\n const precacheController = getOrCreatePrecacheController();\n precacheController.precache(entries);\n}\nexport { precache };\n","/*\n Copyright 2019 Google LLC\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { registerRoute } from 'workbox-routing/registerRoute.js';\nimport { getOrCreatePrecacheController } from './utils/getOrCreatePrecacheController.js';\nimport { PrecacheRoute } from './PrecacheRoute.js';\nimport './_version.js';\n/**\n * Add a `fetch` listener to the service worker that will\n * respond to\n * [network requests]{@link https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers#Custom_responses_to_requests}\n * with precached assets.\n *\n * Requests for assets that aren't precached, the `FetchEvent` will not be\n * responded to, allowing the event to fall through to other `fetch` event\n * listeners.\n *\n * @param {Object} [options] See the {@link workbox-precaching.PrecacheRoute}\n * options.\n *\n * @memberof workbox-precaching\n */\nfunction addRoute(options) {\n const precacheController = getOrCreatePrecacheController();\n const precacheRoute = new PrecacheRoute(precacheController, options);\n registerRoute(precacheRoute);\n}\nexport { addRoute };\n"],"names":["self","_","e","messageGenerator","fallback","code","args","msg","length","JSON","stringify","WorkboxError","Error","constructor","errorCode","details","super","this","name","normalizeHandler","handler","handle","Route","match","method","setCatchHandler","catchHandler","RegExpRoute","regExp","url","result","exec","href","origin","location","index","slice","Router","_routes","Map","_defaultHandlerMap","routes","addFetchListener","addEventListener","event","request","responsePromise","handleRequest","respondWith","addCacheListener","data","type","payload","requestPromises","Promise","all","urlsToCache","map","entry","Request","waitUntil","ports","then","postMessage","URL","protocol","startsWith","sameOrigin","params","route","findMatchingRoute","has","get","err","reject","_catchHandler","catch","async","catchErr","matchResult","Array","isArray","Object","keys","undefined","setDefaultHandler","set","registerRoute","push","unregisterRoute","routeIndex","indexOf","splice","defaultRouter","getOrCreateDefaultRouter","capture","captureUrl","matchCallback","RegExp","moduleName","funcName","paramName","_cacheNameDetails","googleAnalytics","precache","prefix","runtime","suffix","registration","scope","_createCacheName","cacheName","filter","value","join","cacheNames","userCacheName","stripParams","fullURL","ignoreParams","strippedURL","param","searchParams","delete","Deferred","promise","resolve","quotaErrorCallbacks","Set","toRequest","input","StrategyHandler","strategy","options","_cacheKeys","assign","_strategy","_handlerDeferred","_extendLifetimePromises","_plugins","plugins","_pluginStateMap","plugin","fetch","mode","FetchEvent","preloadResponse","possiblePreloadResponse","originalRequest","hasCallback","clone","cb","iterateCallbacks","thrownErrorMessage","message","pluginFilteredRequest","fetchResponse","fetchOptions","callback","response","error","runCallbacks","fetchAndCachePut","responseClone","cachePut","cacheMatch","key","cachedResponse","matchOptions","effectiveRequest","getCacheKey","multiMatchOptions","caches","ms","setTimeout","String","replace","responseToCache","_ensureResponseSafeToCache","cache","open","hasCacheUpdateCallback","oldResponse","strippedRequestURL","keysOptions","ignoreSearch","cacheKeys","cacheKey","cacheMatchIgnoreParams","put","executeQuotaErrorCallbacks","newResponse","state","statefulCallback","statefulParam","doneWaiting","shift","destroy","pluginsUsed","status","Strategy","responseDone","handleAll","_getResponse","_awaitComplete","_handle","waitUntilError","asyncFn","returnPromise","createCacheKey","urlObject","revision","cacheKeyURL","originalURL","PrecacheInstallReportPlugin","updatedURLs","notUpdatedURLs","handlerWillStart","cachedResponseWillBeUsed","PrecacheCacheKeyPlugin","precacheController","cacheKeyWillBeUsed","_precacheController","getCacheKeyForURL","headers","supportStatus","copyResponse","modifier","clonedResponse","responseInit","Headers","statusText","modifiedResponseInit","body","testResponse","Response","canConstructResponseFromBodyStream","blob","PrecacheStrategy","_fallbackToNetwork","fallbackToNetwork","copyRedirectedCacheableResponsesPlugin","_handleInstall","_handleFetch","integrityInManifest","integrity","integrityInRequest","noIntegrityConflict","_useDefaultCacheabilityPluginIfNeeded","defaultPluginIndex","cacheWillUpdatePluginCount","entries","defaultPrecacheCacheabilityPlugin","cacheWillUpdate","redirected","PrecacheController","_urlsToCacheKeys","_urlsToCacheModes","_cacheKeysToIntegrities","install","bind","activate","addToCacheList","_installAndActiveListenersAdded","urlsToWarnAbout","cacheMode","firstEntry","secondEntry","warningMessage","console","warn","installReportPlugin","credentials","currentlyCachedRequests","expectedCacheKeys","values","deletedURLs","getURLsToCacheKeys","getCachedURLs","getIntegrityForCacheKey","matchPrecache","createHandlerBoundToURL","getOrCreatePrecacheController","PrecacheRoute","urlsToCacheKeys","possibleURL","ignoreURLParametersMatching","directoryIndex","cleanURLs","urlManipulation","hash","urlWithoutIgnoredParams","some","test","removeIgnoredSearchParams","pathname","endsWith","directoryURL","cleanURL","additionalURLs","urlToAttempt","generateURLVariations","clients","claim","addRoute"],"mappings":"6CAEA,IACIA,KAAK,uBAAyBC,GAClC,CACA,MAAOC,GAAG,CCEV,MCgBaC,EAdIC,CAACC,KAASC,KACvB,IAAIC,EAAMF,EAIV,OAHIC,EAAKE,OAAS,IACdD,GAAQ,OAAME,KAAKC,UAAUJ,MAE1BC,CAAG,ECId,MAAMI,UAAqBC,MASvBC,WAAAA,CAAYC,EAAWC,GAEnBC,MADgBb,EAAiBW,EAAWC,IAE5CE,KAAKC,KAAOJ,EACZG,KAAKF,QAAUA,CACnB,EC9BJ,IACIf,KAAK,0BAA4BC,GACrC,CACA,MAAOC,GAAG,CCWH,MCAMiB,EAAoBC,GACzBA,GAA8B,iBAAZA,EASXA,EAWA,CAAEC,OAAQD,GCjBzB,MAAME,EAYFT,WAAAA,CAAYU,EAAOH,EAASI,EFhBH,OE8BrBP,KAAKG,QAAUD,EAAiBC,GAChCH,KAAKM,MAAQA,EACbN,KAAKO,OAASA,CAClB,CAMAC,eAAAA,CAAgBL,GACZH,KAAKS,aAAeP,EAAiBC,EACzC,ECnCJ,MAAMO,UAAoBL,EActBT,WAAAA,CAAYe,EAAQR,EAASI,GAiCzBR,OAxBcO,EAAGM,UACb,MAAMC,EAASF,EAAOG,KAAKF,EAAIG,MAE/B,GAAKF,IAODD,EAAII,SAAWC,SAASD,QAA2B,IAAjBH,EAAOK,OAY7C,OAAOL,EAAOM,MAAM,EAAE,GAEbhB,EAASI,EAC1B,ECvCJ,MAAMa,EAIFxB,WAAAA,GACII,KAAKqB,EAAU,IAAIC,IACnBtB,KAAKuB,EAAqB,IAAID,GAClC,CAMA,UAAIE,GACA,OAAOxB,KAAKqB,CAChB,CAKAI,gBAAAA,GAEI1C,KAAK2C,iBAAiB,SAAWC,IAC7B,MAAMC,QAAEA,GAAYD,EACdE,EAAkB7B,KAAK8B,cAAc,CAAEF,UAASD,UAClDE,GACAF,EAAMI,YAAYF,EACtB,GAER,CAuBAG,gBAAAA,GAEIjD,KAAK2C,iBAAiB,WAAaC,IAG/B,GAAIA,EAAMM,MAA4B,eAApBN,EAAMM,KAAKC,KAAuB,CAEhD,MAAMC,QAAEA,GAAYR,EAAMM,KAIpBG,EAAkBC,QAAQC,IAAIH,EAAQI,YAAYC,KAAKC,IACpC,iBAAVA,IACPA,EAAQ,CAACA,IAEb,MAAMb,EAAU,IAAIc,WAAWD,GAC/B,OAAOzC,KAAK8B,cAAc,CAAEF,UAASD,SAAQ,KAKjDA,EAAMgB,UAAUP,GAEZT,EAAMiB,OAASjB,EAAMiB,MAAM,IACtBR,EAAgBS,MAAK,IAAMlB,EAAMiB,MAAM,GAAGE,aAAY,IAEnE,IAER,CAaAhB,aAAAA,EAAcF,QAAEA,EAAOD,MAAEA,IASrB,MAAMf,EAAM,IAAImC,IAAInB,EAAQhB,IAAKK,SAASF,MAC1C,IAAKH,EAAIoC,SAASC,WAAW,QAIzB,OAEJ,MAAMC,EAAatC,EAAII,SAAWC,SAASD,QACrCmC,OAAEA,EAAMC,MAAEA,GAAUpD,KAAKqD,kBAAkB,CAC7C1B,QACAC,UACAsB,aACAtC,QAEJ,IAAIT,EAAUiD,GAASA,EAAMjD,QAe7B,MAAMI,EAASqB,EAAQrB,OAQvB,IAPKJ,GAAWH,KAAKuB,EAAmB+B,IAAI/C,KAKxCJ,EAAUH,KAAKuB,EAAmBgC,IAAIhD,KAErCJ,EAMD,OAkBJ,IAAI0B,EACJ,IACIA,EAAkB1B,EAAQC,OAAO,CAAEQ,MAAKgB,UAASD,QAAOwB,UAC3D,CACD,MAAOK,GACH3B,EAAkBQ,QAAQoB,OAAOD,EACrC,CAEA,MAAM/C,EAAe2C,GAASA,EAAM3C,aAuCpC,OAtCIoB,aAA2BQ,UAC1BrC,KAAK0D,GAAiBjD,KACvBoB,EAAkBA,EAAgB8B,OAAMC,UAEpC,GAAInD,EAUA,IACI,aAAaA,EAAaL,OAAO,CAAEQ,MAAKgB,UAASD,QAAOwB,UAC3D,CACD,MAAOU,GACCA,aAAoBlE,QACpB6D,EAAMK,EAEd,CAEJ,GAAI7D,KAAK0D,EAUL,OAAO1D,KAAK0D,EAActD,OAAO,CAAEQ,MAAKgB,UAASD,UAErD,MAAM6B,CAAG,KAGV3B,CACX,CAgBAwB,iBAAAA,EAAkBzC,IAAEA,EAAGsC,WAAEA,EAAUtB,QAAEA,EAAOD,MAAEA,IAC1C,MAAMH,EAASxB,KAAKqB,EAAQkC,IAAI3B,EAAQrB,SAAW,GACnD,IAAK,MAAM6C,KAAS5B,EAAQ,CACxB,IAAI2B,EAGJ,MAAMW,EAAcV,EAAM9C,MAAM,CAAEM,MAAKsC,aAAYtB,UAASD,UAC5D,GAAImC,EA6BA,OAjBAX,EAASW,GACLC,MAAMC,QAAQb,IAA6B,IAAlBA,EAAO5D,QAI3BuE,EAAYlE,cAAgBqE,QACG,IAApCA,OAAOC,KAAKJ,GAAavE,QAIG,kBAAhBuE,KAPZX,OAASgB,GAcN,CAAEf,QAAOD,SAExB,CAEA,MAAO,EACX,CAeAiB,iBAAAA,CAAkBjE,EAASI,EJ1SF,OI2SrBP,KAAKuB,EAAmB8C,IAAI9D,EAAQL,EAAiBC,GACzD,CAQAK,eAAAA,CAAgBL,GACZH,KAAK0D,EAAgBxD,EAAiBC,EAC1C,CAMAmE,aAAAA,CAAclB,GAiCLpD,KAAKqB,EAAQiC,IAAIF,EAAM7C,SACxBP,KAAKqB,EAAQgD,IAAIjB,EAAM7C,OAAQ,IAInCP,KAAKqB,EAAQkC,IAAIH,EAAM7C,QAAQgE,KAAKnB,EACxC,CAMAoB,eAAAA,CAAgBpB,GACZ,IAAKpD,KAAKqB,EAAQiC,IAAIF,EAAM7C,QACxB,MAAM,IAAIb,EAAa,6CAA8C,CACjEa,OAAQ6C,EAAM7C,SAGtB,MAAMkE,EAAazE,KAAKqB,EAAQkC,IAAIH,EAAM7C,QAAQmE,QAAQtB,GAC1D,KAAIqB,GAAc,GAId,MAAM,IAAI/E,EAAa,yCAHvBM,KAAKqB,EAAQkC,IAAIH,EAAM7C,QAAQoE,OAAOF,EAAY,EAK1D,EC7XJ,IAAIG,EAQG,MAAMC,EAA2BA,KAC/BD,IACDA,EAAgB,IAAIxD,EAEpBwD,EAAcnD,mBACdmD,EAAc5C,oBAEX4C,GCOX,SAASN,EAAcQ,EAAS3E,EAASI,GACrC,IAAI6C,EACJ,GAAuB,iBAAZ0B,EAAsB,CAC7B,MAAMC,EAAa,IAAIhC,IAAI+B,EAAS7D,SAASF,MAkC7CqC,EAAQ,IAAI/C,GAZU2E,EAAGpE,SASdA,EAAIG,OAASgE,EAAWhE,MAGFZ,EAASI,EAC9C,MACK,GAAIuE,aAAmBG,OAExB7B,EAAQ,IAAI1C,EAAYoE,EAAS3E,EAASI,QAEzC,GAAuB,mBAAZuE,EAEZ1B,EAAQ,IAAI/C,EAAMyE,EAAS3E,EAASI,OAEnC,MAAIuE,aAAmBzE,GAIxB,MAAM,IAAIX,EAAa,yBAA0B,CAC7CwF,WAAY,kBACZC,SAAU,gBACVC,UAAW,YANfhC,EAAQ0B,CAQZ,CAGA,OAFsBD,IACRP,cAAclB,GACrBA,CACX,CCnFA,MAAMiC,EAAoB,CACtBC,gBAAiB,kBACjBC,SAAU,cACVC,OAAQ,UACRC,QAAS,UACTC,OAAgC,oBAAjBC,aAA+BA,aAAaC,MAAQ,IAEjEC,EAAoBC,GACf,CAACT,EAAkBG,OAAQM,EAAWT,EAAkBK,QAC1DK,QAAQC,GAAUA,GAASA,EAAMzG,OAAS,IAC1C0G,KAAK,KAODC,EAWSC,GACPA,GAAiBN,EAAiBR,EAAkBE,UAZtDW,EAiBQC,GACNA,GAAiBN,EAAiBR,EAAkBI,SCpCnE,SAASW,EAAYC,EAASC,GAC1B,MAAMC,EAAc,IAAIxD,IAAIsD,GAC5B,IAAK,MAAMG,KAASF,EAChBC,EAAYE,aAAaC,OAAOF,GAEpC,OAAOD,EAAYxF,IACvB,CCGA,MAAM4F,EAIF/G,WAAAA,GACII,KAAK4G,QAAU,IAAIvE,SAAQ,CAACwE,EAASpD,KACjCzD,KAAK6G,QAAUA,EACf7G,KAAKyD,OAASA,CAAM,GAE5B,ECdJ,MAAMqD,EAAsB,IAAIC,ICThC,IACIhI,KAAK,6BAA+BC,GACxC,CACA,MAAOC,GAAG,CCWV,SAAS+H,EAAUC,GACf,MAAwB,iBAAVA,EAAqB,IAAIvE,QAAQuE,GAASA,CAC5D,CAUA,MAAMC,EAiBFtH,WAAAA,CAAYuH,EAAUC,GAClBpH,KAAKqH,EAAa,GA8ClBpD,OAAOqD,OAAOtH,KAAMoH,GACpBpH,KAAK2B,MAAQyF,EAAQzF,MACrB3B,KAAKuH,EAAYJ,EACjBnH,KAAKwH,EAAmB,IAAIb,EAC5B3G,KAAKyH,EAA0B,GAG/BzH,KAAK0H,EAAW,IAAIP,EAASQ,SAC7B3H,KAAK4H,EAAkB,IAAItG,IAC3B,IAAK,MAAMuG,KAAU7H,KAAK0H,EACtB1H,KAAK4H,EAAgBvD,IAAIwD,EAAQ,CAAE,GAEvC7H,KAAK2B,MAAMgB,UAAU3C,KAAKwH,EAAiBZ,QAC/C,CAcA,WAAMkB,CAAMb,GACR,MAAMtF,MAAEA,GAAU3B,KAClB,IAAI4B,EAAUoF,EAAUC,GACxB,GAAqB,aAAjBrF,EAAQmG,MACRpG,aAAiBqG,YACjBrG,EAAMsG,gBAAiB,CACvB,MAAMC,QAAiCvG,EAAMsG,gBAC7C,GAAIC,EAKA,OAAOA,CAEf,CAIA,MAAMC,EAAkBnI,KAAKoI,YAAY,gBACnCxG,EAAQyG,QACR,KACN,IACI,IAAK,MAAMC,KAAMtI,KAAKuI,iBAAiB,oBACnC3G,QAAgB0G,EAAG,CAAE1G,QAASA,EAAQyG,QAAS1G,SAEtD,CACD,MAAO6B,GACH,GAAIA,aAAe7D,MACf,MAAM,IAAID,EAAa,kCAAmC,CACtD8I,mBAAoBhF,EAAIiF,SAGpC,CAIA,MAAMC,EAAwB9G,EAAQyG,QACtC,IACI,IAAIM,EAEJA,QAAsBb,MAAMlG,EAA0B,aAAjBA,EAAQmG,UAAsB5D,EAAYnE,KAAKuH,EAAUqB,cAM9F,IAAK,MAAMC,KAAY7I,KAAKuI,iBAAiB,mBACzCI,QAAsBE,EAAS,CAC3BlH,QACAC,QAAS8G,EACTI,SAAUH,IAGlB,OAAOA,CACV,CACD,MAAOI,GAeH,MARIZ,SACMnI,KAAKgJ,aAAa,eAAgB,CACpCD,MAAOA,EACPpH,QACAwG,gBAAiBA,EAAgBE,QACjCzG,QAAS8G,EAAsBL,UAGjCU,CACV,CACJ,CAWA,sBAAME,CAAiBhC,GACnB,MAAM6B,QAAiB9I,KAAK8H,MAAMb,GAC5BiC,EAAgBJ,EAAST,QAE/B,OADKrI,KAAK2C,UAAU3C,KAAKmJ,SAASlC,EAAOiC,IAClCJ,CACX,CAaA,gBAAMM,CAAWC,GACb,MAAMzH,EAAUoF,EAAUqC,GAC1B,IAAIC,EACJ,MAAMxD,UAAEA,EAASyD,aAAEA,GAAiBvJ,KAAKuH,EACnCiC,QAAyBxJ,KAAKyJ,YAAY7H,EAAS,QACnD8H,EAAoBzF,OAAOqD,OAAOrD,OAAOqD,OAAO,CAAA,EAAIiC,GAAe,CAAEzD,cAC3EwD,QAAuBK,OAAOrJ,MAAMkJ,EAAkBE,GAStD,IAAK,MAAMb,KAAY7I,KAAKuI,iBAAiB,4BACzCe,QACWT,EAAS,CACZ/C,YACAyD,eACAD,iBACA1H,QAAS4H,EACT7H,MAAO3B,KAAK2B,cACTwC,EAEf,OAAOmF,CACX,CAgBA,cAAMH,CAASE,EAAKP,GAChB,MAAMlH,EAAUoF,EAAUqC,GCxP3B,IAAiBO,UD2PF,EC1PX,IAAIvH,SAASwE,GAAYgD,WAAWhD,EAAS+C,MD2PhD,MAAMJ,QAAyBxJ,KAAKyJ,YAAY7H,EAAS,SAiBzD,IAAKkH,EAKD,MAAM,IAAIpJ,EAAa,6BAA8B,CACjDkB,KE1RQA,EF0RY4I,EAAiB5I,IEzRlC,IAAImC,IAAI+G,OAAOlJ,GAAMK,SAASF,MAG/BA,KAAKgJ,QAAQ,IAAI9E,OAAQ,IAAGhE,SAASD,UAAW,OAJ1CJ,MF6RhB,MAAMoJ,QAAwBhK,KAAKiK,EAA2BnB,GAC9D,IAAKkB,EAKD,OAAO,EAEX,MAAMlE,UAAEA,EAASyD,aAAEA,GAAiBvJ,KAAKuH,EACnC2C,QAAcnL,KAAK4K,OAAOQ,KAAKrE,GAC/BsE,EAAyBpK,KAAKoI,YAAY,kBAC1CiC,EAAcD,QJtR5BxG,eAAsCsG,EAAOtI,EAAS0E,EAAciD,GAChE,MAAMe,EAAqBlE,EAAYxE,EAAQhB,IAAK0F,GAEpD,GAAI1E,EAAQhB,MAAQ0J,EAChB,OAAOJ,EAAM5J,MAAMsB,EAAS2H,GAGhC,MAAMgB,EAActG,OAAOqD,OAAOrD,OAAOqD,OAAO,CAAA,EAAIiC,GAAe,CAAEiB,cAAc,IAC7EC,QAAkBP,EAAMhG,KAAKtC,EAAS2I,GAC5C,IAAK,MAAMG,KAAYD,EAEnB,GAAIH,IADwBlE,EAAYsE,EAAS9J,IAAK0F,GAElD,OAAO4D,EAAM5J,MAAMoK,EAAUnB,EAIzC,CIuQoBoB,CAIRT,EAAOV,EAAiBnB,QAAS,CAAC,mBAAoBkB,GACpD,KAKN,UACUW,EAAMU,IAAIpB,EAAkBY,EAAyBJ,EAAgB3B,QAAU2B,EACxF,CACD,MAAOjB,GACH,GAAIA,aAAiBpJ,MAKjB,KAHmB,uBAAfoJ,EAAM9I,YGhT1B2D,iBAKI,IAAK,MAAMiF,KAAY/B,QACb+B,GAQd,CHmS0BgC,GAEJ9B,CAEd,CACA,IAAK,MAAMF,KAAY7I,KAAKuI,iBAAiB,wBACnCM,EAAS,CACX/C,YACAuE,cACAS,YAAad,EAAgB3B,QAC7BzG,QAAS4H,EACT7H,MAAO3B,KAAK2B,QAGpB,OAAO,CACX,CAYA,iBAAM8H,CAAY7H,EAASmG,GACvB,MAAMsB,EAAO,GAAEzH,EAAQhB,SAASmH,IAChC,IAAK/H,KAAKqH,EAAWgC,GAAM,CACvB,IAAIG,EAAmB5H,EACvB,IAAK,MAAMiH,KAAY7I,KAAKuI,iBAAiB,sBACzCiB,EAAmBxC,QAAgB6B,EAAS,CACxCd,OACAnG,QAAS4H,EACT7H,MAAO3B,KAAK2B,MAEZwB,OAAQnD,KAAKmD,UAGrBnD,KAAKqH,EAAWgC,GAAOG,CAC3B,CACA,OAAOxJ,KAAKqH,EAAWgC,EAC3B,CAQAjB,WAAAA,CAAYnI,GACR,IAAK,MAAM4H,KAAU7H,KAAKuH,EAAUI,QAChC,GAAI1H,KAAQ4H,EACR,OAAO,EAGf,OAAO,CACX,CAiBA,kBAAMmB,CAAa/I,EAAMuG,GACrB,IAAK,MAAMqC,KAAY7I,KAAKuI,iBAAiBtI,SAGnC4I,EAASrC,EAEvB,CAUA,iBAAC+B,CAAiBtI,GACd,IAAK,MAAM4H,KAAU7H,KAAKuH,EAAUI,QAChC,GAA4B,mBAAjBE,EAAO5H,GAAsB,CACpC,MAAM8K,EAAQ/K,KAAK4H,EAAgBrE,IAAIsE,GACjCmD,EAAoBxE,IACtB,MAAMyE,EAAgBhH,OAAOqD,OAAOrD,OAAOqD,OAAO,CAAA,EAAId,GAAQ,CAAEuE,UAGhE,OAAOlD,EAAO5H,GAAMgL,EAAc,QAEhCD,CACV,CAER,CAcArI,SAAAA,CAAUiE,GAEN,OADA5G,KAAKyH,EAAwBlD,KAAKqC,GAC3BA,CACX,CAWA,iBAAMsE,GACF,IAAItE,EACJ,KAAQA,EAAU5G,KAAKyH,EAAwB0D,eACrCvE,CAEd,CAKAwE,OAAAA,GACIpL,KAAKwH,EAAiBX,QAAQ,KAClC,CAWA,OAAMoD,CAA2BnB,GAC7B,IAAIkB,EAAkBlB,EAClBuC,GAAc,EAClB,IAAK,MAAMxC,KAAY7I,KAAKuI,iBAAiB,mBAQzC,GAPAyB,QACWnB,EAAS,CACZjH,QAAS5B,KAAK4B,QACdkH,SAAUkB,EACVrI,MAAO3B,KAAK2B,cACTwC,EACXkH,GAAc,GACTrB,EACD,MAwBR,OArBKqB,GACGrB,GAA8C,MAA3BA,EAAgBsB,SACnCtB,OAAkB7F,GAmBnB6F,CACX,EIhfJ,MAAMuB,EAuBF3L,WAAAA,CAAYwH,EAAU,IAQlBpH,KAAK8F,UAAYI,EAA0BkB,EAAQtB,WAQnD9F,KAAK2H,QAAUP,EAAQO,SAAW,GAQlC3H,KAAK4I,aAAexB,EAAQwB,aAQ5B5I,KAAKuJ,aAAenC,EAAQmC,YAChC,CAoBAnJ,MAAAA,CAAOgH,GACH,MAAOoE,GAAgBxL,KAAKyL,UAAUrE,GACtC,OAAOoE,CACX,CAuBAC,SAAAA,CAAUrE,GAEFA,aAAmBY,aACnBZ,EAAU,CACNzF,MAAOyF,EACPxF,QAASwF,EAAQxF,UAGzB,MAAMD,EAAQyF,EAAQzF,MAChBC,EAAqC,iBAApBwF,EAAQxF,QACzB,IAAIc,QAAQ0E,EAAQxF,SACpBwF,EAAQxF,QACRuB,EAAS,WAAYiE,EAAUA,EAAQjE,YAASgB,EAChDhE,EAAU,IAAI+G,EAAgBlH,KAAM,CAAE2B,QAAOC,UAASuB,WACtDqI,EAAexL,KAAK0L,EAAavL,EAASyB,EAASD,GAGzD,MAAO,CAAC6J,EAFYxL,KAAK2L,EAAeH,EAAcrL,EAASyB,EAASD,GAG5E,CACA,OAAM+J,CAAavL,EAASyB,EAASD,GAEjC,IAAImH,QADE3I,EAAQ6I,aAAa,mBAAoB,CAAErH,QAAOC,YAExD,IAKI,GAJAkH,QAAiB9I,KAAK4L,EAAQhK,EAASzB,IAIlC2I,GAA8B,UAAlBA,EAAS5G,KACtB,MAAM,IAAIxC,EAAa,cAAe,CAAEkB,IAAKgB,EAAQhB,KAE5D,CACD,MAAOmI,GACH,GAAIA,aAAiBpJ,MACjB,IAAK,MAAMkJ,KAAY1I,EAAQoI,iBAAiB,mBAE5C,GADAO,QAAiBD,EAAS,CAAEE,QAAOpH,QAAOC,YACtCkH,EACA,MAIZ,IAAKA,EACD,MAAMC,CAOd,CACA,IAAK,MAAMF,KAAY1I,EAAQoI,iBAAiB,sBAC5CO,QAAiBD,EAAS,CAAElH,QAAOC,UAASkH,aAEhD,OAAOA,CACX,CACA,OAAM6C,CAAeH,EAAcrL,EAASyB,EAASD,GACjD,IAAImH,EACAC,EACJ,IACID,QAAiB0C,CACpB,CACD,MAAOzC,GAGH,CAEJ,UACU5I,EAAQ6I,aAAa,oBAAqB,CAC5CrH,QACAC,UACAkH,mBAEE3I,EAAQ+K,aACjB,CACD,MAAOW,GACCA,aAA0BlM,QAC1BoJ,EAAQ8C,EAEhB,CAQA,SAPM1L,EAAQ6I,aAAa,qBAAsB,CAC7CrH,QACAC,UACAkH,WACAC,MAAOA,IAEX5I,EAAQiL,UACJrC,EACA,MAAMA,CAEd,EChMJ,SAASpG,EAAUhB,EAAOmK,GACtB,MAAMC,EAAgBD,IAEtB,OADAnK,EAAMgB,UAAUoJ,GACTA,CACX,CClBA,IACIhN,KAAK,6BAA+BC,GACxC,CACA,MAAOC,GAAG,CCeH,SAAS+M,EAAevJ,GAC3B,IAAKA,EACD,MAAM,IAAI/C,EAAa,oCAAqC,CAAE+C,UAIlE,GAAqB,iBAAVA,EAAoB,CAC3B,MAAMwJ,EAAY,IAAIlJ,IAAIN,EAAOxB,SAASF,MAC1C,MAAO,CACH2J,SAAUuB,EAAUlL,KACpBH,IAAKqL,EAAUlL,KAEvB,CACA,MAAMmL,SAAEA,EAAQtL,IAAEA,GAAQ6B,EAC1B,IAAK7B,EACD,MAAM,IAAIlB,EAAa,oCAAqC,CAAE+C,UAIlE,IAAKyJ,EAAU,CACX,MAAMD,EAAY,IAAIlJ,IAAInC,EAAKK,SAASF,MACxC,MAAO,CACH2J,SAAUuB,EAAUlL,KACpBH,IAAKqL,EAAUlL,KAEvB,CAGA,MAAMoL,EAAc,IAAIpJ,IAAInC,EAAKK,SAASF,MACpCqL,EAAc,IAAIrJ,IAAInC,EAAKK,SAASF,MAE1C,OADAoL,EAAY1F,aAAapC,IAxCC,kBAwC0B6H,GAC7C,CACHxB,SAAUyB,EAAYpL,KACtBH,IAAKwL,EAAYrL,KAEzB,CCzCA,MAAMsL,EACFzM,WAAAA,GACII,KAAKsM,YAAc,GACnBtM,KAAKuM,eAAiB,GACtBvM,KAAKwM,iBAAmB5I,OAAShC,UAASmJ,YAElCA,IACAA,EAAM5C,gBAAkBvG,EAC5B,EAEJ5B,KAAKyM,yBAA2B7I,OAASjC,QAAOoJ,QAAOzB,qBACnD,GAAmB,YAAf3H,EAAMO,MACF6I,GACAA,EAAM5C,iBACN4C,EAAM5C,2BAA2BzF,QAAS,CAE1C,MAAM9B,EAAMmK,EAAM5C,gBAAgBvH,IAC9B0I,EACAtJ,KAAKuM,eAAehI,KAAK3D,GAGzBZ,KAAKsM,YAAY/H,KAAK3D,EAE9B,CAEJ,OAAO0I,CAAc,CAE7B,EC3BJ,MAAMoD,EACF9M,WAAAA,EAAY+M,mBAAEA,IACV3M,KAAK4M,mBAAqBhJ,OAAShC,UAASuB,aAGxC,MAAMuH,GAAYvH,aAAuC,EAASA,EAAOuH,WACrE1K,KAAK6M,EAAoBC,kBAAkBlL,EAAQhB,KAEvD,OAAO8J,EACD,IAAIhI,QAAQgI,EAAU,CAAEqC,QAASnL,EAAQmL,UACzCnL,CAAO,EAEjB5B,KAAK6M,EAAsBF,CAC/B,ECnBJ,IAAIK,ECCAL,ECoBJ/I,eAAeqJ,EAAanE,EAAUoE,GAClC,IAAIlM,EAAS,KAEb,GAAI8H,EAASlI,IAAK,CAEdI,EADoB,IAAI+B,IAAI+F,EAASlI,KAChBI,MACzB,CACA,GAAIA,IAAWjC,KAAKkC,SAASD,OACzB,MAAM,IAAItB,EAAa,6BAA8B,CAAEsB,WAE3D,MAAMmM,EAAiBrE,EAAST,QAE1B+E,EAAe,CACjBL,QAAS,IAAIM,QAAQF,EAAeJ,SACpCzB,OAAQ6B,EAAe7B,OACvBgC,WAAYH,EAAeG,YAGzBC,EAAuBL,EAAWA,EAASE,GAAgBA,EAI3DI,EFjCV,WACI,QAAsBrJ,IAAlB6I,EAA6B,CAC7B,MAAMS,EAAe,IAAIC,SAAS,IAClC,GAAI,SAAUD,EACV,IACI,IAAIC,SAASD,EAAaD,MAC1BR,GAAgB,CACnB,CACD,MAAOjE,GACHiE,GAAgB,CACpB,CAEJA,GAAgB,CACpB,CACA,OAAOA,CACX,CEkBiBW,GACPR,EAAeK,WACTL,EAAeS,OAC3B,OAAO,IAAIF,SAASF,EAAMD,EAC9B,CC7BA,MAAMM,UAAyBtC,EAkB3B3L,WAAAA,CAAYwH,EAAU,IAClBA,EAAQtB,UAAYI,EAA2BkB,EAAQtB,WACvD/F,MAAMqH,GACNpH,KAAK8N,GAC6B,IAA9B1G,EAAQ2G,kBAKZ/N,KAAK2H,QAAQpD,KAAKsJ,EAAiBG,uCACvC,CAQA,OAAMpC,CAAQhK,EAASzB,GACnB,MAAM2I,QAAiB3I,EAAQiJ,WAAWxH,GAC1C,OAAIkH,IAKA3I,EAAQwB,OAAgC,YAAvBxB,EAAQwB,MAAMO,WAClBlC,KAAKiO,EAAerM,EAASzB,SAIjCH,KAAKkO,EAAatM,EAASzB,GAC5C,CACA,OAAM+N,CAAatM,EAASzB,GACxB,IAAI2I,EACJ,MAAM3F,EAAUhD,EAAQgD,QAAU,GAElC,IAAInD,KAAK8N,EAuCL,MAAM,IAAIpO,EAAa,yBAA0B,CAC7CoG,UAAW9F,KAAK8F,UAChBlF,IAAKgB,EAAQhB,MAzCQ,CAMzB,MAAMuN,EAAsBhL,EAAOiL,UAC7BC,EAAqBzM,EAAQwM,UAC7BE,GAAuBD,GAAsBA,IAAuBF,EAG1ErF,QAAiB3I,EAAQ2H,MAAM,IAAIpF,QAAQd,EAAS,CAChDwM,UAA4B,YAAjBxM,EAAQmG,KACbsG,GAAsBF,OACtBhK,KASNgK,GACAG,GACiB,YAAjB1M,EAAQmG,OACR/H,KAAKuO,UACmBpO,EAAQgJ,SAASvH,EAASkH,EAAST,SAQnE,CAuBA,OAAOS,CACX,CACA,OAAMmF,CAAerM,EAASzB,GAC1BH,KAAKuO,IACL,MAAMzF,QAAiB3I,EAAQ2H,MAAMlG,GAIrC,UADwBzB,EAAQgJ,SAASvH,EAASkH,EAAST,SAIvD,MAAM,IAAI3I,EAAa,0BAA2B,CAC9CkB,IAAKgB,EAAQhB,IACb0K,OAAQxC,EAASwC,SAGzB,OAAOxC,CACX,CA4BAyF,CAAAA,GACI,IAAIC,EAAqB,KACrBC,EAA6B,EACjC,IAAK,MAAOvN,EAAO2G,KAAW7H,KAAK2H,QAAQ+G,UAEnC7G,IAAWgG,EAAiBG,yCAI5BnG,IAAWgG,EAAiBc,oCAC5BH,EAAqBtN,GAErB2G,EAAO+G,iBACPH,KAG2B,IAA/BA,EACAzO,KAAK2H,QAAQpD,KAAKsJ,EAAiBc,mCAE9BF,EAA6B,GAA4B,OAAvBD,GAEvCxO,KAAK2H,QAAQhD,OAAO6J,EAAoB,EAGhD,EAEJX,EAAiBc,kCAAoC,CACjD/K,gBAAqBgL,OAAC9F,SAAEA,MACfA,GAAYA,EAASwC,QAAU,IACzB,KAEJxC,GAGf+E,EAAiBG,uCAAyC,CACtDpK,gBAAqBgL,OAAC9F,SAAEA,KACbA,EAAS+F,iBAAmB5B,EAAanE,GAAYA,GCnMpE,MAAMgG,EAWFlP,WAAAA,EAAYkG,UAAEA,EAAS6B,QAAEA,EAAU,GAAEoG,kBAAEA,GAAoB,GAAU,IACjE/N,KAAK+O,EAAmB,IAAIzN,IAC5BtB,KAAKgP,EAAoB,IAAI1N,IAC7BtB,KAAKiP,EAA0B,IAAI3N,IACnCtB,KAAKuH,EAAY,IAAIsG,EAAiB,CAClC/H,UAAWI,EAA2BJ,GACtC6B,QAAS,IACFA,EACH,IAAI+E,EAAuB,CAAEC,mBAAoB3M,QAErD+N,sBAGJ/N,KAAKkP,QAAUlP,KAAKkP,QAAQC,KAAKnP,MACjCA,KAAKoP,SAAWpP,KAAKoP,SAASD,KAAKnP,KACvC,CAKA,YAAImH,GACA,OAAOnH,KAAKuH,CAChB,CAWAhC,QAAAA,CAASmJ,GACL1O,KAAKqP,eAAeX,GACf1O,KAAKsP,IACNvQ,KAAK2C,iBAAiB,UAAW1B,KAAKkP,SACtCnQ,KAAK2C,iBAAiB,WAAY1B,KAAKoP,UACvCpP,KAAKsP,GAAkC,EAE/C,CAQAD,cAAAA,CAAeX,GASX,MAAMa,EAAkB,GACxB,IAAK,MAAM9M,KAASiM,EAAS,CAEJ,iBAAVjM,EACP8M,EAAgBhL,KAAK9B,GAEhBA,QAA4B0B,IAAnB1B,EAAMyJ,UACpBqD,EAAgBhL,KAAK9B,EAAM7B,KAE/B,MAAM8J,SAAEA,EAAQ9J,IAAEA,GAAQoL,EAAevJ,GACnC+M,EAA6B,iBAAV/M,GAAsBA,EAAMyJ,SAAW,SAAW,UAC3E,GAAIlM,KAAK+O,EAAiBzL,IAAI1C,IAC1BZ,KAAK+O,EAAiBxL,IAAI3C,KAAS8J,EACnC,MAAM,IAAIhL,EAAa,wCAAyC,CAC5D+P,WAAYzP,KAAK+O,EAAiBxL,IAAI3C,GACtC8O,YAAahF,IAGrB,GAAqB,iBAAVjI,GAAsBA,EAAM2L,UAAW,CAC9C,GAAIpO,KAAKiP,EAAwB3L,IAAIoH,IACjC1K,KAAKiP,EAAwB1L,IAAImH,KAAcjI,EAAM2L,UACrD,MAAM,IAAI1O,EAAa,4CAA6C,CAChEkB,QAGRZ,KAAKiP,EAAwB5K,IAAIqG,EAAUjI,EAAM2L,UACrD,CAGA,GAFApO,KAAK+O,EAAiB1K,IAAIzD,EAAK8J,GAC/B1K,KAAKgP,EAAkB3K,IAAIzD,EAAK4O,GAC5BD,EAAgBhQ,OAAS,EAAG,CAC5B,MAAMoQ,EACD,qDAAQJ,EAAgBtJ,KAAK,8EAK9B2J,QAAQC,KAAKF,EAKrB,CACJ,CACJ,CAWAT,OAAAA,CAAQvN,GAGJ,OAAOgB,EAAUhB,GAAOiC,UACpB,MAAMkM,EAAsB,IAAIzD,EAChCrM,KAAKmH,SAASQ,QAAQpD,KAAKuL,GAG3B,IAAK,MAAOlP,EAAK8J,KAAa1K,KAAK+O,EAAkB,CACjD,MAAMX,EAAYpO,KAAKiP,EAAwB1L,IAAImH,GAC7C8E,EAAYxP,KAAKgP,EAAkBzL,IAAI3C,GACvCgB,EAAU,IAAIc,QAAQ9B,EAAK,CAC7BwN,YACAlE,MAAOsF,EACPO,YAAa,sBAEX1N,QAAQC,IAAItC,KAAKmH,SAASsE,UAAU,CACtCtI,OAAQ,CAAEuH,YACV9I,UACAD,UAER,CACA,MAAM2K,YAAEA,EAAWC,eAAEA,GAAmBuD,EAIxC,MAAO,CAAExD,cAAaC,iBAAgB,GAE9C,CAWA6C,QAAAA,CAASzN,GAGL,OAAOgB,EAAUhB,GAAOiC,UACpB,MAAMsG,QAAcnL,KAAK4K,OAAOQ,KAAKnK,KAAKmH,SAASrB,WAC7CkK,QAAgC9F,EAAMhG,OACtC+L,EAAoB,IAAIlJ,IAAI/G,KAAK+O,EAAiBmB,UAClDC,EAAc,GACpB,IAAK,MAAMvO,KAAWoO,EACbC,EAAkB3M,IAAI1B,EAAQhB,aACzBsJ,EAAMxD,OAAO9E,GACnBuO,EAAY5L,KAAK3C,EAAQhB,MAMjC,MAAO,CAAEuP,cAAa,GAE9B,CAOAC,kBAAAA,GACI,OAAOpQ,KAAK+O,CAChB,CAOAsB,aAAAA,GACI,MAAO,IAAIrQ,KAAK+O,EAAiB7K,OACrC,CAUA4I,iBAAAA,CAAkBlM,GACd,MAAMqL,EAAY,IAAIlJ,IAAInC,EAAKK,SAASF,MACxC,OAAOf,KAAK+O,EAAiBxL,IAAI0I,EAAUlL,KAC/C,CAMAuP,uBAAAA,CAAwB5F,GACpB,OAAO1K,KAAKiP,EAAwB1L,IAAImH,EAC5C,CAmBA,mBAAM6F,CAAc3O,GAChB,MAAMhB,EAAMgB,aAAmBc,QAAUd,EAAQhB,IAAMgB,EACjD8I,EAAW1K,KAAK8M,kBAAkBlM,GACxC,GAAI8J,EAAU,CAEV,aADoB3L,KAAK4K,OAAOQ,KAAKnK,KAAKmH,SAASrB,YACtCxF,MAAMoK,EACvB,CAEJ,CASA8F,uBAAAA,CAAwB5P,GACpB,MAAM8J,EAAW1K,KAAK8M,kBAAkBlM,GACxC,IAAK8J,EACD,MAAM,IAAIhL,EAAa,oBAAqB,CAAEkB,QAElD,OAAQwG,IACJA,EAAQxF,QAAU,IAAIc,QAAQ9B,GAC9BwG,EAAQjE,OAASc,OAAOqD,OAAO,CAAEoD,YAAYtD,EAAQjE,QAC9CnD,KAAKmH,SAAS/G,OAAOgH,GAEpC,EHnRG,MAAMqJ,EAAgCA,KACpC9D,IACDA,EAAqB,IAAImC,GAEtBnC,GIGX,MAAM+D,UAAsBrQ,EAiBxBT,WAAAA,CAAY+M,EAAoBvF,GAe5BrH,OAdcO,EAAGsB,cACb,MAAM+O,EAAkBhE,EAAmByD,qBAC3C,IAAK,MAAMQ,KCtBhB,UAAgChQ,GAAKiQ,4BAAEA,EAA8B,CAAC,QAAS,YAAWC,eAAEA,EAAiB,aAAYC,UAAEA,GAAY,EAAIC,gBAAEA,GAAqB,IACrK,MAAM/E,EAAY,IAAIlJ,IAAInC,EAAKK,SAASF,MACxCkL,EAAUgF,KAAO,SACXhF,EAAUlL,KAChB,MAAMmQ,ECHH,SAAmCjF,EAAW4E,EAA8B,IAG/E,IAAK,MAAMzL,IAAa,IAAI6G,EAAUxF,aAAavC,QAC3C2M,EAA4BM,MAAMxQ,GAAWA,EAAOyQ,KAAKhM,MACzD6G,EAAUxF,aAAaC,OAAOtB,GAGtC,OAAO6G,CACX,CDNoCoF,CAA0BpF,EAAW4E,GAErE,SADMK,EAAwBnQ,KAC1B+P,GAAkBI,EAAwBI,SAASC,SAAS,KAAM,CAClE,MAAMC,EAAe,IAAIzO,IAAImO,EAAwBnQ,MACrDyQ,EAAaF,UAAYR,QACnBU,EAAazQ,IACvB,CACA,GAAIgQ,EAAW,CACX,MAAMU,EAAW,IAAI1O,IAAImO,EAAwBnQ,MACjD0Q,EAASH,UAAY,cACfG,EAAS1Q,IACnB,CACA,GAAIiQ,EAAiB,CACjB,MAAMU,EAAiBV,EAAgB,CAAEpQ,IAAKqL,IAC9C,IAAK,MAAM0F,KAAgBD,QACjBC,EAAa5Q,IAE3B,CACJ,CDAsC6Q,CAAsBhQ,EAAQhB,IAAKwG,GAAU,CACnE,MAAMsD,EAAWiG,EAAgBpN,IAAIqN,GACrC,GAAIlG,EAAU,CAEV,MAAO,CAAEA,WAAU0D,UADDzB,EAAmB2D,wBAAwB5F,GAEjE,CACJ,CAIA,GAESiC,EAAmBxF,SACpC,eG3BJ,cAAyBoE,EAQrB,OAAMK,CAAQhK,EAASzB,GAUnB,IACI4I,EADAD,QAAiB3I,EAAQiJ,WAAWxH,GAExC,IAAKkH,EAKD,IACIA,QAAiB3I,EAAQ8I,iBAAiBrH,EAC7C,CACD,MAAO4B,GACCA,aAAe7D,QACfoJ,EAAQvF,EAEhB,CAuBJ,IAAKsF,EACD,MAAM,IAAIpJ,EAAa,cAAe,CAAEkB,IAAKgB,EAAQhB,IAAKmI,UAE9D,OAAOD,CACX,kBCxEJ,WACI/J,KAAK2C,iBAAiB,YAAY,IAAM3C,KAAK8S,QAAQC,SACzD,qBCQA,SAA0BpD,EAAStH,ICInC,SAAkBsH,GACa+B,IACRlL,SAASmJ,EAChC,CDNInJ,CAASmJ,GEAb,SAAkBtH,GACd,MAAMuF,EAAqB8D,IAE3BnM,EADsB,IAAIoM,EAAc/D,EAAoBvF,GAEhE,CFHI2K,CAAS3K,EACb"} \ No newline at end of file