diff --git a/404.html b/404.html new file mode 100644 index 000000000..3370bbe87 --- /dev/null +++ b/404.html @@ -0,0 +1,392 @@ +页面没有找到 | 希亚的西红柿の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..07dd29b9f --- /dev/null +++ b/about/index.html @@ -0,0 +1,484 @@ +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..7d9bcbd0d --- /dev/null +++ b/archives/2022/07/index.html @@ -0,0 +1,399 @@ +七月 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..25a62a96d --- /dev/null +++ b/archives/2022/08/index.html @@ -0,0 +1,399 @@ +八月 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..dc2f812bd --- /dev/null +++ b/archives/2022/09/index.html @@ -0,0 +1,399 @@ +九月 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..b982c223f --- /dev/null +++ b/archives/2022/12/index.html @@ -0,0 +1,399 @@ +十二月 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..8f3852796 --- /dev/null +++ b/archives/2022/index.html @@ -0,0 +1,399 @@ +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..a96718633 --- /dev/null +++ b/archives/2022/page/2/index.html @@ -0,0 +1,399 @@ +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..47753396e --- /dev/null +++ b/archives/2023/01/index.html @@ -0,0 +1,399 @@ +一月 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..6833cfac6 --- /dev/null +++ b/archives/2023/03/index.html @@ -0,0 +1,399 @@ +三月 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..7f12bfcb9 --- /dev/null +++ b/archives/2023/10/index.html @@ -0,0 +1,399 @@ +十月 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..4df57a1b3 --- /dev/null +++ b/archives/2023/11/index.html @@ -0,0 +1,399 @@ +十一月 2023 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
文章总览 - 1
2023
前端知识笔记
前端知识笔记
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..718f02b9d --- /dev/null +++ b/archives/2023/index.html @@ -0,0 +1,399 @@ +2023 | 希亚的西红柿の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..238dfbbc0 --- /dev/null +++ b/archives/index.html @@ -0,0 +1,399 @@ +归档 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
文章总览 - 20
2023
前端知识笔记
前端知识笔记
JS拾遗笔记
JS拾遗笔记
JS事件循环
JS事件循环
深入了解Promise
深入了解Promise
Git 笔记
Git 笔记
Vite基础知识总结
Vite基础知识总结
REGEX in JavaScript
REGEX in JavaScript
Windows/Ubuntu双系统安装教程
Windows/Ubuntu双系统安装教程
JAVA学习笔记
JAVA学习笔记
2022
栈与队列
栈与队列
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..b06a804c7 --- /dev/null +++ b/archives/page/2/index.html @@ -0,0 +1,399 @@ +归档 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
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..57b7dab6f --- /dev/null +++ b/atom.xml @@ -0,0 +1,575 @@ + + + 希亚的西红柿のBlog + https://npm.elemecdn.com/nova1751-sources/blogImg/icon.png + 分享学习与生活 + + + + 2023-11-21T15:51:11.000Z + http://refrain.cf/ + + + 希亚的西红柿 + + + + Hexo + + + 前端知识笔记 + + http://refrain.cf/posts/fccf75e5/ + 2023-11-19T03:57:06.000Z + 2023-11-21T15:51:11.000Z + + + + + + + <div class="tip home"><p>本文主要记录了关于前端面试常考与常问的知识笔记,内容较多,不间断更新。</p> +</div> +<h2 id="CSS"><a href="#CSS" class="headerlink" + + + + + + + + + + + + + + JS拾遗笔记 + + http://refrain.cf/posts/b2b6ba06/ + 2023-10-23T04:17:34.000Z + 2023-11-21T13:24:05.000Z + + + + + + + <div class="tip home"><p>本文主要记录了 JS 基础以及 ES6 的相关笔记,随缘更新。</p> +</div> +<h2 id="JS-基础"><a href="#JS-基础" class="headerlink" title="JS 基础"></a>JS + + + + + + + + + + + + + + + + + + JS事件循环 + + http://refrain.cf/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://refrain.cf/posts/2275c8/ + 2023-10-16T04:17:32.000Z + 2023-10-17T09:23:21.000Z + + + + + + + <div class="tip home"><p>本文主要讲解 Promise 的常用方法以及使用技巧,并实现如何手写一个 Promise。</p> +</div> +<h2 id="Promise-的出现"><a href="#Promise-的出现" + + + + + + + + + + + + + + + + + + Git 笔记 + + http://refrain.cf/posts/582b690b/ + 2023-10-13T08:49:02.000Z + 2023-11-05T11:17:25.000Z + + + + + + + <div class="tip home"><p>本文为个人自用 Git 笔记,记录了 Git 的常用命令,不定时更新(●’◡’●)。</p> +</div> +<h2 id="Git-常用命令"><a href="#Git-常用命令" class="headerlink" + + + + + + + + + + + + + + + + + + Vite基础知识总结 + + http://refrain.cf/posts/9614c7d1/ + 2023-10-08T12:48:33.000Z + 2023-11-21T13:24:05.000Z + + + + + + + <div class="tip home"><p>本文介绍了 Vite 的一些常用功能笔记,方便后续记忆与复习。<del>总有种写这个不如直接看文档的感觉QAQ</del></p> +</div> +<h2 id="Vite-工作基本原理"><a + + + + + + + + + + + + + + + + + + + + REGEX in JavaScript + + http://refrain.cf/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://refrain.cf/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://refrain.cf/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://refrain.cf/posts/6534ce06/ + 2022-12-31T06:18:56.000Z + 2023-01-09T06:18:56.000Z + + + 本人书写的数据结构教程 + + + + + + + + + + + + + + + + 线性表 + + http://refrain.cf/posts/40f24371/ + 2022-12-24T13:23:10.000Z + 2022-12-31T13:23:10.000Z + + + 本人书写的数据结构教程 + + + + + + + + + + + + + + Win10/11任务栏透明美化 + + http://refrain.cf/posts/b67f488/ + 2022-12-02T12:18:18.000Z + 2022-12-02T12:18:18.000Z + + + win10/11任务栏美化教程 + + + + + + + + + + + + + + + + 数据结构绪论 + + http://refrain.cf/posts/b06cc6ec/ + 2022-09-27T08:42:26.000Z + 2022-12-12T08:42:26.000Z + + + 本人书写的数据结构教程 + + + + + + + + + + + + + + 机器学习基本概念与知识 + + http://refrain.cf/posts/75378e04/ + 2022-08-20T02:42:24.000Z + 2022-08-20T02:42:24.000Z + + + 机器学习基础知识清单 + + + + + + + + + + + + 使用numpy实现k-means聚类算法 + + http://refrain.cf/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://refrain.cf/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://refrain.cf/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://refrain.cf/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://refrain.cf/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://refrain.cf/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..a579a50b2 --- /dev/null +++ b/categories/DNS/index.html @@ -0,0 +1,399 @@ +分类: 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..5b4917871 --- /dev/null +++ b/categories/Front-End/index.html @@ -0,0 +1,399 @@ +分类: Front End | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
分类 - Front End
2023
前端知识笔记
前端知识笔记
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..27af543f6 --- /dev/null +++ b/categories/Java/index.html @@ -0,0 +1,399 @@ +分类: 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..30ca741f7 --- /dev/null +++ b/categories/JavaScript/index.html @@ -0,0 +1,399 @@ +分类: JavaScript | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
分类 - JavaScript
2023
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/index.html b/categories/index.html new file mode 100644 index 000000000..0784b503e --- /dev/null +++ b/categories/index.html @@ -0,0 +1,484 @@ +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..1fae27da1 --- /dev/null +++ "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204/index.html" @@ -0,0 +1,399 @@ +分类: 数据结构 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
分类 - 数据结构
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..3bc9fd82a --- /dev/null +++ "b/categories/\346\234\272\345\231\250\345\255\246\344\271\240/index.html" @@ -0,0 +1,399 @@ +分类: 机器学习 | 希亚的西红柿の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..5d770c96d --- /dev/null +++ "b/categories/\347\231\276\345\256\235\347\256\261/index.html" @@ -0,0 +1,399 @@ +分类: 百宝箱 | 希亚的西红柿の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..d9ad6b2b0 --- /dev/null +++ b/comments/index.html @@ -0,0 +1,526 @@ +留言板 | 希亚的西红柿の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..d88e6b132 --- /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")}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..a762df95e --- /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}#algolia-search .search-dialog .ais-Hits-list,.category-lists ul,.list-beauty{list-style:none}#algolia-search .search-dialog .ais-Hits-list li,.category-lists ul li,.list-beauty li{position:relative;padding:.12em .4em .12em 1.4em}#algolia-search .search-dialog .ais-Hits-list li:hover:before,.category-lists ul li:hover:before,.list-beauty li:hover:before{border-color:var(--pseudo-hover)}#algolia-search .search-dialog .ais-Hits-list li:before,.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)}#algolia-search .search-dialog .ais-SearchBox input{padding:5px 14px;width:100%;outline:0;border:2px solid #49b1f5;border-radius:40px;background:var(--search-bg);color:var(--search-input-color)}#algolia-search .search-dialog .ais-Hits-list{margin:0;padding:0}#algolia-search .search-dialog .ais-Hits-list a{color:var(--search-a-color)}#algolia-search .search-dialog .ais-Hits-list a:hover{color:#49b1f5}#algolia-search .search-dialog .ais-Hits-list mark{background:0 0;color:#f47466;font-weight:700}#algolia-search .search-dialog .algolia-hits-item-title{font-weight:600}#algolia-search .search-dialog .algolia-hit-item-content{margin:0 0 8px;word-break:break-word}#algolia-search .search-dialog .ais-Pagination{margin:15px 0 0;padding:0;text-align:center}#algolia-search .search-dialog .ais-Pagination .ais-Pagination-list{margin:0;padding:0;list-style:none}#algolia-search .search-dialog .ais-Pagination .ais-Pagination-item{display:inline;margin:0 4px;padding:0}#algolia-search .search-dialog .ais-Pagination .ais-Pagination-item .ais-Pagination-link{display:inline-block;min-width:24px;height:24px;text-align:center;line-height:24px}#algolia-search .search-dialog .ais-Pagination .ais-Pagination-item--selected a{background:#00c4b6;color:#eee;cursor:default}#algolia-search .search-dialog .ais-Pagination .ais-Pagination-item--disabled{visibility:hidden}#algolia-search .search-dialog #algolia-hits>div{overflow-y:scroll;margin:0 -20px;padding:0 22px;max-height:calc(80vh - 240px)}@media screen and (max-width:768px){#algolia-search .search-dialog #algolia-hits>div{max-height:none;height:calc(var(--search-height) - 265px)}}#algolia-search .search-dialog #algolia-info div{display:inline}#algolia-search .search-dialog #algolia-info .algolia-poweredBy{float:right;vertical-align:text-top}#algolia-search .search-dialog #algolia-info .algolia-poweredBy svg{height:1.1em} \ 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..7c6c46024 --- /dev/null +++ b/drive/index.html @@ -0,0 +1,486 @@ +个人网盘 | 希亚的西红柿の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..1c9cc55ba --- /dev/null +++ b/index.html @@ -0,0 +1,628 @@ +希亚的西红柿の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/js/hideLrc.js b/js/hideLrc.js new file mode 100644 index 000000000..67a9d444f --- /dev/null +++ b/js/hideLrc.js @@ -0,0 +1 @@ +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..65effd8ae --- /dev/null +++ b/link/index.html @@ -0,0 +1,484 @@ +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..3453d4c51 --- /dev/null +++ b/music/index.html @@ -0,0 +1,501 @@ +My Music List | 希亚的西红柿のBlog + + + + + + + + + + + + + + + + + +

Witch On The Holy Night Album

+
+

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..08c780ee9 --- /dev/null +++ b/page/2/index.html @@ -0,0 +1,576 @@ +希亚的西红柿の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..98a217216 --- /dev/null +++ b/posts/170bc017/index.html @@ -0,0 +1,656 @@ +使用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://refrain.cf/posts/170bc017/
版权声明: 本博客所有文章除特别声明外,均采用 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..28e5d459a --- /dev/null +++ b/posts/20642/index.html @@ -0,0 +1,573 @@ +线性回归 (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://refrain.cf/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..2ad6fcad1 --- /dev/null +++ b/posts/2275c8/index.html @@ -0,0 +1,1395 @@ +深入了解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 () => {
    +    return new Promise((resolve, reject) => {});
    +  };
    +};
  2. 利用返回的这个Promise对象,可以使用then方法进行回调的处理,即原来函数的回调决定这Promise状态的改变以及执行,所以需要将改变Promise对象的状态的回调函数传入参数中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const promisify = (original) => {
    +  return (...args) => {
    +    return new Promise((resolve, reject) => {
    +      original(...args, (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 对象
+      this.then(
+        (value) => {
+          return Promise.resolve(callback()).then(() => value);
+        },
+        (reason) => {
+          return Promise.reject(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 Promise.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
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
class Promise {
+  constructor(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);
+    }
+  }
+  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 对象
+    this.then(
+      (value) => {
+        return Promise.resolve(callback()).then(() => value);
+      },
+      (reason) => {
+        return Promise.reject(callback()).then(() => {
+          throw reason;
+        });
+      }
+    );
+  }
+  // 定义类身上的静态方法
+  // 定义 resolve 方法
+  static resolve = (value) => {
+    return new Promise((resolve, reject) => {
+      if (value instanceof Promise) value.then(resolve, reject);
+      else resolve(value);
+    });
+  };
+  // 定义 reject 方法
+  static reject = (reason) => {
+    return new Promise((resolve, reject) => {
+      reject(reason);
+    });
+  };
+  // 定义 all 方法
+  static 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 Promise.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);
+        }
+      }
+    });
+  };
+  static 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方法
+  static 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方法
+  static 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));
+            }
+          });
+      });
+    });
+  };
+}
+
文章作者: 希亚的西红柿
文章链接: http://refrain.cf/posts/2275c8/
版权声明: 本博客所有文章除特别声明外,均采用 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..bb9977fa4 --- /dev/null +++ b/posts/355699d5/index.html @@ -0,0 +1,689 @@ +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://refrain.cf/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..7ae6c866b --- /dev/null +++ b/posts/35a88b44/index.html @@ -0,0 +1,688 @@ +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://refrain.cf/posts/35a88b44/
版权声明: 本博客所有文章除特别声明外,均采用 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..2cd7b026c --- /dev/null +++ b/posts/40f24371/index.html @@ -0,0 +1,1495 @@ +线性表 | 希亚的西红柿の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://refrain.cf/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..07e77810e --- /dev/null +++ b/posts/47341/index.html @@ -0,0 +1,498 @@ +关于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://refrain.cf/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..133bf2bca --- /dev/null +++ b/posts/54c08517/index.html @@ -0,0 +1,762 @@ +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://refrain.cf/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..a35c1165f --- /dev/null +++ b/posts/582b690b/index.html @@ -0,0 +1,768 @@ +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表示第二个父提交。

+
+

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
# 创建新的分支并切换
+git checkout 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
# 本地创建了分支而远程没有
+git push -u origin main
+git push --set-upstream origin main
+# 远程创建了分支而本地没有
+git checkout -b dev origin/dev
+git checkout --track origin/dev
+# 远程分支与本地分支都有
+git branch --set-upstream-to=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://refrain.cf/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..9a72a6f03 --- /dev/null +++ b/posts/6534ce06/index.html @@ -0,0 +1,1587 @@ +栈与队列 | 希亚的西红柿の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://refrain.cf/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..1f803514a --- /dev/null +++ b/posts/75378e04/index.html @@ -0,0 +1,628 @@ +机器学习基本概念与知识 | 希亚的西红柿の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://refrain.cf/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..0297e06fb --- /dev/null +++ b/posts/775eb342/index.html @@ -0,0 +1,613 @@ +使用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://refrain.cf/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..65ad0a585 --- /dev/null +++ b/posts/8d55d49a/index.html @@ -0,0 +1,976 @@ +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://refrain.cf/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..a40a10caa --- /dev/null +++ b/posts/9614c7d1/index.html @@ -0,0 +1,737 @@ +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 请求头来实现重新加载页面的相关文件的缓存设置,使用Etag304 Not Modified来判断源码文件是否更新,对依赖文件设置Cache-Control: max-age=31536000,immutable进行强制缓存。
      • +
      +
    • +
    +

    尽管原生 ESM 现在得到了广泛支持,但由于嵌套导入会导致额外的网络往返,在生产环境中发布未打包的 ESM 仍然效率低下(即使使用 HTTP/2)。为了在生产环境中获得最佳的加载性能,最好还是将代码进行 tree-shaking、懒加载和 chunk 分割(以获得更好的缓存)。

    +
    +
    了解Vite HMR基本原理 +
    +

    Vite HMR 是通过 fetch APIWebSocket 实现的。Vite HMR 的原理是这样的:

    • Vitenode 端使用 chokidar 监听文件的变化,当文件发生变化时,会触发 HMR 事件,并将变化的文件名和模块 ID 发送给客户端。
    • Vite 在浏览器端使用 WebSocket 与服务端建立连接,接收 HMR 事件。当收到 HMR 事件时,会根据文件名和模块 ID 找到对应的模块,并使用 fetch API 重新请求该模块的代码。
    • 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),
+        },
+      },
+    },
+  },
+});
+
文章作者: 希亚的西红柿
文章链接: http://refrain.cf/posts/9614c7d1/
版权声明: 本博客所有文章除特别声明外,均采用 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..c3680ed81 --- /dev/null +++ b/posts/b06cc6ec/index.html @@ -0,0 +1,659 @@ +数据结构绪论 | 希亚的西红柿の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://refrain.cf/posts/b06cc6ec/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/posts/b2b6ba06/index.html b/posts/b2b6ba06/index.html new file mode 100644 index 000000000..92861354b --- /dev/null +++ b/posts/b2b6ba06/index.html @@ -0,0 +1,1947 @@ +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. 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",
    +});
    +
  2. +
  3. 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端口");
    +});
    +
  4. +
  5. 读取生成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);
    +}
    +
  6. +
+

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://refrain.cf/posts/b2b6ba06/
版权声明: 本博客所有文章除特别声明外,均采用 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..9d70c91fc --- /dev/null +++ b/posts/b67f488/index.html @@ -0,0 +1,531 @@ +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://refrain.cf/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..d298391e4 --- /dev/null +++ b/posts/d3748e5f/index.html @@ -0,0 +1,651 @@ +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://refrain.cf/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..398a59bba --- /dev/null +++ b/posts/e89bd903/index.html @@ -0,0 +1,999 @@ +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://refrain.cf/posts/e89bd903/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 希亚的西红柿のBlog

评论
avatar
希亚的西红柿
个人博客
Follow Me
公告
本网站评论系统使用的是 Twikoo,若头像没有正常显示,请自行前往 Cravatar绑定邮箱配置。
+ + + + \ No newline at end of file diff --git a/posts/fccf75e5/index.html b/posts/fccf75e5/index.html new file mode 100644 index 000000000..f19aa1dcb --- /dev/null +++ b/posts/fccf75e5/index.html @@ -0,0 +1,1896 @@ +前端知识笔记 | 希亚的西红柿の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
// 防抖
+const debounce = (func, delay) => {
+  let timer;
+  return function (...params) {
+    if (timer) clearTimeout(timer);
+    timer = setTimeout(() => {
+      func.apply(this, params);
+    }, delay);
+  };
+};
+// 节流
+const throttle = (func, delay) => {
+  let startTime = Date.now();
+  let timer = null;
+  return function (...params) {
+    let endTime = Date.now();
+    const remain = delay - (endTime - startTime);
+    clearTimeout(timer);
+    if (remain <= 0) {
+      func.apply(this, params);
+      startTime = Date.now();
+    } else {
+      timer = setTimeout(func, remain);
+    }
+  };
+};
+

循环递归深拷贝

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 等,来增强用户的安全性和隐私性。
  • +
+

计网

httphttps的区别

    +
  • http(HyperText Transfer Protocol),即超文本运输协议,是实现网络通信的一种规范。其传输的数据并不是计算机底层上的二进制包,而是完整的,有意义的数据,如HTML文件,图片文件,查询结果等超文本。能够被上层应用识别到。在实际的应用当中,http协议常用于Web浏览器与网站服务器之间传递消息,以明文方式发送内容,不提供任何方式的数据加密。
  • +
  • 特点:
      +
    • 支持客户/服务器模式。
    • +
    • 简单快速:客户向服务器请求服务的时候,只需要传送请求方法和路径。由于http协议简单,使得http服务器的程序规模小,因而通信速度很快。
    • +
    • 灵活:http允许传输任意类型的数据对象。正在传输的对象用Content-Type加以标记。
    • +
    • 无连接:限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
    • +
    • 无状态:http协议无法根据之前的状态进行本次的请求处理。
    • +
    +
  • +
  • https,即运行在SSL/TLS上的http协议,SSL/TLS运行在TCP/IP协议与各种应用层协议之间。
      +
    • 过程:
        +
      • 浏览器向使用SSL/TLS的网站发送请求的时候,网站服务器会返回一个包含自己的公钥和身份信息的证书,这个证书通常是由可信任的机构颁发的。
      • +
      • 浏览器会验证证书的有效性和真实性,如果证书合法,自己生成一段随机的对称密钥,并用公钥加密发送给服务端。
      • +
      • 服务段用自己的私钥解密对称密钥,然后用这个对称密钥加密数据,实现加密通信。
      • +
      • 浏览器段使用该对称密钥解密数据,显示在页面上。
      • +
      • 这样,浏览器和网站之间的通信就是通过对称密钥加密的,第三方无法窃取或篡改数据,从而实现了安全通信。
      • +
      +
    • +
    +
  • +
+

+

原生方法对预检请求进行响应

使用原生方法对跨域请求的预检请求进行响应,需要在服务器端设置一些响应头,来告诉浏览器是否允许该请求,以及允许的方法、头部、凭证等。具体来说,有以下几个步骤:

+
    +
  • 首先,判断请求是否是跨域请求,可以通过检查请求头中的 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");
+});
+

https相对于http的优势

+

http的一些问题

+
    +
  • 使用明文通信,内容可能被窃听
  • +
  • 不验证通信方的身份,因此有可能遭遇伪装。
  • +
+
+

https 为何更安全

+
    +
  • 对称加密:加密与解密所用的密钥都是同一个,如果保证了密钥的安全,那么就保证了通信的机密性。
  • +
  • 非对称加密:密钥分为公钥和私钥,公钥加密只能用私钥解密,私钥加密只能用公钥解密。
  • +
  • 混合加密:https采用的是对称加密+非对称加密,https采用非对称加密来解决密钥交换的问题。即浏览器使用公钥来加密对称密钥,服务端使用私钥来解密对称密钥,实现密钥的私密性。
  • +
  • 摘要算法:通过摘要算法压缩数据,给数据生成独一无二的ID,验证数据的完整性。
  • +
  • 数字签名:将ID用私钥加密,确保了消息确实由发送方发送出来的。
  • +
  • CA 机构:使用 CA 公钥解密证书,确保证书和公钥的有效性。
  • +
+

DNS查询过程

    +
  1. 查询浏览器 DNS 缓存。
  2. +
  3. 查询操作系统 DNS 缓存。
  4. +
  5. 查询本地域名服务器 DNS 缓存。
  6. +
  7. 查询根域名服务器,返回顶级域名服务器。
  8. +
  9. 本地服务器向顶级域名服务器发送查询请求,返回权限域名服务器的地址。
  10. +
  11. 向权限域名服务器发送请求,获取 IP 地址。
  12. +
  13. 获得 IP 地址形成访问。
  14. +
+

+

CDN原理

    +
  1. 发送请求
  2. +
  3. CNAME记录导向负载均衡系统
  4. +
  5. 返回离用户最近的服务器的IP地址(边缘节点)
  6. +
  7. 客户端向该IP发送请求获取资源
  8. +
+

缓存代理:二级缓存找一级缓存,一级缓存直接找源站

+
    +
  • 命中率
  • +
  • 回源率
  • +
+

UDPTCP的简单理解,两个协议都位于 OSI 的传输层

    +
  • UDP(User Datagram Protocol):是一个面向用户数据包的通信协议,即对应用层交下来的报文,不合并,不拆分,只是在其上面加上首部后就交给了下面的网络层。
  • +
  • TCP(Transmission Control Protocol):是一种可靠的面向字节流的协议,可以想象成流水形式的,发送方 TCP 会将数据放入“蓄水池”(缓存区),等到可以发送的时候就发送,不能发送就等着,TCP 会根据当前网络的拥塞状态来确定每个报文段的大小。
  • +
+

HTTP/1.0/1.1/2.0

HTTP1.0

+
    +
  • 浏览器与服务器只保持短暂的连接,浏览器的每次请求都需要与服务器建立一个 TCP 连接。
  • +
+

HTTP1.1

+
    +
  • 引入了持久连接,即 TCP 连接默认不关闭,可以被多个请求复用
    在同一个 TCP 连接里面,客户端可以同时发送多个请求
  • +
  • 虽然允许复用 TCP 连接,但是同一个 TCP 连接里面,所有的数据通信是按次序进行的,服务器只有处理完一个请求,才会接着处理下一个请求。如果前面的处理特别慢,后面就会有许多请求排队等着
  • +
  • 新增了一些请求方法
  • +
  • 新增了一些请求头和响应头
  • +
+

HTTP2.0

+
    +
  • 采用二进制格式而非文本格式:每个数据流都以消息的形式发送,而消息又由一个或多个帧组成。多个帧之间可以乱序发送,根据帧首部的流标识可以重新组装,这也是多路复用同时发送数据的实现条件
  • +
  • 完全多路复用,而非有序并阻塞的、只需一个连接即可实现并行:在一个连接里,客户端和浏览器都可以同时发送多个请求或回应,而且不用按照顺序一一对应,这样就避免了”队头堵塞”
  • +
  • 使用报头压缩,降低开销
  • +
  • 服务器推送
  • +
+

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" }
      +// base64 编码 后 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
      +
    • +
    • Payload:载荷即消息体,这里会存放实际的内容,也就是Token的数据声明,例如用户的idname,默认情况下也会携带令牌的签发时间iat,通过还可以设置过期时间,如下:

      +
      1
      2
      3
      4
      5
      6
      {
      +  "sub": "1234567890",
      +  "name": "John Doe",
      +  "iat": 1516239022
      +}
      +// base64 编码后 eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
    • +
    • Signature:签名是对头部和载荷内容进行签名,一般情况,设置一个secretKey,对前两个的结果进行HMACSHA25算法,公式如下:
      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. +
+
文章作者: 希亚的西红柿
文章链接: http://refrain.cf/posts/fccf75e5/
版权声明: 本博客所有文章除特别声明外,均采用 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..29f2e0ac0 --- /dev/null +++ b/service-worker.js @@ -0,0 +1 @@ +if(!self.define){let e,i={};const a=(a,c)=>(a=new URL(a+".js",c).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=(c,d)=>{const s=e||("document"in self?document.currentScript.src:"")||location.href;if(i[s])return;let r={};const n=e=>a(e,s),f={module:{uri:s},exports:r,require:n};i[s]=Promise.all(c.map((e=>f[e]||n(e)))).then((e=>(d(...e),r)))}}define(["./workbox-f2630e7e"],(function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"404.html",revision:"50a84fdc8f0a6caaadda5ec6c265a866"},{url:"about/index.html",revision:"0b29036294e7f0f5fbe145f33d53ec9e"},{url:"archives/2022/07/index.html",revision:"2c2ebda73d0d837052844a8a813fef35"},{url:"archives/2022/08/index.html",revision:"2c24a18669c2fa87d9b6f916b0b30764"},{url:"archives/2022/09/index.html",revision:"03612eb395bd023864e8771d4fc6d3cc"},{url:"archives/2022/12/index.html",revision:"40a8a3d49dcc3b2c140e5b039a3e747e"},{url:"archives/2022/index.html",revision:"0cbbe557e1db1d5054bcce867cbafe1d"},{url:"archives/2022/page/2/index.html",revision:"494be847ddb64b0e4cf0b187caeae9c0"},{url:"archives/2023/01/index.html",revision:"1e9db5a49fda18b5732acea201e9f2fe"},{url:"archives/2023/03/index.html",revision:"28c7b4deebbd5d89339ba6417935aa3e"},{url:"archives/2023/10/index.html",revision:"805c50af529eaa2aac5b8103c7ad41ae"},{url:"archives/2023/11/index.html",revision:"8c749c6609d66687a96a2dc11beb03aa"},{url:"archives/2023/index.html",revision:"fff5fdf6b3208601c32b3e2b4e8e9a60"},{url:"archives/index.html",revision:"f4f1e0b16cf8ce127c7c8f9b4bedb68c"},{url:"archives/page/2/index.html",revision:"3bb6c54eb4361c80227eb99d01d5be61"},{url:"categories/DNS/index.html",revision:"75cb1d67945e5911d01a9fff476e98ed"},{url:"categories/Front-End/index.html",revision:"20799954fbc6faa10752d7f3863a2231"},{url:"categories/index.html",revision:"72ed9d654a1316b8d166701adc00ad5e"},{url:"categories/Java/index.html",revision:"db0f090eb8f6f6608ea7de3ffa143604"},{url:"categories/JavaScript/index.html",revision:"b49865b14f9272909277594ddeb2ea6c"},{url:"categories/数据结构/index.html",revision:"5369753ca5aef987536842e6e77cb1b8"},{url:"categories/机器学习/index.html",revision:"82324d6b3c19486960633b690efe9c40"},{url:"categories/百宝箱/index.html",revision:"aa9ad110d7a742dbff81f45f29d4913f"},{url:"comments/index.html",revision:"c32410af430132c6747232d26d90ef50"},{url:"css/custom.css",revision:"fe30bf09ee63fe00fa33b2263de1436d"},{url:"css/index.css",revision:"05fd870a3e45b4eeaca0bc9b7c86438f"},{url:"css/var.css",revision:"d41d8cd98f00b204e9800998ecf8427e"},{url:"drive/index.html",revision:"3c5eb50b00958d0d87b30494fa29b290"},{url:"img/404.jpg",revision:"4ef3cfb882b6dd4128da4c8745e9a507"},{url:"img/favicon.png",revision:"7a8c47cb5a2149c1a1af21e90ecd9ca7"},{url:"img/friend_404.gif",revision:"68af0be9d22722e74665ef44dd532ba8"},{url:"index.html",revision:"97a616618b45e5392a6e8e770f33f774"},{url:"js/hideLrc.js",revision:"439877fffc12b3fc08ca4b254aaa9c65"},{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:"31a8cb98eb2f9de38dbccea47b9fcd8b"},{url:"music/index.html",revision:"52fefd6b392b1e94cd36e0e7b29792ab"},{url:"page/2/index.html",revision:"87946bb5fb1ccd93990dffadf466d7c5"},{url:"posts/170bc017/index.html",revision:"c551c26b1ecab20f149eaeb23271db5c"},{url:"posts/20642/index.html",revision:"cd14bf3f44ea2016957fc49534055f4f"},{url:"posts/2275c8/index.html",revision:"ea8487bd1753b3cf2b3f9b3c09a631f3"},{url:"posts/355699d5/index.html",revision:"73697ce7c47858875710bc2fd2091e27"},{url:"posts/35a88b44/index.html",revision:"ff6c554e021a9170ea92752d392cbfa5"},{url:"posts/40f24371/index.html",revision:"adae1eddd2f8616bf0f4710fc73fca19"},{url:"posts/47341/index.html",revision:"31860404993bfe30fb8332a291b810fd"},{url:"posts/54c08517/index.html",revision:"45ffc5306f0cb2cff9cefb0e25571952"},{url:"posts/582b690b/index.html",revision:"8ab447915c82ee7e83672980536e686a"},{url:"posts/6534ce06/index.html",revision:"80624ef7dc1aaf01f19967c95e6780ea"},{url:"posts/75378e04/index.html",revision:"9c1f09be8ea76e89a164b64d0ed6c35d"},{url:"posts/775eb342/index.html",revision:"5400f4cb08032b192f057993b7a954bc"},{url:"posts/8d55d49a/index.html",revision:"f49912fd69ea9aa42b2e73af89721ec9"},{url:"posts/9614c7d1/index.html",revision:"94b28a1ea716877452318f4b73f50852"},{url:"posts/b06cc6ec/index.html",revision:"9b81ccff6755e80df9b90787167a97d7"},{url:"posts/b2b6ba06/index.html",revision:"935d9f8e5265fd806b44c920592436f4"},{url:"posts/b67f488/index.html",revision:"e3b07aa37e8cc30acdfb5e0d301bd134"},{url:"posts/d3748e5f/index.html",revision:"672b89b420141065eecb97ff06841c1c"},{url:"posts/e89bd903/index.html",revision:"963c9ebf117d274fe9211d1620d89089"},{url:"posts/fccf75e5/index.html",revision:"21408fb26d5281867c5f6f71134f4665"},{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:"9531decf1ff90e52a1fad98b6149d0e0"},{url:"tags/cloudflare/index.html",revision:"f25e65b75536a486423c43b42a59bb23"},{url:"tags/Front-End/index.html",revision:"50aae1510ae1358840b7f32765cacdf3"},{url:"tags/Git/index.html",revision:"b01a7507b4dee0fc5ad17e33992898e5"},{url:"tags/GitHub/index.html",revision:"f900d2c3ccf5a0e07310456e92ebbde2"},{url:"tags/index.html",revision:"19d9946bccbc25a1f4770308b5a285fb"},{url:"tags/Java/index.html",revision:"25867fe0922119ad115a47c6c4bc91ff"},{url:"tags/JavaScript/index.html",revision:"53f66ac296c42481033c0dfddfd6ccc7"},{url:"tags/JS-Basic/index.html",revision:"50a9801ac38998f248e494513af90357"},{url:"tags/k-means/index.html",revision:"ad56be660c2c079fc7f22b53d22157e0"},{url:"tags/kNN算法/index.html",revision:"7348558d4965bcac84b53e18188e1fbb"},{url:"tags/Numpy/index.html",revision:"e6dc72608cdd6595b14511e183805def"},{url:"tags/python/index.html",revision:"60bbd0ae023afb6bfaf29433cdc3acfc"},{url:"tags/Python/index.html",revision:"4f3eb2ef15ed837db7e1d09f385a661b"},{url:"tags/Regex/index.html",revision:"cdbc3ae481079f62cea20fe5e1846654"},{url:"tags/Ubuntu/index.html",revision:"44579834c28252e0c7f4a4923b447b36"},{url:"tags/Vite/index.html",revision:"3edeb32f242f1b35578c9c98c8f073fc"},{url:"tags/Windows/index.html",revision:"336570a3e8d32a941ccd6ff8ba513dc3"},{url:"tags/任务栏/index.html",revision:"a7a29bb8d9959094f3472ffd675fe65b"},{url:"tags/决策树/index.html",revision:"450ba3e651215ab325e0b9f4029e59d6"},{url:"tags/分布式管理系统/index.html",revision:"c3186731ab5fc7f3146fbb756450363a"},{url:"tags/域名/index.html",revision:"4a34b5167afcbcff0b3b781f85f83395"},{url:"tags/数据结构/index.html",revision:"db9abc10d81a85df3886b792a3e94c60"},{url:"tags/机器学习/index.html",revision:"b57a6c4161d488d1bdea2c8bbf40edb0"},{url:"tags/栈/index.html",revision:"7f71bd035d263deae25f883aeb9d00a8"},{url:"tags/线性回归/index.html",revision:"454cfc52ac27af9e4c41303c0464475d"},{url:"tags/线性表/index.html",revision:"218cb7f5cfa55e1b5acd1a44b99a11e0"},{url:"tags/绪论/index.html",revision:"799e232998167ba586335c33ad91a953"},{url:"tags/编程学习/index.html",revision:"ff6a5f370e641cabe1d06075bd29e2f6"},{url:"tags/美化/index.html",revision:"758bb4124ed3873016f56bef3a65c2a3"},{url:"tags/虚拟机/index.html",revision:"5cc731b2a10b41b3fa0226b20bdfa40e"},{url:"tags/逻辑回归/index.html",revision:"d1c2cca88c9cd3f19210e91ab2814c81"},{url:"tags/队列/index.html",revision:"b6d7ee77221ce478e0615bb6878eed61"}],{}),e.registerRoute(/^https:\/\/npm\.elemecdn\.com\/.*/,new e.CacheFirst,"GET"),e.registerRoute(/^https:\/\/cdn\.jsdelivr\.com\/.*/,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..c8fe7dc36 --- /dev/null +++ b/service-worker.js.map @@ -0,0 +1 @@ +{"version":3,"file":"service-worker.js","sources":["../../../../../tmp/c498928f95f34c8ff004ccbf6ef108eb/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\": \"50a84fdc8f0a6caaadda5ec6c265a866\"\n },\n {\n \"url\": \"about/index.html\",\n \"revision\": \"0b29036294e7f0f5fbe145f33d53ec9e\"\n },\n {\n \"url\": \"archives/2022/07/index.html\",\n \"revision\": \"2c2ebda73d0d837052844a8a813fef35\"\n },\n {\n \"url\": \"archives/2022/08/index.html\",\n \"revision\": \"2c24a18669c2fa87d9b6f916b0b30764\"\n },\n {\n \"url\": \"archives/2022/09/index.html\",\n \"revision\": \"03612eb395bd023864e8771d4fc6d3cc\"\n },\n {\n \"url\": \"archives/2022/12/index.html\",\n \"revision\": \"40a8a3d49dcc3b2c140e5b039a3e747e\"\n },\n {\n \"url\": \"archives/2022/index.html\",\n \"revision\": \"0cbbe557e1db1d5054bcce867cbafe1d\"\n },\n {\n \"url\": \"archives/2022/page/2/index.html\",\n \"revision\": \"494be847ddb64b0e4cf0b187caeae9c0\"\n },\n {\n \"url\": \"archives/2023/01/index.html\",\n \"revision\": \"1e9db5a49fda18b5732acea201e9f2fe\"\n },\n {\n \"url\": \"archives/2023/03/index.html\",\n \"revision\": \"28c7b4deebbd5d89339ba6417935aa3e\"\n },\n {\n \"url\": \"archives/2023/10/index.html\",\n \"revision\": \"805c50af529eaa2aac5b8103c7ad41ae\"\n },\n {\n \"url\": \"archives/2023/11/index.html\",\n \"revision\": \"8c749c6609d66687a96a2dc11beb03aa\"\n },\n {\n \"url\": \"archives/2023/index.html\",\n \"revision\": \"fff5fdf6b3208601c32b3e2b4e8e9a60\"\n },\n {\n \"url\": \"archives/index.html\",\n \"revision\": \"f4f1e0b16cf8ce127c7c8f9b4bedb68c\"\n },\n {\n \"url\": \"archives/page/2/index.html\",\n \"revision\": \"3bb6c54eb4361c80227eb99d01d5be61\"\n },\n {\n \"url\": \"categories/DNS/index.html\",\n \"revision\": \"75cb1d67945e5911d01a9fff476e98ed\"\n },\n {\n \"url\": \"categories/Front-End/index.html\",\n \"revision\": \"20799954fbc6faa10752d7f3863a2231\"\n },\n {\n \"url\": \"categories/index.html\",\n \"revision\": \"72ed9d654a1316b8d166701adc00ad5e\"\n },\n {\n \"url\": \"categories/Java/index.html\",\n \"revision\": \"db0f090eb8f6f6608ea7de3ffa143604\"\n },\n {\n \"url\": \"categories/JavaScript/index.html\",\n \"revision\": \"b49865b14f9272909277594ddeb2ea6c\"\n },\n {\n \"url\": \"categories/数据结构/index.html\",\n \"revision\": \"5369753ca5aef987536842e6e77cb1b8\"\n },\n {\n \"url\": \"categories/机器学习/index.html\",\n \"revision\": \"82324d6b3c19486960633b690efe9c40\"\n },\n {\n \"url\": \"categories/百宝箱/index.html\",\n \"revision\": \"aa9ad110d7a742dbff81f45f29d4913f\"\n },\n {\n \"url\": \"comments/index.html\",\n \"revision\": \"c32410af430132c6747232d26d90ef50\"\n },\n {\n \"url\": \"css/custom.css\",\n \"revision\": \"fe30bf09ee63fe00fa33b2263de1436d\"\n },\n {\n \"url\": \"css/index.css\",\n \"revision\": \"05fd870a3e45b4eeaca0bc9b7c86438f\"\n },\n {\n \"url\": \"css/var.css\",\n \"revision\": \"d41d8cd98f00b204e9800998ecf8427e\"\n },\n {\n \"url\": \"drive/index.html\",\n \"revision\": \"3c5eb50b00958d0d87b30494fa29b290\"\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\": \"97a616618b45e5392a6e8e770f33f774\"\n },\n {\n \"url\": \"js/hideLrc.js\",\n \"revision\": \"439877fffc12b3fc08ca4b254aaa9c65\"\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\": \"31a8cb98eb2f9de38dbccea47b9fcd8b\"\n },\n {\n \"url\": \"music/index.html\",\n \"revision\": \"52fefd6b392b1e94cd36e0e7b29792ab\"\n },\n {\n \"url\": \"page/2/index.html\",\n \"revision\": \"87946bb5fb1ccd93990dffadf466d7c5\"\n },\n {\n \"url\": \"posts/170bc017/index.html\",\n \"revision\": \"c551c26b1ecab20f149eaeb23271db5c\"\n },\n {\n \"url\": \"posts/20642/index.html\",\n \"revision\": \"cd14bf3f44ea2016957fc49534055f4f\"\n },\n {\n \"url\": \"posts/2275c8/index.html\",\n \"revision\": \"ea8487bd1753b3cf2b3f9b3c09a631f3\"\n },\n {\n \"url\": \"posts/355699d5/index.html\",\n \"revision\": \"73697ce7c47858875710bc2fd2091e27\"\n },\n {\n \"url\": \"posts/35a88b44/index.html\",\n \"revision\": \"ff6c554e021a9170ea92752d392cbfa5\"\n },\n {\n \"url\": \"posts/40f24371/index.html\",\n \"revision\": \"adae1eddd2f8616bf0f4710fc73fca19\"\n },\n {\n \"url\": \"posts/47341/index.html\",\n \"revision\": \"31860404993bfe30fb8332a291b810fd\"\n },\n {\n \"url\": \"posts/54c08517/index.html\",\n \"revision\": \"45ffc5306f0cb2cff9cefb0e25571952\"\n },\n {\n \"url\": \"posts/582b690b/index.html\",\n \"revision\": \"8ab447915c82ee7e83672980536e686a\"\n },\n {\n \"url\": \"posts/6534ce06/index.html\",\n \"revision\": \"80624ef7dc1aaf01f19967c95e6780ea\"\n },\n {\n \"url\": \"posts/75378e04/index.html\",\n \"revision\": \"9c1f09be8ea76e89a164b64d0ed6c35d\"\n },\n {\n \"url\": \"posts/775eb342/index.html\",\n \"revision\": \"5400f4cb08032b192f057993b7a954bc\"\n },\n {\n \"url\": \"posts/8d55d49a/index.html\",\n \"revision\": \"f49912fd69ea9aa42b2e73af89721ec9\"\n },\n {\n \"url\": \"posts/9614c7d1/index.html\",\n \"revision\": \"94b28a1ea716877452318f4b73f50852\"\n },\n {\n \"url\": \"posts/b06cc6ec/index.html\",\n \"revision\": \"9b81ccff6755e80df9b90787167a97d7\"\n },\n {\n \"url\": \"posts/b2b6ba06/index.html\",\n \"revision\": \"935d9f8e5265fd806b44c920592436f4\"\n },\n {\n \"url\": \"posts/b67f488/index.html\",\n \"revision\": \"e3b07aa37e8cc30acdfb5e0d301bd134\"\n },\n {\n \"url\": \"posts/d3748e5f/index.html\",\n \"revision\": \"672b89b420141065eecb97ff06841c1c\"\n },\n {\n \"url\": \"posts/e89bd903/index.html\",\n \"revision\": \"963c9ebf117d274fe9211d1620d89089\"\n },\n {\n \"url\": \"posts/fccf75e5/index.html\",\n \"revision\": \"21408fb26d5281867c5f6f71134f4665\"\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\": \"9531decf1ff90e52a1fad98b6149d0e0\"\n },\n {\n \"url\": \"tags/cloudflare/index.html\",\n \"revision\": \"f25e65b75536a486423c43b42a59bb23\"\n },\n {\n \"url\": \"tags/Front-End/index.html\",\n \"revision\": \"50aae1510ae1358840b7f32765cacdf3\"\n },\n {\n \"url\": \"tags/Git/index.html\",\n \"revision\": \"b01a7507b4dee0fc5ad17e33992898e5\"\n },\n {\n \"url\": \"tags/GitHub/index.html\",\n \"revision\": \"f900d2c3ccf5a0e07310456e92ebbde2\"\n },\n {\n \"url\": \"tags/index.html\",\n \"revision\": \"19d9946bccbc25a1f4770308b5a285fb\"\n },\n {\n \"url\": \"tags/Java/index.html\",\n \"revision\": \"25867fe0922119ad115a47c6c4bc91ff\"\n },\n {\n \"url\": \"tags/JavaScript/index.html\",\n \"revision\": \"53f66ac296c42481033c0dfddfd6ccc7\"\n },\n {\n \"url\": \"tags/JS-Basic/index.html\",\n \"revision\": \"50a9801ac38998f248e494513af90357\"\n },\n {\n \"url\": \"tags/k-means/index.html\",\n \"revision\": \"ad56be660c2c079fc7f22b53d22157e0\"\n },\n {\n \"url\": \"tags/kNN算法/index.html\",\n \"revision\": \"7348558d4965bcac84b53e18188e1fbb\"\n },\n {\n \"url\": \"tags/Numpy/index.html\",\n \"revision\": \"e6dc72608cdd6595b14511e183805def\"\n },\n {\n \"url\": \"tags/python/index.html\",\n \"revision\": \"60bbd0ae023afb6bfaf29433cdc3acfc\"\n },\n {\n \"url\": \"tags/Python/index.html\",\n \"revision\": \"4f3eb2ef15ed837db7e1d09f385a661b\"\n },\n {\n \"url\": \"tags/Regex/index.html\",\n \"revision\": \"cdbc3ae481079f62cea20fe5e1846654\"\n },\n {\n \"url\": \"tags/Ubuntu/index.html\",\n \"revision\": \"44579834c28252e0c7f4a4923b447b36\"\n },\n {\n \"url\": \"tags/Vite/index.html\",\n \"revision\": \"3edeb32f242f1b35578c9c98c8f073fc\"\n },\n {\n \"url\": \"tags/Windows/index.html\",\n \"revision\": \"336570a3e8d32a941ccd6ff8ba513dc3\"\n },\n {\n \"url\": \"tags/任务栏/index.html\",\n \"revision\": \"a7a29bb8d9959094f3472ffd675fe65b\"\n },\n {\n \"url\": \"tags/决策树/index.html\",\n \"revision\": \"450ba3e651215ab325e0b9f4029e59d6\"\n },\n {\n \"url\": \"tags/分布式管理系统/index.html\",\n \"revision\": \"c3186731ab5fc7f3146fbb756450363a\"\n },\n {\n \"url\": \"tags/域名/index.html\",\n \"revision\": \"4a34b5167afcbcff0b3b781f85f83395\"\n },\n {\n \"url\": \"tags/数据结构/index.html\",\n \"revision\": \"db9abc10d81a85df3886b792a3e94c60\"\n },\n {\n \"url\": \"tags/机器学习/index.html\",\n \"revision\": \"b57a6c4161d488d1bdea2c8bbf40edb0\"\n },\n {\n \"url\": \"tags/栈/index.html\",\n \"revision\": \"7f71bd035d263deae25f883aeb9d00a8\"\n },\n {\n \"url\": \"tags/线性回归/index.html\",\n \"revision\": \"454cfc52ac27af9e4c41303c0464475d\"\n },\n {\n \"url\": \"tags/线性表/index.html\",\n \"revision\": \"218cb7f5cfa55e1b5acd1a44b99a11e0\"\n },\n {\n \"url\": \"tags/绪论/index.html\",\n \"revision\": \"799e232998167ba586335c33ad91a953\"\n },\n {\n \"url\": \"tags/编程学习/index.html\",\n \"revision\": \"ff6a5f370e641cabe1d06075bd29e2f6\"\n },\n {\n \"url\": \"tags/美化/index.html\",\n \"revision\": \"758bb4124ed3873016f56bef3a65c2a3\"\n },\n {\n \"url\": \"tags/虚拟机/index.html\",\n \"revision\": \"5cc731b2a10b41b3fa0226b20bdfa40e\"\n },\n {\n \"url\": \"tags/逻辑回归/index.html\",\n \"revision\": \"d1c2cca88c9cd3f19210e91ab2814c81\"\n },\n {\n \"url\": \"tags/队列/index.html\",\n \"revision\": \"b6d7ee77221ce478e0615bb6878eed61\"\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:\\/\\/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,2BACPC,SAAY,oCAEd,CACED,IAAO,sBACPC,SAAY,oCAEd,CACED,IAAO,6BACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,kCACPC,SAAY,oCAEd,CACED,IAAO,wBACPC,SAAY,oCAEd,CACED,IAAO,6BACPC,SAAY,oCAEd,CACED,IAAO,mCACPC,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,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,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,2BACPC,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,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,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,yBACPC,SAAY,oCAEd,CACED,IAAO,uBACPC,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,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,4BAA6B,IAAIC,aAAiC"} \ No newline at end of file diff --git a/sitemap.txt b/sitemap.txt new file mode 100644 index 000000000..7abd043ad --- /dev/null +++ b/sitemap.txt @@ -0,0 +1,68 @@ +http://refrain.cf/pwa/manifest.json +http://refrain.cf/comments/index.html +http://refrain.cf/posts/fccf75e5/ +http://refrain.cf/posts/b2b6ba06/ +http://refrain.cf/posts/35a88b44/ +http://refrain.cf/posts/9614c7d1/ +http://refrain.cf/posts/e89bd903/ +http://refrain.cf/posts/582b690b/ +http://refrain.cf/posts/2275c8/ +http://refrain.cf/posts/d3748e5f/ +http://refrain.cf/music/index.html +http://refrain.cf/drive/index.html +http://refrain.cf/about/index.html +http://refrain.cf/link/index.html +http://refrain.cf/categories/index.html +http://refrain.cf/tags/index.html +http://refrain.cf/posts/8d55d49a/ +http://refrain.cf/posts/6534ce06/ +http://refrain.cf/posts/40f24371/ +http://refrain.cf/posts/b06cc6ec/ +http://refrain.cf/posts/b67f488/ +http://refrain.cf/posts/75378e04/ +http://refrain.cf/posts/170bc017/ +http://refrain.cf/posts/775eb342/ +http://refrain.cf/posts/54c08517/ +http://refrain.cf/posts/355699d5/ +http://refrain.cf/posts/20642/ +http://refrain.cf/posts/47341/ +http://refrain.cf/ +http://refrain.cf/tags/JavaScript/ +http://refrain.cf/tags/Front-End/ +http://refrain.cf/tags/JS-Basic/ +http://refrain.cf/tags/Regex/ +http://refrain.cf/tags/Vite/ +http://refrain.cf/tags/Build-Tool/ +http://refrain.cf/tags/cloudflare/ +http://refrain.cf/tags/%E5%9F%9F%E5%90%8D/ +http://refrain.cf/tags/Git/ +http://refrain.cf/tags/GitHub/ +http://refrain.cf/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/ +http://refrain.cf/tags/Windows/ +http://refrain.cf/tags/%E4%BB%BB%E5%8A%A1%E6%A0%8F/ +http://refrain.cf/tags/%E7%BE%8E%E5%8C%96/ +http://refrain.cf/tags/Ubuntu/ +http://refrain.cf/tags/%E8%99%9A%E6%8B%9F%E6%9C%BA/ +http://refrain.cf/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/ +http://refrain.cf/tags/%E7%BA%BF%E6%80%A7%E8%A1%A8/ +http://refrain.cf/tags/%E6%A0%88/ +http://refrain.cf/tags/%E9%98%9F%E5%88%97/ +http://refrain.cf/tags/%E7%BB%AA%E8%AE%BA/ +http://refrain.cf/tags/Java/ +http://refrain.cf/tags/%E7%BC%96%E7%A8%8B%E5%AD%A6%E4%B9%A0/ +http://refrain.cf/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/ +http://refrain.cf/tags/python/ +http://refrain.cf/tags/%E5%86%B3%E7%AD%96%E6%A0%91/ +http://refrain.cf/tags/Numpy/ +http://refrain.cf/tags/Python/ +http://refrain.cf/tags/k-means/ +http://refrain.cf/tags/kNN%E7%AE%97%E6%B3%95/ +http://refrain.cf/tags/%E7%BA%BF%E6%80%A7%E5%9B%9E%E5%BD%92/ +http://refrain.cf/tags/%E9%80%BB%E8%BE%91%E5%9B%9E%E5%BD%92/ +http://refrain.cf/categories/JavaScript/ +http://refrain.cf/categories/Front-End/ +http://refrain.cf/categories/DNS/ +http://refrain.cf/categories/%E7%99%BE%E5%AE%9D%E7%AE%B1/ +http://refrain.cf/categories/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/ +http://refrain.cf/categories/Java/ +http://refrain.cf/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..fe497bbcb --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,540 @@ + + + + + http://refrain.cf/pwa/manifest.json + + 2023-11-21 + + monthly + 0.6 + + + + http://refrain.cf/comments/index.html + + 2023-11-21 + + monthly + 0.6 + + + + http://refrain.cf/posts/fccf75e5/ + + 2023-11-21 + + monthly + 0.6 + + + + http://refrain.cf/posts/b2b6ba06/ + + 2023-11-21 + + monthly + 0.6 + + + + http://refrain.cf/posts/35a88b44/ + + 2023-11-21 + + monthly + 0.6 + + + + http://refrain.cf/posts/9614c7d1/ + + 2023-11-21 + + monthly + 0.6 + + + + http://refrain.cf/posts/e89bd903/ + + 2023-11-21 + + monthly + 0.6 + + + + http://refrain.cf/posts/582b690b/ + + 2023-11-05 + + monthly + 0.6 + + + + http://refrain.cf/posts/2275c8/ + + 2023-10-17 + + monthly + 0.6 + + + + http://refrain.cf/posts/d3748e5f/ + + 2023-09-26 + + monthly + 0.6 + + + + http://refrain.cf/music/index.html + + 2023-09-22 + + monthly + 0.6 + + + + http://refrain.cf/drive/index.html + + 2023-09-22 + + monthly + 0.6 + + + + http://refrain.cf/about/index.html + + 2023-09-11 + + monthly + 0.6 + + + + http://refrain.cf/link/index.html + + 2023-05-01 + + monthly + 0.6 + + + + http://refrain.cf/categories/index.html + + 2023-05-01 + + monthly + 0.6 + + + + http://refrain.cf/tags/index.html + + 2023-05-01 + + monthly + 0.6 + + + + http://refrain.cf/posts/8d55d49a/ + + 2023-03-07 + + monthly + 0.6 + + + + http://refrain.cf/posts/6534ce06/ + + 2023-01-09 + + monthly + 0.6 + + + + http://refrain.cf/posts/40f24371/ + + 2022-12-31 + + monthly + 0.6 + + + + http://refrain.cf/posts/b06cc6ec/ + + 2022-12-12 + + monthly + 0.6 + + + + http://refrain.cf/posts/b67f488/ + + 2022-12-02 + + monthly + 0.6 + + + + http://refrain.cf/posts/75378e04/ + + 2022-08-20 + + monthly + 0.6 + + + + http://refrain.cf/posts/170bc017/ + + 2022-08-17 + + monthly + 0.6 + + + + http://refrain.cf/posts/775eb342/ + + 2022-08-14 + + monthly + 0.6 + + + + http://refrain.cf/posts/54c08517/ + + 2022-08-07 + + monthly + 0.6 + + + + http://refrain.cf/posts/355699d5/ + + 2022-08-06 + + monthly + 0.6 + + + + http://refrain.cf/posts/20642/ + + 2022-07-22 + + monthly + 0.6 + + + + http://refrain.cf/posts/47341/ + + 2022-07-18 + + monthly + 0.6 + + + + + http://refrain.cf/ + 2023-11-21 + daily + 1.0 + + + + + http://refrain.cf/tags/JavaScript/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/tags/Front-End/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/tags/JS-Basic/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/tags/Regex/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/tags/Vite/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/tags/Build-Tool/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/tags/cloudflare/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/tags/%E5%9F%9F%E5%90%8D/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/tags/Git/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/tags/GitHub/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/tags/Windows/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/tags/%E4%BB%BB%E5%8A%A1%E6%A0%8F/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/tags/%E7%BE%8E%E5%8C%96/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/tags/Ubuntu/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/tags/%E8%99%9A%E6%8B%9F%E6%9C%BA/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/tags/%E7%BA%BF%E6%80%A7%E8%A1%A8/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/tags/%E6%A0%88/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/tags/%E9%98%9F%E5%88%97/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/tags/%E7%BB%AA%E8%AE%BA/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/tags/Java/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/tags/%E7%BC%96%E7%A8%8B%E5%AD%A6%E4%B9%A0/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/tags/python/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/tags/%E5%86%B3%E7%AD%96%E6%A0%91/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/tags/Numpy/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/tags/Python/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/tags/k-means/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/tags/kNN%E7%AE%97%E6%B3%95/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/tags/%E7%BA%BF%E6%80%A7%E5%9B%9E%E5%BD%92/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/tags/%E9%80%BB%E8%BE%91%E5%9B%9E%E5%BD%92/ + 2023-11-21 + weekly + 0.2 + + + + + + http://refrain.cf/categories/JavaScript/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/categories/Front-End/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/categories/DNS/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/categories/%E7%99%BE%E5%AE%9D%E7%AE%B1/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/categories/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/categories/Java/ + 2023-11-21 + weekly + 0.2 + + + + http://refrain.cf/categories/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/ + 2023-11-21 + weekly + 0.2 + + + diff --git a/tags/Build-Tool/index.html b/tags/Build-Tool/index.html new file mode 100644 index 000000000..0be2e00fb --- /dev/null +++ b/tags/Build-Tool/index.html @@ -0,0 +1,399 @@ +标签: Build Tool | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - Build Tool
2023
Vite基础知识总结
Vite基础知识总结
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..77bfa237f --- /dev/null +++ b/tags/Front-End/index.html @@ -0,0 +1,399 @@ +标签: Front End | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - Front End
2023
前端知识笔记
前端知识笔记
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/Git/index.html b/tags/Git/index.html new file mode 100644 index 000000000..e4b5abc9d --- /dev/null +++ b/tags/Git/index.html @@ -0,0 +1,399 @@ +标签: 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..d9ebfb068 --- /dev/null +++ b/tags/GitHub/index.html @@ -0,0 +1,399 @@ +标签: 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..95cc88324 --- /dev/null +++ b/tags/JS-Basic/index.html @@ -0,0 +1,399 @@ +标签: 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..0eaa55ae7 --- /dev/null +++ b/tags/Java/index.html @@ -0,0 +1,399 @@ +标签: 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..ef1544c8c --- /dev/null +++ b/tags/JavaScript/index.html @@ -0,0 +1,399 @@ +标签: JavaScript | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - JavaScript
2023
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/Numpy/index.html b/tags/Numpy/index.html new file mode 100644 index 000000000..200c6a7a5 --- /dev/null +++ b/tags/Numpy/index.html @@ -0,0 +1,399 @@ +标签: 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..1f4f7547c --- /dev/null +++ b/tags/Python/index.html @@ -0,0 +1,399 @@ +标签: 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/Regex/index.html b/tags/Regex/index.html new file mode 100644 index 000000000..5cd82a97e --- /dev/null +++ b/tags/Regex/index.html @@ -0,0 +1,399 @@ +标签: 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/Ubuntu/index.html b/tags/Ubuntu/index.html new file mode 100644 index 000000000..b31c50e8a --- /dev/null +++ b/tags/Ubuntu/index.html @@ -0,0 +1,399 @@ +标签: 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..956e827c0 --- /dev/null +++ b/tags/Vite/index.html @@ -0,0 +1,399 @@ +标签: Vite | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - Vite
2023
Vite基础知识总结
Vite基础知识总结
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..1066d58fd --- /dev/null +++ b/tags/Windows/index.html @@ -0,0 +1,399 @@ +标签: 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..63336df50 --- /dev/null +++ b/tags/cloudflare/index.html @@ -0,0 +1,399 @@ +标签: 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..becf97560 --- /dev/null +++ b/tags/index.html @@ -0,0 +1,484 @@ +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..4a14fad69 --- /dev/null +++ b/tags/k-means/index.html @@ -0,0 +1,399 @@ +标签: 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..a588be273 --- /dev/null +++ "b/tags/kNN\347\256\227\346\263\225/index.html" @@ -0,0 +1,399 @@ +标签: 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..77cd42df3 --- /dev/null +++ b/tags/python/index.html @@ -0,0 +1,399 @@ +标签: 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..81c622469 --- /dev/null +++ "b/tags/\344\273\273\345\212\241\346\240\217/index.html" @@ -0,0 +1,399 @@ +标签: 任务栏 | 希亚的西红柿の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..79cfcf51f --- /dev/null +++ "b/tags/\345\206\263\347\255\226\346\240\221/index.html" @@ -0,0 +1,399 @@ +标签: 决策树 | 希亚的西红柿の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..fc1d162f0 --- /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,399 @@ +标签: 分布式管理系统 | 希亚的西红柿の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..2f3657ae6 --- /dev/null +++ "b/tags/\345\237\237\345\220\215/index.html" @@ -0,0 +1,399 @@ +标签: 域名 | 希亚的西红柿の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..bd8ba5d2e --- /dev/null +++ "b/tags/\346\225\260\346\215\256\347\273\223\346\236\204/index.html" @@ -0,0 +1,399 @@ +标签: 数据结构 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - 数据结构
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..e950a7036 --- /dev/null +++ "b/tags/\346\234\272\345\231\250\345\255\246\344\271\240/index.html" @@ -0,0 +1,399 @@ +标签: 机器学习 | 希亚的西红柿の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..792294fe3 --- /dev/null +++ "b/tags/\346\240\210/index.html" @@ -0,0 +1,399 @@ +标签: 栈 | 希亚的西红柿のBlog + + + + + + + + + + + + + + + +
标签 - 栈
2022
栈与队列
栈与队列
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..2ac26d280 --- /dev/null +++ "b/tags/\347\272\277\346\200\247\345\233\236\345\275\222/index.html" @@ -0,0 +1,399 @@ +标签: 线性回归 | 希亚的西红柿の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..95dd42dee --- /dev/null +++ "b/tags/\347\272\277\346\200\247\350\241\250/index.html" @@ -0,0 +1,399 @@ +标签: 线性表 | 希亚的西红柿の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..48c4eb4d9 --- /dev/null +++ "b/tags/\347\273\252\350\256\272/index.html" @@ -0,0 +1,399 @@ +标签: 绪论 | 希亚的西红柿の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..f653ca2fd --- /dev/null +++ "b/tags/\347\274\226\347\250\213\345\255\246\344\271\240/index.html" @@ -0,0 +1,399 @@ +标签: 编程学习 | 希亚的西红柿の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..93cc52816 --- /dev/null +++ "b/tags/\347\276\216\345\214\226/index.html" @@ -0,0 +1,399 @@ +标签: 美化 | 希亚的西红柿の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..86ee4dee0 --- /dev/null +++ "b/tags/\350\231\232\346\213\237\346\234\272/index.html" @@ -0,0 +1,399 @@ +标签: 虚拟机 | 希亚的西红柿の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..c09ff018a --- /dev/null +++ "b/tags/\351\200\273\350\276\221\345\233\236\345\275\222/index.html" @@ -0,0 +1,399 @@ +标签: 逻辑回归 | 希亚的西红柿の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..113f37b46 --- /dev/null +++ "b/tags/\351\230\237\345\210\227/index.html" @@ -0,0 +1,399 @@ +标签: 队列 | 希亚的西红柿の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