diff --git "a/2018/09/05/MarkDown\350\257\255\346\263\225\345\260\217\350\256\260/index.html" "b/2018/09/05/MarkDown\350\257\255\346\263\225\345\260\217\350\256\260/index.html" new file mode 100644 index 00000000..9211fd3e --- /dev/null +++ "b/2018/09/05/MarkDown\350\257\255\346\263\225\345\260\217\350\256\260/index.html" @@ -0,0 +1,542 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + markdown 语法小记 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ markdown 语法小记 +

+ + +
+ + + + +
+ + +

第一个 hexo-next 主题的 blog,主要记录下 markdown 语法

+ + +

测试文本样式

测试加粗样式

加粗

+

测试斜体样式

斜体

+

测试删除线样式

删除线

+

测试引用样式

+

山穷水尽疑无路,柳暗花明又一村

+
+

测试代码样式

测试指定代码语言代码样式

1
2
3
4
5
6
var FE_developer = {
name: 'Kuro',
age: '22'
};

console.log('info', FE_developer);
+

测试单行代码样式

在 JS 中我们常用 console.log() 来输出调试信息。

+

测试代码块样式

1
2
3
4
5
6
function test(a, b){
setTimeout(function () {
console.log(a + b);
setTimeout(arguments.callee, 500);
}, 500)
}
+ +

测试连接样式

百度一下:Baidu

+

测试首行缩进样式

  markdown 语法主要考虑的是英文,中文缩进需要依赖 HTML 的空格符号

+
1
2
半角空格:  
全角空格: 
+ +

测试表格样式

+ + + + + + + + + + + + + + + + + + + + + + +
左对齐居中对齐右对齐
Harry PotterGryffindor90
Hermione GrangerGryffindor100
Draco MalfoySlytherin90
+

表格使用 | 来分隔不同的单元格,使用 - 来分隔表头和其他行。
注意:表格前若有文本,需要空一行才能正常显示

+

测试插入图片

来自百度图片:

+西瓜 + + +

测试列表

git 常用语法

+
    +
  • git status
  • +
  • git add .
  • +
  • git commit -m”XXX”
  • +
+
    +
  • git stash
  • +
  • git list
  • +
  • git stash apply stash@{n}
  • +
+
    +
  • git diff
  • +
  • git reset –hard
  • +
+
    +
  1. 列表内容
      +
    • 列表嵌套第一条
    • +
    • 列表嵌套第二条
    • +
    +
  2. +
  3. 列表内容
  4. +
  5. 列表内容
  6. +
+

测试复选框样式

    +
  • 选项一
  • +
  • 选项二
  • +
  • 选项三
  • +
+

测试流程图样式

+ +

其他注意事项

    +
  • 在 markdown 中直接使用尖括号<something>会被文本默认为HTML标签语句而不予显示。
      +
    • 使用转义字符&lt;代替<,用&gt;代替>
    • +
    • 或者左闭合的尖括号前加一个转义符号\,例如:“\<something>”
    • +
    +
  • +
+ +
+ + + + + + + +
+ + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2018/09/07/\343\200\212\347\234\213\350\247\201\343\200\213-\346\237\264\351\235\231/index.html" "b/2018/09/07/\343\200\212\347\234\213\350\247\201\343\200\213-\346\237\264\351\235\231/index.html" new file mode 100644 index 00000000..39fac3d0 --- /dev/null +++ "b/2018/09/07/\343\200\212\347\234\213\350\247\201\343\200\213-\346\237\264\351\235\231/index.html" @@ -0,0 +1,457 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 《看见》-柴静 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 《看见》-柴静 +

+ + +
+ + + + +
+ + +

无意间逛知乎的时候发现的书籍片段,留下了很深的印象。从记者的视角看到平日里生活中接触不到的社会另一面,别有一番感触。不愧是著名记者,文笔犀利,干练不拖沓。值得一读的好书:★★★★★

+ +

第二章 那个温热的跳动就是活着

我对非典的印象还是停留在小学时候,有那么一段时间,教室里每天清晨和下午都要喷洒消毒水,学校的走廊里弥漫着一股医院的味道。那时还小,只知道这是在预防“非典”,但它到底是什么,我并不知道。

+
+

这就是我之前听说的天井。四周楼群间的一块空地,一 个楼与楼之间的天井,加个盖,就成了个完全封闭的空间, 成了输液室,发热的病人都集中到这里来输液。二十七张床 几乎完全挨在一起,中间只有一只拳头的距离。白天也完全靠灯光,没有通风,没有窗,只有一个中央空调的排气口, 这个排气口把病菌传到各处。
病历胡乱地堆在桌上,像小山一样,已经发黄发脆。我 犹豫了一秒钟。朱继红几乎是凄然地一笑,说:“我来吧。” 病例被翻开,上面写的都是“肺炎”。他指给我看墙上的黑 板,上面写了二十二个人的名字,其中十九个后面都用白粉 笔写着:肺炎、肺炎、肺炎……
“实际上都是 SARS。”他说。

+
+
+

一个卫生系统的官员在这里感染,回家又把妻子儿子感染了,想尽办法要住院,只能找到一个床位,夫妇俩让儿子住了进去。两口子发烧得浑身透湿,站不住,只能顫抖着坐在 小板凳上输液。再后来连板凳都坐不住了。孩子痊愈的时候, 父母已经去世。

+
+

明明只是在描述,却让人觉得无比震撼。

+ +
+ + + + + + + +
+ + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2018/11/17/Hexo-\345\237\272\347\241\200\344\275\277\347\224\250/index.html" "b/2018/11/17/Hexo-\345\237\272\347\241\200\344\275\277\347\224\250/index.html" new file mode 100644 index 00000000..0da8edce --- /dev/null +++ "b/2018/11/17/Hexo-\345\237\272\347\241\200\344\275\277\347\224\250/index.html" @@ -0,0 +1,504 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Hexo 基础使用 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ Hexo 基础使用 +

+ + +
+ + + + +
+ + +

记录一下 hexo 基础用法。

+ + +

安装

node 环境下,全局安装 hexo-cli

+
1
npm install hexo-cli -g
+

初始化

进入到一个放置blog的空文件夹

+
1
2
3
4
hexo init 
hexo generate
hexo server # 默认4000端口
hexo s -p 4001 # 在自定义端口启动
+

浏览器输入 localhost:4000,出现 blog 界面

+

换主题

Hexo 官网提供了一些主题 https://hexo.io/themes/

+
    +
  • git clone 主题地址到 blog 目录下,将全局_condig.yml中的theme名字改为clone下来的文件夹的名字
  • +
  • 主题中有可供选择的几套样式,更改主题 _config.yml 里的 scheme
  • +
  • 设置代码高亮样式 更改主题 _condig.yml 里的 hightlight_theme
  • +
  • 切换Hexo语言 在全局 _condig.yml 里的 language 改成 zh-Hans 即为主题下的简体中文(默认为英文)
  • +
  • (更换完主题,需要重启应用,方能生效)
  • +
  • 由于主题也是一个git仓库,下载后记得删除.git文件,否则主题文件是无法提交的
  • +
+

生成文章

1
2
3
4
5
hexo new "postName" # /source/_post/postName & .md
hexo new page "pageName" # /source/pageName/index & index.md
hexo generate # /source/.md -> /public/.html
hexo server
hexo deploy #将.deploy目录部署到GitHub
+ +

删除文章

1
2
3
hexo clean # delete /public
hexo generate # regenerate /public
hexo deploy
+ +

其他

    +
  • 插入本地图片
    每次hexo new 'postName'时,都会创建一个与文章名相同的文件夹,将文章所需资源放入该文件夹里,引用的时候直接写文件名即可。
  • +
  • 页面增加“阅读更多”按钮
    在 .md 文件中增加<!--more-->注释,如果想自动添加“阅读更多”按钮,可在主题下的_config.yml中将auto_excerpt下的enable设置为true
  • +
+

插件

    +
  • hexo-deployer-git 一键部署到 GitPage
  • +
  • hexo-douban 爬取豆瓣相关信息
      +
    • 如遇网络问题爬取失败的报错,将 node 回退到 12.18.x 后重试(issue地址
    • +
    • 如果生成的 /movies 页面访问报错,将 hexo 版本回退到 3.9.0 后重试
    • +
    +
  • +
  • hexo-generator-search 全文搜索功能
  • +
+

部署

hexo d部署前,需要安装npm install hexo-deployer-git --save
修改全局 _config.yml 中的配置:

+
1
2
3
4
5
6
7
8
9
10
deploy:
type: git
repo: <repository url>
branch: [branch]
message: [message]
name: [git user]
email: [git email]
extend_dirs: [extend directory] #其他要提交的目录
ignore_hidden: true #忽略隐藏文件
ignore_pattern: regexp #忽略正则匹配的隐藏文件
+

之后,只需要hexo d -g一条命令就可以生成和部署了。关于 hexo-deployer-git 这个插件的参数 hexo官方文档 介绍的并不全面,建议去 hexo-deployer-git官方文档 查看相关配置参数。

+

注意:

+
    +
  • 默认部署,只将生成的 HTML 相关文件(/public文件夹)推送到 github
  • +
  • 若想把本地的生成器项目相关文件也推送到 github,则要配置 extend_dirs: /
  • +
  • message、name、email 的内容要用引号括起来
  • +
  • name、email 的配置信息用来覆盖全局的 git config 中的配置,更改这两项后,需要删除根目录下的 .deploy_git ,部署时才会生效
  • +
  • master 只能放 /public 下的文件,将项目所有文件放到 master 分支下,会导致页面 build 失败。若想将本地代码全部提交,可部署在其他分支(在 _config.yml 中增加其他分支配置信息,详情参考文档)
  • +
  • 避免提交 node_modules,需在项目下新建.gitignore文件(为什么不使用 extend_dirs ?因为需要添加的文件夹太多…)
  • +
  • 若遇见 Error: EACCES: permission denied, unlink /XXX 相关的错误,大部分是由没权限引起的,使用 sudo chown -R whoami:staff /你的blog目录 即可
  • +
+

搜索功能

全局安装插件 npm install hexo-generator-search --save
修改全局 _config.yml中的配置:

+
1
2
3
4
search:
path: search.xml
field: post
content: true
+ +

修改主题 themes/next/_config.yml 中的配置:

+
1
2
3
local_search:
enable: true
trigger: auto
+ +

生效:hexo cleanhexo ghexo s

+

Hexo 目录解析

1
2
3
4
5
6
7
8
9
├── node_modules # 依赖包-安装插件及所需nodejs模块。
├── public # 最终网页信息。即存放通过 markdown 渲染出来的 html文件。
├── scaffolds # 模板文件夹。即新建文章时,根据 scaffold 生成文件。
├── source # 资源文件夹。即存放用户资源。
| └── _posts # 博客文章目录。
└── themes #存放主题。Hexo根据主题生成静态页面。
├── _config.yml #网站的全局配置信息。标题、网站名称等。
├── db.json:# source 解析所得到的缓存文件。
├── package.json # 应用程序信息。即配置Hexo运行需要js包。
+ +

参考资料

利用 hexo + Gitpage 开发自己的博客
hexo 浅析原理

+ +
+ + + + + + + +
+ + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2018/11/17/\345\220\214\346\227\266\344\275\277\347\224\250\344\270\244\344\270\252\350\264\246\345\217\267\345\210\206\345\210\253\346\223\215\344\275\234Github\345\222\214Gitlab/enter-passphrase.png" "b/2018/11/17/\345\220\214\346\227\266\344\275\277\347\224\250\344\270\244\344\270\252\350\264\246\345\217\267\345\210\206\345\210\253\346\223\215\344\275\234Github\345\222\214Gitlab/enter-passphrase.png" new file mode 100644 index 00000000..49728846 Binary files /dev/null and "b/2018/11/17/\345\220\214\346\227\266\344\275\277\347\224\250\344\270\244\344\270\252\350\264\246\345\217\267\345\210\206\345\210\253\346\223\215\344\275\234Github\345\222\214Gitlab/enter-passphrase.png" differ diff --git "a/2018/11/17/\345\220\214\346\227\266\344\275\277\347\224\250\344\270\244\344\270\252\350\264\246\345\217\267\345\210\206\345\210\253\346\223\215\344\275\234Github\345\222\214Gitlab/index.html" "b/2018/11/17/\345\220\214\346\227\266\344\275\277\347\224\250\344\270\244\344\270\252\350\264\246\345\217\267\345\210\206\345\210\253\346\223\215\344\275\234Github\345\222\214Gitlab/index.html" new file mode 100644 index 00000000..96ba81e1 --- /dev/null +++ "b/2018/11/17/\345\220\214\346\227\266\344\275\277\347\224\250\344\270\244\344\270\252\350\264\246\345\217\267\345\210\206\345\210\253\346\223\215\344\275\234Github\345\222\214Gitlab/index.html" @@ -0,0 +1,470 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 同时使用两个账号分别操作Github和Gitlab | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 同时使用两个账号分别操作Github和Gitlab +

+ + +
+ + + + +
+ + +

公司用 gitlab 存管代码,自己用 github 。懒得下班后用自己电脑提交到 github ,故学习一下如何在同一台电脑上使用两个 git 账号。在 SSH config 中为不同的域名指定不同的 SSH key,之后再将自己本地的 github 库的 git config – local 设置成自己的 github 账号。

+ +

一、生成SSH秘钥

分别对githubn和gitlab生成对应的密钥

+
    +
  • ssh-keygen -t rsa -C "公司邮箱地址"生成对应的gitlab密钥:id_rsa和id_rsa.pub
  • +
  • 将 gitlab 公钥(id_rsa.pub)中的内容配置到公司的gitlab上
  • +
  • ssh-keygen -t rsa -C "自己邮箱地址" -f ~/.ssh/github_rsa生成对应的github密钥:github_rsa 和 github_rsa.pub
  • +
  • 生成公私钥的过程中,会提示你输入passphrase,用作每次进行 ssh 连接时的确认密码。由于电脑和账号都是个人使用所以直接按回车设置为空就可以了
  • +
  • 将 github 公钥(github_rsa.pub)中的内容配置到自己的 github 上
  • +
  • 到目前为止本地 /.ssh 中已经存在 github_rsa、github_rsa.pub、id_rsa、id_rsa.pub 四个文件了,由于 github 和 gitlab 建立连接默认查找的都是/.ssh/id_rsa,所以需要为 github 手动指明使用的私钥名称 github_rsa,否则会报错 Permission denied (publickey)
  • +
  • 进入密钥生成的位置,创建一个 config 文件,添加配置:
    1
    2
    3
    4
    5
    # githab
    Host github.com
    HostName github.com
    User kuro-p
    IdentityFile ~/.ssh/github_rsa
  • +
  • 如果为 github 中配置了两个 ssh,那么在 config 中,谁在前谁生效
  • +
+

二、测试连接

运行ssh -T git@hostName命令测试 ssh key 对 gitlab 与 github 的连接

+ +

如果能看到一些 Welcome 信息,说明是 OK 的。

+

三、配置 git 库账号

为了使 github / gitlab 知道提交的用户是谁,需要对账户名进行配置。由于全局配置是公司的账号,所以只需要对自己想要进行操作的 github 库进行本地配置即可。

+
1
2
git config --local user.name 'username' # github账号名称
git config --local user.email 'username@gmail.com' # github账号邮箱
+

或者直接 init 一个 git 库,配置后 github 的代码都在这个仓库下拉取。

+

参考资料

如何在同一台电脑上使用github和gitlab
同时使用两个账号分别操作Github和Gitlab
由于SSH配置文件的不匹配,导致的Permission denied (publickey)及其解决方法

+ +
+ + + + + + + +
+ + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2018/11/17/\345\220\214\346\227\266\344\275\277\347\224\250\344\270\244\344\270\252\350\264\246\345\217\267\345\210\206\345\210\253\346\223\215\344\275\234Github\345\222\214Gitlab/test-ssh-connect.png" "b/2018/11/17/\345\220\214\346\227\266\344\275\277\347\224\250\344\270\244\344\270\252\350\264\246\345\217\267\345\210\206\345\210\253\346\223\215\344\275\234Github\345\222\214Gitlab/test-ssh-connect.png" new file mode 100644 index 00000000..e1014bd0 Binary files /dev/null and "b/2018/11/17/\345\220\214\346\227\266\344\275\277\347\224\250\344\270\244\344\270\252\350\264\246\345\217\267\345\210\206\345\210\253\346\223\215\344\275\234Github\345\222\214Gitlab/test-ssh-connect.png" differ diff --git "a/2018/11/30/Linux\345\221\275\344\273\244\350\241\214\344\270\216shell\350\204\232\346\234\254\345\255\246\344\271\240/file-permissions.png" "b/2018/11/30/Linux\345\221\275\344\273\244\350\241\214\344\270\216shell\350\204\232\346\234\254\345\255\246\344\271\240/file-permissions.png" new file mode 100644 index 00000000..1021fb85 Binary files /dev/null and "b/2018/11/30/Linux\345\221\275\344\273\244\350\241\214\344\270\216shell\350\204\232\346\234\254\345\255\246\344\271\240/file-permissions.png" differ diff --git "a/2018/11/30/Linux\345\221\275\344\273\244\350\241\214\344\270\216shell\350\204\232\346\234\254\345\255\246\344\271\240/index.html" "b/2018/11/30/Linux\345\221\275\344\273\244\350\241\214\344\270\216shell\350\204\232\346\234\254\345\255\246\344\271\240/index.html" new file mode 100644 index 00000000..9bb8c068 --- /dev/null +++ "b/2018/11/30/Linux\345\221\275\344\273\244\350\241\214\344\270\216shell\350\204\232\346\234\254\345\255\246\344\271\240/index.html" @@ -0,0 +1,656 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Linux命令行与shell脚本学习 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ Linux命令行与shell脚本学习 +

+ + +
+ + + + +
+ + +

《Linux命令行与shell脚本编程大全》读书小结,熟悉一下常用的命令行操作。书籍比较基础,对熟悉Linux命令行的人来说参考意义不大。主要记录下书中提到的、没提到的常用的命令。

+ +

基础操作

    +
  • . 代表当前目录
  • +
  • .. 代表父级目录
  • +
  • ~ 代表根目录 表名当前工作目录位于用户home目录之下
  • +
  • man <directive> 可查看指令可使用的参数手册
  • +
  • tab 键自动补全文件名
  • +
  • cd 切换目录
  • +
  • linux 中的文件路径全部采用正斜线/,windows中的路径都是反斜线\而且带盘符
  • +
  • ls 列出当前路径下的所有文件
      +
    • -F 在显示子目录的时候在它的文件名之后加上一个斜线(“/”)字符
    • +
    • -F -R 遍历(递归)出当前目录下的子文件夹的所有内容(可以缩写成 ls -FR )
    • +
    • -a 列出所有文件,包括隐藏文件
    • +
    • -l 列出文件的所有信息
    • +
    +
  • +
  • pwd 查看当前所在位置的全路径
  • +
  • sudo 以 root 用户身份运行命令
  • +
+

文件基础操作

    +
  • open <fileName> 用默认程序打开文件
      +
    • open –args <参数> 用默认参数打开某个App
    • +
    +
  • +
  • touch <fileName> 创建一个文件 (不可在不存在的目录下新建文件)
  • +
  • mkdir <directory> 创建一个文件夹
      +
    • -p 创建多个层级的文件夹
    • +
    +
  • +
  • rmdir <directory> 只删除空目录
      +
    • 在非空目录下使用 rm -r 命令
    • +
    +
  • +
  • cp <fileName> <targetDirectory/fileName> 复制文件到目标文件夹/文件名
      +
    • -i 强制 shell 询问是否覆盖同名文件
    • +
    +
  • +
  • scp <fileName> <root@targetPath> 远程拷贝文件 可以跨服务器
  • +
  • mv <fileName> <directory/fileName> 用来 移动/重命名 文件
      +
    • -i 强制 shell 询问是否覆盖同名文件
    • +
    +
  • +
  • rm <fileName> 删除文件/文件夹中的所有内容
      +
    • -i 强制 shell 询问是否删除文件
    • +
    • -f 强制删除,没有警告信息也没有声音提示
    • +
    • -r 递归删除目录及目录内所有文件
    • +
    • 注意:Linux 中没有回收站或垃圾箱,文件一旦删除,就无法再找回
    • +
    +
  • +
  • ls -l <fileName> 查看文件权限
  • +
  • chmod value <fileName> 更改文件权限
      +
    • 权限描述顺序依次是:Owner(User)、Group、Other
    • +
    • r=读取属性 //值=4
    • +
    • w=写入属性 //值=2
    • +
    • x=执行属性 //值=1
    • +
    +
  • +
  • chown(选项)(参数) 更改文件夹所有者和所属组
      +
    • chown -R user:group .git 将.git文件夹的权限设置为 group 下的 user
    • +
    +
  • +
  • 获取文件路径:直接将文件拖入命令行即可
  • +
+

文件内容操作

    +
  • file <fileName/directoryName> 查看文件类型信息
  • +
  • du <fileName/directoryName> 用来查看文件或目录所占用的磁盘空间的大小
      +
    • -h 以易于阅读的方式展示
    • +
    • -a 显示目录及其下子目录和文件占用的磁盘空间大小
    • +
    • -s 只展示当前目录占用磁盘空间大小
    • +
    +
  • +
  • cat/more/less <fileName> 查看整个文件内容
      +
    • cat 一次性加载完所有文件内容
    • +
    • more 一次显示一屏文本
    • +
    • less 一次显示一屏文本 可以上下页翻建
    • +
    +
  • +
  • tail/head <fileName> 查看部分文件内容
      +
    • tail 默认展示文件最后10行的效果
        +
      • -n 2 只显示文件最后两行
      • +
      • -f 允许其他进程使用该文件时查看该文件的内容,tail会保持活跃状态,并不断显示添加到文件中的内容。(可用来实时监测系统日志)
      • +
      +
    • +
    • head 默认展示文件前10行内容
        +
      • 不支持 -f 属性
      • +
      +
    • +
    +
  • +
  • grep match_pattern <fileName> 强大的文本搜索工具,可以使用正则表达式搜索文本,并显示出匹配的行数
  • +
  • sed -i ‘s/被替换的内容/要替换的内容/g’ file -i 表示直接修改并保存
      +
    • 使用 sed 命令,报错invalid command code,是因为 -i 原地替换是危险行为,需要指明一个备份的扩展名才可以,若给了空的扩展名,则不会备份源文件。
    • +
    • 如 sed -i ‘’ ‘s/被替换的内容/要替换的内容/g’ file
    • +
    +
  • +
  • ls -> xxx.txt 将命令输出的内容保存为文件
  • +
+

监控进程

    +
  • ps 显示进程信息(瞬间占用情况)
  • +
  • top 显示进程信息(实时占用情况)
  • +
  • lsof 查看进程打开的文件
      +
    • lsof -i:4000 查看4000端口占用情况
    • +
    +
  • +
  • kill [PID] 杀死对应进程
  • +
+

网络情况

    +
  • ping <ip> 测试主机之间的连通性(不会自动结束,需要手动 ctrl + c 强制退出)
  • +
  • dig <url> 域名查询工具,可以用来测试域名系统工作是否正常
  • +
  • nsloopup <url> 域名查询工具,查询 DNS 相关信息
  • +
+

变量

环境变量

    +
  • printenv/env 默认输出所有环境变量(全局)
      +
    • printenv JAVA_HOME 输出全局设置的JAVA SDK位置
    • +
    • env $JAVA_HOME
    • +
    • echo $JAVA_HOME
    • +
    +
  • +
  • echo $variableName 输出变量 ($用来表名它是个变量)
  • +
  • set 输出所有环境变量(全局和局部)
  • +
  • $HOME 表示的用户的主目录,与波浪线~作用一样
  • +
+

普通变量

声明时直接声明即可使用 variable=XXX,变量名区分大小写,但需要注意的是 赋值时,变量名、等号和值之间没有空格 否则会报错 command not found
常用的书写习惯是 所有的环境变量名均使用大写字母,若是自己创建的局部变量或是shell脚本,则用小写字母,变量名区分大小写。

+

vim 操作

    +
  • vim <fileName> 以 vim 编辑器的方式查看当前文件
  • +
  • I 对文件进行 INSERT 操作
  • +
  • esc 退出当前编辑模式
  • +
  • 输入 : 切换到底线命令模式,可以在最底行输入其他命令
  • +
  • 输入 wq ,保存并退出;输入 !q,不保存直接退出
  • +
  • 输入 ggdG,删除当前全部内容;gg 为跳转到文件首行;dG为删除光标所在行以及其下所有行的内容
  • +
  • .swp 文件: 非正常关闭的 vim 编辑器会生成一个 .swp 文件
  • +
+

杂项

大小写转换

    +
  • echo $VAR_NAME | tr ‘[:upper:]’ ‘[:lower:]’
  • +
  • echo $VAR_NAME | tr ‘[A-Z]’ ‘[a-z]’
  • +
+

其他

    +
  • alias 可用来查看当前可用的别名(内建命令)
      +
    • alias 新的命令=’原命令 -选项/参数’ 用来定义命令别名
    • +
    +
  • +
  • sh <fileName.sh> 执行shell文件
  • +
  • .xxxrc 可以看做是xxx启动运行时的配置文件
      +
    • 例如 .zshrc 就是 zsh 运行前要执行配置文件
    • +
    +
  • +
  • source <fileName> 或者 . <fileName> (bash内部命令) 加载文件
  • +
  • 文件\包查找
      +
    • which <fileName> 查找该包编译器所在位置
    • +
    • whereis <fileName> 搜索更大范围的系统目录并输出所有包含的路径
    • +
    • find <fileName> 查找系统是否安装了某个软件包
    • +
    +
  • +
+

代理

    +
  • 参考
  • +
  • 若想要在当前终端中生效,直接输入 export http_proxy='http://ip_address:port' 即可,注意 ip 和端口号是本机的 ip + port;
  • +
  • 想要持久化全局生效的话,可以在 .zhsrc 中配置上述命令
  • +
+

常用的配置文件地址

    +
  • Host 文件 /etc/hosts
  • +
  • 配置的 SSH Key: cat ~/.ssh/id_rsa.pub
  • +
+

常见文件颜色

    +
  • 白色:表示普通文件
  • +
  • 蓝色:表示目录
  • +
  • 绿色:表示可执行文件
  • +
  • 红色:表示压缩文件
  • +
  • 浅蓝色:链接文件
  • +
  • 红色闪烁:表示链接的文件有问题
  • +
  • 黄色:表示设备文件
  • +
  • 灰色:表示其他文件
  • +
+

插件

    +
  • homebrew 包管理器
      +
    • brew install <packageName> 安装插件
    • +
    • brew list 查看电脑安装了哪些插件
    • +
    • 注:每次下载包之前都会进行 brew 更新检查,速度很慢,按一次 Ctrl+C 跳过更新
    • +
    • 官网上的 github 源安装总是会 443 connect timeout,推荐使用国内的镜像安装:
      1
      bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"
    • +
    +
  • +
  • wget 下载网页常用的工具
  • +
  • curl 是用来请求 Web 服务器的命令行工具,类似于 POSTMAN,它的名字就是客户端(client)的 URL 工具的意思。curl 支持的通信协议有 FTP、FTPS、HTTP、HTTPS、TFTP、SFTP、Gopher、SCP、Telnet、DICT、FILE、LDAP、LDAPS、IMAP、POP3、SMTP 和 RTSP。curl 还支持 SSL 认证、HTTP POST、HTTP PUT、FTP上传, HTTP form based upload、proxies、HTTP/2、cookies、用户名+密码认证(Basic, Plain, Digest, CRAM-MD5, NTLM, Negotiate and Kerberos)、file transfer resume、proxy tunneling。
      +
    • curl <url> 直接返回 url 请求结果
        +
      • -A/--user-agent <string\> 设置用户代理发送给服务器
      • +
      • -D/--dump-header <file\> 把 header 信息写入到该文件中
      • +
      • -O/--output <file\> 把输出写到该文件中
      • +
      • -#/--progress-bar 进度条显示当前传送状态
      • +
      • 详细信息
      • +
      +
    • +
    • 需要注意的是,做了反爬措施的网站在直接 curl 请求的时候,结果可能不如预期
    • +
    +
  • +
  • tree 以树状图形式展示目录及其子文件
      +
    • tree <directory> -J 以 json 形式展示文件
    • +
    +
  • +
  • tig 将 git 命令行可视化
  • +
+

其他参考:

+ + +
+ + + + + + + +
+ + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2018/12/31/2018\345\271\264\347\273\210\346\200\273\347\273\223/index.html" "b/2018/12/31/2018\345\271\264\347\273\210\346\200\273\347\273\223/index.html" new file mode 100644 index 00000000..4618a0e1 --- /dev/null +++ "b/2018/12/31/2018\345\271\264\347\273\210\346\200\273\347\273\223/index.html" @@ -0,0 +1,456 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2018年终总结 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 2018年终总结 +

+ + +
+ + + + +
+ + +

2018年仿佛什么都没做,但又仿佛做了些什么;仿佛没有遗憾,但却又心有不甘;以为走到了正确的方向,但“迷茫”二字却困惑了我整整一年。

+ + +
    +
  • 一月:实习、回校考试
  • +
  • 二月:回家过年
  • +
  • 三月:实习、回校选毕设题目
  • +
  • 四月:实习、学习毕设相关知识、投递简历
  • +
  • 五月:实习、开始码毕设、投递简历、跑面试
  • +
  • 六月:回校答辩、毕业
  • +
  • 七月:转正,回公司工作
  • +
  • 八月:工作、去当了一次漫展NPC
  • +
  • 九月:工作、去了一次上海迪士尼
  • +
  • 十月:工作、找房、换房
  • +
  • 十一月:工作
  • +
  • 十二月:工作
  • +
+

18年主要完成事件就是这些。
四、五月 大概是最忙的时候,因为要管的事情太多,忙到脚打后脑勺。
六月 是全年最开心的阶段,因为回学校了,有室友和同学在。虽然答辩时被老师问到怀疑人生,但最后老师还是给了高分,借此拿了一次奖学金的我也是受宠若惊,以为毕业前再也没有机会拿到了。除了感谢老师以外,还得感谢公司leader,毕设题目是他建议的。
七月 本决定给自己一周毕业旅行的时间,奈何职业方向和家里人冲突升级。取消了打算已久的假期,回公司了。工位发生很大变化,前端组的大家这次都坐在一起了。
八月 第一次去了漫展,也是第一次当NPC,不过应该也都是最后一次了哈哈。遇见了很好的小伙伴们,临走前,没有张口要联系方式,挺后悔的。
九月 去了趟迪士尼,事实证明,做攻略还是非常有用滴,项目都玩了,喜欢的也几刷了。遗憾的是,时间来不及,没有买到漫威周边。
十月 相对轻松。十一没出去玩,出去看房,找到合适的就换了,室友也换了,承蒙了之前两位姐姐很多照顾,有时会怀念。十月末公司团建,挺好玩的。
十一月 中旬心心念念的 blog 诞生了,虽然不难,但也是历史性的一步!毕竟从去年就开始惦记着…
十二月 成长。双十二的时候,买了新水彩颜料,终于把雄狮换成鲁本斯了。

+

18年看似很充实,实际一年到头可以收获的东西却寥寥无几,全年没有明确的方向,只是被时间推着做事。
希望19年找到自己的目标和方向。

+ +
+ + + + + + + +
+ + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2019/01/03/\345\211\215\347\253\257\346\250\241\345\235\227\345\214\226/index.html" "b/2019/01/03/\345\211\215\347\253\257\346\250\241\345\235\227\345\214\226/index.html" new file mode 100644 index 00000000..8c994d50 --- /dev/null +++ "b/2019/01/03/\345\211\215\347\253\257\346\250\241\345\235\227\345\214\226/index.html" @@ -0,0 +1,468 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 前端模块化 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 前端模块化 +

+ + +
+ + + + +
+ + +

目前JS模块化规范主要三种:浏览器端的 AMDCMD 规范。经常被 exports、modules.exports、export、require 绕懵,遂来探一探究竟。

+ + +

AMD规范 (requireJS) 浏览器端 异步加载模块 提前执行

AMD (Asynchronous Module Definition): 在浏览器中使用,并用 define 函数定义模块,用require引入模块;
它是 RequireJS 在推广过程中对模块定义的规范化产出,诣在帮开发者解决各个 js 文件的依赖问题,让开发者在页面引入多个 js 时,不必考虑各个 js 的依赖关系。

+

CMD规范 (SeaJS) 浏览器端 异步加载模块 延迟执行

CMD (Common Module Definition): 在浏览器端使用,使用 define 函数定义模块,用 module.exports 暴露模块;
它 是SeaJS 在推广过程中对模块定义的规范化产出。与 AMD 也都是异步加载模块,只是依赖加载的时间点不一样。相比于 AMD 依赖前置,CDM 加载采用就近原则(个人理解:先依赖,先加载)。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
define(function(require, exports, module) {
let val = 'module4'
function getVal() {
console.log(val)
}

// 引入module2 同步
let module2 = require('./module2.js');
module2()

// 异步引入module3
require.async('./module3.js', function(module3) {
module3.module3.getData()
});

// 暴露模块
module.exports = {getVal}
})
+ +

CommonJS 服务端 同步加载模块

NodeJS 的模块机制使用的就是 commonJS 的规范,因为服务端第三方库大多已存于本地,加载速度较快,使用同步加载比较理想。它使用 module.exports 或者是 exports 来导出,使用 require 引入。

+

ES6 的 export 和 import 浏览器端

ESM (ES Modules) 是 JavaScript 从 ES6(ES2015) 开始支持的原生模块机制,使用importexport引入和导出模块。

+

UMD 通用模块机制

UMD (Universal Module Definition) 是一个通用模块的机制,它使一个模块能运行在各种环境下,不论是 CommonJS、AMD,还是非模块化的环境。代码实现原理如下:

+
1
2
3
4
5
6
7
8
9
if (typeof define === 'function') { // 兼容 requireJS AMD、CMD规范   
define(function () {
return moduleName;
});
} else if (typeof exports !== 'undefined') { // 兼容 webpack 引入方式(commonJS)
module.exports = moduleName;
} else {
this.moduleName = moduleName; // 普通引入,注册到全局
}
+ +

常见于打包/编译工具中:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
function webpackConfig(BASE_PATH) {
return {
mode: 'development',
entry: {
index: path.join(__dirname, '../src/index'),
preview: path.join(__dirname, '../src/view')
},
output: {
libraryTarget: 'umd'
},
...
}
}
+ + +

参考资料

前端模块化详解(完整版)
关于commonjs,AMD,CMD之间的异同

+ +
+ + + + + + + +
+ + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2019/04/13/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217\345\255\246\344\271\240\347\254\224\350\256\260/index.html" "b/2019/04/13/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217\345\255\246\344\271\240\347\254\224\350\256\260/index.html" new file mode 100644 index 00000000..043c073e --- /dev/null +++ "b/2019/04/13/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217\345\255\246\344\271\240\347\254\224\350\256\260/index.html" @@ -0,0 +1,631 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 正则表达式学习笔记 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 正则表达式学习笔记 +

+ + +
+ + + + +
+ + +

把2017年的笔记整理一下,方便查找。记录了js正则表达式常用的概念、字符、以及方法。

+ + +

一、RegExp对象

JavaScript通过通过内置对象RegExp支持正则表达式,有两种方法实例化RegExp对象:

+
    +
  1. 字面量:var reg = /文本/g
  2. +
  3. 构造函数:var reg = new RegExp("\\bis\\b\", 'g')
  4. +
+

二、修饰符

+ + + + + + + + + + + + + + + + + + +
修饰符含义
g: glogbal全文搜索(默认搜索到第一个匹配停止)
i: ignore case忽略大小写(默认大小写敏感)
m: multiple lines多行搜索
+

三、元字符

如(*+$^.|(){}[])等,指在正则表达式中有特殊含义的非字母字符。一般情况下正则表达式的一个字符对应字符串的一个字符。
匹配+等特殊字符,可先转义,再匹配,如string.replace(/[\+]/g, "")

+
    +
  1. 普通类[]
    若要对应多个字符,可用元字符[]来构建一个简单的类,所谓类是指符合某些特性的对象,一个泛指,而不是特指某个字符。
    1
    'a1b2c3d4'.replace(/[abc]/gi, 'X') //"X1X2X3X4"
  2. +
  3. 反向类[^xx]
    [^..]使用元字符^创建反向类,即不属于某类的内容。
    1
    'a1b2c3d4'.replace(/[^abc]/gi, 'X') //"aXbXcXXX"
  4. +
  5. 范围类[x-x]
    使用[a-z]来连接两个字符,表示从a到z的任意字符 (包含a,z本身)。在[]组成类的内部可以连写[a-zA-Z]
  6. +
  7. 预定义类
    正则表达式提供预定义类来匹配常见字符类
  8. +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
字符等价类含义
.[^\r\n]除回车符和换行符之外的所有字符
\d[0-9]数字字符
\D[^0-9]非数字字符
\s[\t\n\f\r]空白符
\S[^\t\n\f\r]非空白符
\w[a-zA-Z_0-9]单词字符(字母、数字、下划线)
\W[^a-zA-Z_0-9]非单词字符
5. 边界
常见的边界匹配字符如下
+ + + + + + + + + + + + + + + + + + + + + + + +
字符含义
^以xxx开始(注意^要写在字符前面)
$以xxx结束(注意$要写在字符后面)
\b单词边界
\B非单词边界
+
1
2
'@123@abc@'.replace('^@', 'Q'); //"Q123@abc@" 
'@123@abc@'.replace('@$', 'Q'); //"Q123@abcQ"
+
    +
  1. 量词
    匹配连续出现多次的字符串(仅作用于紧挨着它的字符)
  2. +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
字符含义
?出现零或一次(最多出现一次)
+出现一次或多次(至少出现一次)
*出现零次或多次(任意次)
{n}出现n次
{n,m}出现n到m次
{n,}至少出现n次
{0,m}至多出现m次
+
    +
  • 贪婪模式
    尽可能多的匹配,直到匹配失败
    1
    '12345678'.replace(/\d{3,6}/g,'X'); //"X78"
  • +
  • 非贪婪模式(在量词后边加?
    尽可能少的匹配,一旦匹配成功,不再继续尝试;匹配前面的子表达式零次或一次,等价于 {0,1}
    1
    '12345678'.replace(/\d{3,6}?/g, 'X'); //"XX78"
  • +
+
    +
  1. 分组
    使用()可以达到分组的功能,使量词作用于分组
    1
    'a1b2c3d4'.replace(/([a-z]\d{3})g/, 'X') //"Xd4"
  2. +
  3. 反向引用
    使用$1、$2、$3...来表示和捕获分组后的内容
    1
    '17741881234'.replace(/(.{3})(.{4})(.{4})/, '$1****$3') //"177****1234"
  4. +
  5. 忽略分组
    不希望捕获某些分组,只需要在分组内加上?:即可
  6. +

  7. 使用|可以达到或的效果
  8. +
  9. 前瞻
    正则表达式从文本头部向文本尾部开始解析。 文本尾部的方向,称为“前”,文本头部称为“后”。 前瞻就是在正则表达式匹配规则的时候,向前检查是否符合断言(条件),后顾/后瞻方向相反。JavaScirpt不支持后顾。符合/不符合 特定断言称为 肯定/正向匹配 和 __否定/负向匹配__。
  10. +
+

四、RegExp对象方法

    +
  • RegExp.prototype.test(str)
    用于测试字符串参数中是否存在匹配正则表达式的字符串,若存在返回true,否则返回false
  • +
  • RegExp.prototype.exec(str)
    使用正则表达式模式对字符串执行搜索,并将更新全局RegExp对象的属性以反映匹配结果。如果没有匹配的文本则返回null,否则返回一个结果数组。
  • +
+

五、String对象方法

    +
  • String.prototype.search(reg)
    用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串。方法返回第一个匹配结果的index,查找不到返回-1。search()方法不执行全局匹配,它将忽略标志g,并且总是从字符串的开始进行检索。
  • +
  • String.prototype.match(reg)
    用于检索字符串,以找到一个或者多个与正则表达式匹配的文本。如果匹配到了一个或多个字符串,则返回一个数组,若没有匹配到,则返回null。它不会忽略全局标志g。
  • +
  • String.prototype.split(reg)
    使用split方法把字符串分割为字符数组。
  • +
  • String.prototype.replace(str/reg, str)
    用于替换字符串中匹配正则表达式或字符串的文本。
  • +
+

六、常用的正则表达式记录

+ +
+ + + + + + + +
+ + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2019/05/30/\345\276\256\344\277\241\346\216\210\346\235\203\346\265\201\347\250\213/flowChart.png" "b/2019/05/30/\345\276\256\344\277\241\346\216\210\346\235\203\346\265\201\347\250\213/flowChart.png" new file mode 100644 index 00000000..5cbd6943 Binary files /dev/null and "b/2019/05/30/\345\276\256\344\277\241\346\216\210\346\235\203\346\265\201\347\250\213/flowChart.png" differ diff --git "a/2019/05/30/\345\276\256\344\277\241\346\216\210\346\235\203\346\265\201\347\250\213/index.html" "b/2019/05/30/\345\276\256\344\277\241\346\216\210\346\235\203\346\265\201\347\250\213/index.html" new file mode 100644 index 00000000..7cedb557 --- /dev/null +++ "b/2019/05/30/\345\276\256\344\277\241\346\216\210\346\235\203\346\265\201\347\250\213/index.html" @@ -0,0 +1,469 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 微信授权流程 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 微信授权流程 +

+ + +
+ + + + +
+ + +

在公司制作 H5 页面的时候,有这样一个场景:在微信打开 H5 页面,已经绑定微信的用户直接免密登录,未绑定的用户使用传统账号密码的登录方式。其中免密登录的核心一环就是走一个微信授权流程,原理不难,弄懂它的流程比较重要。

+ +

微信网页授权官方文档

当用户在微信中访问第三方网页的时候,公众号可以通过微信网页授权机制来获取用户基本信息。在授权过程中,openid作为用户的唯一标识,同一个用户不同公众号的openid不同,反之亦然。

+
    +
  • 在发起授权前,需要到微信公众平台开发的官网设置授权回调的域名;
  • +
  • openid: 用户唯一标识;
  • +
  • code: code作为换取access_token的票据,每次用户授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期;
  • +
  • access_token: 网页授权接口调用凭证;
  • +
  • scope:用户授权的作用域;
      +
    • snsapi_basescope发起的网页授权,是用来获取进入页面的用户的openid的,并且是静默授权并自动跳转到回调页(往往是业务页面)。用户无感知。
    • +
    • snsapi_userinfoscope发起的网页授权,是用来获取用户的基本信息的。但这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息。
    • +
    +
  • +
+

微信免密登录流程图

+
+ + + + + + + +
+ + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2019/06/02/IP\345\234\260\345\235\200\345\222\214\345\255\220\347\275\221\345\210\222\345\210\206/index.html" "b/2019/06/02/IP\345\234\260\345\235\200\345\222\214\345\255\220\347\275\221\345\210\222\345\210\206/index.html" new file mode 100644 index 00000000..3e8fb9f2 --- /dev/null +++ "b/2019/06/02/IP\345\234\260\345\235\200\345\222\214\345\255\220\347\275\221\345\210\222\345\210\206/index.html" @@ -0,0 +1,478 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IP地址和子网划分 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ IP地址和子网划分 +

+ + +
+ + + + +
+ + +

计算机知识补全计划:ip地址、子网掩码相关笔记。

+ + +

MAC地址:决定下跳给哪个设备
IP地址:决定数据最终到达的计算机
子网掩码:用来判断两台机器的ip地址是否处于同一网段
课程链接

+

一、IP地址
IP地址是由32位二进制组成的,写成十进制,每四位以逗号分隔:如192.168.30.10。IP地址分为两部分,一部分是网络部分,另一部分是主机部分;在同一网段的计算机,网络部分一样,主机部分不一样,子网掩码就是用来区分主机与网段的。

+
    +
  1. 子网掩码
    两台计算机在通信之前,首先需要判断需要进行通信的设备与当前的设备是否处于同一网段之中:IP地址子网掩码与运算得出的结果就是网络部分,网络部分相同则处于同一网段。
    例如:A计算机想与B计算机通信,首先A将A的子网掩码和A的IP地址进行与运算,再将A的子网掩码和B的IP地址进行运算,若二者结果相同,则处于同一网段。
  2. +
  3. IP地址的分类
      +
    • A类:1-127 缺省子网掩码:255.0.0.0
    • +
    • B类:128-191 缺省子网掩码:255.255.0.0
    • +
    • C类:192-223 缺省子网掩码:255.255.255.0
    • +
    • D类(组播):224-239 缺省子网掩码:无
    • +
    • E类(研究):240-255 缺省子网掩码:无
      1
      [0----------128-----192---224-240-255]
      +在分配IP地址时注意:
      xxx.0.0.0:全0表示这个子网的网络号,不可用;
      xxx.255.255.255:全1表示这个子网的广播地址,代表网段内所有计算机,可跨网段,不可用。(注意,若全为255,则只能发送给本网段的机器,不能跨网段)
      例如C类地址,能设置的主机号只有2-254,一般路由器的ip地址为该网段内的第一个或者最后一个,避免冲突。
    • +
    +
  4. +
  5. 保留地址
      +
    • 保留的私网地址(不在公网上,相互之间不能通信(内网)):
        +
      • A 10.0.0.0 – 10.255.255.255
      • +
      • B 172.16.0.0 – 172.31.255.255
      • +
      • C 192.168.0.0 – 192.168.255.255
      • +
      +
    • +
    • 本地环回地址
        +
      • 127.0.0.1 本机
      • +
      • 169.254.0.0 断网地址
      • +
      • 224.0.0.1 特殊的组播地址,代表所有主机地址
      • +
      +
    • +
    +
  6. +
+ +
+ + + + + + + +
+ + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2019/07/11/\344\275\277\347\224\250 Performance APi \350\277\233\350\241\214\345\211\215\347\253\257\346\200\247\350\203\275\347\233\221\346\216\247/index.html" "b/2019/07/11/\344\275\277\347\224\250 Performance APi \350\277\233\350\241\214\345\211\215\347\253\257\346\200\247\350\203\275\347\233\221\346\216\247/index.html" new file mode 100644 index 00000000..4a360d2d --- /dev/null +++ "b/2019/07/11/\344\275\277\347\224\250 Performance APi \350\277\233\350\241\214\345\211\215\347\253\257\346\200\247\350\203\275\347\233\221\346\216\247/index.html" @@ -0,0 +1,493 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 使用 Performance API 进行前端性能监控 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 使用 Performance API 进行前端性能监控 +

+ + +
+ + + + +
+ + +

  平常只在测试环境测过前端页面性能,到了真实环境用户的手机上,页面性能的具体表现却未曾了解。H5 新增的 Performance API 可以精确的测量网页性能。使开发者可以通过数据上报的方式收集线上 H5 页面的性能表现,以合理优化页面性能短板,提升用户体验。

+ + +

前端性能监控指标

    +
  • 白屏时间: 从打开网站到有内容渲染出来的时间节点
  • +
  • 首屏时间: 首屏内容渲染完毕的时间节点
  • +
  • domReady 时间: 用户可操作的时间节点
  • +
  • onload 时间: 总下载时间
  • +
+

Performance API 简介

  Performace是 HTML5 的新特性之一,该接口会返回当前页面性能相关的信息。Performance 对象一共提供了4个属性:

+
    +
  • navigation: 包含页面加载、刷新、重定向情况
  • +
  • timing: 包含了各种与浏览器性能有关的时间数据
  • +
  • memory: 返回JavaScript对内存的占用
  • +
  • timeOrigin: 返回性能测量开始时的时间的高精度时间戳
  • +
+

本文主要讨论 Performance 的 timing 对象以及其他几种统计指标。

+

performance.timing

timing 对象提供了各种与浏览器处理相关的时间数据(详细),各时间节点可参照下图:

+ + +

其中常用的几项计算指标如下:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var timing = performance.timing;
var times = {};

// 请求耗时
times.request = timing.responseEnd - timing.requestStart || 0;

// 页面白屏时间
times.ttfb = timing.responseStart - timing.navigationStart || 0;

// 页面可操作时间
times.domReady = timing.domComplete - timing.responseEnd || 0;

//dom渲染时间
times.domRender = timing.domContentLoadedEventEnd - timing.navigationStart || 0,

// 总下载时间
times.onload = timing.loadEventEnd - timing.navigationStart || 0;

// DNS解析时间
times.lookupDomain = timing.domainLookupEnd - timing.domainLookupStart || 0;

// TCP建立时间
times.tcp = timing.connectEnd - timing.connectStart || 0,

// 首屏时间
times.now = performance.now();
+

performance.now()

返回当前网页从performance.timing.navigationStart到当前时间之间的微秒数

+

performance.getEntries()

浏览器获取网页时,会对网页中每一个对象(脚本文件、样式表、图片文件等等)发出一个HTTP请求。performance.getEntries方法以数组形式,返回这些请求的时间统计信息,有多少个请求,返回数组就会有多少个成员。

+

数据埋点及上报方式

利用 <script> 标签的 src 属性上报

工作中采用的埋点方式是脚本引入。该脚本负责收集浏览器性能指标信息,并生成一个 <script> 节点,将指标信息拼接成 url param 的形式,通过 <script> 标签的 src 属性发起请求,将数据上报到服务器。

+

利用 <img> 标签的 src 属性上报

谷歌和百度的都是用的1x1 像素的透明 gif 图片,其优点如下:

+
    +
  • 跨域友好
  • +
  • 执行过程无阻塞
  • +
  • 使用image时,部分浏览器内页面关闭不会影响数据上报
  • +
  • gif 的最低合法体积最小(最小的 bmp 文件需要74个字节,png 需要67个字节,而合法的 gif,只需要43个字节)
  • +
+

利用 HTML5 Beacon API 进行数据上报

Beacon API 允许开发者发送少量错误分析和上报的信息,它的特点很明显:

+
    +
  • 在空闲的时候异步发送统计,不影响页面诸如 JS、CSS Animation 等执行
  • +
  • 即使页面在 unload 状态下,也会异步发送统计,不影响页面过渡/跳转到下跳页
  • +
  • 可被客户端优化发送,尤其在 Mobile 环境下,可以将 Beacon 请求合并到其他请求上,一同处理
  • +
+

前端性能监控系统

在 github 上发现的比较好的工具,可以用来参考:

+ +

参考资料

前端性能监控-window.performance
Performance API-ruanyifeng
初探Performance API
前端全(无)埋点之页面停留时长统计

+ +
+ + + + + + + +
+ + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2019/07/11/\344\275\277\347\224\250 Performance APi \350\277\233\350\241\214\345\211\215\347\253\257\346\200\247\350\203\275\347\233\221\346\216\247/performance.png" "b/2019/07/11/\344\275\277\347\224\250 Performance APi \350\277\233\350\241\214\345\211\215\347\253\257\346\200\247\350\203\275\347\233\221\346\216\247/performance.png" new file mode 100644 index 00000000..daf08d3a Binary files /dev/null and "b/2019/07/11/\344\275\277\347\224\250 Performance APi \350\277\233\350\241\214\345\211\215\347\253\257\346\200\247\350\203\275\347\233\221\346\216\247/performance.png" differ diff --git "a/2019/07/15/Prometheus\347\233\221\346\216\247\345\272\224\347\224\250\346\200\247\350\203\275/architecture.png" "b/2019/07/15/Prometheus\347\233\221\346\216\247\345\272\224\347\224\250\346\200\247\350\203\275/architecture.png" new file mode 100644 index 00000000..1610bc02 Binary files /dev/null and "b/2019/07/15/Prometheus\347\233\221\346\216\247\345\272\224\347\224\250\346\200\247\350\203\275/architecture.png" differ diff --git "a/2019/07/15/Prometheus\347\233\221\346\216\247\345\272\224\347\224\250\346\200\247\350\203\275/flowChart.png" "b/2019/07/15/Prometheus\347\233\221\346\216\247\345\272\224\347\224\250\346\200\247\350\203\275/flowChart.png" new file mode 100644 index 00000000..16d5c906 Binary files /dev/null and "b/2019/07/15/Prometheus\347\233\221\346\216\247\345\272\224\347\224\250\346\200\247\350\203\275/flowChart.png" differ diff --git "a/2019/07/15/Prometheus\347\233\221\346\216\247\345\272\224\347\224\250\346\200\247\350\203\275/index.html" "b/2019/07/15/Prometheus\347\233\221\346\216\247\345\272\224\347\224\250\346\200\247\350\203\275/index.html" new file mode 100644 index 00000000..649e0c6f --- /dev/null +++ "b/2019/07/15/Prometheus\347\233\221\346\216\247\345\272\224\347\224\250\346\200\247\350\203\275/index.html" @@ -0,0 +1,519 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Prometheus 监控应用性能 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ Prometheus 监控应用性能 +

+ + +
+ + + + +
+ + +

   Prometheus 是一个开源的监控系统。它可以自动化的监听应用各性能指标的变化情况,并发出报警信息。了解它目的,是想把前端页面的性能指标记录到公司的 Prometheus 监控系统上,利用它监听前端页面各类异常。

+ +

一、Prometheus 系统简介

Prometheus 是一个开源的服务监控系统,社区资源和开发者都很活跃。其主要原理是通过 HTTP 协议从远程的机器收集数据并存储在本地的时序数据库上。Prometheus 通过安装在远程机器上的 exporter (数据暴露)插件来收集监控数据。

+

Prometheus 特点

Prometheus 本身也是一个时序数据库,它通过 HTTP 的方式获取时序数据。Prometheus 自身的查询语言 PromQL 可多维度的查询并实时计算指标的值。通过 PromQL 提供的计算方法,可以自定义数据可视化的指标,以及报警临界值。它有四种数据类型,可针对不同场景使用不同数据类型。

+

Prometheus 系统的组成部分

+ +

在监控流程中,主要由三个部分组成:被监控的应用暴露性能指标(exporter),promethues 应用采集性能指标(collector),数据可视化分析界面(web UI)。详情参照下述:

+
    +
  1. Prometheus server: 用于抓取数据,并存储到时序数据库;
  2. +
  3. Prometheus exporter: 安装在监控目标的上,为 Prometheus server 提供数据抓取的接口;
  4. +
  5. Prometheus web UI: 提供数据可视化分析界面;
  6. +
  7. Alertmanager: 用于处理警报;
  8. +
  9. Pushgateway: 用于 job 推送;
  10. +
+

二、Prometheus 监控流程图

+ +

Promethues server

主要负责数据采集和存储,提供 PromQL 查询语言的支持。可以通过 Prometheus 的 .yml文件中的scrape_config (字段详情)来配置要抓取的应用指标地址。同时,Promethues 也会监控自身的健康情况,默认将指标暴露在自身的 http://localhost:9090/metrics

+

Promethues exporter

参照官方推荐的插件列表,由于本次监听的站点是 NodeJS 站点,所以选择 prom-client 作为 exporter。
注意:被监听的应用需要暴露指标接口供 server 抓取。

+

Prometheus web UI

Grafana 可以连接多种类型的库,选择 Promethues 即可,默认监听 Promethues server 的9090/metrics 路径。

+

三、NodeJS 应用性能监控

使用 prom-clientnode-prometheus-gc-stats 收集 NodeJS 的性能指标。

+
    +
  • prom-client:收集服务端性能指标
  • +
  • node-prometheus-gc-stats:垃圾回收相关指标统计
  • +
+

(prom-client 相当于一个exporter,将默认的指标暴露在 /metrics 接口,之后 Promethues service会根据 .yml 配置中的采集时间定期来这个接口采集数据信息,然后 web UI Grafana 再跟 Promethues server 进行同步)

+

prom-client 文档

一共支持四种数据格式:Histogram、Summary、Gauges 、Counters:

+
    +
  • Histogram(柱状图) 统计数据的分布情况(比如 Http_response_time 的时间分布)
  • +
  • Summary(摘要) 主要用于表示一段时间内数据采样结果(请求持续的时间或响应大小)
  • +
  • Gauges(仪表盘) 最简单的度量指标,监测瞬间状态(监控硬盘容量或者内存的使用量)
  • +
  • Counters(计数器) 从数据量0开始累积计算,在理想状态下只能是永远的增长不会降低
  • +
+

常用的采集方法:

+ +

收集到指标后,就可以利用 PromQL 进行计算了,计算时注意 PromQL 即时向量范围向量 两种向量的区别和转换:

+
1
2
3
4
5
// 计算每分钟垃圾回收bytes数
delta(nodejs_gc_reclaimed_bytes_total{gctype="Scavenge"}[1m])

// 计算个页面5min以内的DomReady均值
delta(FE_Timing_Performance_domReady_sum[5m])/delta(FE_Timing_Performance_domReady_count[5m])
+ +

example-prometheus-nodejs

   这个 demo 是一个完整的 prom-client + Promethues + grafana 监控示例,有助于理解整个监控流程。

+

四、前端异常记录实践结论

并不推荐使用 Prometheus 系统来记录前端页面性能等信息。

+
    +
  1. 从指标上来看,应用的基本性能指标:吞吐量、内存使用量、每秒请求数、请求平均耗时等。这些几乎都是“瞬时”值(由于时间窗口小,可看做瞬时值),而前端性能指标并不是“瞬时”,它更偏向于一段时间内的表现情况(时间窗口大)。Prometheus 系统主要用于监听应用的性能,它的数据类型更多是为应用服务。
  2. +
+
    +
  • 应用性能特点:一个应用,多个指标;
  • +
  • 前端页面的性能指标特点:一个页面,多个指标。多个页面。
  • +
+
    +
  1. 从数据类型上来看:
      +
    • Gauges:不可以用来记录前端的性能表现。因为 Gauge 记录的某一刻的瞬时值,如果用来记录时间,则每次数据都会被最后访问的那名用户刷新;
    • +
    • Counter:计数器虽然可以记录前端某个页面的访问次数,但若页面路由中携带参数,或者结尾带时间戳,则会生成多个重复页面的 Counter,遇到爬虫还会生成大量无用路径,表现并不好;
    • +
    • Histogram、Summary:可以记录多个页面,多个指标;Histogram 和 Summary 很相似,只不过 Histogram 记录原始值,Summary 记录指标的各个占比。
    • +
    +
  2. +
  3. 从可视化 Web UI 上来看:
    公司 Prometheus 系统默认使用的可视化 UI 是 Grafana。之前尝试用 Histogram 来记录前端各页面的性能表现,在 Grafana 中用折线图可视化数据。一个指标对应一个折线图,但由于页面路由多个,导致各个折线图中折线过多难以分辨;若取所有页面该指标的均值或者最大值来展示,又不知道峰值是哪个页面产生的。
  4. +
+

综上可以看出,Prometheus 可以记录前端性能指标,但是受数据类型制约,它并不是最合适的。

+

参考资料

+ +
+ + + + + + + +
+ + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2019/08/05/z-index\345\260\217\347\273\223/index.html" "b/2019/08/05/z-index\345\260\217\347\273\223/index.html" new file mode 100644 index 00000000..b1312913 --- /dev/null +++ "b/2019/08/05/z-index\345\260\217\347\273\223/index.html" @@ -0,0 +1,498 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + z-index 小结 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ z-index 小结 +

+ + +
+ + + + +
+ + +

当 z-index 多个规则多个层级共同作用时,展现的效果往往跟自己的想法有很大差异,论 CSS 基本功的重要性。本文总结了 CSS 层叠的特性、基本准则和创建条件,内容大多为张鑫旭大神的《CSS世界》读书小结。

+ + +

层叠的基本概念

    +
  • 层叠上下文(stacking context):当前元素所处的层叠规则,即元素所处的 z 轴。一个页面中,层叠上下文不止一个。
  • +
  • 层叠水平(stacking level):同一个层叠上下文中元 素在 z 轴上的显示等级。
  • +
  • 层叠顺序(stacking order):
      +
    • background/border 指在同一层叠上下文元素的边框和背景色。
    • +
    • inline水平盒子指的是包括inline/inline-block/inline-table元素的“层叠顺序”,它们都是同等级别的。
    • +
    • 内联元素的层叠顺序要比浮动元素和块状元素都高,是因为float元素在起始时是作为布局元素存在的。由于“内容”的重要性远大于“装饰”和“布局”,所以内容元素层叠顺序比较高,详情见下图:
    • +
    +
  • +
+ + +

z-index

z-index 属性只有和定位元素(position 不为 static 的元素)在一起的时候才有作用,可以是正数也可以是负数。在同一层叠上下文中,数值越大层级越高。 在CSS3中,z-index 已经并非只对定位元素有效,flex 盒子的子元素 也可以设置 z-index 属性。

+

层叠准则

    +
  1. 谁大谁上:在同一个层叠上下文领域,具有明显的层叠水平标识的时候,层叠水平值大的那一个覆盖小的那一个,例如 z-index 属性值。
  2. +
  3. 后来居上:当元素的层叠水平一致、层叠顺序相同的时候,在 DOM 流中处于后面的元素会覆盖前面的元素。
  4. +
+

层叠上下文的特性

    +
  • 层叠上下文的层叠水平要比普通元素高。
  • +
  • 层叠上下文可以阻断元素的混合模式。
  • +
  • 层叠上下文可以嵌套,内部层叠上下文及其所有子元素均受制于外部的“层叠上下文”。
  • +
  • 每个层叠上下文和兄弟元素独立,也就是说,当进行层叠变化或渲染的时候,只需要考虑后代元素。
  • +
  • 每个层叠上下文是自成体系的,当元素发生层叠的时候,整个元素被认为是在父层叠上下文的层叠顺序中。
  • +
+

页面中的层叠上下文

    +
  • 根层叠上下文 :页面根元素具有层叠上下文,称为“根层叠上下文”。故页面中所有的元素至少处于一个层叠上下文中。
  • +
  • 定位元素与传统层叠上下文 :对于 position 值为 relative/absolute 以及 Firefox/IE 浏览器(不包括 Chrome 浏览 器)下含有 position:fixed 声明的定位元素,当其 z-index 值不是 auto 的时候,会创建层叠上下文(z-index 一旦变成数值,即使是 0,也创建一个层叠上下文)。
  • +
  • CSS3新属性的层叠上下文
      +
    • 元素为 flex 布局元素(父元素 display:flex | inline-flex),同时 z-index 值不是 auto;
    • +
    • 元素的 opacity 值不是 1;
    • +
    • 元素的 transform 值不是 none;
    • +
    • 元素 mix-blend-mode 值不是 normal;
    • +
    • 元素的 filter 值不是 none;
    • +
    • 元素的 isolation 值是 isolate;
    • +
    • 元素的 will-change 属性值为上面 2~6 的任意一个(如 will-change:opacity、will-chang:transform 等);
    • +
    • 元素的-webkit-overflow-scrolling 设为 touch;
    • +
    +
  • +
+

CSS3 属性与 z-index 的兼容性问题

+ +
+ + + + + + + +
+ + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2019/08/05/z-index\345\260\217\347\273\223/stacking-order.png" "b/2019/08/05/z-index\345\260\217\347\273\223/stacking-order.png" new file mode 100644 index 00000000..5060d348 Binary files /dev/null and "b/2019/08/05/z-index\345\260\217\347\273\223/stacking-order.png" differ diff --git "a/2019/10/22/\343\200\212\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\343\200\213-http-\351\203\250\345\210\206\350\257\273\344\271\246\347\254\224\350\256\260/index.html" "b/2019/10/22/\343\200\212\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\343\200\213-http-\351\203\250\345\210\206\350\257\273\344\271\246\347\254\224\350\256\260/index.html" new file mode 100644 index 00000000..16cb5c7e --- /dev/null +++ "b/2019/10/22/\343\200\212\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\343\200\213-http-\351\203\250\345\210\206\350\257\273\344\271\246\347\254\224\350\256\260/index.html" @@ -0,0 +1,505 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 《计算机网络》- http 部分读书笔记 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 《计算机网络》- http 部分读书笔记 +

+ + +
+ + + + +
+ + +

《计算机网络(第7版)-谢希仁》http 部分的读书小结和扩展,因为工作中最常打交道的就是这部分了。整本书都很不错,语言通俗易懂;各协议的关系、发展过程以及区别都概括的很好。
本文主要概括 HTTP、HTTP1.0、HTTP2.0、HTTPS 的之间的差异。

+ +

万维网 WWW

万维网(World Wide Web)是一个分布式的超媒体系统,是超文本系统的扩充。万维网使用 统一资源定位符URL(Uniform Resource Locator) 来标志万维网上的各种文档。

+

URL 的格式

URL 的一般形式由以下四个部分组成:
     <协议>://<主机>:<端口>/<路径>
URL 的<协议>就是指出使用什么协议来获取万维网文档。现在最常用的协议就是 http,其次是 ftp。有些浏览器为方便用户,在输入 URL 时,可以把最前面的“http://”甚至把主机名最前面的“www”省略,然后浏览器替用户把省略的字补上。
URL里面的字母不分大小写,但是为了便于阅读,有时故意使用一些大写字母。

+

超文本传送协议 HTTP

HTTP(HyperText Transfer Protocol,超文本传输协议),使用了面向连接的 TCP 作为传输层协议,监听 80 端口,信息是明文传输,其本身是无状态的。

+
    +
  • HTTP1.0(1996)每次请求都会单独建立一个TCP连接,用完关闭;(缺点:每次请求都耗费时间在连接上,非持续连接 使服务器开销很重)。
  • +
  • HTTP1.1(1999)在服务器发送完响应后仍在一段时间内保持这条连接,浏览器和该服务器可以继续在该连接上传送后续的请求报文和响应报文,使用 持续连接
  • +
  • HTTP2.0(2015)使用了新的二进制格式、多路复用、以及 header 压缩,性能相对于 HTTP1.x 提升明显。改善了在 HTTP1.1 中,浏览器客户端在同一时间,针对同一域名下的请求有一定数量限制(连接数量),超过限制会被阻塞;基于 HTTPS,天生具有安全性,可以避免单纯使用 HTTPS 带来的性能下降。
  • +
+

影响 HTTP 网络请求的因素

影响因素主要有两个:带宽和延迟。

+
    +
  • 带宽 :在浏览器刚流行的时候,大部分用户是通过拨号来上网,由于受当时的带宽条件的限制,无法使得用户的同时多个请求被处理。同时,当时的服务器的配置也比现在差很多,所以限制每个浏览器的连接数的大小也是有必要的。浏览器默认对同一域下的资源,只保持一定的连接数,阻塞过多的连接,以提高访问速度和解决阻塞问题。不同浏览器的默认值不一样,对于不同的 HTTP 协议其值也不一样。
  • +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
浏览器HTTP 1.1HTTP 1.0
IE 6、724
IE 866
FireFox 228
FireFox 366
Safari 3、444
+

如果说我们还停留在拨号上网的阶段,带宽可能会成为一个比较严重影响请求的问题,但是现在网络基础建设已经使得带宽得到极大的提升,我们不再会担心由带宽而影响网速,那么就只剩下延迟了。

+
    +
  • 延迟
      +
    • 浏览器阻塞(HOL blocking):浏览器会因为一些原因阻塞请求。浏览器对于同一个域名,同时只能有 4 个连接(这个根据浏览器内核不同可能会有所差异),超过浏览器最大连接数限制(见上表),后续请求就会被阻塞。
    • +
    • DNS 查询(DNS Lookup):浏览器需要知道目标服务器的 IP 才能建立连接。将域名解析为 IP 的这个系统就是 DNS。这个通常可以利用DNS缓存结果来达到减少这个时间的目的。
    • +
    • 建立连接(Initial connection):HTTP 是基于 TCP 协议的,浏览器最快也要在第三次握手时才能捎带 HTTP 请求报文,达到真正的建立连接,但是这些连接无法复用会导致每次请求都经历三次握手和慢启动。三次握手在高延迟的场景下影响较明显,慢启动则对文件类大请求影响较大。
    • +
    +
  • +
+

应用层安全协议 HTTPS

HTTPS 是使用 SSL(Secure Socket Layer,安全套接字层)协议的 HTTP 协议。SSL 作用在 HTTP 和运输层之间,在 TCP 之上建立起一个安全通道,为通过 TCP 传输的应用层数据提供安全保障。
HTTPS 监听 TCP 的 443 端口,信息是密文传输。

+ +
+ + + + + + + +
+ + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2019/11/14/Flink-\345\210\235\346\216\242/index.html" "b/2019/11/14/Flink-\345\210\235\346\216\242/index.html" new file mode 100644 index 00000000..51ae062e --- /dev/null +++ "b/2019/11/14/Flink-\345\210\235\346\216\242/index.html" @@ -0,0 +1,488 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Flink 初探 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ Flink 初探 +

+ + +
+ + + + +
+ + +

Apache Flink 是一个分布式处理引擎,在有界或无界数据流上进行有状态的计算。工作时偶然接触到一点点,有些概念虽然有点抽象,但是思路却值得借鉴。本文记录用 Flink 实时求均值、水印生成、以及迟到的数据元触发计算更新等等,是一篇纯探索性文章。用笔记形式记录,以便忘记。

+ +

https://flink.apachecn.org/docs/1.7-SNAPSHOT/#/

+

Flink 是一个针对流数据和批数据的分布式处理引擎,代码主要是由 Java 实现,部分代码是 Scala。它可以处理有界的批量数据集、也可以处理无界的实时数据集。对 Flink 而言,其主要处理的场景就是流数据。

+

二、流处理和批处理的区别

批处理 特点:离线、单次处理的数据量大、处理速度慢、非实时计算。常见的批处理就是数据库深夜定时跑任务,因为批量计算会占用大量资源。
流处理 特点:在线,单次处理数据量小、处理速度快、实时计算。常见的应用场景就是监控、统计、实时推荐等。

+

三、学习目标

用 Flink 消费已有数据源,实时计算数据均值,并允许数据元延迟到来时,重新触发计算。

+

四、涉及到的名词概念

    +
  1. 窗口 (Windows):对某段数据流进行统计,即统计区间;Windows 可以是时间驱动的(例如:每30秒)或数据驱动(例如:每100个数据元)。
  2. +
  3. 时间 (Time):程序中引用的时间;Flink 支持三种时间:事件时间、摄取时间和处理时间。
  4. +
  5. 算子 (Operator):Flink 内部提供的时间/数据流/数据元的处理函数。
  6. +
  7. 时间戳 (TimeStamp)/水印 (WaterMark):使用数据源的时间或者系统时间为到来的数据元加上时间戳;数据流加上水印标记,为了等下个数据元到来时知道该数据元是否应该被包含在当前次计算中。
    注:Watermark 是随数据产生的,窗口时间现在处于什么位置看 Watermark,只有新产生的一条数据超出窗口长度,这个窗口才会触发计算。(当使用事件时间窗口时,可能会发生数据元迟到的情况,则必须为数据流设置时间戳和水印)
  8. +
+

允许迟到 allowedLateness

只要应该属于此窗口的第一个数据元到达,就会创建一个窗口,当时间(事件或处理时间)超过其结束时间戳加上用户指定 allowed lateness 时,窗口将被完全删除。
allowedLateness 用来设置窗口销毁时间 ,而 waterMark 是用来设置窗口激活时间。当时延迟时间超过 allowedLateness 设置的时间,这个计算窗口就会被销毁,开始下一个窗口,即使被销毁的窗口还没有触发计算。

+

窗口函数

Flink 的窗口函数会暴露出数据流不同状态时的处理函数,具体的高级操作或者运算例如聚合、求均值等函数需要我们自己去实现。
例如聚合窗口 stream.aggregate 的参数 AggregateFunction <IN, ACC, OUT>,具有三种的类型:输入类型(IN)、累加器类型(ACC)和输出类型(OUT)。
使用 AggregateFunction 求均值(示例代码来自官网):

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private static class AverageAggregate
implements AggregateFunction<Tuple2<String, Long>, Tuple2<Long, Long>, Double> {
@Override
public Tuple2<Long, Long> createAccumulator() {
return new Tuple2<>(0L, 0L);
}

@Override
public Tuple2<Long, Long> add(Tuple2<String, Long> value, Tuple2<Long, Long> accumulator) {
return new Tuple2<>(accumulator.f0 + value.f1, accumulator.f1 + 1L);
}

@Override
public Double getResult(Tuple2<Long, Long> accumulator) {
return ((double) accumulator.f0) / accumulator.f1;
}

@Override
public Tuple2<Long, Long> merge(Tuple2<Long, Long> a, Tuple2<Long, Long> b) {
return new Tuple2<>(a.f0 + b.f0, a.f1 + b.f1);
}

DataStream<Tuple2<String, Long>> input = ...;

input
.keyBy(<key selector>)
.window(<window assigner>)
.aggregate(new AverageAggregate());
}
+ +

五、遇到的问题

    +
  • 数据流过滤后,只剩下被过滤的数据:
      +
    • SingleOutputStreamOperator 旁路分支:这个分支用来获取被过滤掉的数据,并不是过滤后的数据。
    • +
    +
  • +
  • 给数据流设置时间戳之后,迟到的数据没有被抛弃:
      +
    • stream.assignTimestampsAndWatermarks 定期生成水印:最简单的特殊情况是给定源任务看到的时间戳按升序发生的情况。在这种情况下,当前时间戳始终可以充当水印,因为没有更早的时间戳会到达。且生成的时间戳会覆盖事件原有的,若存在迟到的数据元,用这个方法,则数据不会被抛弃。
    • +
    • BoundedOutOfOrdernessTimestampExtractor :Flink 提供此参数为固定数量的迟到者分配时间戳和水印。若有数据元可能迟到的场景,请应用此方法。
    • +
    +
  • +
  • 设置的水印时间戳,超时告警,但是数据没有被丢弃?
  • +
  • 最新记录没有被统计,只有下一条数据写入时,之前的数据才会被触发统计?
  • +
+

六、数据下沉 Data Sink

Flink 可以自己指定数据源连接器,以及数据下沉(接收)目标。从 Flink 官网上来看连接器支持 Kalfa、Elasticsearch、HDFS、RabbitMQ 等等,公司已有 RabbitMQ 数据源,使用 RabbitMQ sink 接收数时,注意事件消费者不要和事件生产者的队列名不要相同,否则会报错。

+

参考链接

+ +
+ + + + + + + +
+ + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2019/12/26/PWA-Service-Worker-\345\260\217\347\273\223\357\274\210\344\270\200\357\274\211\345\220\204\347\261\273\347\274\223\345\255\230\345\257\271\346\257\224/CDN&&Nginx.jpg" "b/2019/12/26/PWA-Service-Worker-\345\260\217\347\273\223\357\274\210\344\270\200\357\274\211\345\220\204\347\261\273\347\274\223\345\255\230\345\257\271\346\257\224/CDN&&Nginx.jpg" new file mode 100644 index 00000000..516611fb Binary files /dev/null and "b/2019/12/26/PWA-Service-Worker-\345\260\217\347\273\223\357\274\210\344\270\200\357\274\211\345\220\204\347\261\273\347\274\223\345\255\230\345\257\271\346\257\224/CDN&&Nginx.jpg" differ diff --git "a/2019/12/26/PWA-Service-Worker-\345\260\217\347\273\223\357\274\210\344\270\200\357\274\211\345\220\204\347\261\273\347\274\223\345\255\230\345\257\271\346\257\224/index.html" "b/2019/12/26/PWA-Service-Worker-\345\260\217\347\273\223\357\274\210\344\270\200\357\274\211\345\220\204\347\261\273\347\274\223\345\255\230\345\257\271\346\257\224/index.html" new file mode 100644 index 00000000..dc0c29c1 --- /dev/null +++ "b/2019/12/26/PWA-Service-Worker-\345\260\217\347\273\223\357\274\210\344\270\200\357\274\211\345\220\204\347\261\273\347\274\223\345\255\230\345\257\271\346\257\224/index.html" @@ -0,0 +1,539 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PWA-Service Worker 小结(一)各类缓存对比 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ PWA-Service Worker 小结(一)各类缓存对比 +

+ + +
+ + + + +
+ + +

年底了,总结一下上半年探索的 PWA 的离线缓存技术。顺带总结了一下前端全流程每一步中都可能遇到的缓存,大部分都是概念、名词的理解和说明。涉及到的缓存有:HTTP 缓存、Manifest 缓存、CDN 缓存、Nginx 服务器缓存、Service Worker 缓存。

+ + +

缓存的好处:
存储频繁访问的数据,降低服务器压力;
减少网络延迟,加快页面打开速度;

+

一、HTTP 缓存

浏览器缓存机制

    +
  1. 在未设置相应头缓存字段的时候,只有用户点击“回退”按钮的时候,页面才会从缓存中读取
  2. +
  3. 过期机制 :与服务器协商获取。对于浏览器来说,如何缓存一个资源是服务器端制定的策略,服务器对每个资源的 HTTP 响应头设置属性和值,自己只负责执行。常用的为以下几种:
      +
    • Expires: 设置过期时间(单位日期),某日期之前都不再询问;浏览器再次命中这个资源,直至XXX时间前都不会发起 HTTP 请求,而是直接从缓存(在硬盘中)读取。
        +
      • 如:200 (from cache) 这种缓存速度最快。
      • +
      +
    • +
    • Last-Modified: 设置资源上次修改时间(单位日期),每次请求命中资源,都去询问资源是否过期;通过这种缓存方式,无论资源是否发生变更,都会发生至少一来一去的 HTTP 头传输和接收,速度比不上 Expires;
        +
      • 如:304,若文件发生变更,则返回200。
      • +
      +
    • +
    • Cache-Control:
        +
      • max-age= 设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒);标准中规定 max-age 的值最大不能超过一年,且以秒为单位,所以值为 31536000;
      • +
      • no-cache  字面意义“不缓存”。实际机制是对资源仍使用缓存,但每次使用前必须(MUST)向服务器对缓存资源进行验证;
      • +
      • no-store 不使用任何缓存;
      • +
      +
    • +
    +
  4. +
  5. 验证机制 :服务器返回资源的时候有时会在头信息中携带 __Etag(Entity Tag)__,它可作为浏览器再次请求过程的校验标识。如发现校验标识不匹配,说明资源已经修改或过期,浏览器需要重新获取资源内容。
    ETag 可以保证每一个资源是唯一的,资源变化都会导致 ETag 变化。服务器根据浏览器上送的 ETag / If-None-Match 值来判断是否命中缓存。在精准度上,Etag 优于 Last-Modified。因为 Etag 是按照内容为资源增加标识,而 Last-Modified 是根据文件最后修改时间判断。
  6. +
+ + +

常用的缓存策略:

    +
  • 对于动态生成的 HTML 页面使用 HTTP 头: Cache-Control : no-cache;
  • +
  • 对于静态 HTML 页面使用 HTTP 头: Last-Modified;
  • +
  • 其他所有文件类型都设置 Cache-Control 头,并且在文件内容有所修改都时候修改文件名。
  • +
+

如何更新文件:

按照 HTTP 规范,如果修改了请求资源的 Query String,就应该被视为一个新的文件。但是遇到运营商劫持时,会忽略 Query String,遇到这种情况只能修改文件名。

+

疑问:

给 HTML 都设置了 Cache-Control: no-cache; 对 CSS 和 JS都用了 gulp 进行了打包编译处理,每次有变化都会变更文件名;那么此种情况下,是否还需要设置 Last-Modified?
直接设置 Cache-Control max-age 或者 Expires 难道不会节省更多 HTTP 请求吗?避免服务器为做出应答返回大量 304。

+

二、Manifest 缓存

manifest 在前端含义很多,常见的四个使用场景如下:

+
    +
  1. HTML 标签的 manifest 属性,用来离线缓存 HTML 文档以及资源的;
      +
    • 如 <html manifest=”xxx”></html>,由于坑太多,现在已经被废弃;
    • +
    +
  2. +
  3. PWA 的 manifest 功能:将 web 应用程序安装到设备的主屏幕;
      +
    • 如 <link rel=”manifest” href=”/manifest.json”>;
    • +
    • 在 manifest.json 中配置应用的图标、名称等信息;通过一系列配置,就可以为 Web App 添加一个图标到手机上,点击图标即可打开站点;
    • +
    +
  4. +
  5. webpack 打包时会生成个 manifest.json 的文件,用来分析打包后的文件;
  6. +
  7. gulp 处理静态资源时,使用 gulp 的 gulp-rev 插件生成 manifest.json,用来记录源文件与处理后的目标文件的对照
  8. +
+

三、CDN缓存

即使为各类资源文件设置了 HTTP 头,当用户手动清除缓存 ,或者由于磁盘容量限制,先缓存的文件被挤出磁盘,此时依旧需要请求资源,为了快速响应用户请求,使用 CDN 加速。CDN的分流作用不仅减少了用户的访问延时,也减少了源站的负载。
当用户手动清理本地缓存后,将去请求距离最近的 CDN 边缘节点。
CDN 边缘节点缓存策略因服务商不同而不同,但一般会遵循 HTTP 标准协议。通过 HTTP 响应头中的 Cache-Control: max-age 的字段来设置CDN边缘节点数据缓存时间,若数据失效,则向源站发出回源请求,拉取最新的数据;当源站内容有更新的时候,源站主动把内容推到CDN节点。

+

各家 CDN 缓存参考:https://segmentfault.com/a/1190000006673084

+

CDN 回源原理:https://www.jianshu.com/p/e7751ecb6f21

+

四、nginx 服务器缓存

这里又牵扯到了两个地方…就像家用路由器和企业级路由器虽然都叫路由器但是功能完全不一样…
nginx 大名 负载均衡服务器,它是服务器不是服务;CDN 加速是运营商提供的一种服务….,这俩玩意一点关系都没有。如果网站既使用了 CDN 加速,同时又使用了 Nginx 代理,那么 CDN 的位置相比于 Nginx 服务器更靠近用户。

+ + +

网站管理者可以通过为网站配置 Nginx 服务器来达到负载均衡的目的, Nginx 可以重写静态资源的 HTTP 头的缓存信息等,也可以用 Nginx 搭建自己的 CDN 节点(原理跟运营商 CDN 差不多,都是转发到合适的机器;只不过 CDN 是将静态资源存在运营商的机器上,Nginx 做 CDN 的话就缓存在自己的机器上)。具体选择时可通过银子的多少来判断是选 CDN 加速,还是 Nginx 搭建 CDN。

+

综上,当 Nginx 服务器承载“CDN 加速”的功能时,可通过配置 proxy_cache 将文件缓存到本地的一个目录,缓存命中原理当与 CDN 相同;当 Nginx 服务器不充当 CDN,只是重写静态文件的响应头时,此时跟服务器写命令没差,缓存在浏览器中,原理见浏览器缓存命中机制,不再进行赘述。

+

五、Service Worker 缓存

Service Worker 是一个位于浏览器和网络之间的客户端代理,可以拦截、处理流经的 HTTP 请求,使开发者可以从缓存中向 Web 应用提供资源。可以把它看成是用户设备中的缓存提供服务器,功能十分强大。它缓存的文件同样存储在客户端(用户设备)中:

+ + +

Service Worker 是 PWA 实现离线应用的核心技术。它可以:

+
    +
  • 让网页可以离线访问;
  • +
  • 让网页在弱网情况,使用缓存快速打开应用,提升体验;
  • +
  • 同时在网络正常的情况下走网络缓存减少请求的带宽;
  • +
  • 对不支持的手机没有影响;
  • +
+

缓存有各自的优先级,当依次查找缓存且都没有命中的时候,才会去请求网络:

+
    +
  1. Service Worker
  2. +
  3. Memory Cache
  4. +
  5. Disk Cache
  6. +
  7. 网络请求
  8. +
+

参考资料:

+ +
+ + + + + + + +
+ + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2019/12/26/PWA-Service-Worker-\345\260\217\347\273\223\357\274\210\344\270\200\357\274\211\345\220\204\347\261\273\347\274\223\345\255\230\345\257\271\346\257\224/web\345\272\224\347\224\250\347\274\223\345\255\230\344\275\215\347\275\256\345\233\276.png" "b/2019/12/26/PWA-Service-Worker-\345\260\217\347\273\223\357\274\210\344\270\200\357\274\211\345\220\204\347\261\273\347\274\223\345\255\230\345\257\271\346\257\224/web\345\272\224\347\224\250\347\274\223\345\255\230\344\275\215\347\275\256\345\233\276.png" new file mode 100644 index 00000000..0c25b062 Binary files /dev/null and "b/2019/12/26/PWA-Service-Worker-\345\260\217\347\273\223\357\274\210\344\270\200\357\274\211\345\220\204\347\261\273\347\274\223\345\255\230\345\257\271\346\257\224/web\345\272\224\347\224\250\347\274\223\345\255\230\344\275\215\347\275\256\345\233\276.png" differ diff --git "a/2019/12/26/PWA-Service-Worker-\345\260\217\347\273\223\357\274\210\344\270\200\357\274\211\345\220\204\347\261\273\347\274\223\345\255\230\345\257\271\346\257\224/\345\215\217\345\225\206\347\274\223\345\255\230\345\221\275\344\270\255\350\277\207\347\250\213.png" "b/2019/12/26/PWA-Service-Worker-\345\260\217\347\273\223\357\274\210\344\270\200\357\274\211\345\220\204\347\261\273\347\274\223\345\255\230\345\257\271\346\257\224/\345\215\217\345\225\206\347\274\223\345\255\230\345\221\275\344\270\255\350\277\207\347\250\213.png" new file mode 100644 index 00000000..76c19c9d Binary files /dev/null and "b/2019/12/26/PWA-Service-Worker-\345\260\217\347\273\223\357\274\210\344\270\200\357\274\211\345\220\204\347\261\273\347\274\223\345\255\230\345\257\271\346\257\224/\345\215\217\345\225\206\347\274\223\345\255\230\345\221\275\344\270\255\350\277\207\347\250\213.png" differ diff --git "a/2019/12/31/2019\345\271\264\347\273\210\346\200\273\347\273\223/index.html" "b/2019/12/31/2019\345\271\264\347\273\210\346\200\273\347\273\223/index.html" new file mode 100644 index 00000000..6945698e --- /dev/null +++ "b/2019/12/31/2019\345\271\264\347\273\210\346\200\273\347\273\223/index.html" @@ -0,0 +1,458 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2019年终总结 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 2019年终总结 +

+ + +
+ + + + +
+ + +

2019年终总结(✖️)
2020待做清单(✔️)

+ + +
    +
  • 一月:工作
  • +
  • 二月:回家过年
  • +
  • 三月:PWA 离线缓存初步探索
  • +
  • 四月:多页应用 Service Worker 合适的落地方案持续寻找中(暂缓);前端性能指标 Performance 采集上报
  • +
  • 五月:前端性能指标尝试用 Prometheus 监控系统记录并用 Grafana 做可视化,尝试结果并不好;其他工作中过了一遍 RabbitMQ 事件队列收发流程
  • +
  • 六月:Elasticsearch 日志监控系统初探,目的是根据日志中的某些关键词,发出微信、邮件告警通知
  • +
  • 七月:elk 告警通知进行落地测试调优,微信和邮箱刚开始差点被通知给炸没了…
  • +
  • 八月:告警通知发现了很多不规范的日志,以及某些机器大清早的刷接口…持续使用中…但怎样区分业务的错误日志和代码的错误日志一直是一个问题…
  • +
  • 九月:学习计算机网络相关知识
  • +
  • 十月:Flink 实时计算性能指标探索
  • +
  • 十一月:网络工程师考试
  • +
  • 十二月:灌水
  • +
+

今年是工作上探索新技术比较多的一年,前端的、不止前端的…虽然最终落地实践并产生结果的并不多,但是多种类型和方向的尝试让我开拓了不少眼界。纵观全年技术探索,leader 在带我们建立 FE 团队的性能监控和异常监控系统方向上做努力。19年没搞完的总结,就是20年的todolist…
希望 2020 年的我对技术研究能上升个层次,不仅仅是学习新技术、了解新技术带来什么好处,还要能想到引入新技术带来的一系列后续的优化和落地。

+

其他方面,就是缺少的计科知识,出来混,总要还的。报了个网工的考试督促自己系统学习下计科网络相关的知识,然后用实力证明忘的比学的快。

+

二月份 回家过年无聊的时候下了个游戏叫《守望先锋》,从人机模式简单->困难,到快速游戏,再到竞技比赛:体验团队合作游戏的乐趣 + 1,一言不合口吐芬芳 + 10086 。概括起来就是,如果这局赢了,那是队友真强;如果这局输了,那是我正常发挥。不知不觉,这游戏已经玩了一年了…
六月底 终于不堪某水果的电池续航和微信卡顿问题转战 HUAWEI 阵营,不得不说,真香…,但是安卓9下的抓包真是让我脑壳疼了好几天…
八月份 gxTodo 总是莫名其妙的卡死崩溃,改用【滴答清单】了。印象笔记也是真的好用…
十月份 找房换房。没想到第一次坐在电动车后面吹着微风晒着暖阳周游北京,居然是自如管家驮着我。带我看房的自如管家是位体型娇小的南方妹子,我站在她面前就像是一个五大三粗的钢铁硬汉…怪不好意思的…
十一月份 软考北京考点在房山…(╯°□°)╯︵┻━┻)

+

19年还算充实,虽然相比于18年更宅了一点…最大的希望就是在 2020 年懒癌和拖延症能治好,找到比较感兴趣的东西。还有,别迟到。

+ +
+ + + + + + + +
+ + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2020/01/02/PWA-Service-Worker-\345\260\217\347\273\223\357\274\210\344\272\214\357\274\211\345\256\236\350\267\265/Service-Worker-Lifecycle.png" "b/2020/01/02/PWA-Service-Worker-\345\260\217\347\273\223\357\274\210\344\272\214\357\274\211\345\256\236\350\267\265/Service-Worker-Lifecycle.png" new file mode 100644 index 00000000..f83f969c Binary files /dev/null and "b/2020/01/02/PWA-Service-Worker-\345\260\217\347\273\223\357\274\210\344\272\214\357\274\211\345\256\236\350\267\265/Service-Worker-Lifecycle.png" differ diff --git "a/2020/01/02/PWA-Service-Worker-\345\260\217\347\273\223\357\274\210\344\272\214\357\274\211\345\256\236\350\267\265/index.html" "b/2020/01/02/PWA-Service-Worker-\345\260\217\347\273\223\357\274\210\344\272\214\357\274\211\345\256\236\350\267\265/index.html" new file mode 100644 index 00000000..2db03439 --- /dev/null +++ "b/2020/01/02/PWA-Service-Worker-\345\260\217\347\273\223\357\274\210\344\272\214\357\274\211\345\256\236\350\267\265/index.html" @@ -0,0 +1,513 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PWA-Service Worker 小结(二)实践 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ PWA-Service Worker 小结(二)实践 +

+ + +
+ + + + +
+ + +

Service Worker 的初衷是极致优化用户体验,带来丝滑般流畅的离线应用。但同时也可以用作站点缓存使用。它本身类似于一个介于浏览器和服务端之间的网络代理,可以拦截请求并操作响应内容。功能强大,但由于兼容性问题,更适合用作渐进增强来使用。

+ + +

一、前言

    +
  • Service Worker 是独立于当前页面的一段运行在浏览器后台进程里的脚本,它有自己独立的注册文件;它是 Web Worker 的一种,不能够直接操作 DOM;
  • +
  • 出于安全问题考虑,它只能在 HTTPS 域名下或者 localhost 本地运行;
  • +
  • 可以通过 postMessage 接口传递数据给其他 JS 文件;
  • +
  • Service Worker 中运行的代码不会被阻塞,也不会阻塞其他页面的 JS 文件中的代码;
  • +
  • 每个 Service Worker(注册文件)都有自己的作用域,它只会处理自己作用域下的请求,而 Service Worker 的存放位置就是它的最大作用域;
  • +
  • 缓存的资源存储在 Cache Storage 中,缓存不会过期,但是浏览器对每个网站的 Cache Storage 的大小有硬性限制,所以需要清理不必要的缓存;
  • +
+

二、Service Worker 的生命周期

    +
  1. 注册 Service worker,在网页上生效;
  2. +
  3. 安装成功,激活 或者 安装失败(下次加载会尝试重新安装);
  4. +
  5. 激活后,在 sw 的作用域下作用所有的页面,首次注册 sw 不会生效,下次加载页面才会生效;已经注册的 sw 不会重复注册;不会因为页面的关闭而被销毁;
  6. +
  7. sw 作用页面后,处理 fetch(网络请求)和 postMessage(页面消息)事件 或者 被终止(节省内存)。
  8. +
+ + +

三、Service Worker 安装注册

注册文件

1
2
3
4
5
6
7
8
9
10
// service worker 注册文件
if ('serviceWorker' in window.navigator) {
navigator.serviceWorker.register('./sw.js', { scope: './' })
.then(function (reg) {
console.log('success', reg);
})
.catch(function (err) {
console.log('fail', err);
});

+

register 方法接受两个参数,第一个是 service worker 文件的路径,第二个参数是 Serivce Worker 的配置项,可选填,其中比较重要的是 scope 属性。

+

拓展 Service Worker 作用域

scope的默认值为 ./(注意,这里所有的相对路径不是相对于页面,而是相对于sw.js脚本的),因此,navigator.serviceWorker.register('/static/home/js/sw.js')代码中的 scope 实际上是/static/home/js,Service Worker也就注册在了/static/home/js路径下,显然无法在/home下生效。

+

可以通过添加 Service-Worker-Allowed 响应头的方式来扩展 service worker 的作用域:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// express 扩展 service worker scope
app.use(serveStatic(`${sourceRoot}/home`, {
maxAge: 0,
setHeaders: function (res, path, stat) {
if (/\/sw\/.+\.js/.test(path)) {
res.set({
'Content-Type': 'application/javascript',
'Service-Worker-Allowed': `/${sourceRoot}//home`,
'Cache-control': 'no-store'
});
}
}
}));

+ +

打包工具生成静态资源注册文件

自己本地调试,可以一个个写进 Service Worker 的注册文件里调试;实际开发中可以借助 gulp / webpack 等打包工具等生成站点静态文件的 sw 注册文件;
以 gulp 为例,使用 sw-precache 插件生成注册文件:

+
1
2
3
4
5
6
7
8
gulp.task('generate-service-worker', function(callback) {

swPrecache.write('./service-worker.js', {
staticFileGlobs: ['./build/public' + '/**/*.{js,css,png,jpg,webp,gif,svg,eot,ttf,woff}'],
stripPrefix: './build'
}, callback);

});
+ +

四、Service Worker.js 注意事项

    +
  1. 不要给 service-worker.js 设置不同的名字
    实际开发过程中,为了避免静态资源缓存,通常的做法是在打包压缩静态资源的时候,在文件名后边加上 MD5 后缀,让浏览器认为这是一个新文件从而重新发起请求,但是这种做法在 service-worker.js 上是不可取的;
    第一种情况:如果缓存了 html 文件,service-worker.js 的文件因为是在 html 中引入的,所以更改 service-worker.js 的名字并不会更新。
    第二种情况:只缓存了css,js 文件,未缓存 html 文件;页面引入了新的 service-worker.js ,但是旧版本的 service-worker.js 还在使用中,会导致页面状态有问题。
  2. +
  3. 不要给 service-worker.js 设置缓存
    理由和第一点类似,也是为了防止在浏览器需要请求新版本的 sw 时,因为缓存的干扰而无法实现。毕竟我们不能要求用户去清除缓存。因此给 sw 及相关的 JS (例如 sw-register.js,如果独立出来的话)设置 Cache-control: no-store 是比较安全的。
  4. +
+

五、遇到的问题

    +
  1. 接收不到浏览器的fetch事件:
    原因:静态资源缓存:页面路径不能大于 Service worker 的 scope (详情)
  2. +
  3. public/* 无法匹配public路径下的所有文件, addCaches 时只能写fileName?
    原因:service worker 没有通配符 * 这个概念,/sw-test/ 这个 path 只是让 sw 寻找缓存时的一个入口,用以区分各个路径的缓存(详情);
    解决方案:service-worker.js 使用官方的 sw-precache 插件生成(详情);
  4. +
  5. 如果 service worker 缓存的了全部的js和img 会不会导致 cacheStorage 很占用用户的系统空间?
    不会,各个浏览器分配给各站点的 cacheStorrage 的值不一样,同时也受用户设备空间影响。
  6. +
+

落地情况

个人觉得 Service Worker 更适合在单页应用、文档类应用的等场景使用,才能把离线缓存的优势发挥出来。比如 Vue 的官网。



2019.4.23
未落地。主要原因有两点:

+
    +
  1. 工作中想要使用 Service worker 提供离线缓存服务的是一个负责 APP 内嵌页面的 H5 站点,HTML都是动态渲染的,活动数据是实时的,不能离线访问;
  2. +
  3. 这个站点的页面入口都是几乎都是单独的活动页,没有一个统一 sw 注册的入口;
  4. +
+
+*2020.3.16* +重新看这篇文章的时候,如果在几个主要的活动入口页引入 sw 的注册文件,那么这几个长期的活动就可以应用 sw 缓存了,但这并没有覆盖全站,所以依然不是好的解决方案。 + +

应用场景

这部分总结摘录自这篇文章:Service Worker 从入门到出门

+
    +
  • 网站功能趋于稳定:频繁迭代的网站似乎不方便加 Service Worker。
  • +
  • 网站需要拥有大量用户:管理后台、OA系统等场景似乎不是很有必要加 Service Worker。
  • +
  • 网站真的在追求用户体验:Bug 多多、脸不好看的网站似乎不是很有必要加 Service Worker。
  • +
  • 网站用户体验关乎用户留存:12306 似乎完全不需要加 Service Worker。
  • +
+

简单总结:Service Worker 的初衷是极致优化用户体验,是用来锦上添花的,技术只是技术,但实际应用前,应考虑成本和收益。

+

参考链接

+ +
+ + + + + + + +
+ + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2020/07/23/\343\200\220\350\257\221\343\200\221\344\273\216-ES2016-\345\210\260-ES2020-\347\232\204\346\211\200\346\234\211\347\211\271\346\200\247/index.html" "b/2020/07/23/\343\200\220\350\257\221\343\200\221\344\273\216-ES2016-\345\210\260-ES2020-\347\232\204\346\211\200\346\234\211\347\211\271\346\200\247/index.html" new file mode 100644 index 00000000..7555da6c --- /dev/null +++ "b/2020/07/23/\343\200\220\350\257\221\343\200\221\344\273\216-ES2016-\345\210\260-ES2020-\347\232\204\346\211\200\346\234\211\347\211\271\346\200\247/index.html" @@ -0,0 +1,654 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 【译】从 ES2016 到 ES2020 的所有特性 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 【译】从 ES2016 到 ES2020 的所有特性 +

+ + +
+ + + + +
+ + +

自 ECMA2015 (6th) 大幅更新之后, ECMA 标准变更成每年6月发布一个版本进行小幅度更新。为方便温习和查找,汇总一下近五年的所有版本特性。本文共涵盖了 ES2016、ES2017、ES2018、ES2019、ES2020 五个版本的更新内容。翻译有删改,仅供快速查找使用。

+ + +

前言:关于ECMA

ECMA 相关stage-x 处于某个阶段,描述的是 ECMA 标准相关的内容。根据提案划分界限,stage-x 大致分为以下阶段:

+
    +
  • stage-0:还是一个设想,只能由 TC39 成员或 TC39 贡献者提出。
  • +
  • stage-1:提案阶段,比较正式的提议,只能由 TC39 成员发起,这个提案要解决的问题必须有正式的书面描述。
  • +
  • stage-2:草案,有了初始规范,必须对功能语法和语义进行正式描述,包括一些实验性的实现。
  • +
  • stage-3:候选,该提议基本已经实现,需要等待实验验证,用户反馈及验收测试通过。
  • +
  • stage-4:已完成,必须通过 Test262 验收测试,下一步就纳入 ECMA 标准。
  • +
+

总结起来就是数字越大,越成熟。

+

ES2016 新特性

ES2016 只更新了两个特性:

+
    +
  • Array.prototype.includes()
  • +
  • 指数运算符
  • +
+

Array.prototype.includes()

该方法用于检测数组中是否包含某个值,包含则返回 true,否则返回 false。

+
1
2
3
4
5
6
let array = [1, 2, 4, 5];

array.includes(2);
// true
array.includes(3);
// false
+
+

结合 fromIndex 使用:

+
+

可以为 .includes() 提供一个起始索引,默认是 0,接受负数值。

+
1
2
3
4
5
6
7
8
9
10
11
12
let array = [ 1, 3, 5, 7, 9, 11 ];

array.includes(3, 1);
// find the number 3 starting from array index 1
// true
array.includes(5, 4);
//false
array.includes(1, -1);
// find the number 1 starting from the ending of the array going backwards
// false
array.includes(11, -3);
// true
+ +

指数操作符 (**)

在 ES2016 前我们会这样写:

+
1
2
3
4
Math.pow(2, 2);
// 4
Math.pow(2, 3);
// 8
+ +

现在,有了指数运算符之后,可以这样写:

+
1
2
3
4
2 ** 2;
// 4
2 ** 3;
// 8
+

这在多次操作指数运算的时候很有用:

+
1
2
3
4
2 ** 2 ** 2
// 16
Math.pow(Math.pow(2, 2), 2);
// 16
+

Math.pow() 需要连续调用,这会使代码看起来很长不宜阅读。使用指数运算符的方式更快更简洁。

+

ES2017 新特性

ES2017 介绍了更多新特性,如 String padding,Object.entries(), Object.values(), 原子性操作, 以及 Async、Await 等。

+

字符串填充 ( String.padStart() 和 String.padEnd() )

.padStart() 对字符串头部进行填充, .padEnd() 对字符串尾部进行填充:

+
1
2
3
4
"hello".padStart(6);
// " hello"
"hello".padEnd(6);
// "hello "
+

为什么只填充1个空格而不是6个?是因为 “hello” 一共是五个字符,而 .padStart.padEnd 的入参是填充后的字符串长度,所以之只会填充一个空格。

+
+

使用 padStart 实现文本右对齐

+
+
1
2
3
4
5
6
7
8
9
10
const strings = ["short", "medium length", "very long string"];

const longestString = strings.sort((s1, s2) => s2.length - s1.length).map(str => str.length)[0];

strings.forEach(str => console.log(str.padStart(longestString)));

// very long string
// medium length
// short

+

第一步获取了数组中最长字符串的长度,接下来用该长度填充数组中的每个字符串,即打印出一组右对齐的字符串。

+
+

自定义填充值

+
+

除了默认的空格,还可以使用字符串和数字进行填充。

+
1
2
3
4
5
6
"hello".padEnd(13," Alberto");
// "hello Alberto"
"1".padStart(3, 0);
// "001"
"99".padStart(3, 0);
// "099"
+ +

Object.entries() 和 Object.values()

首先创建一个Object:

+
1
2
3
4
5
const family = {
father: "Jonathan Kent",
mother: "Martha Kent",
son: "Clark Kent",
}
+

在上个版本的 javascript 中,我们可以使用如下方式获取 Object 中的值:

+
1
2
3
4
Object.keys(family);
// ["father", "mother", "son"]
family.father;
"Jonathan Kent"
+

Object.keys() 仅会返回对象中所有的键名。

+

现在又多了两种可以访问对象的方法:

+
1
2
3
4
5
6
7
Object.values(family);
// ["Jonathan Kent", "Martha Kent", "Clark Kent"]

Object.entries(family);
// ["father", "Jonathan Kent"]
// ["mother", "Martha Kent"]
// ["son", "Clark Kent"]
+

Object.values() 以数组形式返回对象所有值。
Object.entries() 同样以数组形式返回对象中的键值对。

+

Object.getOwnPropertyDescriptors()

这个方法会返回对象所有自身属性的描述。描述性的字段有:valuewritable, get, set, configurableenumerable

+
1
2
3
4
5
6
7
8
9
10
11
12
13
const myObj = {
name: "Alberto",
age: 25,
greet() {
console.log("hello");
},
}
Object.getOwnPropertyDescriptors(myObj);
// age: {value: 25, writable: true, enumerable: true, configurable: true}

// greet: {value: ƒ, writable: true, enumerable: true, configurable: true}

// name: {value: "Alberto", writable: true, enumerable: true, configurable: true}
+ +

尾行逗号

这仅仅是语法上的一个小改变。现在在写 Object 属性值时,我们可以在每个值后边加上一个逗号,不论它是否是最后一个。

+
1
2
3
4
5
6
7
8
9
10
11
// from this
const object = {
prop1: "prop",
prop2: "propop"
}

// to this
const object = {
prop1: "prop",
prop2: "propop",
}
+

注意上述第二个例子中的最后一个逗号,即使你不写它也不会报错,只是写上会更方便的开发者们协作。

+

共享内存和原子性操作

下述引自 MDN:

+
+

多个共享内存的线程能够同时读写同一位置上的数据。原子操作会确保正在读或写的数据的值是符合预期的,即下一个原子操作一定会在上一个原子操作结束后才会开始,其操作过程不会中断。

+
+

这些原子操作属于 Atomics 模块。与一般的全局对象不同,Atomics 不是构造函数,因此不能使用 new 操作符调用,也不能将其当作函数直接调用。Atomics 的所有属性和方法都是静态的(与 Math 对象一样)。

+

方法示例:

+
    +
  • add / sub
  • +
  • and / or / xor
  • +
  • load / store
  • +
+

Atomics 通常和 SharedArrayBuffer 对象(通用的固定长度二进制数据缓冲区)一起使用。
来看一下几个 Atomics方法的使用示例:

+
Atomics.add(), Atomics.sub(), Atomics.load(), and Atomics.store()

Atomics.add() 共接受三个参数:array、index、value。并返回该索引在执行操作前的值。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// create a `SharedArrayBuffer`
const buffer = new SharedArrayBuffer(16);
const uint8 = new Uint8Array(buffer);

// add a value at the first position
uint8[0] = 10;

console.log(Atomics.add(uint8, 0, 5));
// 10

// 10 + 5 = 15
console.log(uint8[0])
// 15
console.log(Atomics.load(uint8, 0));
// 15
+ +

要从数组中检索特定的值,可以使用 Atomics.load() 并传递两个参数,一个数组和一个索引。
Atomics.sub() 的使用方式与 Atomics.add() 类似,只不过它是减去某个值。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// create a `SharedArrayBuffer`
const buffer = new SharedArrayBuffer(16);
const uint8 = new Uint8Array(buffer);

// add a value at the first position
uint8[0] = 10;

console.log(Atomics.sub(uint8, 0, 5));
// 10

// 10 - 5 = 5
console.log(uint8[0])
// 5
console.log(Atomics.store(uint8, 0, 3));
// 3
console.log(Atomics.load(uint8, 0));
// 3
+

上述示例调用 Atomics.sub() 方法,实现 unit8[0] - 5 ,相当于 10 - 5。如同 Atomics.add() 一样,该方法也会返回数组中该索引在执行操作前的值。

+

使用 Atomics.store() 来存储一个值,使用 Atomics.load() 来加载一个值。

+
Atomics.and(), Atomics.or(), Atomics.xor()

这三个方法都在数组的给定位置执行按位的 AND、OR 和 XOR 操作。不再赘述。

+

Async 和 Await

ES2017 提供了两个操作 Promise 的新方法:”async/await”。

+
回顾一下 Promise

在介绍新语法之前,让我们快速浏览下之前我们是怎么使用 Promise 的:

+
1
2
3
4
5
6
7
8
9
10
11
// fetch a user from github
fetch('api.github.com/user/AlbertoMontalesi').then( res => {
// return the data in json format
return res.json();
}).then(res => {
// if everything went well, print the data
console.log(res);
}).catch( err => {
// or print the error
console.log(err);
})
+

上述是一个非常简单的例子:请求一个 Github 用户的数据,并打印。下面来看个复杂点的:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function walk(amount) {
return new Promise((resolve, reject) => {
if (amount < 500) {
reject ("the value is too small");
}
setTimeout(() => resolve(`you walked for ${amount}ms`),amount);
});
}

walk(1000).then(res => {
console.log(res);
return walk(500);
}).then(res => {
console.log(res);
return walk(700);
}).then(res => {
console.log(res);
return walk(800);
}).then(res => {
console.log(res);
return walk(100);
}).then(res => {
console.log(res);
return walk(400);
}).then(res => {
console.log(res);
return walk(600);
});

// you walked for 1000ms
// you walked for 500ms
// you walked for 700ms
// you walked for 800ms
// uncaught exception: the value is too small
+

来看下,如何用新语法 async / await 来重写 Promise

+
Async 和 Await
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function walk(amount) {
return new Promise((resolve, reject) => {
if (amount < 500) {
reject ("the value is too small");
}
setTimeout(() => resolve(`you walked for ${amount}ms`),amount);
});
}

// create an async function
async function go() {
// use the keyword `await` to wait for the response
const res = await walk(500);
console.log(res);
const res2 = await walk(900);
console.log(res2);
const res3 = await walk(600);
console.log(res3);
const res4 = await walk(700);
console.log(res4);
const res5 = await walk(400);
console.log(res5);
console.log("finished");
}

go();

// you walked for 500ms
// you walked for 900ms
// you walked for 600ms
// you walked for 700ms
// uncaught exception: the value is too small
+

让我们来分解一下上述代码都做了什么:

+
    +
  • 创建一个异步函数需要在 function 前面添加 async 关键词
  • +
  • 这个关键词会告诉 Javascript 返回一个 Promise
  • +
  • 如果指定 async 函数返回一个非 Promise 的值,那么这个值将会被包含在 Promise 中然后被返回
  • +
  • 顾名思义, await 会告诉 Javascript 等待 promise 返回结果
  • +
+
错误处理

通常在 promise 中,我们使用 .catch() 捕获最终的错误。现在有一点不同了:

+
1
2
3
4
5
6
7
8
9
10
11
async function asyncFunc() {

try {
let response = await fetch('http:your-url');
} catch (err) {
console.log(err);
}
}

asyncFunc();
// TypeError: failed to fetch
+ +

ES2018 新特性

对象扩展运算符

还记得 ES6 中我们可以使用扩展运算符来做什么吗:

+
1
2
3
4
5
6
const veggie = ["tomato", "cucumber", "beans"];
const meat = ["pork", "beef", "chicken"];

const menu = [...veggie, "pasta", ...meat];
console.log(menu);
// Array [ "tomato", "cucumber", "beans", "pasta", "pork", "beef", "chicken" ]
+

现在,扩展运算符同样适用于对象:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let myObj = {
a: 1,
b: 3,
c: 5,
d: 8,
}

// we use the rest operator to grab everything else left in the object.
let { a, b, ...z } = myObj;
console.log(a); // 1
console.log(b); // 3
console.log(z); // {c: 5, d: 8}

// using the spread syntax we cloned our Object
let clone = { ...myObj };
console.log(clone);
// {a: 1, b: 3, c: 5, d: 8}
myObj.e = 15;
console.log(clone)
// {a: 1, b: 3, c: 5, d: 8}
console.log(myObj)
// {a: 1, b: 3, c: 5, d: 8, e: 15}
+

使用扩展运算符,我们可以轻松的复制对象(浅复制)。

+

异步的迭代

使用异步的迭代,我们可以异步的遍历数据。
引自文档

+
+

异步迭代器很像迭代器,只不过迭代器的 next 方法返回一对 { value, done }

+
+

为此,我们将使用一个 for-await-of 循环,它将迭代转换成 Promise。

+
1
2
3
4
5
6
7
8
9
10
11
12
const iterables = [1, 2, 3];

async function test() {
for await (const value of iterables) {
console.log(value);
}
}

test();
// 1
// 2
// 3
+

在执行过程中,[Symbol.asyncIterator]() 方法将会创造一个异步的迭代器,每次访问序列中的下一个值时,我们都会隐式地等待迭代器方法返回 Promise。

+

Promise.prototype.finally()

引自 MDN:

+
+

finally() 方法返回一个 Promise。在 Promise 结束时,无论结果是 fulfilled 或者是 rejected,都会执行指定的回调函数。这为在 Promise 是否成功完成后都需要执行的代码提供了一种方式。避免了同样的语句需要在 then() 和 catch() 中各写一次的情况。

+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const myPromise = new Promise((resolve, reject) => {
resolve();
});

myPromise
.then(() => {
console.log('still working');
})
.catch(() => {
console.log('there was an error');
})
.finally(()=> {
console.log('Done!');
})
+

.finally() 同样会返回一个 promise,所以我们可以继续链式调用 thencatch 方法,但是它们是基于之前的 promise 进行调用的。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const myPromise = new Promise((resolve, reject) => {
resolve();
});

myPromise
.then(() => {
console.log('still working');
return 'still working';
})
.finally(()=> {
console.log('Done!');
return 'Done!';
})
.then(res => {
console.log(res);
})
// still working
// Done!
// still working
+

从上边代码可以看到 finally 后边的 then 返回的值是由第一个 then 创建的,而不是 finally

+

正则表达式的新特性

在新版的 ECMA 中,共更新了 4 个关于正则的特性。

+
    +
  • 正则表达式的 s (doAll) 标志
  • +
  • 正则表达式捕获组命名
  • +
  • 正则表达式反向断言 (Lookbehind Assertions)
  • +
  • unicode 字符转义 (Unicode property escapes)
  • +
+
s (doAll) 标志
+

引自MDN

+
+

dotAll 属性表明是否在正则表达式中一起使用 “s“ 修饰符(引入 /s 修饰符,使得.可以匹配任意单个字符,包括换行符和回车符)

+
1
2
/foo.bar/s.test('foo\nbar');
// true
+ +
捕获组命名
+

想要引用正则匹配到的某一部分字符串可以为捕获组编号。每个捕获组的数字都是唯一的,可以对应的数字引用它们,但是这使正则表达式难以阅读和维护。例如 /(\d{4})-(\d{2})-(\d{2})/ 匹配一个日期,但如果不看上下文的代码,就无法确定哪一组对应于月份,哪一组是一天。当然,如果哪一天需要交换日期和月份的顺序,那么对应的组引用也需要更新。现在,可以使用 (?<name>...) 来为捕获组命名,以表示任何标识符名称。重写上述例子:/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u 每一个命名都是唯一且遵循 ECMA 命名规范的。命名的组可以通过匹配结果的 result 属性来访问。对组的数字引用也会被建立,就像未命名的组一样。看下边几个例子:

+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
let result = re.exec('2015-01-02');
// result.groups.year === '2015';
// result.groups.month === '01';
// result.groups.day === '02';

// result[0] === '2015-01-02';
// result[1] === '2015';
// result[2] === '01';
// result[3] === '02';

let { groups: { one, two } } = /^(?<one>.*):(?<two>.*)$/u.exec('foo:bar');
console.log(`one: ${one}, two: ${two}`);
// one: foo, two: bar
+ +
反向断言
+

使用反向断言可以确保匹配之前或者之后没有其他匹配。反向断言的语法表示为 (?<=...)
例如:匹配一个美元数值且不包含美元符号可以这样写 /(?<=$)\d+(\.\d*)?/,这个表达式会匹配 $10.53 并返回 10.53,而并不会匹配 €10.53。而 (?<!...) 匹配的规则正相反,它会匹配不存在表达式中的匹配项,例如 /(?<!$)\d+(?:\.\d*)/ 不会匹配 $10.53,但是会匹配 €10.53

+
+
Unicode 字符转义
+

Unicode 字符转义是一种新的转义序列,u 作为字符转义的标志, \p{...}\P{...} 用来添加转义符。有了这个特性,匹配 Unicode 字符可以这样写:

+
+
1
2
3
const regexGreekSymbol = /\p{Script=Greek}/u;
regexGreekSymbol.test('π');
// true
+ +

解除模板字符限制

当使用 Tagged 模板字符串时,转义字符的限制被移除了(阅读更多

+

ES2019 新特性

Array.prototype.flat() / Array.prototype.flatMap()

Array.prototype.flat() 会递归地展平一个数组并作为新值返回,它接受一个表示递归深度的值,未传值则默认深度为1。可以用 Infinity 去展平所有嵌套的数组。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const letters = ['a', 'b', ['c', 'd', ['e', 'f']]];
// default depth of 1
letters.flat();
// ['a', 'b', 'c', 'd', ['e', 'f']]

// depth of 2
letters.flat(2);
// ['a', 'b', 'c', 'd', 'e', 'f']

// which is the same as executing flat with depth of 1 twice
letters.flat().flat();
// ['a', 'b', 'c', 'd', 'e', 'f']

// Flattens recursively until the array contains no nested arrays
letters.flat(Infinity)
// ['a', 'b', 'c', 'd', 'e', 'f']
+ +

Array.prototype.flatMap() 与深度值为1的 flat 几乎相同,但它并非仅仅展平数组。 flatMap 接收一个处理函数,使用 flatMap() 可以在展平的同时更改对应的值并返回一个新的数组。

+
1
2
3
let greeting = ["Greetings from", " ", "Vietnam"];
greeting.flatMap(x => x.split(" "))
// ["Greetings", "from", "", "", "Vietnam"]
+

这有点类似于 map() 方法,只不过多了一次展平操作。

+

Object.fromEntries()

Object.fromEntries() 将一组键值对转换成对象。

+
1
2
3
4
5
6
7
const keyValueArray = [
['key1', 'value1'],
['key2', 'value2']
]

const obj = Object.fromEntries(keyValueArray)
// {key1: "value1", key2: "value2"}
+

我们可以将任何可迭代的值作为 Object.entries() 方法的参数,不论它是一个 Array 还是 Map,或是其他实现了迭代协议的值。

+

注:可迭代协议( Iteration Protocols )是 ES2015 提出的,通常通过常量 Symbol.iterator 访问该对象的可迭代属性。

+
1
2
3
4
5
6
7
8
var someString = "hi";
typeof someString[Symbol.iterator]; // "function"
var iterator = someString[Symbol.iterator]();

iterator + ""; // "[object String Iterator]"
iterator.next() // { value: "h", done: false }
iterator.next(); // { value: "i", done: false }
iterator.next(); // { value: undefined, done: true }
+

阅读更多关于迭代协议的内容

+

String.prototype.trimStart() / .trimEnd()

String.prototype.trimStart() 移除字符串前面的空白符,String.prototype.trimEnd() 移除字符串后面的空白符。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let str = "    this string has a lot of whitespace   ";

str.length;
// 42

str = str.trimStart();
// "this string has a lot of whitespace "
str.length;
// 38

str = str.trimEnd();
// "this string has a lot of whitespace"
str.length;
// 35
+

也可以使用 .trimStart()trimEnd() 的别名: .trimLeft().trimRight()

+

可选的 catch 捕获参数

在 ES2019 之前,你必须为 catch 捕获传递一个表示异常的变量,现在这个变量不是必要的了。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
// Before
try {
...
} catch(error) {
...
}

// ES2019
try {
...
} catch {
...
}
+

这在你想忽略错误参数的时候很有用。

+

Function.ptototype.toString()

.toString() 方法返回一个代表函数源码的字符串。

+
1
2
3
4
5
6
7
8
function sum(a, b) {
return a + b;
}

console.log(sum.toString());
// function sum(a, b) {
// return a + b;
// }
+ +

注释也会被包含其中:

+
1
2
3
4
5
6
7
8
9
10
function sum(a, b) {
// perform a sum
return a + b;
}

console.log(sum.toString());
// function sum(a, b) {
// // perform a sum
// return a + b;
// }
+ +

Symbol.prototype.description

.description 返回 Symbol 对象可选描述的字符串。

+
1
2
3
4
5
6
const me = Symbol("Alberto");
me.description;
// "Alberto"

me.toString()
// "Symbol(Alberto)"
+ +

ES2020 特性

BigInt 类型

BigInt 是 JavaScript 第七个原始类型,它允许开发者操作非常大的整型。
数字类型可以处理 2 ** 53 - 19007199254740991 以内的数。可以通过常量 MAX_SAFE_INTEGER 来访问这个值。

+
1
Number.MAX_SAFE_INTEGER; // 9007199254740991
+

顾名思义,若操作的 number 值超过最大值时,运行结果就会变的奇怪。使用 BigInt 类型则没有明确的界限,因为它的界限取决于运行设备的内存。
定义 BigInt 类型,你即可以通过给 BigInt() 构造函数传递一个字符串值来创建,也可以像平常一样使用字面量语法来创建,但是要在尾部加上一个字符 n

+
1
2
3
4
const myBigInt = BigInt("999999999999999999999999999999");
const mySecondBigInt = 999999999999999999999999999999n;

typeof myBigInt; // "bigint"
+

注意,BigInt 类型与常规类型的数字并不是完全兼容的,这意味这你确定最好仅在操作比较大的数据时使用它。

+
1
2
3
4
5
6
7
const bigInt = 1n; // small number, but still of BigInt type
const num = 1;

num === bigInt; // false -> they aren't strictly equal
num == bigInt; // true
num >= bigInt; // true -> they can be compared
num + bigInt; // error -> they can't operate with one another
+

总之,使用 JS 做比较复杂的数学运算时 BigInt 是个不错的选择。它在替换专门用于处理大量数字的库方面表现良好。现在至少在整型方向有所进展,而目前我们对 BigDecimal 的提案了解的还很少。

+

动态导入(Dynamic imports)

动态导入,允许在浏览器端动态地加载代码模块。使用 import() 语法来导入你的代码块。

+
1
2
3
4
5
6
7
8
import("module.js").then((module) => {
// ...
});

// or
async () => {
const module = await import("module.js");
};
+

import() 返回一个 promise,resolve 中会返回代码模块加载后的内容。可以使用 ES6 的 .then() 方法或者 async/await 来处理加载结果。

+

空值合并操作符(??)

空值合并操作符(??)是一个新的 JS 运算符,当所访问的值是 null 或者 undefined 时,它会提供一个默认值。

+
1
2
3
4
5
const basicValue = "test";
const nullishValue = null;

const firstExample = basicValue ?? "example"; // "test"
const secondExample = nullishValue ?? "example"; // "example"
+

但是这跟 逻辑或(||)有什么区别呢?当第一个数是虚值 (在 Boolean 上下文中认定为 false 的值),如 false, 0, 或者"",以及空值 nullundefined,那么 逻辑或 将会使用第二个操作数。而空值合并操作符仅仅是在第一个值为空值而不是虚值的时候才会使用第二个操作数。如果你的代码可以接受除了 nullundefined 以外的任何值,那么空值合并操作符就是最佳选择。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const falseValue = false;
const zeroValue = 0;
const emptyValue = "";
const nullishValue = null;

const firstExampleOR = falseValue || "example"; // "example"
const secondExampleOR = zeroValue || "example"; // "example"
const thirdExampleOR = emptyValue || "example"; // "example"
const forthExampleOR = nullish || "example"; // "example"

const firstExample = falseValue ?? "example"; // false
const secondExample = zeroValue ?? "example"; // 0
const thirdExample = emptyValue ?? "example"; // ""
const forthExample = nullish ?? "example"; // "example"
+

可选链(?.)

与空值合并操作符类似,只不过可选链是处理 Object 中 nullundefined 的。鉴于直接从空值中国获取属性值会报错,现在可选链会直接将空值返回。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
const obj = {
prop: {
subProp: {
value: 1,
},
},
};

obj.prop.subProp.value; // 1
obj.prop.secondSubProp.value; // error

obj?.prop?.subProp?.value; // 1
obj?.prop?.secondSubProp?.value; // undefined
+

当然,这只是一个语法糖,但也是一个很受欢迎的补充。记住不要在代码里到处使用这些操作符,他们虽然用起来方便,但从性能角度来说,它比普通的 . 开销要大。而且,若是代码是经过 Babel 和 TypeScript 转义的,则更要谨慎使用。

+

GlobalThis

由于 JavaScript 的代码可以运行在多个不同的环境,例如 浏览器、Node.js、Web Worker 等,要实现这种交叉兼容性绝非易事,globalThis 的出现方便了这些操作。
globalThis 是一个新的全局属性,通常它引用的是当前环境下的全局对象。就像是 self 对于 Web Workers,window 对于浏览器,global 对于 Node.js,以及其他实现了ES2020标准的运行环境。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Hacky globalThis polyfill you had to use pre-ES2020
const getGlobal = () => {
if (typeof self !== "undefined") {
return self;
}
if (typeof window !== "undefined") {
return window;
}
if (typeof global !== "undefined") {
return global;
}
throw new Error("Couldn't detect global");
};

getGlobal() === globalThis; // true (for browser, Web Worker and Node.js)
globalThis === window; // true (if you're in browser)
+ +

Promise.allSettled()

这个新增的方法看起来有点像 Promise.all()
Promise.all() 的参数中的 promise 若有一个失败,则此实例回调失败。而 Promise.allSettled()不论成功或者失败,都会返回处理结束后的对象数组。

+

String.matchAll()

如果你之前使用正则,那么相比于在 while 循环中使用 RegExp.exec() 并开启标志 g 来匹配,String.matchAll() 会是更好的选择。它会返回一个包含了所有匹配结果的数组,包括捕获组的匹配结果。

+
1
2
3
4
5
6
const regexp = /t(e)(st(\d?))/g;
const str = "test1test2";
const resultsArr = [...str.matchAll(regexp)]; // convert iterator to an array

resultsArr[0]; // ["test1", "e", "st1", "1"]
resultsArr[1]; // ["test2", "e", "st2", "2"]
+ +

原文链接

+ +
+ + + + + + + +
+ + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2020/12/10/\343\200\220\350\257\221\343\200\221Can-NodeJS-use-ES6-import-syntax/index.html" "b/2020/12/10/\343\200\220\350\257\221\343\200\221Can-NodeJS-use-ES6-import-syntax/index.html" new file mode 100644 index 00000000..62b37c58 --- /dev/null +++ "b/2020/12/10/\343\200\220\350\257\221\343\200\221Can-NodeJS-use-ES6-import-syntax/index.html" @@ -0,0 +1,565 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 【译】Can NodeJS use ES6 import syntax ? | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 【译】Can NodeJS use ES6 import syntax ? +

+ + +
+ + + + +
+ + +

偶然在一篇文章中看到 Node 可以使用 import 语法了,无需再使用 babel 做额外的转换,遂去了解下 Node 相关的更新。本文主要介绍在最新版本 Node(14.15.1) 中如何使用 import 语法。大部分内容翻译自官网和外网文章。关于 JS 模块机制之前已经总结过一篇文章,这里不再赘述。

+

( PS:原本是公司部门要求的 kpi 文章,现做了精简 )

+ + +

概览

本文主要内容:

+
    +
  • Node 对 ES Modules 的支持
  • +
  • 在 Node 使用 import 语法
  • +
  • Node 中 ES Modules 的现状和未来
  • +
+

Node 对 ES Modules 支持

Node 13.2.0 开始正式支持 ES Modules 特性(移除了 –experimental-modules 启动参数).

+

注意:相关的 ESM 的实验性标志都虽然被移除
(但是由于 ESM loader 还是实验性的,所以运行 ES Modules 代码依然会有警告:

+
1
(node:47324) ExperimentalWarning: The ESM module loader is experimental.
+ +

在 NodeJS 中使用 ES Modules

使 Node 支持 ES modules 有两种方式:

+
    +
  1. 在 package.json中,增加 type: "module"配置,即可在 node 代码中使用 importexport语法:
  2. +
+

文件目录结构:

+
1
2
3
4
5
.
├── index.js
├── package.json
└── utils
└── speak.js
+ +
1
2
3
4
5
6
7
8
// utils/speak.js
export function speak() {
console.log('Come from speak.')
}

// index.js
import { speak } from './utils/speak.js';
speak(); //come from speak
+ +
    +
  1. 在 .mjs 文件中直接使用 importexport
  2. +
+

文件目录结构:

+
1
2
3
4
5
.
├── index.mjs
├── package.json
└── utils
└── sing.mjs
+ +
1
2
3
4
5
6
7
8
// utils/sing.mjs
export function sing() {
console.log('Come from sing')
}

// index.mjs
import { sing } from './utils/sing.mjs';
sing(); //come from sing
+ +

注意:

+
    +
  • 若不添加上述两项中任一项,直接使用在 Node 中使用 ES modules,则会抛出警告:
    1
    Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
  • +
  • 根据ESM规范,使用import关键字并不会像 CommonJS 模块那样,在默认情况下以文件扩展名完成文件路径。因此,ES Modules 必须明确文件扩展名。
  • +
+

模块作用域

一个模块的作用域,由父级中有 type: "module" 的 package.json 文件路径定义。而使用.mjs扩展文件加载模块,则不受限于包的作用域。
同理,package.json中没有type标志的包都会默认采用 CommonJS 模块机制,.cjs类型的扩展文件使用 CommonJS 方式加载模块同样不受限于包的作用域。

+

包的入口

定义包的入口有两种方式,在 package.json 中定义main字段或者exports字段

+
1
2
3
4
{
"main": "./main.js",
"exports": "./main.js"
}
+

需要注意的是,当exports字段被定义后,包的所有子路径都将被封装,子路径的文件不可再被导入。例如 require(‘pkg/subpath.js’) 将会报错:

+
1
ERR_PACKAGE_PATH_NOT_EXPORTED error.
+

参考官方文档:https://nodejs.org/api/packages.html#packages_main_entry_point_export

+

两个模块机制在执行时机上的区别

    +
  • ES Modules 导入的模块会被预解析,以便在代码运行前导入:
      +
    • 根据 EMS 规范 import / export 必须位于模块顶级,不能位于作用域内;
    • +
    • 模块内的 import/export 会提升到模块顶部;
    • +
    +
  • +
  • 在 CommonJS 中,模块将在运行时解析;
  • +
+

举一个简单的例子来直观的对比下二者的差别:

+
1
2
3
4
5
6
7
8
9
10
// ES Modules

// a.js
console.log('Come from a.js.');
import { hello } from './b.js';
console.log(hello);

// b.js
console.log('Come from b.js.');
export const hello = 'Hello from b.js';
+

输出:

+
1
2
3
Come from b.js.
Come from a.js.
Hello from b.js
+ +

同样的代码使用 CommonJS 机制:

+
1
2
3
4
5
6
7
8
9
10
// CommonJS

// a.js
console.log('Come from a.js.');
const hello = require('./b.js');
console.log(hello);

// b.js
console.log('Come from b.js.');
module.exports = 'Hello from b.js';
+

输出:

+
1
2
3
Come from a.js.
Come from b.js.
Hello from b.js
+

可以看到 ES Modules 预先解析了模块代码,而 CommonJS 是代码运行的时候解析的。

+

两个模块在原理上的区别

    +
  1. CommonJS
  2. +
+

Node 将每个文件都视为独立的模块,它定义了一个 Module 构造函数,它代表模块自身:

+
1
2
3
4
5
6
7
8
9
function Module(id = '', parent) {
this.id = id;
this.path = path.dirname(id);
this.exports = {};
this.parent = parent;
this.filename = null;
this.loaded = false;
this.children = [];
};
+ +

而 require 函数接收一个代表模块ID或者路径的值作为参数,它返回的是用module.exports导出的对象。在执行代码模块之前,NodeJs 将使一 个包装器对模块中的代码其进行封装:

+
1
2
3
(function(exports, require, module, __filename, __dirname) { 
// Module code actually lives in here
});
+ +
+

引自 NodeJS 官网

+

通过这样做,Node.js 实现了以下几点:

+
    +
  • 它保持了顶层的变量(用 var、 const 或 let 定义)作用在模块范围内,而不是全局对象。
  • +
  • 它有助于提供一些看似全局的但实际上是模块特定的变量,例如:
      +
    • 实现者可以用于从模块中导出值的 module 和 exports 对象。
    • +
    • 包含模块绝对文件名和目录路径的快捷变量 __filename 和 __dirname 。
    • +
    +
  • +
+
+

简言之,每个模块都有自己的函数包装器, Node 通过此种方式确保模块内的代码对它是私有的。在包装器执行之前,模块内的导出内容是不确定的。
除此之外,第一次加载的模块会被缓存到 Module._cache中。一个完整的加载周期大致如下:

+
1
Resolution (解析) –> Loading (加载) –> Wrapping (私有化) –> Evaluation (执行) –> Caching (缓存)
+ +
    +
  1. ES Modules
  2. +
+

在 ESM 中,import 语句用于在解析代码时导入模块依赖的静态链接。文件的依赖关系在编译阶段就确定了。对于 ESM,模块的加载大致分为三步:

+
1
Construction (解析) -> Instantiation (实例化、建立链接) -> Evaluation (执行)
+

这些步骤是异步执行的,每一步都可以看作是相互独立的。这一点跟 CommonJS 有很大不同,对于 CommonJS 来说,每一步都是同步进行的。

+

两种模块间的相互引用

CommonJS 和 ES Modules 都支持 Dynamic import()。它可以支持两种模块机制的导入:

+

在 CommonJS 文件中导入 ES Modules 模块

由于 ES Modules 的加载、解析和执行都是异步的,而 require() 的过程是同步的、所以不能通过 require() 来引用一个 ES6 模块。ES6 提议的 import() 函数将会返回一个 Promise,它在 ES Modules 加载后标记完成。借助于此,我们可以在 CommonJS 中使用异步的方式导入 ES Modules:

+
1
2
3
4
5
6
// 使用 then() 来进行模块导入后的操作
import("es6-modules.mjs").then((module)=>{/*…*/}).catch((err)=>{/**…*/})
// 或者使用 async 函数
(async () => {
await import('./es6-modules.mjs');
})();
+ +

在 ES Modules 文件中导入 CommonJS 模块

在 ES6 模块里可以很方便地使用 import 来引用一个 CommonJS 模块,因为在 ES6 模块里异步加载并非是必须的:

+
1
2
3
4
5
6
7
8
import { default as cjs } from 'cjs';

// The following import statement is "syntax sugar" (equivalent but sweeter)
// for `{ default as cjsSugar }` in the above import statement:
import cjsSugar from 'cjs';

console.log(cjs);
console.log(cjs === cjsSugar);
+ +

Node 中 ES Modules 的现状和未来

在引入 ES6 标准之前,服务器端 JavaScript 代码都是依赖 CommonJS 模块机制进行包管理的。
如今,随着 ES Modules 的引入,开发人员可以享受到与发布规范相关的许多好处。但需要注意的是,截止至当前时间(2020.11.30),在最新版 Node v15.1.0 中,该特性依然是实验性的(Stability: 1),不建议在生产环境中使用该功能。

+

最后,由于两种模块格式之间存在不兼容问题,将当前项目从 CommonJS 到 ES Modules 转换将是一个很大的挑战。可以借助 Babel 相关插件实现 CommonJS 和 ES Modules 间的相互转换:

+ +

参考链接

翻译原文

+

官方文档

+ +
+ + + + + + + +
+ + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2020/12/31/2020\345\271\264\347\273\210\346\200\273\347\273\223/index.html" "b/2020/12/31/2020\345\271\264\347\273\210\346\200\273\347\273\223/index.html" new file mode 100644 index 00000000..c7d3dd93 --- /dev/null +++ "b/2020/12/31/2020\345\271\264\347\273\210\346\200\273\347\273\223/index.html" @@ -0,0 +1,472 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2020年终总结 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 2020年终总结 +

+ + +
+ + + + +
+ + +

2020年终总结(✖️)
2020年流水账(✔️)

+ + +
    +
  • 1.22 回家过年
  • +
  • 2.9 疫情 家里东拼西凑最好的口罩都给我拿到了北京,高铁上人很少,人们不怎么说话也不怎么吃东西
  • +
  • 3.20 被裁 P2P 行业终没能熬过政策寒冬,和之前的同事们由于疫情的原因最后也没能聚上一餐
  • +
  • 4.2 新公司 入职某二手平台,开启9点后下班的生活
  • +
  • 4.4 搬家 不搬家来公司要一个半小时
  • +
  • 5.1 购入Switch 笔记本拿去维修了,于是买了心仪已久的 Switch 和塞尔达来消磨时光
  • +
  • 8.18 同学婚礼 去了满洲里见证大学室友的婚礼,两三年不见,大家变化都很大,仿佛只有我还在原地
  • +
  • 9.27 购入Mac Pro 赶着最后一波教育优惠,买了自己的 MacBook,顺带入了 Air Pods
  • +
  • 10.15 搬家 又换房子了,房东不租了给了一个月搬家时间,自如赔付了一个月的房租
  • +
  • 10.29 转正 改了六七版述职PPT,终于成为公司正式的一员了,有时会不禁会想,如果时间能回到以前就好了
  • +
  • 11.1 欢乐谷 搭上万圣节的尾巴和公司的小伙伴们去了欢乐谷,各种失重旋转高速俯冲的过山车超级好玩,可是一起同行的小伙伴却说啥也不来第二次了
  • +
  • 12.8 取消成都之旅 一场说走就走的成都之旅最终没能逃离疫情的影响,周五买的元旦机票也只能匆匆退掉
  • +
  • 12.28 深圳&优化 一起吃饭的小伙伴有一位还在试用期,被”调“去了深圳,31号就是在公司的最后一天,有点不舍
  • +
+

工作

这一年,最大的变化就是换公司了吧。

新公司 leader 人挺好,每月都会面谈与我们沟通,只是我每次都不知道说什么,只是每次的问题都没有什么变化。
新公司的同事们都很年轻,也很 nice,虽然我依旧不是很健谈,饭桌上也总是沉默。

+

技术

+
    +
  • 之前没接触过小程序,没用过 TS,也没用过 React,今年在工作中都浅浅的用到了;
  • +
  • 公司内很多内部封装好的组件库,用起来很方便,也有专门负责维护的同学;
  • +
  • 内部技术分享很丰富,但自己的消化速度没想象那么强;
  • +
+

作息
疫情期间找工作,虽然已经做好了就算新公司是 996 也无所谓的准备,但真的接触这种作息才发现,如果可以我还是希望有自己的时间;

+

购入 Mac

在公司配备的 Windows 上设计稿总是展示不完全,要么看不清阴影,要么找不到虚线,被设计谈了两三次之后,索性就买了。
刚开始不是很适应新版 Mac 的键盘手感,后来敲着敲着也无所谓了。
Air Pods 着实很惊艳,开启降噪模式,专心程度 up~ up~。

+

神奇的小程序(uniapp + ts)

首次接触小程序还是在实习的时候,在学校的一个课题中用过一点,当时也没觉得什么。但是新公司小程序体积很大,改一行代码,编译要好久好久…先要等 uniapp 编译完,然后要等小程序开发者工具编译,这两段编译时间足够我接杯水顺便去上个厕所;
6、7月份的时候想要尝试过去优化这个编译速度,却发现无从下手,小程序项目的编译工具是 uniapp 自己的,开发者工具的编译也无法介入,最后组内同事给出一个方案,就是注释掉开发中不需要的路由,然而速度依然差强人意;
买了 Mac 之后,以为多少速度能快点,神奇的是,不仅没快,全量编译小程序一不小心就能把 Mac 搞死机,看着黑屏的电脑,我不仅发出感叹:”这,就是小程序的力量吗。“

+

生活

无法拉上拉链的伴娘服。

刚收到大学室友结婚消息的时候,还是挺震惊的,我们还一起相聚的夜晚仿佛还在昨天,现在算算,我都已经工作两年半了,毕业一别就我们就没再见过面。
室友是辽宁人,是我们正儿八经的东北妹砸,远嫁到了内蒙古,很佩服她的勇气,毕竟亲人和好友都不在那边,而且是一个全新的生活环境。
也是靠着这次机会,和久别的同学见了一面。感觉大家都变了,大部分已经褪去了学生的稚气,仿佛只有我,也只有我,行为处事依旧像个没长大的学生。
草原很美,一望无垠,很羡慕这里的生活节奏,草原上的牛羊偶尔成团,偶尔散开,都是低着头各吃各的草。
坐了两个小时的车,从满洲里到新左旗。从乌云密布下着雨的草原一边,行驶到只有落日和霞光的另一边,如此美景,想发朋友圈却没信号。
见了新娘,感叹时光飞逝,试了伴娘服,感叹体重为什么不飞逝;加之参加婚礼之前,去剪了剪头发,婚礼当天的我就像是个胖胖的人妖,丢人到是没有,辣眼倒是会有一点。
要走了,拥抱一下,一转身两行热泪就下来了:”为什么哭了?“,”因为明天还要上班…“。

+

身体

虽然逢人便说”我还年轻“,虽然别人也常说”你还年轻“,但身体仿佛已经不太认同年轻这两个字了。
越来越重的眼袋和越来越僵硬的后背,连尾椎的疼痛也变得越来越难以忍受。
除此之外,体重也以肉眼可见的速度上涨,如此说来,今年自己都没有做过几顿饭,全靠外卖过活。
再这样下去,怕是要早早步入 ICU 了。

+

这一年

这一年,即使有千百种不适应,也要有千百种方式去适应。

+

絮叨絮叨,一年比一年能叨叨,年终总结写的像越来越像流水账。挺丧的一年,年底之前还是没能去海拉鲁城堡看公主一眼,也没能跟帮助过的我人道声谢,崩坏的查莉娅线稿依旧没能画完…

+

对 2021 没有特别想立的 flag,希望做一些正确的事吧。

+

PS: 不要听民谣写年终总结, (╯°□°)╯( ┻━┻ 越写越丧。

+ +
+ + + + + + + +
+ + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2021/03/22/\345\276\256\344\277\241\345\260\217\347\250\213\345\272\217\344\273\2160\345\210\2601/index.html" "b/2021/03/22/\345\276\256\344\277\241\345\260\217\347\250\213\345\272\217\344\273\2160\345\210\2601/index.html" new file mode 100644 index 00000000..9691aed1 --- /dev/null +++ "b/2021/03/22/\345\276\256\344\277\241\345\260\217\347\250\213\345\272\217\344\273\2160\345\210\2601/index.html" @@ -0,0 +1,685 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 微信小程序从 0 到 1 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 微信小程序从 0 到 1 +

+ + +
+ + + + +
+ + +

大概这一年左右的时间,都在跟小程序相关的需求。从开发到上线,流程上会跟以往的 Web 开发有些不同。此前除了大学时的一次课设,其他时间未曾接触过小程序,算是从 0 开始吧。不过得益于 Uniapp 基于 Vue.js 的语法封装,除了小程序自己的 API 之外,语法学习成本几乎没有。

+ + + +

与H5相比,孰优孰劣

对比

    +
  • 运行环境
      +
    • ​网页开发渲染线程和脚本线程是互斥的,这也是为什么长时间的脚本运行可能会导致页面失去响应;
    • +
    • 在小程序中渲染层和逻辑层分别运行在不同的线程中。即 双线程模型
    • +
    +
  • +
  • 开发差异
      +
    • 小程序原生写法很像前端框架中的 Vue,也是 MVVM 模式,但是写法上没有完全照抄,都可以用类似虚拟 DOM 的形式能保证你的数据变化自动响应到模板;
    • +
    • 小程序里不能使用任何 window 下的属性和方法;
    • +
    • 小程序不可以过虚拟 DOM 来操作 DOM,不能使用任何 DOM 和 BOM 相关API;
        +
      • 这是因为:小程序的逻辑层和渲染层是分开的,逻辑层运行在 JSCore 中,并没有一个完整浏览器对象,因而缺少相关的 DOM API 和 BOM API;
      • +
      +
    • +
    • 小程序提供了很多 SDK 方法,几乎涵盖了 APP 能赋予 H5 的所有能力;
    • +
    • 小程序类似于离线包,只要用户访问过,就会把主包代码下载到本地。
    • +
    +
  • +
  • 维护成本
      +
    • 网页开发者需要面对各式的浏览器兼容 :如 PC 端需要面对 IE、Chrome、QQ浏览器等,在移动端需要面对 Safari、Chrome 以及 iOS、Android 系统中的各式 WebView,开发时只需要常用的编辑器和浏览器即可 ;
    • +
    • 小程序开发过程中需要面对的是两大操作系统 iOS 和 Android 的 微信客户端 ,以及用于辅助开发的小程序开发者工具;小程序的 开发者需要经过申请小程序帐号、安装小程序开发者工具、配置项目 等等过程才可进行小程序开发。
    • +
    +
  • +
+

开发前准备

+

账号相关权限

开发者和测试相关的权限需要在微信后台添加;权限分为项目成员和体验成员,都有数量限制。
一般将开发者添加为 项目成员 ,将测试人员或者 PM 添加为 体验成员

+

开发上手

相关文档

+

项目目录

一个小程序主体部分由三个文件组成,必须放在项目的根目录:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
文件必须作用
app.js小程序逻辑:调用小程序实例、小程序生命周期 hook
app.json全局配置:决定页面文件的路径、窗口表现、设置网络超时时间、设置多 tab 等
app.wxss小程序公共样式表
project.config.json项目配置文件(如:appId、编译时配置、依赖等)
+

相关概念

小程序运行机制

冷启动

如果用户首次打开,或小程序销毁后被用户再次打开,此时小程序需要重新加载启动,即冷启动。冷启动不保留上次的浏览场景,打开即直接进入首页(可以使用 restartStrategy 配置冷启动进入的页面)。

+
热启动

如果用户已经打开过某小程序,然后在一定时间内再次打开该小程序,此时小程序并未被销毁,只是从后台状态进入前台状态,这个过程就是热启动。热启动保留上次浏览的 path。

+

小程序更新机制

开发者在管理后台发布新版本的小程序之后,微信客户端会静默更新到新版本。但是无法立刻影响到所有现网用户,最差情况下,也在发布之后 24 小时之内下发新版本信息到用户。
如果需要马上应用最新版本,可以使用 wx.getUpdateManager API 进行处理。

+
    +
  • UpdateManager.applyUpdate():强制小程序重启并使用新版本,在小程序新版本下载完成后调用;
  • +
  • UpdateManager.onCheckForUpdate():监听向微信后台请求检查更新结果事件。微信在小程序冷启动时自动检查更新,不需由开发者主动触发;
  • +
  • UpdateManager.onUpdateReady():监听小程序有版本更新事件。客户端主动触发下载(无需开发者触发),下载成功后回调;
  • +
  • UpdateManager.onUpdateFailed():监听小程序更新失败事件;
  • +
+

基础库

小程序的能力需要微信客户端来支撑,每一个基础库都只能在对应的客户端版本上运行,高版本的基础库无法兼容低版本的微信客户端。
参考:基础库版本分布

+

分包

某些情况下,开发者需要将小程序划分成不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载。这样做可以优化小程序首次启动的下载时间,在多团队共同开发时可以更好的解耦协作。
在小程序启动时,默认会下载主包并启动主包内页面,当用户进入分包内某个页面时,客户端会把对应分包下载下来,下载完成后再进行展示。

+

目前小程序分包大小有以下限制:

+
    +
  • 整个小程序所有分包大小不超过 20M
  • +
  • 单个分包/主包大小不能超过 2M
  • +
+

开发者通过在 app.json subpackages 字段声明项目分包结构:
写成 subPackages 也支持:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"pages":[
"pages/index",
"pages/logs"
],
"subpackages": [
{
"root": "packageA",
"pages": [
"pages/cat",
]
}, {
"root": "packageB",
"name": "pack2",
"pages": [
"pages/apple",
]
}
]
}
+

打包原则

+
    +
  • 声明 subpackages 后,将按 subpackages 配置路径进行打包,subpackages 配置路径外的目录将被打包到 app(主包)中
  • +
  • app(主包)也可以有自己的 pages(即最外层的 pages 字段);
  • +
  • subpackage 的根目录不能是另外一个 subpackage 内的子目录;
  • +
  • tabBar 页面必须在 app(主包)内。
  • +
+

引用原则

+
    +
  • packageA 无法 require packageB JS 文件,但可以 require app、自己 package 内的 JS 文件;
  • +
  • packageA 无法 import packageB 的 template,但可以 require app、自己 package 内的 template;
  • +
  • packageA 无法使用 packageB 的资源,但可以使用 app、自己 package 内的资源。
  • +
+

例如:nodemodules 包中引用的代码会打包到主包中,因为该文件路径在 subPages 之外。

+

鉴权登录

小程序鉴权登录流程图

+

常用 API 及能力

    +
  • 常用事件如 Tap、longPress 参照:WXML的冒泡事件列表

    +
  • +
  • getApp():获取全局的应用实例,全局数据可以在 App 中设置

    +
    1
    2
    3
    4
    5
    6
    7
    8
    // app.js
    App({
    globalData: 1
    })

    // a.js
    var app = getApp()
    app.globalData++
  • +
  • 授权相关信息

    +
      +
    • 获取用户手机号
        +
      • 需要将 <button> 组件 open-type 的值设置为 getPhoneNumber,当用户点击同意之后,可以通过 bindgetphonenumber 事件回调获取到微信服务器返回的加密数据;
        1
        <button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber"></button>
      • +
      +
    • +
    • wx.getSetting() 获取用户当前权限配置,常可以用来在调用某项系统功能时,查看用户是否授权(例如保存存图片到相册)
    • +
    • wx.authorize() 向用户发起授权请求,调用后会立即弹窗询问用户是否同意授权小程序使用某项功能( 如果用户之前已经同意授权,则不会出现弹窗
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      // 下边这段代码就是上两个 API 的应用
      wx.getSetting({
      success: (res?: any) => {
      // 判断是否已经授权
      if (!res['scope.writePhotosAlbum']) {
      wx.authorize({
      scope: 'scope.writePhotosAlbum',
      success: () => {
      // 存储图片
      wx.saveImageToPhotosAlbum(...)
      }
      })
      } else {
      // 调起客户端小程序设置界面,返回用户设置的操作结果
      wx.openSetting()
      }
      }
      })
    • +
    • 若想取消当前用户授权,可【点击小程序右上角三个点】->【设置】->【用户信息】里取消授权
    • +
    +
  • +
  • 生成小程序二维码

    +
      +
    • wxacode.createQRCode:获取小程序二维码,适用于需要的码数量较少的业务场景。通过该接口生成的小程序码,永久有效,有数量限制;
    • +
    +
  • +
  • 小程序运行版本的区分

    +
      +
    • __wxConfig.envVersion:会返回当前小程序运行版本
        +
      • develop - 开发版
      • +
      • trial - 体验版
      • +
      • release - 正式版
      • +
      +
    • +
    • 注:此方法没有在官方文档上注明,只是挂在在全局 this 下,使用时注意该对象是否存在。
    • +
    • 客户端分享的小程序链接可以指定小程序版本,需要跟客户端同学提前约定好,例如:
    • +
    +
  • +
  • 与其他第三方应用进行交互

    +
      +
    • 跳转第三方小程序:需要将被调用的第三方小程序的 AppId 加入到小程序项目白名单中,正式版只能打开正式版;
        +
      • wx.navigateToMiniProgram
      • +
      • wx.navigateBackMiniProgram
      • +
      +
    • +
    • 小程序内关注公众号:
        +
      • <official-account> 原生组件,只能关注与小程序主体相同的公众号(后台配置),且样式不允许自定义,使用场景受限(扫码);
      • +
      +
    • +
    • 小程序唤起 app:
        +
      • 直接唤起:否;
      • +
      • 由 app 直接调起小程序,然后小程序可以通过操作再调起 app;
      • +
      • 从 app 分享出去的小程序,可以调起 app:需要将 <button> 组件 open-type 的值设置为 launchApp,可通过 app-parameter 参数给 App 传参(详情
        (PS:App分享到小程序的参数,支持选择 正式版、体验版、开发板)
      • +
      • 2021.5.19 后,小程序不再支持唤起 App 的能力;
      • +
      • 无法从小程序的 webview 组件中唤起 App,微信做了 Url Scheme 拦截;
      • +
      +
    • +
    • 小程序内 webview 访问 H5:
        +
      • <web-view> 原生组件,个人类型的小程序暂不支持使用,需要在微信后台将域名加入白名单;
      • +
      • 在微信后台下载”校验文件“,并将校验文件上传至网站根目录,供小程序平台进行验证,验证通过了才能成功添加域名;
      • +
      • 注:若要从 webview 打开的 H5 跳转回小程序原生页,则需要提前引入 wx-js-sdk,使用 wx.miniProgram.navigateTo 方法 (官方文档)
      • +
      +
    • +
    • 小程序内打开公众号文章:
        +
      • 使用 <web-view> 组件即可打开相关联的公众号文章,非关联的公众号则提示“无法打开图文消息”;
      • +
      • 在微信管理后台:设置 -> 关联设置 中可以看到“关联的公众号”,(需要到公众号中关联小程序)。
      • +
      +
    • +
    +
  • +
  • 第三方应用与小程序的交互

    +
      +
    • APP 调起微信小程序(只能调用与当前APP相关联的小程序) 参考
        +
      • 在微信管理后台:设置 -> 关联设置 中可以看到“关联的移动应用”;
      • +
      • 可跳转到指定页面
      • +
      • 限制
          +
        • APP和小程序相同主体:如果在同一个主体下,不存在调用个数限制;
        • +
        • APP和小程序不同主体:如果不在同一个主体下,一个app最多只能关联3个小程序。也就是说,非相同主体的小程序最多拉起3个;
        • +
        +
      • +
      +
    • +
    • 外部 H5 调起微信小程序
        +
      • 直接调用:否;
      • +
      • 可根原生同学协商,使用 APP 提供的 SDK 方法调用;
      • +
      • 或者使用使用微信云开发能力的托管 H5,免鉴权直接跳转任意合法的小程序;
      • +
      +
    • +
    • 短信跳转小程序
        +
      • 直接调用:否;
      • +
      • 微信开放能力 - 服务端接口 - 可以获取打开小程序任意页面的 URL Link。适用于从短信、邮件、网页、微信中直接打开;
      • +
      • 使用微信云开发能力,打开M页跳转小程序(待调研);
      • +
      +
    • +
    • 公众号打开小程序(只能调用与当前公众号相关联的小程序)参考
    • +
    +
  • +
+

常见的问题

+

项目测试

可以从 H5 直接进入小程序体验版

在移动端打开:

+

https://open.weixin.qq.com/sns/getexpappinfo?appid={AppId}&path={pagesPath}.html

+

即可访问小程序体验版,并跳转到对应路径(注意:此链接只能在移动端微信中打开).

+

也可以,通过判断微信版本,自己写一个测试/入口构造页面来作为测试入口。

+

缓存

小程序的所有缓存数据上线为 10MB,像 storage 中的数据,除非用户主动删除或因存储空间原因被系统清理,否则数据都一直可用。
清除缓存:

+
    +
  • 发现-小程序-在列表中删除掉测试的小程序;
  • +
  • 微信-我-设置-通用-存储空间;
  • +
  • 安卓在私信聊天页输入 debugx5.qq.com ,利用腾讯的工具清理 cookie;
  • +
  • 退出登录,重新登录。
  • +
+

Android

由于安卓9的安全限制,无法信任用户自行安装的证书,正常状态连代理打开小程序会报错“获取运行环境失败”;
将手机 root 后,解决证书信任问题后才能访问。

+

IOS

直接连代理,访问小程序即可。

+

框架对比

业内知常见小程序框架:wepy、mpvue、uni-app、taro、chameleon。
主流框架对比:详情
主流框架性能对比:详情
目前使用的是 Uniapp,因为可以编译多平台的小程序,且与 Vue 的语法能无缝衔接,开发成本较低。

+

个人偏见

关于功能开发

从一个开发者的角度,我并不希望听到 PM 说“这个功能和页面要和APP保持一致”。
个人认为 APP 承载的功能是核心且重的,也是在用户体验上最优的一端,若将 H5 和小程序的功能完全与 APP 拉齐,不仅开发周期长,维护难度高,同时会让小程序和 H5 失去本身的轻量优势。
小程序 和 H5 应该承载更多引流的功能,而不是一整套完备的 APP,当然了,这句话是针对公司有核心 APP 的情况;若是主要产品就是小程序方向,就看功能利弊的权衡了,只是个人认为“小程序”不应该变成一个庞然大物,对于 PM 而言应该更侧重于对于不同端的用户给出不同的产品特性,而不是一味的追求“复刻”。

+

关于设计还原

由于小程序提供的通用的原生组件有的时候,是不允许开发者更改某些样式的,此时要跟设计同学及时反馈;若要自己开发某些组件,记得增加工期。
用现有的框架也可以:汇总9款优秀的开源小程序UI框架

+

关于部署上线

最后一点,不管是开发还是审核部署,小程序强烈依赖微信运行环境,被封禁和能否上线的话语权(例如小程序中有游戏广告之类的,通常就会被封禁)并非掌握在自己手里,需要做好被封禁时的准备,域名同理。

+

参考链接

+ +
+ + + + + + + +
+ + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2021/03/22/\345\276\256\344\277\241\345\260\217\347\250\213\345\272\217\344\273\2160\345\210\2601/\347\272\246\345\256\232miniprogramType\345\200\274.png" "b/2021/03/22/\345\276\256\344\277\241\345\260\217\347\250\213\345\272\217\344\273\2160\345\210\2601/\347\272\246\345\256\232miniprogramType\345\200\274.png" new file mode 100644 index 00000000..d469f9a8 Binary files /dev/null and "b/2021/03/22/\345\276\256\344\277\241\345\260\217\347\250\213\345\272\217\344\273\2160\345\210\2601/\347\272\246\345\256\232miniprogramType\345\200\274.png" differ diff --git "a/2021/04/12/H5\344\270\216App\344\271\213\351\227\264\347\232\204\344\272\244\344\272\222/Android.png" "b/2021/04/12/H5\344\270\216App\344\271\213\351\227\264\347\232\204\344\272\244\344\272\222/Android.png" new file mode 100644 index 00000000..606f74b7 Binary files /dev/null and "b/2021/04/12/H5\344\270\216App\344\271\213\351\227\264\347\232\204\344\272\244\344\272\222/Android.png" differ diff --git "a/2021/04/12/H5\344\270\216App\344\271\213\351\227\264\347\232\204\344\272\244\344\272\222/IOS.png" "b/2021/04/12/H5\344\270\216App\344\271\213\351\227\264\347\232\204\344\272\244\344\272\222/IOS.png" new file mode 100644 index 00000000..69469390 Binary files /dev/null and "b/2021/04/12/H5\344\270\216App\344\271\213\351\227\264\347\232\204\344\272\244\344\272\222/IOS.png" differ diff --git "a/2021/04/12/H5\344\270\216App\344\271\213\351\227\264\347\232\204\344\272\244\344\272\222/index.html" "b/2021/04/12/H5\344\270\216App\344\271\213\351\227\264\347\232\204\344\272\244\344\272\222/index.html" new file mode 100644 index 00000000..21ceba9c --- /dev/null +++ "b/2021/04/12/H5\344\270\216App\344\271\213\351\227\264\347\232\204\344\272\244\344\272\222/index.html" @@ -0,0 +1,531 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + H5 与 App 之间的交互 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ H5 与 App 之间的交互 +

+ + +
+ + + + +
+ + +

遗留了很久的一个学习任务,最近正好在总结归纳小程序在 App 之间的交互,顺便拾起一些学过的和没学过的知识。主要涉及的知识点:URL Scheme、Webview、H5 与 App 之间的通信以及 JSBridge 的概念。

+ + +

H5 唤起 App

唤起方式

H5 可以通过 location.href、iframe、a 标签三种方式来调用 URL Scheme 来实现唤起 App。
URL Scheme 是系统提供的一种机制,它可以由应用程序注册,然后其他程序通过 URL Scheme 来调用该应用程序,其基本格式为 scheme://[path][?query]

+
    +
  • scheme :应用标识,已安装的APP注册在系统中的标识;
  • +
  • path :应用行为,表示应用某个页面或功能;
  • +
  • query :应用参数,标识应用页面或者应用功能所需的条件参数;
    但是此方式无法得知 App 是否唤起成功,有可能存在 App 未下载的情况。通常用计时器,监听页面是否已隐藏(监听页面 visibilityChange),若未曾隐藏则认为打开失败,再根据不同的平台跳转不同的渠道下载页。
  • +
+

除此之外,还有一种链接叫:Universal Link (通用链接) , 是 Apple 在 iOS9 推出的一种能够通过 HTTPS 链接来启动 APP 的功能。当应用支持此链接时,则会无缝跳转到 APP,而不需要其他判断;但需要注意的是,这个链接是可以访问的,直接在浏览器中打开并不会跳转 App,需要跨域访问才可以。

+

一个完善的唤起流程如下:

+
+ +

若在小程序的 Webview 中尝试唤起 App 会怎么样?

(以下的逻辑参照上图的流程)
由于微信拦截了 URL Scheme,所以并不会打开 App;此时就会判断 Webview 环境,跳转对应的下载页:
如:在 Mac 上的开发者工具中会跳转 App Store:

+ + +

而在安卓手机的微信小程序中则会跳转(腾讯的安装渠道):

+ + +

但是由于这两个地址的域名都没有在微信后台配置,所以都会被微信认成不可信的域名,只会跳转到一个空白页面,然后提示域名不可信。

+

PS:主要禁止的原因是,小程序不允许将流量导出到 APP 之外;
2021.4.12 不过网上还有一种说法就是将 App 关联到腾讯的应用宝上,到时候就会自己跳转到 App Store (参考
2021.4.13 经验证,此方法不可行,在信任应用宝域名的前提下(sj.qq.com),依旧无法唤起下载/跳转:

+
    +
  • 安卓表现为停止在当前 H5 并提示使用浏览器打开;
  • +
  • IOS 表现为卡在当前 H5,点击下载按钮无反应;
  • +
+

H5 与 App 之间的通信

关于 Webview 能力

Webview 是 Android / IOS 操作系统的一个组件,它可以让应用程序直接显示网页内容。
它提供了很多能力,其中最重要的一项就是 添加 JS 和执行 JS 的能力。H5 与 App 之间的通信正是依赖于此。

+

还有很多通用能力如 注入 Cookie添加/移除响应头监听页面返回操作拦截 Url拦截弹窗 、获取/放置证书、监听下载事件 等等。更多 API 可以查阅 官方文档

+

通信方式

通信过程主要依赖 Webview 提供的 JS API,可以简单的看成两个方向:

+
    +
  • App 调用 JS 代码
  • +
  • JS 调用 App 代码
  • +
+

接下来示例的方法均以 Android API 为例。

+

App 调用 JS 代码

在 Webview 中是可以获取到 window 对象的,所以 App 可以访问挂载在全局对象上的方法。只需告知 App 方法名即可。
Andiroid 中使用这两个方法执行 JS:

+
    +
  • 使用 WebView 的 loadUrl() 方法:参数 为 js 文件路径;
  • +
  • 使用 WebView 的 evaluateJavascript() 方法:参数为 js 方法名,以及回调函数;
  • +
+

JS 调用 App 代码

JS 调用 App 代码主要有两种方式,一种是页面发起行为,App 拦截 行为解析语义响应操作;另一种是 App 提前将方法映射成 JS,注入 到 window 对象上供 JS 调用。

+
    +
  • 通过 WebChromeClient 的 onJsAlert()onJsConfirm()onJsPrompt() 方法回调 拦截 JS 对话框 alert()confirm()prompt() 消息;
      +
    • 得到消息内容后解析,再做相应的处理;
    • +
    +
  • +
  • 通过 WebViewClient 的 shouldOverrideUrlLoading() 方法回调 拦截 url
      +
    • 一般使用这种方法拦截事先约定好 URL Scheme 上的挂载参数,再执行不同的逻辑;
    • +
    +
  • +
  • 通过 WebView 的 addJavascriptInterface() 进行对象映射;
      +
    • 此方法可以将 Java 对象映射映射成 JS 对象,JS 直接调用即可。
    • +
    +
  • +
+

关于 JS Bridge

JS Bridge 只是 Native 和 H5 交互方案的一种统称,犹如它的名字一样,Webview 和 H5 将 JS 用作沟通的桥梁。它赋予了 JavaScript 操作 Native 的能力,同时也给了 Native 调用 JavaScript 的能力。上述的通信方案都可以称之为 JS Bridge 的实现。

+

联调注意事项

    +
  1. 同一方法,若确定方法名、参数等没有问题,但是调用结果与预期不一致,注意同时对比 IOS 端和 Android 端表现是否一致,若表现不一致,则应找对应的客户端同事去修改;
  2. +
  3. 注意测试 App 版本号,以及 H5 中引用的 sdk 版本号,排查问题时考虑是否是版本过旧导致的;
  4. +
  5. 对于用作工具的测试页面出现问题,即时反馈,有可能是测试页面未更新。
  6. +
+

参考链接

    +
  • Android:你要的WebView与 JS 交互方式 都在这里了
  • +
  • h5 与原生 app 交互的原理
  • +
  • In-depth Profiling of JSBridge
  • +
  • Android Build Version
  • +
+ +
+ + + + + + + +
+ + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/index.html" "b/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/index.html" new file mode 100644 index 00000000..41670716 --- /dev/null +++ "b/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/index.html" @@ -0,0 +1,571 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 《写给大家看的设计书》- 小结 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 《写给大家看的设计书》- 小结 +

+ + +
+ + + + +
+ + +

很好的排版设计入门书,19年年底的时候在地铁里用了不到两周的时间看完了,一边看书,一边拿出平日里设计师给的设计稿对比着看,总会有一种“我不明白,但我大受震撼”的感觉。 书很快读完了,读书小结却从19年拖到了21年,再过三个月就22年了,现在拿出来翻翻,如获新书( ̄ε(# ̄)。

+ + +

前言

好的排版会让信息的可读性大大增加,一眼抓住重点。这本书主要介绍排版中最基础的四大设计原则 “亲密性、对齐、重复、对比”, 读完之后会发现在设计稿和 PPT 的应用中有一些规律可循。书中有大量示例,小结中只摘取最具代表性的几个,以方便查看和参考。

+

亲密性(Proximity)

概念

物理位置的接近意味着存在关联。彼此相关的项应当组织在一起,形成一个视觉单元, 而不是多个孤立的元素。
要有意识地注意你是怎样阅读的,你的视线怎样移动:从哪里开始;沿着怎样的路径;到哪里结束;读完之后,接下来看哪里?整个过程当是一个合理的过程,有确定的开始,而且要有确定的结束。

+

示例

    +
  1. 调整前(左) / 调整后(右)

    + +
  2. +
  3. 从左到右 逐次调整

    +
  4. +
+

要避免的问题

    +
  • 避免一个页面上有太多孤立的元素;
  • +
  • 不要在元素之间留出同样大小的空白,除非各组同属于一个子集;
  • +
  • 不属于一组的元素之间不要建立关系:如果元素彼此无关,就把它们分开;
  • +
  • 不要仅仅因为有空白就把元素放在角落或中央;
  • +
+

对齐(Alignment)

思想:任何东西都不能在页面上随意安放。每个元素都应当与页面上另一个元素有某种视觉联系。这样能建立一种清晰、精巧的外观。
如果页面上某些元素是对齐的,即使对齐的元素物理位置是彼此分离的,但在你眼里(以及你的心里),它们之间也会有一条看不见的线把彼此连接在一起。
对齐的根本目的是 使页面统一而且有条理。

+

示例

    +
  1. 调整前视线在不同位置停留5次 调整后结构变的清晰有序

    + +
  2. +
  3. 文章主题与标题采用相同的对齐方式 才不会显得杂乱

    + +
  4. +
  5. 对齐线”边界的强度为布局提供了力度

    +
  6. +
+

要避免的问题

    +
  • 避免在页面上混合使用多种文本对齐方式;
    (也就是说,不要将某些文本居中,而另外一些文本右对齐)
  • +
  • 尽量不要将居中对齐作为默认选择,除非你有意识地想要想要创建一种比较正式的表示;
  • +
+

重复(Repetition)

思想:设计中视觉元素的重复可以将作品中的各部分连在一起,从而 统一 并增强整个作品。
重复的元素可以是一种粗字体、一条粗线、某种颜色、空间关系等。
不过 重复不只是自然的一致,而是统一设计各个部分的有意识的行为。
如果一个作品看起来很有趣,它往往也更易阅读。
(列表项就是一个典型应用重复的例子)

+

示例

+ +

要避免的问题

    +
  • 避免过多地重复一个元素,重复太多会让人讨厌。要注意对比的价值;
  • +
+

对比(Contrast)

思想:对比是为页面增加视觉效果最有效的途径,也是在不同元素之间建立一种有组织的层次结构最有效的方法。
可以通过字体、线宽、颜色、形状、大小、空间等来增加对比。但要记住一个原则:要想实现有效的对比,对比就必须强烈,如果元素不同,那就让它们截然不同。

+

示例

    +
  1. 背景色与文字颜色的强烈对比 更易阅读

    + +
  2. +
  3. 一条无形的对角线以及放大的图片也增加了趣味性

    +
  4. +
+

要避免的问题

    +
  • 不要将一种粗线与一种更粗的线进行对比,不要将棕色文本与黑色标题建立对比。要避免使用两种或多种类似的字体;
  • +
  • 如果各个项不完全一样,那就让它们截然不同;
  • +
+

颜色运用

色轮基础

    +
  • 色轮的基础是 红、黄、蓝 3种颜色,它们被称之为 三原色, 因为它们无法被创建。
  • +
  • 将相邻的三原色等量的混合 ,就会得到 三间色(secondary color)
  • +
  • 其余位置将相邻的两个颜色等量混合,就会得到 第三色
  • +
+ + +

颜色关系

    +
  • 互补色:色轮上相对的颜色为互补色 。利用它们的对立关系,常见搭配是一种作为主色,而另一种用于强调。
  • +
  • 三色组:彼此等距的三种颜色叫做三色组 。三色组中的颜色都有基础色使其相互连接,因此看上去十分协调。
  • +
  • 分裂互补三色组:从色轮的一边选择一种颜色,再找出它的互补色,但并不直接使用这个互补色,而是使用 该互补色两侧的颜色 。这样的组合会有一种更为细致的边界。
  • +
  • 类似色:类似色由色轮上彼此相邻的颜色组成 ,它们都有相同的基础色。用不同的亮色和暗色组合一组类似色,会获得醒目的效果。
  • +
  • 单色组:单色组合由一种色调及其相应的多种亮色和暗色组成 ,如黑白照片。
  • +
  • 暗色和亮色的组合:不使用色调,而是使用这些颜的不同亮色和暗色 。这样既丰富了选择,也可以放心颜色的协调性。
  • +
+ + +

颜色变化

    +
  • 亮色和暗色
      +
    • 纯色就是 色调
    • +
    • 向色调增加黑色就构成一个 暗色
    • +
    • 向色调增加白色就构成一个 亮色
    • +
    +
  • +
  • 色质:指某种颜色的明暗度、深浅度或色调。
  • +
  • 暖色与冷色
      +
    • 暖色:其中包含红色或者黄色。暖色是趋进型的,趋于做视觉提醒;
    • +
    • 冷色:其中包含蓝色。冷色属于后退型的,更趋于做背景色;
    • +
    +
  • +
+

颜色模型

    +
  • CMYK:由四种墨色组成,可以打印成千上万种颜色。常用于纸张打印、书籍印刷等;
  • +
  • RGB:由 Red、Green、Blue 三种颜色组成。常用于显示器、电视、手机屏幕等;
  • +
+

要注意的问题

    +
  • 避免组合中的色质过于接近。如果色质很接近,对比太过微弱,看上去就会模糊不清;
  • +
  • 不要让冷暖色过于均衡,要充分利用冷暖色的视觉特质;
  • +
  • CMYK 和 RGB 之间的转换会有数据损失,所以最好用 RGB 处理图像,最后再把它们转换为 CMYK 格式;
  • +
+

文字与字体

    +
  • 衬线体:衬线又被称为“字脚”,衬线体(Serif)就是有边角装饰的字体,如宋体;
  • +
  • 无衬线体:无衬线体(Sans-serif)则与衬线体相反,通常是机械和统一粗细的线条,没有边角的装饰,易读性更好,如黑体;
  • +
+

标点符号

    +
  • 跟随在有样式文字后的标点:如果一个单词的样式是粗体,那么跟随在文字之后的符号也应该是粗体。
  • +
  • 括号中的标点:
      +
    • 如果若括号中的文字是整个句子的一部分,那么标点就应该在括号之外
      (就像这个例子一样)。
    • +
    • 如果括号内的文字是一个完整的句子,那么标点应该出现在括号内。
      (这就是一个标点出现在括号内的例子。)
    • +
    +
  • +
  • (英文)标点后面一个空格,破折号两侧无空格。
  • +
  • 方框中的文字:如果你确实要把文字放进方框里,那就要在四周留出足够的空间。
  • +
+

更多提示与技巧

    +
  • 创建中心点:页面上应该有一个 最突出的主导元素。
  • +
  • 使用有对比的子标题:不仅视觉效果强烈,而且能充分地表达含义。
  • +
  • 段落缩进:第一段不要缩进。即使跟在子标题后面也如此。
    段落之间要么有额外的空间,要么缩进,但不要二者都有。
  • +
  • 主标题可采用无衬线体来增加对比。正文中可以使用衬线体。
    若使用无衬线体,则需要预留较宽的行间距,行的长度应缩短;
  • +
  • 不要把元素堆到角落里,也别总要填满空白,更不要把它们都变成一个尺寸或者类似尺寸。
  • +
  • 打破规则: 只要你清楚有哪些规则,适当地打破规则,且结果合理,那就大胆去做。
    只要让人看出来你是有意为之,而不是把页面弄的杂乱无序就好。
  • +
+ +
+ + + + + + + +
+ + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/\344\272\262\345\257\206\346\200\247-1.png" "b/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/\344\272\262\345\257\206\346\200\247-1.png" new file mode 100644 index 00000000..1c8613a1 Binary files /dev/null and "b/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/\344\272\262\345\257\206\346\200\247-1.png" differ diff --git "a/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/\344\272\262\345\257\206\346\200\247-2.png" "b/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/\344\272\262\345\257\206\346\200\247-2.png" new file mode 100644 index 00000000..55a17304 Binary files /dev/null and "b/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/\344\272\262\345\257\206\346\200\247-2.png" differ diff --git "a/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/\345\257\271\346\257\224-1.png" "b/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/\345\257\271\346\257\224-1.png" new file mode 100644 index 00000000..b4e4f70b Binary files /dev/null and "b/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/\345\257\271\346\257\224-1.png" differ diff --git "a/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/\345\257\271\346\257\224-2.png" "b/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/\345\257\271\346\257\224-2.png" new file mode 100644 index 00000000..61bb8bd5 Binary files /dev/null and "b/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/\345\257\271\346\257\224-2.png" differ diff --git "a/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/\345\257\271\351\275\220-1.png" "b/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/\345\257\271\351\275\220-1.png" new file mode 100644 index 00000000..5a3e6d38 Binary files /dev/null and "b/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/\345\257\271\351\275\220-1.png" differ diff --git "a/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/\345\257\271\351\275\220-2.png" "b/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/\345\257\271\351\275\220-2.png" new file mode 100644 index 00000000..a20f5fff Binary files /dev/null and "b/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/\345\257\271\351\275\220-2.png" differ diff --git "a/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/\345\257\271\351\275\220-3.png" "b/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/\345\257\271\351\275\220-3.png" new file mode 100644 index 00000000..1a8869d6 Binary files /dev/null and "b/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/\345\257\271\351\275\220-3.png" differ diff --git "a/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/\350\211\262\350\275\256\345\237\272\347\241\200.png" "b/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/\350\211\262\350\275\256\345\237\272\347\241\200.png" new file mode 100644 index 00000000..bfd4540f Binary files /dev/null and "b/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/\350\211\262\350\275\256\345\237\272\347\241\200.png" differ diff --git "a/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/\351\207\215\345\244\215-1.png" "b/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/\351\207\215\345\244\215-1.png" new file mode 100644 index 00000000..042a99a9 Binary files /dev/null and "b/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/\351\207\215\345\244\215-1.png" differ diff --git "a/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/\351\242\234\350\211\262\345\205\263\347\263\273.png" "b/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/\351\242\234\350\211\262\345\205\263\347\263\273.png" new file mode 100644 index 00000000..32da799a Binary files /dev/null and "b/2021/09/14/\343\200\212\345\206\231\347\273\231\345\244\247\345\256\266\347\234\213\347\232\204\350\256\276\350\256\241\344\271\246\343\200\213-\345\260\217\347\273\223/\351\242\234\350\211\262\345\205\263\347\263\273.png" differ diff --git "a/2021/12/03/2021\345\271\264\347\273\210\346\200\273\347\273\223/index.html" "b/2021/12/03/2021\345\271\264\347\273\210\346\200\273\347\273\223/index.html" new file mode 100644 index 00000000..c9243589 --- /dev/null +++ "b/2021/12/03/2021\345\271\264\347\273\210\346\200\273\347\273\223/index.html" @@ -0,0 +1,524 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2021年终总结 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 2021年终总结 +

+ + +
+ + + + +
+ + +

2021年终总结(✖️)
2021大师难度通关记(✔️)

+ + +
    +
  • 1月
      +
    • 筹备团建 :疫情管控比较严,筹备工作进行的并不顺利;
    • +
    • 年终绩效(年终奖) :坚持工作不跑路的精神支柱,做好了绩效为D的准备,领导比较照顾给了C,拿了一笔钱准备回家过年;
    • +
    +
  • +
  • 2月
      +
    • 各部门聚餐 :春节前的两周平均每天都有聚餐,在中午,或是在晚上;
        +
      • 团队聚餐常常在一家湘菜馆,印象最深的一道菜是红辣椒炒绿辣椒;
      • +
      • 趁着聚餐活动去玩了一把卡丁车,体验在轰鸣声中狂飙的感觉,只是车技捉急;
      • +
      +
    • +
    • 回家过年 :应国家号召”就地过年“,有顾虑的人留下,没有顾虑的人启程返乡;
    • +
    • 整理照片 :筛选了一些从毕业到工作后的照片整理成相册。只是照片数量较多,即使到返程也没能整理完;
    • +
    +
  • +
  • 3月
      +
    • 加班
    • +
    +
  • +
  • 4月
      +
    • 加班/看工作
    • +
    +
  • +
  • 5月
      +
    • 加班/看工作/面试
    • +
    +
  • +
  • 6月
      +
    • 加班/看工作/面试
    • +
    • 好友毕业/奇葩解压馆/欢乐谷
    • +
    • 离职申请
    • +
    +
  • +
  • 7月
      +
    • 最后一次团建 : 团建是在一个草原上,视野开阔,景色优美;夜晚下起了雨,同事们在屋内远程联调排查 bug;
    • +
    • 离职 :HR 给了满满一页的表格需要对应的负责人签字,下午找遍了楼层的每一处,没想到居然是在这一天才将各个大佬和部门的位置熟悉了个遍。同事将我送出园区的时候,天还亮着。迎着夕阳下班,感觉真实又不真实。
    • +
    • 搬家 :又从海淀/昌平区搬回朝阳区了;
    • +
    • 入职 :那两天恰逢北京暴雨,于是入职时间往后顺移了两天,提前感受到了公司的人文主义关怀。
    • +
    • 加班
    • +
    +
  • +
  • 8月
      +
    • 加班
    • +
    +
  • +
  • 9月
      +
    • 工作
    • +
    +
  • +
  • 10月
      +
    • 国庆假期 :通关了积灰已久的塞尔达,终于以一副兽皮人面的样子来到了公主身前。
    • +
    • 工作
    • +
    +
  • +
  • 11月
      +
    • 工作
    • +
    • 海口/火星演唱会 :演唱会跟想象中的体验出入还蛮大的,如果可以重来一次,能不能把票钱还我。
    • +
    +
  • +
  • 12月
      +
    • 工作
    • +
    +
  • +
+

一些无关紧要的记录

    +
  • 鼠标:换成了罗技的 Anywhere3,很香很丝滑;
  • +
  • 模玩:上半年入了从大学就开始惦记的 MARK 3,下半年为了情怀入了 MARK 85;
    (嗯,明年打算喝西北风 (  ̄ー ̄) );
  • +
  • 记录工具:印象笔记内容变多之后,不管是网页端还是客户端,用起来都很笨重,而且一言不合就进行设备限制弹广告,bug 也变多了,逐渐让人难以忍受,遂寻找新的笔记工具。期间试过 马克飞象、Notion、网易云笔记、语雀。最后选择了语雀,不管是目录分类还是工具布局,都非常合理,用起来得心应手。
    现在已经成为 dida、语雀 的重度使用用户了。
  • +
+

为什么辞职

给了自己一年的时间去适应公司,约定好若一年之后还是如刚入职般的状态,便离职。
这一年过的并不轻松,从未知道原来安心敲代码,没有其他事打扰是这么奢侈的事。或许当时匆匆做选择,没有深思熟虑就入职等同于埋坑。

+

最焦虑的时候,恰逢朋友毕业,来北京陪我两天。
这两天我们去了减压馆,去了欢乐谷。
但是人在乐园里,心在工作上,这份焦灼似乎并没有被化解。

+

好友返程的那天上午,早会时分,我站在人群后面,听着别人的需求进度,思绪早已飘到了九霄云外。
回到工位后,打开电脑,开始码离职申请,

+ +

这一天晚上,下班回家走在路上,我感觉到,心底的那块石头终于被搬走了。

+

关于前公司

周围的同事们都很优秀,大家之间的氛围很棒,目前遇到的公司中无出其右。
这里的人追求技术,敢于创新。
只是,来来往往很多人,还没来得及再次打招呼,工位就空了。
待了一年多,大大小小的会议,各种各样的事情,朝令夕改的要求,也将我对大厂的幻想磨的粉碎。
公司说不上哪里好,也说不上哪里特别不好。
只是在我漫漫找工作的路途中,为我上了重要一课,

+

关于现公司

哪怕已经入职新公司小半年了,可是这对我来说依然还是如梦如幻般的经历。
工作的自由度很高,曾经有的束缚现在都没有了。
同时也有些难过,失去了一些共同工作的伙伴,曾经是一群人,现在是一个人。

+

但做人也不能太贪心,世上本就没有两全之事。

+

一场义无反顾的海口之旅

忙忙碌碌的下半年,一直想去的演唱会终于在年底有了消息,
觉得无论如何也要去看一看,害怕一错过就又等不来了。

+

虽然疫情防控各种措施,跨省看演唱会困难重重,好在抵达海口后一切顺利。
海风吹拂的那一瞬间,让我觉得这一切都是值得的。

+

因为一场演唱会,结识了一群小伙伴。
我们在落日海边散步,在海大夜市吃夜宵,去逛免税店,去尝椰子鸡。
三天短暂而又充实的海口之旅,玩的很开心。
这次受时间限制,没来得及去三亚玩一圈,如果有机会,想去亚特兰蒂斯水世界玩一玩。

+

全年总结

受尽心理折磨终于迎来转折的一年,生活节奏和心态也在慢慢调整过来。
努力工作是为了更好的生活,要保持本心。
只可惜,方方面面而言,今年也是没啥进步的一年 ╮(╯▽╰)╭。
希望新一年的自己能战胜懒惰,把拖延的毛病改掉,还有就是早点起床早点上班,不要天天睡到11点才起床收拾去公司…

+

PS:不确定字迹是否跟心态有关系,上半年“草书”,下半年“行楷”。

+ +
+ + + + + + + +
+ + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2022/03/11/\347\275\221\345\267\245\347\233\270\345\205\263/index.html" "b/2022/03/11/\347\275\221\345\267\245\347\233\270\345\205\263/index.html" new file mode 100644 index 00000000..d1f7a514 --- /dev/null +++ "b/2022/03/11/\347\275\221\345\267\245\347\233\270\345\205\263/index.html" @@ -0,0 +1,473 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 网工相关 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 网工相关 +

+ + +
+ + + + +
+ + +

之前网工学习部分知识点总结,主要是整理汇总下分散在不同地方的笔记。
工作中用到的不多,仅当笔记备份。

+ + +

加密算法

可逆算法

字面意思,经过算法处理后的信息可通过某种逆向计算得到原信息的算法称为可逆算法。
PS:常见的 base64 并不是一种加密算法。它的编码过程完全公开,逆向解码即可得到原来的信息。经过 base64 编码后的字符串也都是常规字符,这样在信息传递的过程中,UNICODE 字符串就不会发生不能识别或者丢失的现象了。故 base64 只是一种编码方式,并不能归类为加密算法当中。

+

对称加密

对称加密指的是加密、解密用的是同一个密钥。特点是 加密速度快,但安全性不高。
常见的对称加密算法有:DES、3DES、AES。

+

非对称加密

非对称加密指的是加密、解密用的是不同的密钥,它们是成对出现的、称为公钥和私钥,公钥加密的内容,用私钥才可以解密。特点是 安全性高,加密速度慢。
常见的非对称加密算法有:RSA。

+

不可逆算法

不可逆算法的特征是加密过程中不需要使用密钥,输入明文后通过算法生成密文,密文信息无法被解密。(常见的场景是:用户账密校验, 常见的算法是:哈希算法-信息摘要算法)
常见算法:HASH 算法。

+
    +
  • MD5 算法:通过不可逆的字符串变换法,产生一个唯一的 MD5 信息摘要。
    (每个文件都有一个数字指纹)
  • +
  • SHA 算法:信息摘要算法,主要用于验证数据的完整性。在传输过程中,若数据发生变化,那么就会产生不同的摘要。
  • +
+

更多算法细节介绍参考:常见加密算法

+

HTTPS 中用的是什么算法

// TODO 我记得这里分成了两步 既要保证安全 又要保证速度 查一下书吧

+

HTTPS 证书过期,内容是否还是加密的

不论证书是否有效,只要用户通过浏览器确认要发起协商会话,那么就依然会从证书里拿到密钥,走一个加密通话的过程。
(证书的作用主要是认证,去辨明浏览器的真伪)
参考文章:如果没有有效的证书,HTTPS连接是否加密

+

域名解析 A记录、CNAME、NX

    +
  • A记录 就是把一个域名解析到一个IP地址(Address,特制数字IP地址),创建不带 www 的域名记录需要在域名前加一个 ‘@’;
  • +
  • CNAME记录 可以看做是称域名的别名。它可以将多个域名指向同一个ip(实际上都指向了一个域名);
  • +
  • MX记录 (Mail eXchanger) 设置域名邮箱用的:直接添加记录值为对应的域名邮箱,网易、腾讯、等等;
  • +
  • AAAA 记录 是一个指向 IPv6 地址的记录;
  • +
  • NS 记录 (Name Server) 用于记录域名服务器,用于指定域名由哪台服务器来解析。可以使用 nslook -qt=ns [域名] 来查询当前 ip;
  • +
+

参考文章:

+ + +
+ + + + + + + +
+ + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2022/06/09/\343\200\220\350\257\221\343\200\221Why-Is-WKWebView-So-Heavy-and-Why-Is-Leaking-It-So-Bad/Xcode-debug.png" "b/2022/06/09/\343\200\220\350\257\221\343\200\221Why-Is-WKWebView-So-Heavy-and-Why-Is-Leaking-It-So-Bad/Xcode-debug.png" new file mode 100644 index 00000000..a6e994f3 Binary files /dev/null and "b/2022/06/09/\343\200\220\350\257\221\343\200\221Why-Is-WKWebView-So-Heavy-and-Why-Is-Leaking-It-So-Bad/Xcode-debug.png" differ diff --git "a/2022/06/09/\343\200\220\350\257\221\343\200\221Why-Is-WKWebView-So-Heavy-and-Why-Is-Leaking-It-So-Bad/index.html" "b/2022/06/09/\343\200\220\350\257\221\343\200\221Why-Is-WKWebView-So-Heavy-and-Why-Is-Leaking-It-So-Bad/index.html" new file mode 100644 index 00000000..5952ed73 --- /dev/null +++ "b/2022/06/09/\343\200\220\350\257\221\343\200\221Why-Is-WKWebView-So-Heavy-and-Why-Is-Leaking-It-So-Bad/index.html" @@ -0,0 +1,494 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 【译】Why Is WKWebView So Heavy and Why Is Leaking It So Bad? | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 【译】Why Is WKWebView So Heavy and Why Is Leaking It So Bad? +

+ + +
+ + + + +
+ + +

从 iOS8 开始,就引入了新的浏览器控件 WKWebView,用于取代 UIWebView。在新版本系统中使用 UIWebView 会发出警告 ⚠️ 提醒更换控件。坊间传闻 WKWebView 存在内存占用过大的问题…

+

声明:这是一篇翻译水文,有用的内容不多,之前是因为好奇翻译了一半,翻译完发现并没有什么有用的知识点…

+ + +

在 Embrace 公司,我们帮助移动应用公司解决他们最困难的生产问题。其中常见的 bug 是 iOS 上对 WKWebView 的不当管理产生的。而问题是 Webview 对象在资源中占用较重。大量被占用的内存未被正确的释放则会导致系统卡顿、死机甚至崩溃。

+

本文中,我们将会介绍以下内容:

+
    +
  • 为什么 WKWebView 会这么重
  • +
  • 常见的 WKWebView 导致的内存泄露方式
  • +
  • 使用 WKWebView 时怎样发现内存泄露
  • +
  • 使用 WKWebView 的最佳实践
  • +
+

为什么 WKWebView 会这么重

在开始之前,我们在先前的文章中已经介绍了content process 终止导致 WebView 阻塞显示空白以及降级导致的 WebView 空包。如果你依然因为空白的 WebView 而苦恼,看看这些文章或许会有帮助。

+

文本将主要探讨在加载过程中 WebView 被阻塞以及在你的 App 中存在了过多的 WebView 的问题。WebView 是可控的最重的对象之一。基本上,你可以用你的 App 来启动另一个应用并添加两个附属进程 —— content process 和 networking process。

+

所以如果你的应用中有 一个 WebView ,则意味着你的应用实际运行在 三个系统进程 上:应用进程、Web Content Process 和 Web Networking Process。

+

两个 WebView 则意味着有 五个进程
三个 WenView 则意味着有 七个进程

+

当示例个数成倍增加时,并没有形成一个规模经济效应(即进程越多越高效)。事实上,正相反。创建的 WebView 越多,你的 App 运行就越慢。

+

常见的 WKWebView 导致的内存泄露方式

WKWebView 致使内存泄露最常见的原因就是 新建 ,而不是复用已经创建好的实例。一些时候,工程师们以为他们已经复用了 Webview 了,但是他们并没有检查在 Xcode 已经构建的 WKWebView 实例。因为 WKWebView 是存放在 Apple 系统目录中,工程师在调试性能问题时很容易把这部分忽略掉。

+

例如,你有一个轮播组件(Carousel),每当用户滑动时就会加载一个 WebView,内容如以下几种:

+
    +
  • 加载一篇 新闻/杂志 网站的文章
  • +
  • 加载一个 电商 网站的产品列表页
  • +
  • 加载一段 流媒体 如视频
  • +
+

对于轮播组件来说,在内存中的 WebView 数量最好永远不要超过两个。一个为用户展示当前内容,另一个用作下一个内容的承接。一旦用户滑动切换到下一个 WebView,应该清空第一个 WebView 并且使之为下一次切换做准备。这样无论用户切换多少次,你的 App 中始终就只有两个 WebView。

+

对于 ScrollView,在同一时间内可能会有多个可见的 WebView 存在。这种情况下,其最大数量取决于填满屏幕大小需要的 WebView 个数外加一个用于预加载 WebView。

+

另一中泄露方式是已崩溃的 WebView 一直被保留而没有得到释放。 无论用户在何时遇见白屏页面,你都应该有一个状态码来确定当前页面是应该重新加载还是应该被移除。例如,如果是付款页面出了问题,你会想去重新加载;如果是广告页面出了问题,当你不能够修复时你会选择删除它。

+

使用 WKWebView 时怎样发现内存泄露

通过 Xcode 内存图表来查看内存泄露。 用 Xcode 进行 debug 时,查看 WebView 模块,可以在展开左边侧栏中看到当前内存中的 WebView 数量。此时滑过刚刚我们创建的组件,就可以看到到内存使用情况。

+ + +

使用 WKWebView 的最佳实践

首先最好的实践就是限制应用应用内 WKWebView 的数量。在 iOS 应用中,最繁重的操作之一就是创建新的 WKWebView。它们占用大量的内存并添加额外的进程。无论何时尝试将 WebView 用系统本身的某功能来代替,都是有意义的。

+

第二个实践是复用已有的 WKWebView 而不是新建。清除现有的旧内容并将新内容加载到现有的 WKWebView 实例中,这比直接删除和创建的性能要好的多。

+

第三个实践是写一些适当的测试用例来标记溢出的 WebView,代码可以严格一点。如果你仅在轮播组件中使用了 WebView,那么你很明确的知道同一时刻的内存中最多应该包含两个 WebView。当超过两个 WebView 存在时,测试用例将报错。

+

同理,如果你有一个产品列表在 ScrollView 中,那么你就可以通过计算填满屏幕所需的 WebView 数量来计算最大值。测试用例也是同样的方法。利用 Xcode 的内存图和适当的测试用例来发现 WebView 的泄露是很重要的,这样就可以使你的应用程序性能更佳。

+

总结一下

iOS 应用卡顿和反应慢的问题之一是创建了太多 WKWebView 实例,对已存在的 webview 没复用也没销毁(这不卡才怪…)。

+

参考文章

https://blog.embrace.io/wkwebview-memory-leaks/

+

翻译总结

    +
  • 文章部分内容写的过于重复,并不是很干货,让我想起了国内的营销号;
  • +
  • 强烈怀疑原文是隔壁机翻成英文的,或者作者母语并非英语;
  • +
  • 机翻比自己脑子翻好用…
  • +
+ +
+ + + + + + + +
+ + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2023/01/13/2022\345\271\264\347\273\210\346\200\273\347\273\223/index.html" "b/2023/01/13/2022\345\271\264\347\273\210\346\200\273\347\273\223/index.html" new file mode 100644 index 00000000..2eb2380c --- /dev/null +++ "b/2023/01/13/2022\345\271\264\347\273\210\346\200\273\347\273\223/index.html" @@ -0,0 +1,481 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2022年终总结 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 2022年终总结 +

+ + +
+ + + + +
+ + +

工作是否也有五年之痒,2022 年度吐槽汇总。

+ + +

一篇比往年迟了半个月的年度总结。拖延症的魔抓终于也伸向了年终总结…

+
    +
  • 一月
      +
    • 工作 :年假见底,在公司苟到了年前最后一刻。除夕前一天 Leader 给谈了谈入职半年的工作表现;
    • +
    +
  • +
  • 二月
      +
    • 回家过年(一) :过年了,老妈为了准备了一桌子的零食。晚上我打开电视看花滑表演,她在我旁边嗑瓜子。或许好久没这么安逸过了,感动哭了。
    • +
    • 回家过年(二) :接下来的三四天里,天天以泪洗面,别问,问就是感动的。一年过去了,别的方面没见长,泪腺到是发达了。
    • +
    • 回家过年(二) :过年几乎每天都会出去串个门或者跟朋友吃饭。还去了家乡的海棠山爬到了山顶。在商场的娃娃机里抓出了好几只娃娃,小时候在娃娃机上的怨念,长大了算是找补回来了。
    • +
    +
  • +
  • 三月: 工作
  • +
  • 四月、五月
      +
    • 居家工作 :公司园区附近出现了阳性,全员居家办公。居家这段时间正是需求比较多的时期,电脑放床上,睡醒了敲,敲完了睡。此时总是会想念公司宽阔的办公桌,清晰的扩展屏,自动饮水机和温度适宜的空调,以及到点联系不到人的状态。
    • +
    +
  • +
  • 六月、七月:工作
  • +
  • 八月
      +
    • 云南九日游(一) :单方面宣布大理就是海拉鲁本鲁,不接受任何反驳。云南的天和云给我感觉就是,动漫诚不欺我。苍山上的云从山峰一直延绵到脚下,伴随着下午三四点明晃晃的阳光,明亮美丽,仿佛置身天堂,我当时都怀疑是不是上帝要来接我了,同行的朋友瞟了我一眼,示意我别废话了赶紧把氧气罐打开。没想到我一米六五的东北大汉居然折在高原反应上,但是同样东北出身的她却啥事没有,这是旅行中我觉得最离谱的事。
    • +
    • 云南见朋友(二) :云南之行其实是一场说走就走的旅行。我们去了丽江、大理、昆明,最后一天去见了见大学同学,我的同学到还是老样子,以前爱笑,现在也是。继 2020 年之后的又一次彻夜长谈,听起来她的 2021 也并不是一帆风顺。
    • +
    • 工作(噩梦模式) :在忙碌期抽空出去玩回来的下场就是放假回来忙成狗。跟 PM 提出了延期的想法,被驳回。着急上火加高强度开发,直接给我人中干出了三四个大痘,那段时间照镜子我感觉我就是山本小次郎。
    • +
    +
  • +
  • 九月
      +
    • 工作(噩梦模式) :头疼欲裂,布洛芬止痛片,每个打工人都值得拥有。
    • +
    • 环球影城一日游 :水上表演和剧场表演都非常精彩,想着什么时候能带父母也来看看。
    • +
    +
  • +
  • 十月、十一月、十二月
      +
    • BEC 考试取消 :想考个中级,大概备考了小 3 个月,北京突发疫情,朝阳区考点几乎全军覆没,考试被取消。
    • +
    • 工作(地狱模式) :走在去公司的路上,经常会怀疑自己开启了地狱副本。OKR 加上应接不暇的业务需求,有点让我喘不过气。好消息是按期干完了,坏消息新需求在接下来的时间里依旧骑脸输出。
    • +
    • 游戏笔记本 :游戏下好了才发现老电脑带不起来,等反应回来时发现新的笔记本已经下单了。之前本想自己搭个台式,但是租的房子实在没地方放,搬家也会是问题,故作罢。这是第二次为了游戏买设备了,买完十分后悔,后悔为啥不早点买。感谢暴雪退出中国市场,让我拓展视野去玩其他好游戏。
    • +
    • 阳了 :没想到居家一个月,大门不出二门不迈也能阳。没发烧,嗓子冒烟几天之后好了。
    • +
    +
  • +
+

关于职业

算上今年,作为一名前端开发,已经工作了五年了。那些曾经在网页上吸引我的、跳来跳去的动画,现在也只是静静看着,心中再无波澜,只是想着,这花里胡哨的交互可不能让我们公司设计师给看到(bushi。

+

有的时候很羡慕客户端的开发同学,原生语言写的页面流畅又好用,也不用受到浏览器的层层限制,可以直接调用系统级 api,他们想法和声音也能受到大家的重视,开发出的产品也可以留存。再对比于随写随弃的前端活动页,真的羡慕的不是一点半点…

+

如果职业选择能读档重来,我还会选择做前端吗。当年对这份职业的热忱,也被这几年这些细碎的事情消磨的一干二净。

+

呃啊,被嫌弃的切图仔的一生…

+

全年总结

虽然是忙到脚打后脑勺的一年,但是内心变得更佛系了,很多事变得无所谓了…

+

看了下去年给今年TODO,是早睡早起 + 治好拖延症,嗯…今年还是未完成,留给明年继续吧…

+ +
+ + + + + + + +
+ + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md new file mode 100644 index 00000000..71151450 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# Kuro-P.github.io +My blog record. [https://kuro-p.github.io/](https://kuro-p.github.io/) diff --git a/about/index.html b/about/index.html new file mode 100644 index 00000000..3ad7aa61 --- /dev/null +++ b/about/index.html @@ -0,0 +1,398 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 关于我 | Daily record + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + + + +
+ + + + + +
+
+ +

关于我 +

+ + + +
+ + + + +
+
+我毕业于一所技术院校辽宁工程技术大学,是一名通信学子。 +大一的时候学做 ppt,大二的时候搞 Ps 和 Ae ,大三上学期无意间被网页上的动画效果吸引,大四的时候迈入了前端的坑。 + +

关于通信

上大学的时候,同学们都调侃,“为啥通信的学生要学这么多,几乎电信学院的各个专业各科我们都会掺和一脚”。大三上专业课的时候,学习信号与系统通信原理移动通信,…铺天盖地的数学砸下来,才知道:“哦,原来通信是搞数学的。”
用专业的话说,通信也是考量人发量的一门学科。

+

关于前端

一入前端深似海,从此学习常相伴。
前端知识面广而杂,计算机专业基础知识的欠缺,让我无时不刻觉得我应该在土里,不是在前端的坑底认真学习的重要性。
Good good study, day day up ╰( ̄▽ ̄)╭ .

+

关于我

大三的时候,身边的大多数同学决定考研,继续研究数学通信。
而当时的我,一手掐着前端的学习资料,一手抱着通信课本,毅然决然的准备成为一名准前端er。
现在,我在一家互联网金融公司继续挖坑、填坑。
现在,我在一家二手交易平台继续打酱油。
现在,我在一家社区网站继续修修补补。

+

关于辽工大

辽工大坐落在葫芦岛兴城,四周山水环绕,风景秀美。
除了夏日太热,冬日太冷,平常风太大以外,几乎没有缺点。
工大er们大多热情、优秀,还很逗比

+ +
+ + + +
+ + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2018/09/index.html b/archives/2018/09/index.html new file mode 100644 index 00000000..8d8c01bd --- /dev/null +++ b/archives/2018/09/index.html @@ -0,0 +1,416 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2018 +
+ + + + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2018/11/index.html b/archives/2018/11/index.html new file mode 100644 index 00000000..4fbb80d6 --- /dev/null +++ b/archives/2018/11/index.html @@ -0,0 +1,436 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2018 +
+ + + + + + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2018/12/index.html b/archives/2018/12/index.html new file mode 100644 index 00000000..a11d0a29 --- /dev/null +++ b/archives/2018/12/index.html @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2018 +
+ + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2018/index.html b/archives/2018/index.html new file mode 100644 index 00000000..6e05a576 --- /dev/null +++ b/archives/2018/index.html @@ -0,0 +1,496 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2018 +
+ + + + + + + + + + + + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2019/01/index.html b/archives/2019/01/index.html new file mode 100644 index 00000000..e3feccd5 --- /dev/null +++ b/archives/2019/01/index.html @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2019 +
+ + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2019/04/index.html b/archives/2019/04/index.html new file mode 100644 index 00000000..8497a414 --- /dev/null +++ b/archives/2019/04/index.html @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2019 +
+ + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2019/05/index.html b/archives/2019/05/index.html new file mode 100644 index 00000000..99e19e92 --- /dev/null +++ b/archives/2019/05/index.html @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2019 +
+ + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2019/06/index.html b/archives/2019/06/index.html new file mode 100644 index 00000000..9f65072e --- /dev/null +++ b/archives/2019/06/index.html @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2019 +
+ + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2019/07/index.html b/archives/2019/07/index.html new file mode 100644 index 00000000..bcdf7c6a --- /dev/null +++ b/archives/2019/07/index.html @@ -0,0 +1,416 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2019 +
+ + + + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2019/08/index.html b/archives/2019/08/index.html new file mode 100644 index 00000000..27ef8bb3 --- /dev/null +++ b/archives/2019/08/index.html @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2019 +
+ + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2019/10/index.html b/archives/2019/10/index.html new file mode 100644 index 00000000..092fc322 --- /dev/null +++ b/archives/2019/10/index.html @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2019 +
+ + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2019/11/index.html b/archives/2019/11/index.html new file mode 100644 index 00000000..ddaaec10 --- /dev/null +++ b/archives/2019/11/index.html @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2019 +
+ + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2019/12/index.html b/archives/2019/12/index.html new file mode 100644 index 00000000..cbfee3ea --- /dev/null +++ b/archives/2019/12/index.html @@ -0,0 +1,416 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2019 +
+ + + + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2019/index.html b/archives/2019/index.html new file mode 100644 index 00000000..da497d7b --- /dev/null +++ b/archives/2019/index.html @@ -0,0 +1,579 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2019 +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2019/page/2/index.html b/archives/2019/page/2/index.html new file mode 100644 index 00000000..23f97ef2 --- /dev/null +++ b/archives/2019/page/2/index.html @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2019 +
+ + + + +
+
+ + + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2020/01/index.html b/archives/2020/01/index.html new file mode 100644 index 00000000..b9749f5b --- /dev/null +++ b/archives/2020/01/index.html @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2020 +
+ + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2020/07/index.html b/archives/2020/07/index.html new file mode 100644 index 00000000..fb823e2c --- /dev/null +++ b/archives/2020/07/index.html @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2020 +
+ + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2020/12/index.html b/archives/2020/12/index.html new file mode 100644 index 00000000..9de93e58 --- /dev/null +++ b/archives/2020/12/index.html @@ -0,0 +1,416 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2020 +
+ + + + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2020/index.html b/archives/2020/index.html new file mode 100644 index 00000000..0597cb68 --- /dev/null +++ b/archives/2020/index.html @@ -0,0 +1,456 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2020 +
+ + + + + + + + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2021/03/index.html b/archives/2021/03/index.html new file mode 100644 index 00000000..44a547b3 --- /dev/null +++ b/archives/2021/03/index.html @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2021 +
+ + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2021/04/index.html b/archives/2021/04/index.html new file mode 100644 index 00000000..7ece5ace --- /dev/null +++ b/archives/2021/04/index.html @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2021 +
+ + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2021/09/index.html b/archives/2021/09/index.html new file mode 100644 index 00000000..4ba41d63 --- /dev/null +++ b/archives/2021/09/index.html @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2021 +
+ + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2021/12/index.html b/archives/2021/12/index.html new file mode 100644 index 00000000..60b752ed --- /dev/null +++ b/archives/2021/12/index.html @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2021 +
+ + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2021/index.html b/archives/2021/index.html new file mode 100644 index 00000000..bf165590 --- /dev/null +++ b/archives/2021/index.html @@ -0,0 +1,456 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2021 +
+ + + + + + + + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2022/03/index.html b/archives/2022/03/index.html new file mode 100644 index 00000000..8c93d141 --- /dev/null +++ b/archives/2022/03/index.html @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2022 +
+ + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2022/06/index.html b/archives/2022/06/index.html new file mode 100644 index 00000000..12ed1e6e --- /dev/null +++ b/archives/2022/06/index.html @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2022 +
+ + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2022/index.html b/archives/2022/index.html new file mode 100644 index 00000000..e61f2d9b --- /dev/null +++ b/archives/2022/index.html @@ -0,0 +1,416 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2022 +
+ + + + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2023/01/index.html b/archives/2023/01/index.html new file mode 100644 index 00000000..a9f1c920 --- /dev/null +++ b/archives/2023/01/index.html @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2023 +
+ + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2023/index.html b/archives/2023/index.html new file mode 100644 index 00000000..71639ec9 --- /dev/null +++ b/archives/2023/index.html @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2023 +
+ + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/index.html b/archives/index.html new file mode 100644 index 00000000..922ed017 --- /dev/null +++ b/archives/index.html @@ -0,0 +1,588 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2023 +
+ + +
+ 2022 +
+ + + + +
+ 2021 +
+ + + + + + + + +
+ 2020 +
+ + + + + + + + +
+
+ + + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/page/2/index.html b/archives/page/2/index.html new file mode 100644 index 00000000..5861cd19 --- /dev/null +++ b/archives/page/2/index.html @@ -0,0 +1,582 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2020 +
+ + +
+ 2019 +
+ + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/page/3/index.html b/archives/page/3/index.html new file mode 100644 index 00000000..196a7733 --- /dev/null +++ b/archives/page/3/index.html @@ -0,0 +1,542 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2019 +
+ + + + +
+ 2018 +
+ + + + + + + + + + + + + + +
+
+ + + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/books/index.html b/books/index.html new file mode 100644 index 00000000..a24df93a --- /dev/null +++ b/books/index.html @@ -0,0 +1,625 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 读书 | Daily record + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+ +

读书 +

+ + + +
+ + + + +
+
+

+
+ + + + +
+
+ +
+ 首页 + 上一页 + 1 / 1 + 下一页 + 尾页 +
+ + + + +
+ + + +
+ + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/categories/App/index.html b/categories/App/index.html new file mode 100644 index 00000000..da787bca --- /dev/null +++ b/categories/App/index.html @@ -0,0 +1,397 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 分类: App | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+

App + 分类 +

+
+ + +
+ 2022 +
+ + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/categories/git/index.html b/categories/git/index.html new file mode 100644 index 00000000..02d06f97 --- /dev/null +++ b/categories/git/index.html @@ -0,0 +1,397 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 分类: git | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+

git + 分类 +

+
+ + +
+ 2018 +
+ + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/categories/index.html b/categories/index.html new file mode 100644 index 00000000..2b3e473a --- /dev/null +++ b/categories/index.html @@ -0,0 +1,397 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 分类 | Daily record + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + + + +
+ + + + + +
+
+ +

分类 +

+ + + +
+ + + + +
+ + +
+ + + +
+ + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/categories/\345\205\266\344\273\226\345\260\217\347\273\223/index.html" "b/categories/\345\205\266\344\273\226\345\260\217\347\273\223/index.html" new file mode 100644 index 00000000..eda3d810 --- /dev/null +++ "b/categories/\345\205\266\344\273\226\345\260\217\347\273\223/index.html" @@ -0,0 +1,417 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 分类: 其他小结 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+

其他小结 + 分类 +

+
+ + +
+ 2018 +
+ + + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/categories/\345\211\215\347\253\257/CSS/index.html" "b/categories/\345\211\215\347\253\257/CSS/index.html" new file mode 100644 index 00000000..a8664fe3 --- /dev/null +++ "b/categories/\345\211\215\347\253\257/CSS/index.html" @@ -0,0 +1,397 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 分类: CSS | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+

CSS + 分类 +

+
+ + +
+ 2019 +
+ + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/categories/\345\211\215\347\253\257/JavaScript/index.html" "b/categories/\345\211\215\347\253\257/JavaScript/index.html" new file mode 100644 index 00000000..9bbcbc4b --- /dev/null +++ "b/categories/\345\211\215\347\253\257/JavaScript/index.html" @@ -0,0 +1,440 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 分类: JavaScript | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+

JavaScript + 分类 +

+
+ + +
+ 2020 +
+ + +
+ 2019 +
+ + + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/categories/\345\211\215\347\253\257/PWA/index.html" "b/categories/\345\211\215\347\253\257/PWA/index.html" new file mode 100644 index 00000000..a417d049 --- /dev/null +++ "b/categories/\345\211\215\347\253\257/PWA/index.html" @@ -0,0 +1,420 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 分类: PWA | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+

PWA + 分类 +

+
+ + +
+ 2020 +
+ + +
+ 2019 +
+ + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/categories/\345\211\215\347\253\257/index.html" "b/categories/\345\211\215\347\253\257/index.html" new file mode 100644 index 00000000..47388368 --- /dev/null +++ "b/categories/\345\211\215\347\253\257/index.html" @@ -0,0 +1,563 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 分类: 前端 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+

前端 + 分类 +

+
+ + +
+ 2021 +
+ + + + +
+ 2020 +
+ + + + +
+ 2019 +
+ + + + + + + + + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/categories/\345\211\215\347\253\257/\345\276\256\344\277\241\345\274\200\345\217\221\347\233\270\345\205\263/index.html" "b/categories/\345\211\215\347\253\257/\345\276\256\344\277\241\345\274\200\345\217\221\347\233\270\345\205\263/index.html" new file mode 100644 index 00000000..21a1f070 --- /dev/null +++ "b/categories/\345\211\215\347\253\257/\345\276\256\344\277\241\345\274\200\345\217\221\347\233\270\345\205\263/index.html" @@ -0,0 +1,397 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 分类: 微信开发相关 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+

微信开发相关 + 分类 +

+
+ + +
+ 2021 +
+ + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/categories/\345\220\216\347\253\257/index.html" "b/categories/\345\220\216\347\253\257/index.html" new file mode 100644 index 00000000..630b35b7 --- /dev/null +++ "b/categories/\345\220\216\347\253\257/index.html" @@ -0,0 +1,397 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 分类: 后端 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+

后端 + 分类 +

+
+ + +
+ 2020 +
+ + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/categories/\345\244\247\346\225\260\346\215\256/index.html" "b/categories/\345\244\247\346\225\260\346\215\256/index.html" new file mode 100644 index 00000000..5dd0f648 --- /dev/null +++ "b/categories/\345\244\247\346\225\260\346\215\256/index.html" @@ -0,0 +1,397 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 分类: 大数据 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+

大数据 + 分类 +

+
+ + +
+ 2019 +
+ + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/categories/\345\276\256\344\277\241\345\274\200\345\217\221\347\233\270\345\205\263/index.html" "b/categories/\345\276\256\344\277\241\345\274\200\345\217\221\347\233\270\345\205\263/index.html" new file mode 100644 index 00000000..4a804e12 --- /dev/null +++ "b/categories/\345\276\256\344\277\241\345\274\200\345\217\221\347\233\270\345\205\263/index.html" @@ -0,0 +1,397 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 分类: 微信开发相关 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+

微信开发相关 + 分类 +

+
+ + +
+ 2019 +
+ + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/categories/\345\276\256\344\277\241\345\274\200\345\217\221\347\233\270\345\205\263/\345\205\266\344\273\226\345\260\217\347\273\223/index.html" "b/categories/\345\276\256\344\277\241\345\274\200\345\217\221\347\233\270\345\205\263/\345\205\266\344\273\226\345\260\217\347\273\223/index.html" new file mode 100644 index 00000000..6953eedd --- /dev/null +++ "b/categories/\345\276\256\344\277\241\345\274\200\345\217\221\347\233\270\345\205\263/\345\205\266\344\273\226\345\260\217\347\273\223/index.html" @@ -0,0 +1,397 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 分类: 其他小结 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+

其他小结 + 分类 +

+
+ + +
+ 2019 +
+ + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/categories/\347\233\221\346\216\247/index.html" "b/categories/\347\233\221\346\216\247/index.html" new file mode 100644 index 00000000..3e646e12 --- /dev/null +++ "b/categories/\347\233\221\346\216\247/index.html" @@ -0,0 +1,397 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 分类: 监控 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+

监控 + 分类 +

+
+ + +
+ 2019 +
+ + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/categories/\350\256\241\347\256\227\346\234\272\347\233\270\345\205\263\347\237\245\350\257\206/index.html" "b/categories/\350\256\241\347\256\227\346\234\272\347\233\270\345\205\263\347\237\245\350\257\206/index.html" new file mode 100644 index 00000000..fc685b55 --- /dev/null +++ "b/categories/\350\256\241\347\256\227\346\234\272\347\233\270\345\205\263\347\237\245\350\257\206/index.html" @@ -0,0 +1,463 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 分类: 计算机相关知识 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+

计算机相关知识 + 分类 +

+
+ + +
+ 2022 +
+ + +
+ 2019 +
+ + + + +
+ 2018 +
+ + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/categories/\351\227\262\346\232\207\350\257\273\347\211\251/index.html" "b/categories/\351\227\262\346\232\207\350\257\273\347\211\251/index.html" new file mode 100644 index 00000000..37c56c82 --- /dev/null +++ "b/categories/\351\227\262\346\232\207\350\257\273\347\211\251/index.html" @@ -0,0 +1,420 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 分类: 闲暇读物 | Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+

闲暇读物 + 分类 +

+
+ + +
+ 2021 +
+ + +
+ 2018 +
+ + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/css/main.css b/css/main.css new file mode 100644 index 00000000..7885fbc0 --- /dev/null +++ b/css/main.css @@ -0,0 +1,2777 @@ +:root { + --body-bg-color: #f5f7f9; + --content-bg-color: #fff; + --card-bg-color: #f5f5f5; + --text-color: #555; + --blockquote-color: #666; + --link-color: #555; + --link-hover-color: #222; + --brand-color: #fff; + --brand-hover-color: #fff; + --table-row-odd-bg-color: #f9f9f9; + --table-row-hover-bg-color: #f5f5f5; + --menu-item-bg-color: #f5f5f5; + --btn-default-bg: #fff; + --btn-default-color: #555; + --btn-default-border-color: #555; + --btn-default-hover-bg: #222; + --btn-default-hover-color: #fff; + --btn-default-hover-border-color: #222; +} +@media (prefers-color-scheme: dark) { + :root { + --body-bg-color: #282828; + --content-bg-color: #333; + --card-bg-color: #555; + --text-color: #ccc; + --blockquote-color: #bbb; + --link-color: #ccc; + --link-hover-color: #eee; + --brand-color: #ddd; + --brand-hover-color: #ddd; + --table-row-odd-bg-color: #282828; + --table-row-hover-bg-color: #363636; + --menu-item-bg-color: #555; + --btn-default-bg: #222; + --btn-default-color: #ccc; + --btn-default-border-color: #555; + --btn-default-hover-bg: #666; + --btn-default-hover-color: #ccc; + --btn-default-hover-border-color: #666; + } + img { + opacity: 0.75; + } + img:hover { + opacity: 0.9; + } +} +html { + line-height: 1.15; /* 1 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} +body { + margin: 0; +} +main { + display: block; +} +h1 { + font-size: 2em; + margin: 0.67em 0; +} +hr { + box-sizing: content-box; /* 1 */ + height: 0; /* 1 */ + overflow: visible; /* 2 */ +} +pre { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} +a { + background: transparent; +} +abbr[title] { + border-bottom: none; /* 1 */ + text-decoration: underline; /* 2 */ + text-decoration: underline dotted; /* 2 */ +} +b, +strong { + font-weight: bolder; +} +code, +kbd, +samp { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} +small { + font-size: 80%; +} +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} +sub { + bottom: -0.25em; +} +sup { + top: -0.5em; +} +img { + border-style: none; +} +button, +input, +optgroup, +select, +textarea { + font-family: inherit; /* 1 */ + font-size: 100%; /* 1 */ + line-height: 1.15; /* 1 */ + margin: 0; /* 2 */ +} +button, +input { +/* 1 */ + overflow: visible; +} +button, +select { +/* 1 */ + text-transform: none; +} +button, +[type='button'], +[type='reset'], +[type='submit'] { + -webkit-appearance: button; +} +button::-moz-focus-inner, +[type='button']::-moz-focus-inner, +[type='reset']::-moz-focus-inner, +[type='submit']::-moz-focus-inner { + border-style: none; + padding: 0; +} +button:-moz-focusring, +[type='button']:-moz-focusring, +[type='reset']:-moz-focusring, +[type='submit']:-moz-focusring { + outline: 1px dotted ButtonText; +} +fieldset { + padding: 0.35em 0.75em 0.625em; +} +legend { + box-sizing: border-box; /* 1 */ + color: inherit; /* 2 */ + display: table; /* 1 */ + max-width: 100%; /* 1 */ + padding: 0; /* 3 */ + white-space: normal; /* 1 */ +} +progress { + vertical-align: baseline; +} +textarea { + overflow: auto; +} +[type='checkbox'], +[type='radio'] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} +[type='number']::-webkit-inner-spin-button, +[type='number']::-webkit-outer-spin-button { + height: auto; +} +[type='search'] { + outline-offset: -2px; /* 2 */ + -webkit-appearance: textfield; /* 1 */ +} +[type='search']::-webkit-search-decoration { + -webkit-appearance: none; +} +::-webkit-file-upload-button { + font: inherit; /* 2 */ + -webkit-appearance: button; /* 1 */ +} +details { + display: block; +} +summary { + display: list-item; +} +template { + display: none; +} +[hidden] { + display: none; +} +::selection { + background: #262a30; + color: #eee; +} +html, +body { + height: 100%; +} +body { + background: var(--body-bg-color); + color: var(--text-color); + font-family: "PingFang SC", "Microsoft YaHei", sans-serif; + font-size: 0.87em; + line-height: 2; +} +@media (max-width: 991px) { + body { + padding-left: 0 !important; + padding-right: 0 !important; + } +} +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: "PingFang SC", "Microsoft YaHei", sans-serif; + font-weight: bold; + line-height: 1.5; + margin: 20px 0 15px; +} +h1 { + font-size: 1.5em; +} +h2 { + font-size: 1.375em; +} +h3 { + font-size: 1.25em; +} +h4 { + font-size: 1.125em; +} +h5 { + font-size: 1em; +} +h6 { + font-size: 0.875em; +} +p { + margin: 0 0 20px 0; +} +a, +span.exturl { + border-bottom: 1px solid #999; + color: var(--link-color); + outline: 0; + text-decoration: none; + overflow-wrap: break-word; + word-wrap: break-word; + cursor: pointer; +} +a:hover, +span.exturl:hover { + border-bottom-color: var(--link-hover-color); + color: var(--link-hover-color); +} +iframe, +img, +video { + display: block; + margin-left: auto; + margin-right: auto; + max-width: 100%; +} +hr { + background-image: repeating-linear-gradient(-45deg, #ddd, #ddd 4px, transparent 4px, transparent 8px); + border: 0; + height: 3px; + margin: 40px 0; +} +blockquote { + border-left: 4px solid #ddd; + color: var(--blockquote-color); + margin: 0; + padding: 0 15px; +} +blockquote cite::before { + content: '-'; + padding: 0 5px; +} +dt { + font-weight: bold; +} +dd { + margin: 0; + padding: 0; +} +kbd { + background-color: #f5f5f5; + background-image: linear-gradient(#eee, #fff, #eee); + border: 1px solid #ccc; + border-radius: 0.2em; + box-shadow: 0.1em 0.1em 0.2em rgba(0,0,0,0.1); + color: #555; + font-family: inherit; + padding: 0.1em 0.3em; + white-space: nowrap; +} +.table-container { + overflow: auto; +} +table { + border-collapse: collapse; + border-spacing: 0; + font-size: 0.875em; + margin: 0 0 20px 0; + width: 100%; +} +tbody tr:nth-of-type(odd) { + background: var(--table-row-odd-bg-color); +} +tbody tr:hover { + background: var(--table-row-hover-bg-color); +} +caption, +th, +td { + font-weight: normal; + padding: 8px; + vertical-align: middle; +} +th, +td { + border: 1px solid #ddd; + border-bottom: 3px solid #ddd; +} +th { + font-weight: 700; + padding-bottom: 10px; +} +td { + border-bottom-width: 1px; +} +.btn { + background: var(--btn-default-bg); + border: 2px solid var(--btn-default-border-color); + border-radius: 2px; + color: var(--btn-default-color); + display: inline-block; + font-size: 0.875em; + line-height: 2; + padding: 0 20px; + text-decoration: none; + transition-property: background-color; + transition-delay: 0s; + transition-duration: 0.2s; + transition-timing-function: ease-in-out; +} +.btn:hover { + background: var(--btn-default-hover-bg); + border-color: var(--btn-default-hover-border-color); + color: var(--btn-default-hover-color); +} +.btn + .btn { + margin: 0 0 8px 8px; +} +.btn .fa-fw { + text-align: left; + width: 1.285714285714286em; +} +.toggle { + line-height: 0; +} +.toggle .toggle-line { + background: #fff; + display: inline-block; + height: 2px; + left: 0; + position: relative; + top: 0; + transition: all 0.4s; + vertical-align: top; + width: 100%; +} +.toggle .toggle-line:not(:first-child) { + margin-top: 3px; +} +.toggle.toggle-arrow .toggle-line-first { + left: 50%; + top: 2px; + transform: rotate(45deg); + width: 50%; +} +.toggle.toggle-arrow .toggle-line-middle { + left: 2px; + width: 90%; +} +.toggle.toggle-arrow .toggle-line-last { + left: 50%; + top: -2px; + transform: rotate(-45deg); + width: 50%; +} +.toggle.toggle-close .toggle-line-first { + transform: rotate(-45deg); + top: 5px; +} +.toggle.toggle-close .toggle-line-middle { + opacity: 0; +} +.toggle.toggle-close .toggle-line-last { + transform: rotate(45deg); + top: -5px; +} +.highlight, +pre { + background: #f7f7f7; + color: #4d4d4c; + line-height: 1.6; + margin: 0 auto 20px; +} +pre, +code { + font-family: consolas, Menlo, monospace, "PingFang SC", "Microsoft YaHei"; +} +code { + background: #eee; + border-radius: 3px; + color: #555; + padding: 2px 4px; + overflow-wrap: break-word; + word-wrap: break-word; +} +.highlight *::selection { + background: #d6d6d6; +} +.highlight pre { + border: 0; + margin: 0; + padding: 10px 0; +} +.highlight table { + border: 0; + margin: 0; + width: auto; +} +.highlight td { + border: 0; + padding: 0; +} +.highlight figcaption { + background: #eff2f3; + color: #4d4d4c; + display: flex; + font-size: 0.875em; + justify-content: space-between; + line-height: 1.2; + padding: 0.5em; +} +.highlight figcaption a { + color: #4d4d4c; +} +.highlight figcaption a:hover { + border-bottom-color: #4d4d4c; +} +.highlight .gutter { + -moz-user-select: none; + -ms-user-select: none; + -webkit-user-select: none; + user-select: none; +} +.highlight .gutter pre { + background: #eff2f3; + color: #869194; + padding-left: 10px; + padding-right: 10px; + text-align: right; +} +.highlight .code pre { + background: #f7f7f7; + padding-left: 10px; + width: 100%; +} +.gist table { + width: auto; +} +.gist table td { + border: 0; +} +pre { + overflow: auto; + padding: 10px; +} +pre code { + background: none; + color: #4d4d4c; + font-size: 0.875em; + padding: 0; + text-shadow: none; +} +pre .deletion { + background: #fdd; +} +pre .addition { + background: #dfd; +} +pre .meta { + color: #eab700; + -moz-user-select: none; + -ms-user-select: none; + -webkit-user-select: none; + user-select: none; +} +pre .comment { + color: #8e908c; +} +pre .variable, +pre .attribute, +pre .tag, +pre .name, +pre .regexp, +pre .ruby .constant, +pre .xml .tag .title, +pre .xml .pi, +pre .xml .doctype, +pre .html .doctype, +pre .css .id, +pre .css .class, +pre .css .pseudo { + color: #c82829; +} +pre .number, +pre .preprocessor, +pre .built_in, +pre .builtin-name, +pre .literal, +pre .params, +pre .constant, +pre .command { + color: #f5871f; +} +pre .ruby .class .title, +pre .css .rules .attribute, +pre .string, +pre .symbol, +pre .value, +pre .inheritance, +pre .header, +pre .ruby .symbol, +pre .xml .cdata, +pre .special, +pre .formula { + color: #718c00; +} +pre .title, +pre .css .hexcolor { + color: #3e999f; +} +pre .function, +pre .python .decorator, +pre .python .title, +pre .ruby .function .title, +pre .ruby .title .keyword, +pre .perl .sub, +pre .javascript .title, +pre .coffeescript .title { + color: #4271ae; +} +pre .keyword, +pre .javascript .function { + color: #8959a8; +} +.blockquote-center { + border-left: none; + margin: 40px 0; + padding: 0; + position: relative; + text-align: center; +} +.blockquote-center .fa { + display: block; + opacity: 0.6; + position: absolute; + width: 100%; +} +.blockquote-center .fa-quote-left { + border-top: 1px solid #ccc; + text-align: left; + top: -20px; +} +.blockquote-center .fa-quote-right { + border-bottom: 1px solid #ccc; + text-align: right; + bottom: -20px; +} +.blockquote-center p, +.blockquote-center div { + text-align: center; +} +.post-body .group-picture img { + margin: 0 auto; + padding: 0 3px; +} +.group-picture-row { + margin-bottom: 6px; + overflow: hidden; +} +.group-picture-column { + float: left; + margin-bottom: 10px; +} +.post-body .label { + color: #555; + display: inline; + padding: 0 2px; +} +.post-body .label.default { + background: #f0f0f0; +} +.post-body .label.primary { + background: #efe6f7; +} +.post-body .label.info { + background: #e5f2f8; +} +.post-body .label.success { + background: #e7f4e9; +} +.post-body .label.warning { + background: #fcf6e1; +} +.post-body .label.danger { + background: #fae8eb; +} +.post-body .tabs { + margin-bottom: 20px; +} +.post-body .tabs, +.tabs-comment { + display: block; + padding-top: 10px; + position: relative; +} +.post-body .tabs ul.nav-tabs, +.tabs-comment ul.nav-tabs { + display: flex; + flex-wrap: wrap; + margin: 0; + margin-bottom: -1px; + padding: 0; +} +@media (max-width: 413px) { + .post-body .tabs ul.nav-tabs, + .tabs-comment ul.nav-tabs { + display: block; + margin-bottom: 5px; + } +} +.post-body .tabs ul.nav-tabs li.tab, +.tabs-comment ul.nav-tabs li.tab { + border-bottom: 1px solid #ddd; + border-left: 1px solid transparent; + border-right: 1px solid transparent; + border-top: 3px solid transparent; + flex-grow: 1; + list-style-type: none; + border-radius: 0 0 0 0; +} +@media (max-width: 413px) { + .post-body .tabs ul.nav-tabs li.tab, + .tabs-comment ul.nav-tabs li.tab { + border-bottom: 1px solid transparent; + border-left: 3px solid transparent; + border-right: 1px solid transparent; + border-top: 1px solid transparent; + } +} +@media (max-width: 413px) { + .post-body .tabs ul.nav-tabs li.tab, + .tabs-comment ul.nav-tabs li.tab { + border-radius: 0; + } +} +.post-body .tabs ul.nav-tabs li.tab a, +.tabs-comment ul.nav-tabs li.tab a { + border-bottom: initial; + display: block; + line-height: 1.8; + outline: 0; + padding: 0.25em 0.75em; + text-align: center; + transition-delay: 0s; + transition-duration: 0.2s; + transition-timing-function: ease-out; +} +.post-body .tabs ul.nav-tabs li.tab a i, +.tabs-comment ul.nav-tabs li.tab a i { + width: 1.285714285714286em; +} +.post-body .tabs ul.nav-tabs li.tab.active, +.tabs-comment ul.nav-tabs li.tab.active { + border-bottom: 1px solid transparent; + border-left: 1px solid #ddd; + border-right: 1px solid #ddd; + border-top: 3px solid #fc6423; +} +@media (max-width: 413px) { + .post-body .tabs ul.nav-tabs li.tab.active, + .tabs-comment ul.nav-tabs li.tab.active { + border-bottom: 1px solid #ddd; + border-left: 3px solid #fc6423; + border-right: 1px solid #ddd; + border-top: 1px solid #ddd; + } +} +.post-body .tabs ul.nav-tabs li.tab.active a, +.tabs-comment ul.nav-tabs li.tab.active a { + color: var(--link-color); + cursor: default; +} +.post-body .tabs .tab-content .tab-pane, +.tabs-comment .tab-content .tab-pane { + border: 1px solid #ddd; + border-top: 0; + padding: 20px 20px 0 20px; + border-radius: 0; +} +.post-body .tabs .tab-content .tab-pane:not(.active), +.tabs-comment .tab-content .tab-pane:not(.active) { + display: none; +} +.post-body .tabs .tab-content .tab-pane.active, +.tabs-comment .tab-content .tab-pane.active { + display: block; +} +.post-body .tabs .tab-content .tab-pane.active:nth-of-type(1), +.tabs-comment .tab-content .tab-pane.active:nth-of-type(1) { + border-radius: 0 0 0 0; +} +@media (max-width: 413px) { + .post-body .tabs .tab-content .tab-pane.active:nth-of-type(1), + .tabs-comment .tab-content .tab-pane.active:nth-of-type(1) { + border-radius: 0; + } +} +.post-body .note { + border-radius: 3px; + margin-bottom: 20px; + padding: 1em; + position: relative; + border: 1px solid #eee; + border-left-width: 5px; +} +.post-body .note h2, +.post-body .note h3, +.post-body .note h4, +.post-body .note h5, +.post-body .note h6 { + margin-top: 0; + border-bottom: initial; + margin-bottom: 0; + padding-top: 0; +} +.post-body .note p:first-child, +.post-body .note ul:first-child, +.post-body .note ol:first-child, +.post-body .note table:first-child, +.post-body .note pre:first-child, +.post-body .note blockquote:first-child, +.post-body .note img:first-child { + margin-top: 0; +} +.post-body .note p:last-child, +.post-body .note ul:last-child, +.post-body .note ol:last-child, +.post-body .note table:last-child, +.post-body .note pre:last-child, +.post-body .note blockquote:last-child, +.post-body .note img:last-child { + margin-bottom: 0; +} +.post-body .note.default { + border-left-color: #777; +} +.post-body .note.default h2, +.post-body .note.default h3, +.post-body .note.default h4, +.post-body .note.default h5, +.post-body .note.default h6 { + color: #777; +} +.post-body .note.primary { + border-left-color: #6f42c1; +} +.post-body .note.primary h2, +.post-body .note.primary h3, +.post-body .note.primary h4, +.post-body .note.primary h5, +.post-body .note.primary h6 { + color: #6f42c1; +} +.post-body .note.info { + border-left-color: #428bca; +} +.post-body .note.info h2, +.post-body .note.info h3, +.post-body .note.info h4, +.post-body .note.info h5, +.post-body .note.info h6 { + color: #428bca; +} +.post-body .note.success { + border-left-color: #5cb85c; +} +.post-body .note.success h2, +.post-body .note.success h3, +.post-body .note.success h4, +.post-body .note.success h5, +.post-body .note.success h6 { + color: #5cb85c; +} +.post-body .note.warning { + border-left-color: #f0ad4e; +} +.post-body .note.warning h2, +.post-body .note.warning h3, +.post-body .note.warning h4, +.post-body .note.warning h5, +.post-body .note.warning h6 { + color: #f0ad4e; +} +.post-body .note.danger { + border-left-color: #d9534f; +} +.post-body .note.danger h2, +.post-body .note.danger h3, +.post-body .note.danger h4, +.post-body .note.danger h5, +.post-body .note.danger h6 { + color: #d9534f; +} +.pagination .prev, +.pagination .next, +.pagination .page-number, +.pagination .space { + display: inline-block; + margin: 0 10px; + padding: 0 11px; + position: relative; + top: -1px; +} +@media (max-width: 767px) { + .pagination .prev, + .pagination .next, + .pagination .page-number, + .pagination .space { + margin: 0 5px; + } +} +.pagination { + border-top: 1px solid #eee; + margin: 120px 0 0; + text-align: center; +} +.pagination .prev, +.pagination .next, +.pagination .page-number { + border-bottom: 0; + border-top: 1px solid #eee; + transition-property: border-color; + transition-delay: 0s; + transition-duration: 0.2s; + transition-timing-function: ease-in-out; +} +.pagination .prev:hover, +.pagination .next:hover, +.pagination .page-number:hover { + border-top-color: #222; +} +.pagination .space { + margin: 0; + padding: 0; +} +.pagination .prev { + margin-left: 0; +} +.pagination .next { + margin-right: 0; +} +.pagination .page-number.current { + background: #ccc; + border-top-color: #ccc; + color: #fff; +} +@media (max-width: 767px) { + .pagination { + border-top: none; + } + .pagination .prev, + .pagination .next, + .pagination .page-number { + border-bottom: 1px solid #eee; + border-top: 0; + margin-bottom: 10px; + padding: 0 10px; + } + .pagination .prev:hover, + .pagination .next:hover, + .pagination .page-number:hover { + border-bottom-color: #222; + } +} +.comments { + margin-top: 60px; + overflow: hidden; +} +.comment-button-group { + display: flex; + flex-wrap: wrap-reverse; + justify-content: center; + margin: 1em 0; +} +.comment-button-group .comment-button { + margin: 0.1em 0.2em; +} +.comment-button-group .comment-button.active { + background: var(--btn-default-hover-bg); + border-color: var(--btn-default-hover-border-color); + color: var(--btn-default-hover-color); +} +.comment-position { + display: none; +} +.comment-position.active { + display: block; +} +.tabs-comment { + background: var(--content-bg-color); + margin-top: 4em; + padding-top: 0; +} +.tabs-comment .comments { + border: 0; + box-shadow: none; + margin-top: 0; + padding-top: 0; +} +.container { + min-height: 100%; + position: relative; +} +.main-inner { + margin: 0 auto; + width: calc(100% - 20px); +} +@media (min-width: 1200px) { + .main-inner { + width: 1160px; + } +} +@media (min-width: 1600px) { + .main-inner { + width: 73%; + } +} +@media (max-width: 767px) { + .content-wrap { + padding: 0 20px; + } +} +.header { + background: transparent; +} +.header-inner { + margin: 0 auto; + width: calc(100% - 20px); +} +@media (min-width: 1200px) { + .header-inner { + width: 1160px; + } +} +@media (min-width: 1600px) { + .header-inner { + width: 73%; + } +} +.site-brand-container { + display: flex; + flex-shrink: 0; + padding: 0 10px; +} +.headband { + background: #222; + height: 3px; +} +.site-meta { + flex-grow: 1; + text-align: center; +} +@media (max-width: 767px) { + .site-meta { + text-align: center; + } +} +.brand { + border-bottom: none; + color: var(--brand-color); + display: inline-block; + line-height: 1.5em; + padding: 0 40px; + position: relative; +} +.brand:hover { + color: var(--brand-hover-color); +} +.site-title { + font-family: "PingFang SC", "Microsoft YaHei", sans-serif; + font-size: 1.5em; + font-weight: normal; + margin: 0; +} +.site-subtitle { + color: #ddd; + font-size: 0.8125em; + margin: 10px 0; +} +.use-motion .brand { + opacity: 0; +} +.use-motion .site-title, +.use-motion .site-subtitle, +.use-motion .custom-logo-image { + opacity: 0; + position: relative; + top: -10px; +} +.site-nav-toggle, +.site-nav-right { + display: none; +} +@media (max-width: 767px) { + .site-nav-toggle, + .site-nav-right { + display: flex; + flex-direction: column; + justify-content: center; + } +} +.site-nav-toggle .toggle, +.site-nav-right .toggle { + color: var(--text-color); + padding: 10px; + width: 22px; +} +.site-nav-toggle .toggle .toggle-line, +.site-nav-right .toggle .toggle-line { + background: var(--text-color); + border-radius: 1px; +} +.site-nav { + display: block; +} +@media (max-width: 767px) { + .site-nav { + clear: both; + display: none; + } +} +.site-nav.site-nav-on { + display: block; +} +.menu { + margin-top: 20px; + padding-left: 0; + text-align: center; +} +.menu-item { + display: inline-block; + list-style: none; + margin: 0 10px; +} +@media (max-width: 767px) { + .menu-item { + display: block; + margin-top: 10px; + } + .menu-item.menu-item-search { + display: none; + } +} +.menu-item a, +.menu-item span.exturl { + border-bottom: 0; + display: block; + font-size: 0.8125em; + transition-property: border-color; + transition-delay: 0s; + transition-duration: 0.2s; + transition-timing-function: ease-in-out; +} +@media (hover: none) { + .menu-item a:hover, + .menu-item span.exturl:hover { + border-bottom-color: transparent !important; + } +} +.menu-item .fa, +.menu-item .fab, +.menu-item .far, +.menu-item .fas { + margin-right: 8px; +} +.menu-item .badge { + display: inline-block; + font-weight: bold; + line-height: 1; + margin-left: 0.35em; + margin-top: 0.35em; + text-align: center; + white-space: nowrap; +} +@media (max-width: 767px) { + .menu-item .badge { + float: right; + margin-left: 0; + } +} +.menu-item-active a, +.menu .menu-item a:hover, +.menu .menu-item span.exturl:hover { + background: var(--menu-item-bg-color); +} +.use-motion .menu-item { + opacity: 0; +} +.sidebar { + background: #222; + bottom: 0; + box-shadow: inset 0 2px 6px #000; + position: fixed; + top: 0; +} +@media (max-width: 991px) { + .sidebar { + display: none; + } +} +.sidebar-inner { + color: #999; + padding: 18px 10px; + text-align: center; +} +.cc-license { + margin-top: 10px; + text-align: center; +} +.cc-license .cc-opacity { + border-bottom: none; + opacity: 0.7; +} +.cc-license .cc-opacity:hover { + opacity: 0.9; +} +.cc-license img { + display: inline-block; +} +.site-author-image { + border: 1px solid #eee; + display: block; + margin: 0 auto; + max-width: 120px; + padding: 2px; + border-radius: 50%; +} +.site-author-name { + color: var(--text-color); + font-weight: 600; + margin: 0; + text-align: center; +} +.site-description { + color: #999; + font-size: 0.8125em; + margin-top: 0; + text-align: center; +} +.links-of-author { + margin-top: 15px; +} +.links-of-author a, +.links-of-author span.exturl { + border-bottom-color: #555; + display: inline-block; + font-size: 0.8125em; + margin-bottom: 10px; + margin-right: 10px; + vertical-align: middle; +} +.links-of-author a::before, +.links-of-author span.exturl::before { + background: #ff8f7a; + border-radius: 50%; + content: ' '; + display: inline-block; + height: 4px; + margin-right: 3px; + vertical-align: middle; + width: 4px; +} +.sidebar-button { + margin-top: 15px; +} +.sidebar-button a { + border: 1px solid #fc6423; + border-radius: 4px; + color: #fc6423; + display: inline-block; + padding: 0 15px; +} +.sidebar-button a .fa, +.sidebar-button a .fab, +.sidebar-button a .far, +.sidebar-button a .fas { + margin-right: 5px; +} +.sidebar-button a:hover { + background: #fc6423; + border: 1px solid #fc6423; + color: #fff; +} +.sidebar-button a:hover .fa, +.sidebar-button a:hover .fab, +.sidebar-button a:hover .far, +.sidebar-button a:hover .fas { + color: #fff; +} +.links-of-blogroll { + font-size: 0.8125em; + margin-top: 10px; +} +.links-of-blogroll-title { + font-size: 0.875em; + font-weight: 600; + margin-top: 0; +} +.links-of-blogroll-list { + list-style: none; + margin: 0; + padding: 0; +} +#sidebar-dimmer { + display: none; +} +@media (max-width: 767px) { + #sidebar-dimmer { + background: #000; + display: block; + height: 100%; + left: 100%; + opacity: 0; + position: fixed; + top: 0; + width: 100%; + z-index: 1100; + } + .sidebar-active + #sidebar-dimmer { + opacity: 0.7; + transform: translateX(-100%); + transition: opacity 0.5s; + } +} +.sidebar-nav { + margin: 0; + padding-bottom: 20px; + padding-left: 0; +} +.sidebar-nav li { + border-bottom: 1px solid transparent; + color: var(--text-color); + cursor: pointer; + display: inline-block; + font-size: 0.875em; +} +.sidebar-nav li.sidebar-nav-overview { + margin-left: 10px; +} +.sidebar-nav li:hover { + color: #fc6423; +} +.sidebar-nav .sidebar-nav-active { + border-bottom-color: #fc6423; + color: #fc6423; +} +.sidebar-nav .sidebar-nav-active:hover { + color: #fc6423; +} +.sidebar-panel { + display: none; + overflow-x: hidden; + overflow-y: auto; +} +.sidebar-panel-active { + display: block; +} +.sidebar-toggle { + background: #222; + bottom: 45px; + cursor: pointer; + height: 14px; + left: 30px; + padding: 5px; + position: fixed; + width: 14px; + z-index: 1300; +} +@media (max-width: 991px) { + .sidebar-toggle { + left: 20px; + opacity: 0.8; + display: none; + } +} +.sidebar-toggle:hover .toggle-line { + background: #fc6423; +} +.post-toc { + font-size: 0.875em; +} +.post-toc ol { + list-style: none; + margin: 0; + padding: 0 2px 5px 10px; + text-align: left; +} +.post-toc ol > ol { + padding-left: 0; +} +.post-toc ol a { + transition-property: all; + transition-delay: 0s; + transition-duration: 0.2s; + transition-timing-function: ease-in-out; +} +.post-toc .nav-item { + line-height: 1.8; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.post-toc .nav .nav-child { + display: none; +} +.post-toc .nav .active > .nav-child { + display: block; +} +.post-toc .nav .active-current > .nav-child { + display: block; +} +.post-toc .nav .active-current > .nav-child > .nav-item { + display: block; +} +.post-toc .nav .active > a { + border-bottom-color: #fc6423; + color: #fc6423; +} +.post-toc .nav .active-current > a { + color: #fc6423; +} +.post-toc .nav .active-current > a:hover { + color: #fc6423; +} +.site-state { + display: flex; + justify-content: center; + line-height: 1.4; + margin-top: 10px; + overflow: hidden; + text-align: center; + white-space: nowrap; +} +.site-state-item { + padding: 0 15px; +} +.site-state-item:not(:first-child) { + border-left: 1px solid #eee; +} +.site-state-item a { + border-bottom: none; +} +.site-state-item-count { + display: block; + font-size: 1em; + font-weight: 600; + text-align: center; +} +.site-state-item-name { + color: #999; + font-size: 0.8125em; +} +.footer { + color: #999; + font-size: 0.875em; + padding: 20px 0; +} +.footer.footer-fixed { + bottom: 0; + left: 0; + position: absolute; + right: 0; +} +.footer-inner { + box-sizing: border-box; + margin: 0 auto; + text-align: center; + width: calc(100% - 20px); +} +@media (min-width: 1200px) { + .footer-inner { + width: 1160px; + } +} +@media (min-width: 1600px) { + .footer-inner { + width: 73%; + } +} +.languages { + display: inline-block; + font-size: 1.125em; + position: relative; +} +.languages .lang-select-label span { + margin: 0 0.5em; +} +.languages .lang-select { + height: 100%; + left: 0; + opacity: 0; + position: absolute; + top: 0; + width: 100%; +} +.with-love { + color: ; + display: inline-block; + margin: 0 5px; +} +.powered-by, +.theme-info { + display: inline-block; +} +@-moz-keyframes iconAnimate { + 0%, 100% { + transform: scale(1); + } + 10%, 30% { + transform: scale(0.9); + } + 20%, 40%, 60%, 80% { + transform: scale(1.1); + } + 50%, 70% { + transform: scale(1.1); + } +} +@-webkit-keyframes iconAnimate { + 0%, 100% { + transform: scale(1); + } + 10%, 30% { + transform: scale(0.9); + } + 20%, 40%, 60%, 80% { + transform: scale(1.1); + } + 50%, 70% { + transform: scale(1.1); + } +} +@-o-keyframes iconAnimate { + 0%, 100% { + transform: scale(1); + } + 10%, 30% { + transform: scale(0.9); + } + 20%, 40%, 60%, 80% { + transform: scale(1.1); + } + 50%, 70% { + transform: scale(1.1); + } +} +@keyframes iconAnimate { + 0%, 100% { + transform: scale(1); + } + 10%, 30% { + transform: scale(0.9); + } + 20%, 40%, 60%, 80% { + transform: scale(1.1); + } + 50%, 70% { + transform: scale(1.1); + } +} +.back-to-top { + font-size: 12px; + text-align: center; + transition-delay: 0s; + transition-duration: 0.2s; + transition-timing-function: ease-in-out; +} +.back-to-top { + background: #222; + bottom: -100px; + box-sizing: border-box; + color: #fff; + cursor: pointer; + left: 30px; + opacity: 0.6; + padding: 0 6px; + position: fixed; + transition-property: bottom; + z-index: 1300; + width: 24px; +} +.back-to-top span { + display: none; +} +.back-to-top:hover { + color: #fc6423; +} +.back-to-top.back-to-top-on { + bottom: 30px; +} +@media (max-width: 991px) { + .back-to-top { + left: 20px; + opacity: 0.8; + } +} +.post-body { + font-family: "PingFang SC", "Microsoft YaHei", sans-serif; + overflow-wrap: break-word; + word-wrap: break-word; +} +@media (min-width: 1200px) { + .post-body { + font-size: 1.125em; + } +} +.post-body .exturl .fa { + font-size: 0.875em; + margin-left: 4px; +} +.post-body .image-caption, +.post-body .figure .caption { + color: #999; + font-size: 0.875em; + font-weight: bold; + line-height: 1; + margin: -20px auto 15px; + text-align: center; +} +.post-sticky-flag { + display: inline-block; + transform: rotate(30deg); +} +.post-button { + margin-top: 40px; + text-align: center; +} +.use-motion .post-block, +.use-motion .pagination, +.use-motion .comments { + opacity: 0; +} +.use-motion .post-header { + opacity: 0; +} +.use-motion .post-body { + opacity: 0; +} +.use-motion .collection-header { + opacity: 0; +} +.posts-collapse { + margin-left: 35px; + position: relative; +} +@media (max-width: 767px) { + .posts-collapse { + margin-left: 0px; + margin-right: 0px; + } +} +.posts-collapse .collection-title { + font-size: 1.125em; + position: relative; +} +.posts-collapse .collection-title::before { + background: #999; + border: 1px solid #fff; + border-radius: 50%; + content: ' '; + height: 10px; + left: 0; + margin-left: -6px; + margin-top: -4px; + position: absolute; + top: 50%; + width: 10px; +} +.posts-collapse .collection-year { + font-size: 1.5em; + font-weight: bold; + margin: 60px 0; + position: relative; +} +.posts-collapse .collection-year::before { + background: #bbb; + border-radius: 50%; + content: ' '; + height: 8px; + left: 0; + margin-left: -4px; + margin-top: -4px; + position: absolute; + top: 50%; + width: 8px; +} +.posts-collapse .collection-header { + display: block; + margin: 0 0 0 20px; +} +.posts-collapse .collection-header small { + color: #bbb; + margin-left: 5px; +} +.posts-collapse .post-header { + border-bottom: 1px dashed #ccc; + margin: 30px 0; + padding-left: 15px; + position: relative; + transition-property: border; + transition-delay: 0s; + transition-duration: 0.2s; + transition-timing-function: ease-in-out; +} +.posts-collapse .post-header::before { + background: #bbb; + border: 1px solid #fff; + border-radius: 50%; + content: ' '; + height: 6px; + left: 0; + margin-left: -4px; + position: absolute; + top: 0.75em; + transition-property: background; + width: 6px; + transition-delay: 0s; + transition-duration: 0.2s; + transition-timing-function: ease-in-out; +} +.posts-collapse .post-header:hover { + border-bottom-color: #666; +} +.posts-collapse .post-header:hover::before { + background: #222; +} +.posts-collapse .post-meta { + display: inline; + font-size: 0.75em; + margin-right: 10px; +} +.posts-collapse .post-title { + display: inline; +} +.posts-collapse .post-title a, +.posts-collapse .post-title span.exturl { + border-bottom: none; + color: var(--link-color); +} +.posts-collapse .post-title .fa-external-link-alt { + font-size: 0.875em; + margin-left: 5px; +} +.posts-collapse::before { + background: #f5f5f5; + content: ' '; + height: 100%; + left: 0; + margin-left: -2px; + position: absolute; + top: 1.25em; + width: 4px; +} +.post-eof { + background: #ccc; + height: 1px; + margin: 80px auto 60px; + text-align: center; + width: 8%; +} +.post-block:last-of-type .post-eof { + display: none; +} +.content { + padding-top: 40px; +} +@media (min-width: 992px) { + .post-body { + text-align: justify; + } +} +@media (max-width: 991px) { + .post-body { + text-align: justify; + } +} +.post-body h1, +.post-body h2, +.post-body h3, +.post-body h4, +.post-body h5, +.post-body h6 { + padding-top: 10px; +} +.post-body h1 .header-anchor, +.post-body h2 .header-anchor, +.post-body h3 .header-anchor, +.post-body h4 .header-anchor, +.post-body h5 .header-anchor, +.post-body h6 .header-anchor { + border-bottom-style: none; + color: #ccc; + float: right; + margin-left: 10px; + visibility: hidden; +} +.post-body h1 .header-anchor:hover, +.post-body h2 .header-anchor:hover, +.post-body h3 .header-anchor:hover, +.post-body h4 .header-anchor:hover, +.post-body h5 .header-anchor:hover, +.post-body h6 .header-anchor:hover { + color: inherit; +} +.post-body h1:hover .header-anchor, +.post-body h2:hover .header-anchor, +.post-body h3:hover .header-anchor, +.post-body h4:hover .header-anchor, +.post-body h5:hover .header-anchor, +.post-body h6:hover .header-anchor { + visibility: visible; +} +.post-body iframe, +.post-body img, +.post-body video { + margin-bottom: 20px; +} +.post-body .video-container { + height: 0; + margin-bottom: 20px; + overflow: hidden; + padding-top: 75%; + position: relative; + width: 100%; +} +.post-body .video-container iframe, +.post-body .video-container object, +.post-body .video-container embed { + height: 100%; + left: 0; + margin: 0; + position: absolute; + top: 0; + width: 100%; +} +.post-gallery { + align-items: center; + display: grid; + grid-gap: 10px; + grid-template-columns: 1fr 1fr 1fr; + margin-bottom: 20px; +} +@media (max-width: 767px) { + .post-gallery { + grid-template-columns: 1fr 1fr; + } +} +.post-gallery a { + border: 0; +} +.post-gallery img { + margin: 0; +} +.posts-expand .post-header { + font-size: 1.125em; +} +.posts-expand .post-title { + font-size: 1.5em; + font-weight: normal; + margin: initial; + text-align: center; + overflow-wrap: break-word; + word-wrap: break-word; +} +.posts-expand .post-title-link { + border-bottom: none; + color: var(--link-color); + display: inline-block; + position: relative; + vertical-align: top; +} +.posts-expand .post-title-link::before { + background: var(--link-color); + bottom: 0; + content: ''; + height: 2px; + left: 0; + position: absolute; + transform: scaleX(0); + visibility: hidden; + width: 100%; + transition-delay: 0s; + transition-duration: 0.2s; + transition-timing-function: ease-in-out; +} +.posts-expand .post-title-link:hover::before { + transform: scaleX(1); + visibility: visible; +} +.posts-expand .post-title-link .fa-external-link-alt { + font-size: 0.875em; + margin-left: 5px; +} +.posts-expand .post-meta { + color: #999; + font-family: "PingFang SC", "Microsoft YaHei", sans-serif; + font-size: 0.75em; + margin: 3px 0 60px 0; + text-align: center; +} +.posts-expand .post-meta .post-description { + font-size: 0.875em; + margin-top: 2px; +} +.posts-expand .post-meta time { + border-bottom: 1px dashed #999; + cursor: pointer; +} +.post-meta .post-meta-item + .post-meta-item::before { + content: '|'; + margin: 0 0.5em; +} +.post-meta-divider { + margin: 0 0.5em; +} +.post-meta-item-icon { + margin-right: 3px; +} +@media (max-width: 991px) { + .post-meta-item-icon { + display: inline-block; + } +} +@media (max-width: 991px) { + .post-meta-item-text { + display: none; + } +} +.post-nav { + border-top: 1px solid #eee; + display: flex; + justify-content: space-between; + margin-top: 15px; + padding: 10px 5px 0; +} +.post-nav-item { + flex: 1; +} +.post-nav-item a { + border-bottom: none; + display: block; + font-size: 0.875em; + line-height: 1.6; + position: relative; +} +.post-nav-item a:active { + top: 2px; +} +.post-nav-item .fa { + font-size: 0.75em; +} +.post-nav-item:first-child { + margin-right: 15px; +} +.post-nav-item:first-child .fa { + margin-right: 5px; +} +.post-nav-item:last-child { + margin-left: 15px; + text-align: right; +} +.post-nav-item:last-child .fa { + margin-left: 5px; +} +.rtl.post-body p, +.rtl.post-body a, +.rtl.post-body h1, +.rtl.post-body h2, +.rtl.post-body h3, +.rtl.post-body h4, +.rtl.post-body h5, +.rtl.post-body h6, +.rtl.post-body li, +.rtl.post-body ul, +.rtl.post-body ol { + direction: rtl; + font-family: UKIJ Ekran; +} +.rtl.post-title { + font-family: UKIJ Ekran; +} +.post-tags { + margin-top: 40px; + text-align: center; +} +.post-tags a { + display: inline-block; + font-size: 0.8125em; +} +.post-tags a:not(:last-child) { + margin-right: 10px; +} +.post-widgets { + border-top: 1px solid #eee; + margin-top: 15px; + text-align: center; +} +.wp_rating { + height: 20px; + line-height: 20px; + margin-top: 10px; + padding-top: 6px; + text-align: center; +} +.social-like { + display: flex; + font-size: 0.875em; + justify-content: center; + text-align: center; +} +.reward-container { + margin: 20px auto; + padding: 10px 0; + text-align: center; + width: 90%; +} +.reward-container button { + background: transparent; + border: 1px solid #fc6423; + border-radius: 0; + color: #fc6423; + cursor: pointer; + line-height: 2; + outline: 0; + padding: 0 15px; + vertical-align: text-top; +} +.reward-container button:hover { + background: #fc6423; + border: 1px solid transparent; + color: #fa9366; +} +#qr { + padding-top: 20px; +} +#qr a { + border: 0; +} +#qr img { + display: inline-block; + margin: 0.8em 2em 0 2em; + max-width: 100%; + width: 180px; +} +#qr p { + text-align: center; +} +.category-all-page .category-all-title { + text-align: center; +} +.category-all-page .category-all { + margin-top: 20px; +} +.category-all-page .category-list { + list-style: none; + margin: 0; + padding: 0; +} +.category-all-page .category-list-item { + margin: 5px 10px; +} +.category-all-page .category-list-count { + color: #bbb; +} +.category-all-page .category-list-count::before { + content: ' ('; + display: inline; +} +.category-all-page .category-list-count::after { + content: ') '; + display: inline; +} +.category-all-page .category-list-child { + padding-left: 10px; +} +.event-list { + padding: 0; +} +.event-list hr { + background: #222; + margin: 20px 0 45px 0; +} +.event-list hr::after { + background: #222; + color: #fff; + content: 'NOW'; + display: inline-block; + font-weight: bold; + padding: 0 5px; + text-align: right; +} +.event-list .event { + background: #222; + margin: 20px 0; + min-height: 40px; + padding: 15px 0 15px 10px; +} +.event-list .event .event-summary { + color: #fff; + margin: 0; + padding-bottom: 3px; +} +.event-list .event .event-summary::before { + animation: dot-flash 1s alternate infinite ease-in-out; + color: #fff; + content: '\f111'; + display: inline-block; + font-size: 10px; + margin-right: 25px; + vertical-align: middle; + font-family: 'Font Awesome 5 Free'; + font-weight: 900; +} +.event-list .event .event-relative-time { + color: #bbb; + display: inline-block; + font-size: 12px; + font-weight: normal; + padding-left: 12px; +} +.event-list .event .event-details { + color: #fff; + display: block; + line-height: 18px; + margin-left: 56px; + padding-bottom: 6px; + padding-top: 3px; + text-indent: -24px; +} +.event-list .event .event-details::before { + color: #fff; + display: inline-block; + margin-right: 9px; + text-align: center; + text-indent: 0; + width: 14px; + font-family: 'Font Awesome 5 Free'; + font-weight: 900; +} +.event-list .event .event-details.event-location::before { + content: '\f041'; +} +.event-list .event .event-details.event-duration::before { + content: '\f017'; +} +.event-list .event-past { + background: #f5f5f5; +} +.event-list .event-past .event-summary, +.event-list .event-past .event-details { + color: #bbb; + opacity: 0.9; +} +.event-list .event-past .event-summary::before, +.event-list .event-past .event-details::before { + animation: none; + color: #bbb; +} +@-moz-keyframes dot-flash { + from { + opacity: 1; + transform: scale(1); + } + to { + opacity: 0; + transform: scale(0.8); + } +} +@-webkit-keyframes dot-flash { + from { + opacity: 1; + transform: scale(1); + } + to { + opacity: 0; + transform: scale(0.8); + } +} +@-o-keyframes dot-flash { + from { + opacity: 1; + transform: scale(1); + } + to { + opacity: 0; + transform: scale(0.8); + } +} +@keyframes dot-flash { + from { + opacity: 1; + transform: scale(1); + } + to { + opacity: 0; + transform: scale(0.8); + } +} +ul.breadcrumb { + font-size: 0.75em; + list-style: none; + margin: 1em 0; + padding: 0 2em; + text-align: center; +} +ul.breadcrumb li { + display: inline; +} +ul.breadcrumb li + li::before { + content: '/\00a0'; + font-weight: normal; + padding: 0.5em; +} +ul.breadcrumb li + li:last-child { + font-weight: bold; +} +.tag-cloud { + text-align: center; +} +.tag-cloud a { + display: inline-block; + margin: 10px; +} +.tag-cloud a:hover { + color: var(--link-hover-color) !important; +} +.header { + margin: 0 auto; + position: relative; + width: calc(100% - 20px); +} +@media (min-width: 1200px) { + .header { + width: 1160px; + } +} +@media (min-width: 1600px) { + .header { + width: 73%; + } +} +@media (max-width: 991px) { + .header { + width: auto; + } +} +.header-inner { + background: var(--content-bg-color); + border-radius: initial; + box-shadow: initial; + overflow: hidden; + padding: 0; + position: absolute; + top: 0; + width: 240px; +} +@media (min-width: 1200px) { + .header-inner { + width: 240px; + } +} +@media (max-width: 991px) { + .header-inner { + border-radius: initial; + position: relative; + width: auto; + } +} +.main-inner { + align-items: flex-start; + display: flex; + justify-content: space-between; + flex-direction: row-reverse; +} +@media (max-width: 991px) { + .main-inner { + width: auto; + } +} +.content-wrap { + background: var(--content-bg-color); + border-radius: initial; + box-shadow: initial; + box-sizing: border-box; + padding: 40px; + width: calc(100% - 264px); +} +@media (max-width: 991px) { + .content-wrap { + border-radius: initial; + padding: 20px; + width: 100%; + } +} +.footer-inner { + padding-left: 260px; +} +.back-to-top { + left: auto; + right: 30px; +} +@media (max-width: 991px) { + .back-to-top { + right: 20px; + } +} +@media (max-width: 991px) { + .footer-inner { + padding-left: 0; + padding-right: 0; + width: auto; + } +} +.site-brand-container { + background: #222; +} +@media (max-width: 991px) { + .site-brand-container { + box-shadow: 0 0 16px rgba(0,0,0,0.5); + } +} +.site-meta { + padding: 20px 0; +} +.brand { + padding: 0; +} +.site-subtitle { + margin: 10px 10px 0; +} +.custom-logo-image { + margin-top: 20px; +} +@media (max-width: 991px) { + .custom-logo-image { + display: none; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .site-nav-toggle, + .site-nav-right { + display: flex; + flex-direction: column; + justify-content: center; + } +} +.site-nav-toggle .toggle, +.site-nav-right .toggle { + color: #fff; +} +.site-nav-toggle .toggle .toggle-line, +.site-nav-right .toggle .toggle-line { + background: #fff; +} +@media (min-width: 768px) and (max-width: 991px) { + .site-nav { + display: none; + } +} +.menu .menu-item { + display: block; + margin: 0; +} +.menu .menu-item a, +.menu .menu-item span.exturl { + padding: 5px 20px; + position: relative; + text-align: left; + transition-property: background-color; +} +@media (max-width: 991px) { + .menu .menu-item.menu-item-search { + display: none; + } +} +.menu .menu-item .badge { + background: #ccc; + border-radius: 10px; + color: #fff; + float: right; + padding: 2px 5px; + text-shadow: 1px 1px 0 rgba(0,0,0,0.1); + vertical-align: middle; +} +.main-menu .menu-item-active a::after { + background: #bbb; + border-radius: 50%; + content: ' '; + height: 6px; + margin-top: -3px; + position: absolute; + right: 15px; + top: 50%; + width: 6px; +} +.sub-menu { + background: var(--content-bg-color); + border-bottom: 1px solid #ddd; + margin: 0; + padding: 6px 0; +} +.sub-menu .menu-item { + display: inline-block; +} +.sub-menu .menu-item a, +.sub-menu .menu-item span.exturl { + background: transparent; + margin: 5px 10px; + padding: initial; +} +.sub-menu .menu-item a:hover, +.sub-menu .menu-item span.exturl:hover { + background: transparent; + color: #fc6423; +} +.sub-menu .menu-item-active a { + border-bottom-color: #fc6423; + color: #fc6423; +} +.sub-menu .menu-item-active a:hover { + border-bottom-color: #fc6423; +} +.sidebar { + background: var(--body-bg-color); + box-shadow: none; + margin-top: 100%; + position: static; + width: 240px; +} +@media (max-width: 991px) { + .sidebar { + display: none; + } +} +.sidebar-toggle { + display: none; +} +.sidebar-inner { + background: var(--content-bg-color); + border-radius: initial; + box-shadow: initial; + box-sizing: border-box; + color: var(--text-color); + width: 240px; + opacity: 0; +} +.sidebar-inner.affix { + position: fixed; + top: 24px; +} +.sidebar-inner.affix-bottom { + position: absolute; +} +.site-state-item { + padding: 0 10px; +} +.sidebar-button { + border-bottom: 1px dotted #ccc; + border-top: 1px dotted #ccc; + margin-top: 10px; + text-align: center; +} +.sidebar-button a { + border: 0; + color: #fc6423; + display: block; +} +.sidebar-button a:hover { + background: none; + border: 0; + color: #e34603; +} +.sidebar-button a:hover .fa, +.sidebar-button a:hover .fab, +.sidebar-button a:hover .far, +.sidebar-button a:hover .fas { + color: #e34603; +} +.links-of-author { + display: flex; + flex-wrap: wrap; + margin-top: 10px; + justify-content: center; +} +.links-of-author-item { + margin: 5px 0 0; + width: 50%; +} +.links-of-author-item a, +.links-of-author-item span.exturl { + box-sizing: border-box; + display: inline-block; + margin-bottom: 0; + margin-right: 0; + max-width: 216px; + overflow: hidden; + padding: 0 5px; + text-overflow: ellipsis; + white-space: nowrap; +} +.links-of-author-item a, +.links-of-author-item span.exturl { + border-bottom: none; + display: block; + text-decoration: none; +} +.links-of-author-item a::before, +.links-of-author-item span.exturl::before { + display: none; +} +.links-of-author-item a:hover, +.links-of-author-item span.exturl:hover { + background: var(--body-bg-color); + border-radius: 4px; +} +.links-of-author-item .fa, +.links-of-author-item .fab, +.links-of-author-item .far, +.links-of-author-item .fas { + margin-right: 2px; +} +.links-of-blogroll-item { + padding: 0; +} +.site-nav { + font-size: 1.2em; +} +.sidebar { + font-size: 1.1em; +} +.site-author .gelatine { + animation: gelatine 0.5s infinite; +} +.site-author .hithere { + animation: hithere 1s ease infinite; +} +.site-author .shake { + animation: shake 2s ease infinite; +} +.site-author .hinge { + animation: hinge 2s ease infinite; +} +.site-author:hover .site-author-image { + animation: shake 1s ease infinite; +} +@-moz-keyframes gelatine { + from, to { + transform: scale(1, 1); + } + 25% { + transform: scale(0.9, 1.1); + } + 50% { + transform: scale(1.1, 0.9); + } + 75% { + transform: scale(0.95, 1.05); + } +} +@-webkit-keyframes gelatine { + from, to { + transform: scale(1, 1); + } + 25% { + transform: scale(0.9, 1.1); + } + 50% { + transform: scale(1.1, 0.9); + } + 75% { + transform: scale(0.95, 1.05); + } +} +@-o-keyframes gelatine { + from, to { + transform: scale(1, 1); + } + 25% { + transform: scale(0.9, 1.1); + } + 50% { + transform: scale(1.1, 0.9); + } + 75% { + transform: scale(0.95, 1.05); + } +} +@keyframes gelatine { + from, to { + transform: scale(1, 1); + } + 25% { + transform: scale(0.9, 1.1); + } + 50% { + transform: scale(1.1, 0.9); + } + 75% { + transform: scale(0.95, 1.05); + } +} +@-moz-keyframes hithere { + 30% { + transform: scale(1.2); + } + 40%, 60% { + transform: rotate(-20deg) scale(1.2); + } + 50% { + transform: rotate(20deg) scale(1.2); + } + 70% { + transform: rotate(0deg) scale(1.2); + } + 100% { + transform: scale(1); + } +} +@-webkit-keyframes hithere { + 30% { + transform: scale(1.2); + } + 40%, 60% { + transform: rotate(-20deg) scale(1.2); + } + 50% { + transform: rotate(20deg) scale(1.2); + } + 70% { + transform: rotate(0deg) scale(1.2); + } + 100% { + transform: scale(1); + } +} +@-o-keyframes hithere { + 30% { + transform: scale(1.2); + } + 40%, 60% { + transform: rotate(-20deg) scale(1.2); + } + 50% { + transform: rotate(20deg) scale(1.2); + } + 70% { + transform: rotate(0deg) scale(1.2); + } + 100% { + transform: scale(1); + } +} +@keyframes hithere { + 30% { + transform: scale(1.2); + } + 40%, 60% { + transform: rotate(-20deg) scale(1.2); + } + 50% { + transform: rotate(20deg) scale(1.2); + } + 70% { + transform: rotate(0deg) scale(1.2); + } + 100% { + transform: scale(1); + } +} +@-moz-keyframes shake { + 0%, 100% { + transform: translateX(0); + } + 10%, 30%, 50%, 70%, 90% { + transform: translateX(-10px); + } + 20%, 40%, 60%, 80% { + transform: translateX(10px); + } +} +@-webkit-keyframes shake { + 0%, 100% { + transform: translateX(0); + } + 10%, 30%, 50%, 70%, 90% { + transform: translateX(-10px); + } + 20%, 40%, 60%, 80% { + transform: translateX(10px); + } +} +@-o-keyframes shake { + 0%, 100% { + transform: translateX(0); + } + 10%, 30%, 50%, 70%, 90% { + transform: translateX(-10px); + } + 20%, 40%, 60%, 80% { + transform: translateX(10px); + } +} +@keyframes shake { + 0%, 100% { + transform: translateX(0); + } + 10%, 30%, 50%, 70%, 90% { + transform: translateX(-10px); + } + 20%, 40%, 60%, 80% { + transform: translateX(10px); + } +} +@-moz-keyframes hinge { + 0% { + transform: rotate(0); + transform-origin: top left; + animation-timing-function: ease-in-out; + } + 20%, 60% { + transform: rotate(80deg); + transform-origin: top left; + animation-timing-function: ease-in-out; + } + 40% { + transform: rotate(60deg); + transform-origin: top left; + animation-timing-function: ease-in-out; + } + 80% { + transform: rotate(60deg) translateY(0); + opacity: 1; + transform-origin: top left; + animation-timing-function: ease-in-out; + } + 100% { + transform: translateY(700px); + opacity: 0; + } +} +@-webkit-keyframes hinge { + 0% { + transform: rotate(0); + transform-origin: top left; + animation-timing-function: ease-in-out; + } + 20%, 60% { + transform: rotate(80deg); + transform-origin: top left; + animation-timing-function: ease-in-out; + } + 40% { + transform: rotate(60deg); + transform-origin: top left; + animation-timing-function: ease-in-out; + } + 80% { + transform: rotate(60deg) translateY(0); + opacity: 1; + transform-origin: top left; + animation-timing-function: ease-in-out; + } + 100% { + transform: translateY(700px); + opacity: 0; + } +} +@-o-keyframes hinge { + 0% { + transform: rotate(0); + transform-origin: top left; + animation-timing-function: ease-in-out; + } + 20%, 60% { + transform: rotate(80deg); + transform-origin: top left; + animation-timing-function: ease-in-out; + } + 40% { + transform: rotate(60deg); + transform-origin: top left; + animation-timing-function: ease-in-out; + } + 80% { + transform: rotate(60deg) translateY(0); + opacity: 1; + transform-origin: top left; + animation-timing-function: ease-in-out; + } + 100% { + transform: translateY(700px); + opacity: 0; + } +} +@keyframes hinge { + 0% { + transform: rotate(0); + transform-origin: top left; + animation-timing-function: ease-in-out; + } + 20%, 60% { + transform: rotate(80deg); + transform-origin: top left; + animation-timing-function: ease-in-out; + } + 40% { + transform: rotate(60deg); + transform-origin: top left; + animation-timing-function: ease-in-out; + } + 80% { + transform: rotate(60deg) translateY(0); + opacity: 1; + transform-origin: top left; + animation-timing-function: ease-in-out; + } + 100% { + transform: translateY(700px); + opacity: 0; + } +} +.posts-expand .post-meta { + font-size: 0.85em; + margin-bottom: 30px; +} +.post-footer .post-eof { + margin: 55px auto 45px; +} +.post-toc .nav-item { + line-height: 2.1; +} diff --git a/games/index.html b/games/index.html new file mode 100644 index 00000000..28d30162 --- /dev/null +++ b/games/index.html @@ -0,0 +1,625 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 游戏 | Daily record + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+ +

游戏 +

+ + + +
+ + + + +
+
+

+
+ + + + +
+
+ +
+ 首页 + 上一页 + 1 / 1 + 下一页 + 尾页 +
+ + + + +
+ + + +
+ + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/images/algolia_logo.svg b/images/algolia_logo.svg new file mode 100644 index 00000000..47024234 --- /dev/null +++ b/images/algolia_logo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/images/apple-touch-icon-next.png b/images/apple-touch-icon-next.png new file mode 100644 index 00000000..86a0d1d3 Binary files /dev/null and b/images/apple-touch-icon-next.png differ diff --git a/images/avatar.gif b/images/avatar.gif new file mode 100644 index 00000000..28411fd0 Binary files /dev/null and b/images/avatar.gif differ diff --git a/images/avatar.jpeg b/images/avatar.jpeg new file mode 100644 index 00000000..a740a14a Binary files /dev/null and b/images/avatar.jpeg differ diff --git a/images/cc-by-nc-nd.svg b/images/cc-by-nc-nd.svg new file mode 100644 index 00000000..79a4f2e0 --- /dev/null +++ b/images/cc-by-nc-nd.svg @@ -0,0 +1,121 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/images/cc-by-nc-sa.svg b/images/cc-by-nc-sa.svg new file mode 100644 index 00000000..bf6bc26f --- /dev/null +++ b/images/cc-by-nc-sa.svg @@ -0,0 +1,121 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/images/cc-by-nc.svg b/images/cc-by-nc.svg new file mode 100644 index 00000000..36973490 --- /dev/null +++ b/images/cc-by-nc.svg @@ -0,0 +1,121 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/images/cc-by-nd.svg b/images/cc-by-nd.svg new file mode 100644 index 00000000..934c61e1 --- /dev/null +++ b/images/cc-by-nd.svg @@ -0,0 +1,117 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/images/cc-by-sa.svg b/images/cc-by-sa.svg new file mode 100644 index 00000000..463276a8 --- /dev/null +++ b/images/cc-by-sa.svg @@ -0,0 +1,121 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/images/cc-by.svg b/images/cc-by.svg new file mode 100644 index 00000000..4bccd14f --- /dev/null +++ b/images/cc-by.svg @@ -0,0 +1,121 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/images/cc-zero.svg b/images/cc-zero.svg new file mode 100644 index 00000000..0f866392 --- /dev/null +++ b/images/cc-zero.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/images/favicon-16x16-next.png b/images/favicon-16x16-next.png new file mode 100644 index 00000000..de8c5d3a Binary files /dev/null and b/images/favicon-16x16-next.png differ diff --git a/images/favicon-32x32-next.png b/images/favicon-32x32-next.png new file mode 100644 index 00000000..e02f5f4d Binary files /dev/null and b/images/favicon-32x32-next.png differ diff --git a/images/logo.svg b/images/logo.svg new file mode 100644 index 00000000..cbb3937e --- /dev/null +++ b/images/logo.svg @@ -0,0 +1,23 @@ + +image/svg+xml diff --git a/index.html b/index.html new file mode 100644 index 00000000..c114e4e3 --- /dev/null +++ b/index.html @@ -0,0 +1,1151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Daily record + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

Daily record

+ +
+

琐记随笔

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + +
+ + + + + +
+

+ + +

+ + +
+ + + + +
+ + +

工作是否也有五年之痒,2022 年度吐槽汇总。

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + +
+
+
+
+ + + + + + + +
+ + + + + +
+

+ + +

+ + +
+ + + + +
+ + +

从 iOS8 开始,就引入了新的浏览器控件 WKWebView,用于取代 UIWebView。在新版本系统中使用 UIWebView 会发出警告 ⚠️ 提醒更换控件。坊间传闻 WKWebView 存在内存占用过大的问题…

+

声明:这是一篇翻译水文,有用的内容不多,之前是因为好奇翻译了一半,翻译完发现并没有什么有用的知识点…

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + +
+
+
+
+ + + + + + + +
+ + + + + +
+

+ + +

+ + +
+ + + + +
+ + +

之前网工学习部分知识点总结,主要是整理汇总下分散在不同地方的笔记。
工作中用到的不多,仅当笔记备份。

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + +
+
+
+
+ + + + + + + +
+ + + + + +
+

+ + +

+ + +
+ + + + +
+ + +

2021年终总结(✖️)
2021大师难度通关记(✔️)

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + +
+
+
+
+ + + + + + + +
+ + + + + +
+

+ + +

+ + +
+ + + + +
+ + +

很好的排版设计入门书,19年年底的时候在地铁里用了不到两周的时间看完了,一边看书,一边拿出平日里设计师给的设计稿对比着看,总会有一种“我不明白,但我大受震撼”的感觉。 书很快读完了,读书小结却从19年拖到了21年,再过三个月就22年了,现在拿出来翻翻,如获新书( ̄ε(# ̄)。

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + +
+
+
+
+ + + + + + + +
+ + + + + +
+

+ + +

+ + +
+ + + + +
+ + +

遗留了很久的一个学习任务,最近正好在总结归纳小程序在 App 之间的交互,顺便拾起一些学过的和没学过的知识。主要涉及的知识点:URL Scheme、Webview、H5 与 App 之间的通信以及 JSBridge 的概念。

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + +
+
+
+
+ + + + + + + +
+ + + + + +
+

+ + +

+ + +
+ + + + +
+ + +

大概这一年左右的时间,都在跟小程序相关的需求。从开发到上线,流程上会跟以往的 Web 开发有些不同。此前除了大学时的一次课设,其他时间未曾接触过小程序,算是从 0 开始吧。不过得益于 Uniapp 基于 Vue.js 的语法封装,除了小程序自己的 API 之外,语法学习成本几乎没有。

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + +
+
+
+
+ + + + + + + +
+ + + + + +
+

+ + +

+ + +
+ + + + +
+ + +

2020年终总结(✖️)
2020年流水账(✔️)

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + +
+
+
+
+ + + + + + + +
+ + + + + +
+

+ + +

+ + +
+ + + + +
+ + +

偶然在一篇文章中看到 Node 可以使用 import 语法了,无需再使用 babel 做额外的转换,遂去了解下 Node 相关的更新。本文主要介绍在最新版本 Node(14.15.1) 中如何使用 import 语法。大部分内容翻译自官网和外网文章。关于 JS 模块机制之前已经总结过一篇文章,这里不再赘述。

+

( PS:原本是公司部门要求的 kpi 文章,现做了精简 )

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + +
+
+
+
+ + + + + + + +
+ + + + + +
+

+ + +

+ + +
+ + + + +
+ + +

自 ECMA2015 (6th) 大幅更新之后, ECMA 标准变更成每年6月发布一个版本进行小幅度更新。为方便温习和查找,汇总一下近五年的所有版本特性。本文共涵盖了 ES2016、ES2017、ES2018、ES2019、ES2020 五个版本的更新内容。翻译有删改,仅供快速查找使用。

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + +
+
+
+
+ + + + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/js/algolia-search.js b/js/algolia-search.js new file mode 100644 index 00000000..01a5f0b0 --- /dev/null +++ b/js/algolia-search.js @@ -0,0 +1,124 @@ +/* global instantsearch, algoliasearch, CONFIG */ + +document.addEventListener('DOMContentLoaded', () => { + const algoliaSettings = CONFIG.algolia; + const { indexName, appID, apiKey } = algoliaSettings; + + let search = instantsearch({ + indexName, + searchClient : algoliasearch(appID, apiKey), + searchFunction: helper => { + let searchInput = document.querySelector('.search-input'); + if (searchInput.value) { + helper.search(); + } + } + }); + + window.pjax && search.on('render', () => { + window.pjax.refresh(document.getElementById('algolia-hits')); + }); + + // Registering Widgets + search.addWidgets([ + instantsearch.widgets.configure({ + hitsPerPage: algoliaSettings.hits.per_page || 10 + }), + + instantsearch.widgets.searchBox({ + container : '.search-input-container', + placeholder : algoliaSettings.labels.input_placeholder, + // Hide default icons of algolia search + showReset : false, + showSubmit : false, + showLoadingIndicator: false, + cssClasses : { + input: 'search-input' + } + }), + + instantsearch.widgets.stats({ + container: '#algolia-stats', + templates: { + text: data => { + let stats = algoliaSettings.labels.hits_stats + .replace(/\$\{hits}/, data.nbHits) + .replace(/\$\{time}/, data.processingTimeMS); + return `${stats} + + Algolia + +
`; + } + } + }), + + instantsearch.widgets.hits({ + container: '#algolia-hits', + templates: { + item: data => { + let link = data.permalink ? data.permalink : CONFIG.root + data.path; + return `${data._highlightResult.title.value}`; + }, + empty: data => { + return `
+ ${algoliaSettings.labels.hits_empty.replace(/\$\{query}/, data.query)} +
`; + } + }, + cssClasses: { + item: 'algolia-hit-item' + } + }), + + instantsearch.widgets.pagination({ + container: '#algolia-pagination', + scrollTo : false, + showFirst: false, + showLast : false, + templates: { + first : '', + last : '', + previous: '', + next : '' + }, + cssClasses: { + root : 'pagination', + item : 'pagination-item', + link : 'page-number', + selectedItem: 'current', + disabledItem: 'disabled-item' + } + }) + ]); + + search.start(); + + // Handle and trigger popup window + document.querySelectorAll('.popup-trigger').forEach(element => { + element.addEventListener('click', () => { + document.body.style.overflow = 'hidden'; + document.querySelector('.search-pop-overlay').classList.add('search-active'); + document.querySelector('.search-input').focus(); + }); + }); + + // Monitor main search box + const onPopupClose = () => { + document.body.style.overflow = ''; + document.querySelector('.search-pop-overlay').classList.remove('search-active'); + }; + + document.querySelector('.search-pop-overlay').addEventListener('click', event => { + if (event.target === document.querySelector('.search-pop-overlay')) { + onPopupClose(); + } + }); + document.querySelector('.popup-btn-close').addEventListener('click', onPopupClose); + window.addEventListener('pjax:success', onPopupClose); + window.addEventListener('keyup', event => { + if (event.key === 'Escape') { + onPopupClose(); + } + }); +}); diff --git a/js/bookmark.js b/js/bookmark.js new file mode 100644 index 00000000..7c2438e1 --- /dev/null +++ b/js/bookmark.js @@ -0,0 +1,56 @@ +/* global CONFIG */ + +document.addEventListener('DOMContentLoaded', () => { + 'use strict'; + + var doSaveScroll = () => { + localStorage.setItem('bookmark' + location.pathname, window.scrollY); + }; + + var scrollToMark = () => { + var top = localStorage.getItem('bookmark' + location.pathname); + top = parseInt(top, 10); + // If the page opens with a specific hash, just jump out + if (!isNaN(top) && location.hash === '') { + // Auto scroll to the position + window.anime({ + targets : document.scrollingElement, + duration : 200, + easing : 'linear', + scrollTop: top + }); + } + }; + // Register everything + var init = function(trigger) { + // Create a link element + var link = document.querySelector('.book-mark-link'); + // Scroll event + window.addEventListener('scroll', () => link.classList.toggle('book-mark-link-fixed', window.scrollY === 0)); + // Register beforeunload event when the trigger is auto + if (trigger === 'auto') { + // Register beforeunload event + window.addEventListener('beforeunload', doSaveScroll); + window.addEventListener('pjax:send', doSaveScroll); + } + // Save the position by clicking the icon + link.addEventListener('click', () => { + doSaveScroll(); + window.anime({ + targets : link, + duration: 200, + easing : 'linear', + top : -30, + complete: () => { + setTimeout(() => { + link.style.top = ''; + }, 400); + } + }); + }); + scrollToMark(); + window.addEventListener('pjax:success', scrollToMark); + }; + + init(CONFIG.bookmark.save); +}); diff --git a/js/local-search.js b/js/local-search.js new file mode 100644 index 00000000..31f945fd --- /dev/null +++ b/js/local-search.js @@ -0,0 +1,278 @@ +/* global CONFIG */ + +document.addEventListener('DOMContentLoaded', () => { + // Popup Window + let isfetched = false; + let datas; + let isXml = true; + // Search DB path + let searchPath = CONFIG.path; + if (searchPath.length === 0) { + searchPath = 'search.xml'; + } else if (searchPath.endsWith('json')) { + isXml = false; + } + const input = document.querySelector('.search-input'); + const resultContent = document.getElementById('search-result'); + + const getIndexByWord = (word, text, caseSensitive) => { + if (CONFIG.localsearch.unescape) { + let div = document.createElement('div'); + div.innerText = word; + word = div.innerHTML; + } + let wordLen = word.length; + if (wordLen === 0) return []; + let startPosition = 0; + let position = []; + let index = []; + if (!caseSensitive) { + text = text.toLowerCase(); + word = word.toLowerCase(); + } + while ((position = text.indexOf(word, startPosition)) > -1) { + index.push({ position, word }); + startPosition = position + wordLen; + } + return index; + }; + + // Merge hits into slices + const mergeIntoSlice = (start, end, index, searchText) => { + let item = index[index.length - 1]; + let { position, word } = item; + let hits = []; + let searchTextCountInSlice = 0; + while (position + word.length <= end && index.length !== 0) { + if (word === searchText) { + searchTextCountInSlice++; + } + hits.push({ + position, + length: word.length + }); + let wordEnd = position + word.length; + + // Move to next position of hit + index.pop(); + while (index.length !== 0) { + item = index[index.length - 1]; + position = item.position; + word = item.word; + if (wordEnd > position) { + index.pop(); + } else { + break; + } + } + } + return { + hits, + start, + end, + searchTextCount: searchTextCountInSlice + }; + }; + + // Highlight title and content + const highlightKeyword = (text, slice) => { + let result = ''; + let prevEnd = slice.start; + slice.hits.forEach(hit => { + result += text.substring(prevEnd, hit.position); + let end = hit.position + hit.length; + result += `${text.substring(hit.position, end)}`; + prevEnd = end; + }); + result += text.substring(prevEnd, slice.end); + return result; + }; + + const inputEventFunction = () => { + if (!isfetched) return; + let searchText = input.value.trim().toLowerCase(); + let keywords = searchText.split(/[-\s]+/); + if (keywords.length > 1) { + keywords.push(searchText); + } + let resultItems = []; + if (searchText.length > 0) { + // Perform local searching + datas.forEach(({ title, content, url }) => { + let titleInLowerCase = title.toLowerCase(); + let contentInLowerCase = content.toLowerCase(); + let indexOfTitle = []; + let indexOfContent = []; + let searchTextCount = 0; + keywords.forEach(keyword => { + indexOfTitle = indexOfTitle.concat(getIndexByWord(keyword, titleInLowerCase, false)); + indexOfContent = indexOfContent.concat(getIndexByWord(keyword, contentInLowerCase, false)); + }); + + // Show search results + if (indexOfTitle.length > 0 || indexOfContent.length > 0) { + let hitCount = indexOfTitle.length + indexOfContent.length; + // Sort index by position of keyword + [indexOfTitle, indexOfContent].forEach(index => { + index.sort((itemLeft, itemRight) => { + if (itemRight.position !== itemLeft.position) { + return itemRight.position - itemLeft.position; + } + return itemLeft.word.length - itemRight.word.length; + }); + }); + + let slicesOfTitle = []; + if (indexOfTitle.length !== 0) { + let tmp = mergeIntoSlice(0, title.length, indexOfTitle, searchText); + searchTextCount += tmp.searchTextCountInSlice; + slicesOfTitle.push(tmp); + } + + let slicesOfContent = []; + while (indexOfContent.length !== 0) { + let item = indexOfContent[indexOfContent.length - 1]; + let { position, word } = item; + // Cut out 100 characters + let start = position - 20; + let end = position + 80; + if (start < 0) { + start = 0; + } + if (end < position + word.length) { + end = position + word.length; + } + if (end > content.length) { + end = content.length; + } + let tmp = mergeIntoSlice(start, end, indexOfContent, searchText); + searchTextCount += tmp.searchTextCountInSlice; + slicesOfContent.push(tmp); + } + + // Sort slices in content by search text's count and hits' count + slicesOfContent.sort((sliceLeft, sliceRight) => { + if (sliceLeft.searchTextCount !== sliceRight.searchTextCount) { + return sliceRight.searchTextCount - sliceLeft.searchTextCount; + } else if (sliceLeft.hits.length !== sliceRight.hits.length) { + return sliceRight.hits.length - sliceLeft.hits.length; + } + return sliceLeft.start - sliceRight.start; + }); + + // Select top N slices in content + let upperBound = parseInt(CONFIG.localsearch.top_n_per_article, 10); + if (upperBound >= 0) { + slicesOfContent = slicesOfContent.slice(0, upperBound); + } + + let resultItem = ''; + + if (slicesOfTitle.length !== 0) { + resultItem += `
  • ${highlightKeyword(title, slicesOfTitle[0])}`; + } else { + resultItem += `
  • ${title}`; + } + + slicesOfContent.forEach(slice => { + resultItem += `

    ${highlightKeyword(content, slice)}...

    `; + }); + + resultItem += '
  • '; + resultItems.push({ + item: resultItem, + id : resultItems.length, + hitCount, + searchTextCount + }); + } + }); + } + if (keywords.length === 1 && keywords[0] === '') { + resultContent.innerHTML = '
    '; + } else if (resultItems.length === 0) { + resultContent.innerHTML = '
    '; + } else { + resultItems.sort((resultLeft, resultRight) => { + if (resultLeft.searchTextCount !== resultRight.searchTextCount) { + return resultRight.searchTextCount - resultLeft.searchTextCount; + } else if (resultLeft.hitCount !== resultRight.hitCount) { + return resultRight.hitCount - resultLeft.hitCount; + } + return resultRight.id - resultLeft.id; + }); + resultContent.innerHTML = ``; + window.pjax && window.pjax.refresh(resultContent); + } + }; + + const fetchData = () => { + fetch(CONFIG.root + searchPath) + .then(response => response.text()) + .then(res => { + // Get the contents from search data + isfetched = true; + datas = isXml ? [...new DOMParser().parseFromString(res, 'text/xml').querySelectorAll('entry')].map(element => { + return { + title : element.querySelector('title').textContent, + content: element.querySelector('content').textContent, + url : element.querySelector('url').textContent + }; + }) : JSON.parse(res); + // Only match articles with not empty titles + datas = datas.filter(data => data.title).map(data => { + data.title = data.title.trim(); + data.content = data.content ? data.content.trim().replace(/<[^>]+>/g, '') : ''; + data.url = decodeURIComponent(data.url).replace(/\/{2,}/g, '/'); + return data; + }); + // Remove loading animation + document.getElementById('no-result').innerHTML = ''; + inputEventFunction(); + }); + }; + + if (CONFIG.localsearch.preload) { + fetchData(); + } + + if (CONFIG.localsearch.trigger === 'auto') { + input.addEventListener('input', inputEventFunction); + } else { + document.querySelector('.search-icon').addEventListener('click', inputEventFunction); + input.addEventListener('keypress', event => { + if (event.key === 'Enter') { + inputEventFunction(); + } + }); + } + + // Handle and trigger popup window + document.querySelectorAll('.popup-trigger').forEach(element => { + element.addEventListener('click', () => { + document.body.style.overflow = 'hidden'; + document.querySelector('.search-pop-overlay').classList.add('search-active'); + input.focus(); + if (!isfetched) fetchData(); + }); + }); + + // Monitor main search box + const onPopupClose = () => { + document.body.style.overflow = ''; + document.querySelector('.search-pop-overlay').classList.remove('search-active'); + }; + + document.querySelector('.search-pop-overlay').addEventListener('click', event => { + if (event.target === document.querySelector('.search-pop-overlay')) { + onPopupClose(); + } + }); + document.querySelector('.popup-btn-close').addEventListener('click', onPopupClose); + window.addEventListener('pjax:success', onPopupClose); + window.addEventListener('keyup', event => { + if (event.key === 'Escape') { + onPopupClose(); + } + }); +}); diff --git a/js/motion.js b/js/motion.js new file mode 100644 index 00000000..026199aa --- /dev/null +++ b/js/motion.js @@ -0,0 +1,177 @@ +/* global NexT, CONFIG, Velocity */ + +if (window.$ && window.$.Velocity) window.Velocity = window.$.Velocity; + +NexT.motion = {}; + +NexT.motion.integrator = { + queue : [], + cursor: -1, + init : function() { + this.queue = []; + this.cursor = -1; + return this; + }, + add: function(fn) { + this.queue.push(fn); + return this; + }, + next: function() { + this.cursor++; + var fn = this.queue[this.cursor]; + typeof fn === 'function' && fn(NexT.motion.integrator); + }, + bootstrap: function() { + this.next(); + } +}; + +NexT.motion.middleWares = { + logo: function(integrator) { + var sequence = []; + var brand = document.querySelector('.brand'); + var image = document.querySelector('.custom-logo-image'); + var title = document.querySelector('.site-title'); + var subtitle = document.querySelector('.site-subtitle'); + var logoLineTop = document.querySelector('.logo-line-before i'); + var logoLineBottom = document.querySelector('.logo-line-after i'); + + brand && sequence.push({ + e: brand, + p: {opacity: 1}, + o: {duration: 200} + }); + + function getMistLineSettings(element, translateX) { + return { + e: element, + p: {translateX}, + o: { + duration : 500, + sequenceQueue: false + } + }; + } + + function pushImageToSequence() { + sequence.push({ + e: image, + p: {opacity: 1, top: 0}, + o: {duration: 200} + }); + } + + CONFIG.scheme === 'Mist' && logoLineTop && logoLineBottom + && sequence.push( + getMistLineSettings(logoLineTop, '100%'), + getMistLineSettings(logoLineBottom, '-100%') + ); + + CONFIG.scheme === 'Muse' && image && pushImageToSequence(); + + title && sequence.push({ + e: title, + p: {opacity: 1, top: 0}, + o: {duration: 200} + }); + + subtitle && sequence.push({ + e: subtitle, + p: {opacity: 1, top: 0}, + o: {duration: 200} + }); + + (CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini') && image && pushImageToSequence(); + + if (sequence.length > 0) { + sequence[sequence.length - 1].o.complete = function() { + integrator.next(); + }; + Velocity.RunSequence(sequence); + } else { + integrator.next(); + } + + if (CONFIG.motion.async) { + integrator.next(); + } + }, + + menu: function(integrator) { + Velocity(document.querySelectorAll('.menu-item'), 'transition.slideDownIn', { + display : null, + duration: 200, + complete: function() { + integrator.next(); + } + }); + + if (CONFIG.motion.async) { + integrator.next(); + } + }, + + subMenu: function(integrator) { + var subMenuItem = document.querySelectorAll('.sub-menu .menu-item'); + if (subMenuItem.length > 0) { + subMenuItem.forEach(element => { + element.style.opacity = 1; + }); + } + integrator.next(); + }, + + postList: function(integrator) { + var postBlock = document.querySelectorAll('.post-block, .pagination, .comments'); + var postBlockTransition = CONFIG.motion.transition.post_block; + var postHeader = document.querySelectorAll('.post-header'); + var postHeaderTransition = CONFIG.motion.transition.post_header; + var postBody = document.querySelectorAll('.post-body'); + var postBodyTransition = CONFIG.motion.transition.post_body; + var collHeader = document.querySelectorAll('.collection-header'); + var collHeaderTransition = CONFIG.motion.transition.coll_header; + + if (postBlock.length > 0) { + var postMotionOptions = window.postMotionOptions || { + stagger : 100, + drag : true, + complete: function() { + integrator.next(); + } + }; + + if (CONFIG.motion.transition.post_block) { + Velocity(postBlock, 'transition.' + postBlockTransition, postMotionOptions); + } + if (CONFIG.motion.transition.post_header) { + Velocity(postHeader, 'transition.' + postHeaderTransition, postMotionOptions); + } + if (CONFIG.motion.transition.post_body) { + Velocity(postBody, 'transition.' + postBodyTransition, postMotionOptions); + } + if (CONFIG.motion.transition.coll_header) { + Velocity(collHeader, 'transition.' + collHeaderTransition, postMotionOptions); + } + } + if (CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini') { + integrator.next(); + } + }, + + sidebar: function(integrator) { + var sidebarAffix = document.querySelector('.sidebar-inner'); + var sidebarAffixTransition = CONFIG.motion.transition.sidebar; + // Only for Pisces | Gemini. + if (sidebarAffixTransition && (CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini')) { + Velocity(sidebarAffix, 'transition.' + sidebarAffixTransition, { + display : null, + duration: 200, + complete: function() { + // After motion complete need to remove transform from sidebar to let affix work on Pisces | Gemini. + sidebarAffix.style.transform = 'initial'; + } + }); + } + integrator.next(); + } +}; diff --git a/js/next-boot.js b/js/next-boot.js new file mode 100644 index 00000000..52ec9aec --- /dev/null +++ b/js/next-boot.js @@ -0,0 +1,114 @@ +/* global NexT, CONFIG, Velocity */ + +NexT.boot = {}; + +NexT.boot.registerEvents = function() { + + NexT.utils.registerScrollPercent(); + NexT.utils.registerCanIUseTag(); + + // Mobile top menu bar. + document.querySelector('.site-nav-toggle .toggle').addEventListener('click', () => { + event.currentTarget.classList.toggle('toggle-close'); + var siteNav = document.querySelector('.site-nav'); + var animateAction = siteNav.classList.contains('site-nav-on') ? 'slideUp' : 'slideDown'; + + if (typeof Velocity === 'function') { + Velocity(siteNav, animateAction, { + duration: 200, + complete: function() { + siteNav.classList.toggle('site-nav-on'); + } + }); + } else { + siteNav.classList.toggle('site-nav-on'); + } + }); + + var TAB_ANIMATE_DURATION = 200; + document.querySelectorAll('.sidebar-nav li').forEach((element, index) => { + element.addEventListener('click', event => { + var item = event.currentTarget; + var activeTabClassName = 'sidebar-nav-active'; + var activePanelClassName = 'sidebar-panel-active'; + if (item.classList.contains(activeTabClassName)) return; + + var targets = document.querySelectorAll('.sidebar-panel'); + var target = targets[index]; + var currentTarget = targets[1 - index]; + window.anime({ + targets : currentTarget, + duration: TAB_ANIMATE_DURATION, + easing : 'linear', + opacity : 0, + complete: () => { + // Prevent adding TOC to Overview if Overview was selected when close & open sidebar. + currentTarget.classList.remove(activePanelClassName); + target.style.opacity = 0; + target.classList.add(activePanelClassName); + window.anime({ + targets : target, + duration: TAB_ANIMATE_DURATION, + easing : 'linear', + opacity : 1 + }); + } + }); + + [...item.parentNode.children].forEach(element => { + element.classList.remove(activeTabClassName); + }); + item.classList.add(activeTabClassName); + }); + }); + + window.addEventListener('resize', NexT.utils.initSidebarDimension); + + window.addEventListener('hashchange', () => { + var tHash = location.hash; + if (tHash !== '' && !tHash.match(/%\S{2}/)) { + var target = document.querySelector(`.tabs ul.nav-tabs li a[href="${tHash}"]`); + target && target.click(); + } + }); +}; + +NexT.boot.refresh = function() { + + /** + * Register JS handlers by condition option. + * Need to add config option in Front-End at 'layout/_partials/head.swig' file. + */ + CONFIG.fancybox && NexT.utils.wrapImageWithFancyBox(); + CONFIG.mediumzoom && window.mediumZoom('.post-body :not(a) > img, .post-body > img'); + CONFIG.lazyload && window.lozad('.post-body img').observe(); + CONFIG.pangu && window.pangu.spacingPage(); + + CONFIG.exturl && NexT.utils.registerExtURL(); + CONFIG.copycode.enable && NexT.utils.registerCopyCode(); + NexT.utils.registerTabsTag(); + NexT.utils.registerActiveMenuItem(); + NexT.utils.registerLangSelect(); + NexT.utils.registerSidebarTOC(); + NexT.utils.wrapTableWithBox(); + NexT.utils.registerVideoIframe(); +}; + +NexT.boot.motion = function() { + // Define Motion Sequence & Bootstrap Motion. + if (CONFIG.motion.enable) { + NexT.motion.integrator + .add(NexT.motion.middleWares.logo) + .add(NexT.motion.middleWares.menu) + .add(NexT.motion.middleWares.postList) + .add(NexT.motion.middleWares.sidebar) + .bootstrap(); + } + NexT.utils.updateSidebarPosition(); +}; + +document.addEventListener('DOMContentLoaded', () => { + NexT.boot.registerEvents(); + NexT.boot.refresh(); + NexT.boot.motion(); +}); diff --git a/js/schemes/muse.js b/js/schemes/muse.js new file mode 100644 index 00000000..f4be56df --- /dev/null +++ b/js/schemes/muse.js @@ -0,0 +1,113 @@ +/* global NexT, CONFIG, Velocity */ + +document.addEventListener('DOMContentLoaded', () => { + + var isRight = CONFIG.sidebar.position === 'right'; + var SIDEBAR_WIDTH = CONFIG.sidebar.width || 320; + var SIDEBAR_DISPLAY_DURATION = 200; + var mousePos = {}; + + var sidebarToggleLines = { + lines: document.querySelector('.sidebar-toggle'), + init : function() { + this.lines.classList.remove('toggle-arrow', 'toggle-close'); + }, + arrow: function() { + this.lines.classList.remove('toggle-close'); + this.lines.classList.add('toggle-arrow'); + }, + close: function() { + this.lines.classList.remove('toggle-arrow'); + this.lines.classList.add('toggle-close'); + } + }; + + var sidebarToggleMotion = { + sidebarEl : document.querySelector('.sidebar'), + isSidebarVisible: false, + init : function() { + sidebarToggleLines.init(); + + window.addEventListener('mousedown', this.mousedownHandler.bind(this)); + window.addEventListener('mouseup', this.mouseupHandler.bind(this)); + document.querySelector('#sidebar-dimmer').addEventListener('click', this.clickHandler.bind(this)); + document.querySelector('.sidebar-toggle').addEventListener('click', this.clickHandler.bind(this)); + document.querySelector('.sidebar-toggle').addEventListener('mouseenter', this.mouseEnterHandler.bind(this)); + document.querySelector('.sidebar-toggle').addEventListener('mouseleave', this.mouseLeaveHandler.bind(this)); + window.addEventListener('sidebar:show', this.showSidebar.bind(this)); + window.addEventListener('sidebar:hide', this.hideSidebar.bind(this)); + }, + mousedownHandler: function(event) { + mousePos.X = event.pageX; + mousePos.Y = event.pageY; + }, + mouseupHandler: function(event) { + var deltaX = event.pageX - mousePos.X; + var deltaY = event.pageY - mousePos.Y; + var clickingBlankPart = Math.sqrt((deltaX * deltaX) + (deltaY * deltaY)) < 20 && event.target.matches('.main'); + if (this.isSidebarVisible && (clickingBlankPart || event.target.matches('img.medium-zoom-image, .fancybox img'))) { + this.hideSidebar(); + } + }, + clickHandler: function() { + this.isSidebarVisible ? this.hideSidebar() : this.showSidebar(); + }, + mouseEnterHandler: function() { + if (!this.isSidebarVisible) { + sidebarToggleLines.arrow(); + } + }, + mouseLeaveHandler: function() { + if (!this.isSidebarVisible) { + sidebarToggleLines.init(); + } + }, + showSidebar: function() { + this.isSidebarVisible = true; + this.sidebarEl.classList.add('sidebar-active'); + if (typeof Velocity === 'function') { + Velocity(document.querySelectorAll('.sidebar .motion-element'), isRight ? 'transition.slideRightIn' : 'transition.slideLeftIn', { + stagger: 50, + drag : true + }); + } + + sidebarToggleLines.close(); + NexT.utils.isDesktop() && window.anime(Object.assign({ + targets : document.body, + duration: SIDEBAR_DISPLAY_DURATION, + easing : 'linear' + }, isRight ? { + 'padding-right': SIDEBAR_WIDTH + } : { + 'padding-left': SIDEBAR_WIDTH + })); + }, + hideSidebar: function() { + this.isSidebarVisible = false; + this.sidebarEl.classList.remove('sidebar-active'); + + sidebarToggleLines.init(); + NexT.utils.isDesktop() && window.anime(Object.assign({ + targets : document.body, + duration: SIDEBAR_DISPLAY_DURATION, + easing : 'linear' + }, isRight ? { + 'padding-right': 0 + } : { + 'padding-left': 0 + })); + } + }; + sidebarToggleMotion.init(); + + function updateFooterPosition() { + var footer = document.querySelector('.footer'); + var containerHeight = document.querySelector('.header').offsetHeight + document.querySelector('.main').offsetHeight + footer.offsetHeight; + footer.classList.toggle('footer-fixed', containerHeight <= window.innerHeight); + } + + updateFooterPosition(); + window.addEventListener('resize', updateFooterPosition); + window.addEventListener('scroll', updateFooterPosition); +}); diff --git a/js/schemes/pisces.js b/js/schemes/pisces.js new file mode 100644 index 00000000..41633eac --- /dev/null +++ b/js/schemes/pisces.js @@ -0,0 +1,86 @@ +/* global NexT, CONFIG */ + +var Affix = { + init: function(element, options) { + this.element = element; + this.offset = options || 0; + this.affixed = null; + this.unpin = null; + this.pinnedOffset = null; + this.checkPosition(); + window.addEventListener('scroll', this.checkPosition.bind(this)); + window.addEventListener('click', this.checkPositionWithEventLoop.bind(this)); + window.matchMedia('(min-width: 992px)').addListener(event => { + if (event.matches) { + this.offset = NexT.utils.getAffixParam(); + this.checkPosition(); + } + }); + }, + getState: function(scrollHeight, height, offsetTop, offsetBottom) { + let scrollTop = window.scrollY; + let targetHeight = window.innerHeight; + if (offsetTop != null && this.affixed === 'top') { + if (document.querySelector('.content-wrap').offsetHeight < offsetTop) return 'top'; + return scrollTop < offsetTop ? 'top' : false; + } + if (this.affixed === 'bottom') { + if (offsetTop != null) return this.unpin <= this.element.getBoundingClientRect().top ? false : 'bottom'; + return scrollTop + targetHeight <= scrollHeight - offsetBottom ? false : 'bottom'; + } + let initializing = this.affixed === null; + let colliderTop = initializing ? scrollTop : this.element.getBoundingClientRect().top + scrollTop; + let colliderHeight = initializing ? targetHeight : height; + if (offsetTop != null && scrollTop <= offsetTop) return 'top'; + if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom'; + return false; + }, + getPinnedOffset: function() { + if (this.pinnedOffset) return this.pinnedOffset; + this.element.classList.remove('affix-top', 'affix-bottom'); + this.element.classList.add('affix'); + return (this.pinnedOffset = this.element.getBoundingClientRect().top); + }, + checkPositionWithEventLoop() { + setTimeout(this.checkPosition.bind(this), 1); + }, + checkPosition: function() { + if (window.getComputedStyle(this.element).display === 'none') return; + let height = this.element.offsetHeight; + let { offset } = this; + let offsetTop = offset.top; + let offsetBottom = offset.bottom; + let { scrollHeight } = document.body; + let affix = this.getState(scrollHeight, height, offsetTop, offsetBottom); + if (this.affixed !== affix) { + if (this.unpin != null) this.element.style.top = ''; + let affixType = 'affix' + (affix ? '-' + affix : ''); + this.affixed = affix; + this.unpin = affix === 'bottom' ? this.getPinnedOffset() : null; + this.element.classList.remove('affix', 'affix-top', 'affix-bottom'); + this.element.classList.add(affixType); + } + if (affix === 'bottom') { + this.element.style.top = scrollHeight - height - offsetBottom + 'px'; + } + } +}; + +NexT.utils.getAffixParam = function() { + const sidebarOffset = CONFIG.sidebar.offset || 12; + + let headerOffset = document.querySelector('.header-inner').offsetHeight; + let footerOffset = document.querySelector('.footer').offsetHeight; + + document.querySelector('.sidebar').style.marginTop = headerOffset + sidebarOffset + 'px'; + + return { + top : headerOffset, + bottom: footerOffset + }; +}; + +document.addEventListener('DOMContentLoaded', () => { + + Affix.init(document.querySelector('.sidebar-inner'), NexT.utils.getAffixParam()); +}); diff --git a/js/utils.js b/js/utils.js new file mode 100644 index 00000000..74a6dfd5 --- /dev/null +++ b/js/utils.js @@ -0,0 +1,415 @@ +/* global NexT, CONFIG */ + +HTMLElement.prototype.wrap = function(wrapper) { + this.parentNode.insertBefore(wrapper, this); + this.parentNode.removeChild(this); + wrapper.appendChild(this); +}; + +NexT.utils = { + + /** + * Wrap images with fancybox. + */ + wrapImageWithFancyBox: function() { + document.querySelectorAll('.post-body :not(a) > img, .post-body > img').forEach(element => { + var $image = $(element); + var imageLink = $image.attr('data-src') || $image.attr('src'); + var $imageWrapLink = $image.wrap(``).parent('a'); + if ($image.is('.post-gallery img')) { + $imageWrapLink.attr('data-fancybox', 'gallery').attr('rel', 'gallery'); + } else if ($image.is('.group-picture img')) { + $imageWrapLink.attr('data-fancybox', 'group').attr('rel', 'group'); + } else { + $imageWrapLink.attr('data-fancybox', 'default').attr('rel', 'default'); + } + + var imageTitle = $image.attr('title') || $image.attr('alt'); + if (imageTitle) { + $imageWrapLink.append(`

    ${imageTitle}

    `); + // Make sure img title tag will show correctly in fancybox + $imageWrapLink.attr('title', imageTitle).attr('data-caption', imageTitle); + } + }); + + $.fancybox.defaults.hash = false; + $('.fancybox').fancybox({ + loop : true, + helpers: { + overlay: { + locked: false + } + } + }); + }, + + registerExtURL: function() { + document.querySelectorAll('span.exturl').forEach(element => { + let link = document.createElement('a'); + // https://stackoverflow.com/questions/30106476/using-javascripts-atob-to-decode-base64-doesnt-properly-decode-utf-8-strings + link.href = decodeURIComponent(atob(element.dataset.url).split('').map(c => { + return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); + }).join('')); + link.rel = 'noopener external nofollow noreferrer'; + link.target = '_blank'; + link.className = element.className; + link.title = element.title; + link.innerHTML = element.innerHTML; + element.parentNode.replaceChild(link, element); + }); + }, + + /** + * One-click copy code support. + */ + registerCopyCode: function() { + document.querySelectorAll('figure.highlight').forEach(element => { + const box = document.createElement('div'); + element.wrap(box); + box.classList.add('highlight-container'); + box.insertAdjacentHTML('beforeend', '
    '); + var button = element.parentNode.querySelector('.copy-btn'); + button.addEventListener('click', event => { + var target = event.currentTarget; + var code = [...target.parentNode.querySelectorAll('.code .line')].map(line => line.innerText).join('\n'); + var ta = document.createElement('textarea'); + ta.style.top = window.scrollY + 'px'; // Prevent page scrolling + ta.style.position = 'absolute'; + ta.style.opacity = '0'; + ta.readOnly = true; + ta.value = code; + document.body.append(ta); + const selection = document.getSelection(); + const selected = selection.rangeCount > 0 ? selection.getRangeAt(0) : false; + ta.select(); + ta.setSelectionRange(0, code.length); + ta.readOnly = false; + var result = document.execCommand('copy'); + if (CONFIG.copycode.show_result) { + target.querySelector('i').className = result ? 'fa fa-check fa-fw' : 'fa fa-times fa-fw'; + } + ta.blur(); // For iOS + target.blur(); + if (selected) { + selection.removeAllRanges(); + selection.addRange(selected); + } + document.body.removeChild(ta); + }); + button.addEventListener('mouseleave', event => { + setTimeout(() => { + event.target.querySelector('i').className = 'fa fa-clipboard fa-fw'; + }, 300); + }); + }); + }, + + wrapTableWithBox: function() { + document.querySelectorAll('table').forEach(element => { + const box = document.createElement('div'); + box.className = 'table-container'; + element.wrap(box); + }); + }, + + registerVideoIframe: function() { + document.querySelectorAll('iframe').forEach(element => { + const supported = [ + 'www.youtube.com', + 'player.vimeo.com', + 'player.youku.com', + 'player.bilibili.com', + 'www.tudou.com' + ].some(host => element.src.includes(host)); + if (supported && !element.parentNode.matches('.video-container')) { + const box = document.createElement('div'); + box.className = 'video-container'; + element.wrap(box); + let width = Number(element.width); + let height = Number(element.height); + if (width && height) { + element.parentNode.style.paddingTop = (height / width * 100) + '%'; + } + } + }); + }, + + registerScrollPercent: function() { + var THRESHOLD = 50; + var backToTop = document.querySelector('.back-to-top'); + var readingProgressBar = document.querySelector('.reading-progress-bar'); + // For init back to top in sidebar if page was scrolled after page refresh. + window.addEventListener('scroll', () => { + if (backToTop || readingProgressBar) { + var docHeight = document.querySelector('.container').offsetHeight; + var winHeight = window.innerHeight; + var contentVisibilityHeight = docHeight > winHeight ? docHeight - winHeight : document.body.scrollHeight - winHeight; + var scrollPercent = Math.min(100 * window.scrollY / contentVisibilityHeight, 100); + if (backToTop) { + backToTop.classList.toggle('back-to-top-on', window.scrollY > THRESHOLD); + backToTop.querySelector('span').innerText = Math.round(scrollPercent) + '%'; + } + if (readingProgressBar) { + readingProgressBar.style.width = scrollPercent.toFixed(2) + '%'; + } + } + }); + + backToTop && backToTop.addEventListener('click', () => { + window.anime({ + targets : document.scrollingElement, + duration : 500, + easing : 'linear', + scrollTop: 0 + }); + }); + }, + + /** + * Tabs tag listener (without twitter bootstrap). + */ + registerTabsTag: function() { + // Binding `nav-tabs` & `tab-content` by real time permalink changing. + document.querySelectorAll('.tabs ul.nav-tabs .tab').forEach(element => { + element.addEventListener('click', event => { + event.preventDefault(); + var target = event.currentTarget; + // Prevent selected tab to select again. + if (!target.classList.contains('active')) { + // Add & Remove active class on `nav-tabs` & `tab-content`. + [...target.parentNode.children].forEach(element => { + element.classList.remove('active'); + }); + target.classList.add('active'); + var tActive = document.getElementById(target.querySelector('a').getAttribute('href').replace('#', '')); + [...tActive.parentNode.children].forEach(element => { + element.classList.remove('active'); + }); + tActive.classList.add('active'); + // Trigger event + tActive.dispatchEvent(new Event('tabs:click', { + bubbles: true + })); + } + }); + }); + + window.dispatchEvent(new Event('tabs:register')); + }, + + registerCanIUseTag: function() { + // Get responsive height passed from iframe. + window.addEventListener('message', ({ data }) => { + if ((typeof data === 'string') && data.includes('ciu_embed')) { + var featureID = data.split(':')[1]; + var height = data.split(':')[2]; + document.querySelector(`iframe[data-feature=${featureID}]`).style.height = parseInt(height, 10) + 5 + 'px'; + } + }, false); + }, + + registerActiveMenuItem: function() { + document.querySelectorAll('.menu-item').forEach(element => { + var target = element.querySelector('a[href]'); + if (!target) return; + var isSamePath = target.pathname === location.pathname || target.pathname === location.pathname.replace('index.html', ''); + var isSubPath = !CONFIG.root.startsWith(target.pathname) && location.pathname.startsWith(target.pathname); + element.classList.toggle('menu-item-active', target.hostname === location.hostname && (isSamePath || isSubPath)); + }); + }, + + registerLangSelect: function() { + let selects = document.querySelectorAll('.lang-select'); + selects.forEach(sel => { + sel.value = CONFIG.page.lang; + sel.addEventListener('change', () => { + let target = sel.options[sel.selectedIndex]; + document.querySelectorAll('.lang-select-label span').forEach(span => span.innerText = target.text); + let url = target.dataset.href; + window.pjax ? window.pjax.loadUrl(url) : window.location.href = url; + }); + }); + }, + + registerSidebarTOC: function() { + const navItems = document.querySelectorAll('.post-toc li'); + const sections = [...navItems].map(element => { + var link = element.querySelector('a.nav-link'); + var target = document.getElementById(decodeURI(link.getAttribute('href')).replace('#', '')); + // TOC item animation navigate. + link.addEventListener('click', event => { + event.preventDefault(); + var offset = target.getBoundingClientRect().top + window.scrollY; + window.anime({ + targets : document.scrollingElement, + duration : 500, + easing : 'linear', + scrollTop: offset + 10 + }); + }); + return target; + }); + + var tocElement = document.querySelector('.post-toc-wrap'); + function activateNavByIndex(target) { + if (target.classList.contains('active-current')) return; + + document.querySelectorAll('.post-toc .active').forEach(element => { + element.classList.remove('active', 'active-current'); + }); + target.classList.add('active', 'active-current'); + var parent = target.parentNode; + while (!parent.matches('.post-toc')) { + if (parent.matches('li')) parent.classList.add('active'); + parent = parent.parentNode; + } + // Scrolling to center active TOC element if TOC content is taller then viewport. + window.anime({ + targets : tocElement, + duration : 200, + easing : 'linear', + scrollTop: tocElement.scrollTop - (tocElement.offsetHeight / 2) + target.getBoundingClientRect().top - tocElement.getBoundingClientRect().top + }); + } + + function findIndex(entries) { + let index = 0; + let entry = entries[index]; + if (entry.boundingClientRect.top > 0) { + index = sections.indexOf(entry.target); + return index === 0 ? 0 : index - 1; + } + for (; index < entries.length; index++) { + if (entries[index].boundingClientRect.top <= 0) { + entry = entries[index]; + } else { + return sections.indexOf(entry.target); + } + } + return sections.indexOf(entry.target); + } + + function createIntersectionObserver(marginTop) { + marginTop = Math.floor(marginTop + 10000); + let intersectionObserver = new IntersectionObserver((entries, observe) => { + let scrollHeight = document.documentElement.scrollHeight + 100; + if (scrollHeight > marginTop) { + observe.disconnect(); + createIntersectionObserver(scrollHeight); + return; + } + let index = findIndex(entries); + activateNavByIndex(navItems[index]); + }, { + rootMargin: marginTop + 'px 0px -100% 0px', + threshold : 0 + }); + sections.forEach(element => { + element && intersectionObserver.observe(element); + }); + } + createIntersectionObserver(document.documentElement.scrollHeight); + }, + + hasMobileUA: function() { + let ua = navigator.userAgent; + let pa = /iPad|iPhone|Android|Opera Mini|BlackBerry|webOS|UCWEB|Blazer|PSP|IEMobile|Symbian/g; + return pa.test(ua); + }, + + isTablet: function() { + return window.screen.width < 992 && window.screen.width > 767 && this.hasMobileUA(); + }, + + isMobile: function() { + return window.screen.width < 767 && this.hasMobileUA(); + }, + + isDesktop: function() { + return !this.isTablet() && !this.isMobile(); + }, + + supportsPDFs: function() { + let ua = navigator.userAgent; + let isFirefoxWithPDFJS = ua.includes('irefox') && parseInt(ua.split('rv:')[1].split('.')[0], 10) > 18; + let supportsPdfMimeType = typeof navigator.mimeTypes['application/pdf'] !== 'undefined'; + let isIOS = /iphone|ipad|ipod/i.test(ua.toLowerCase()); + return isFirefoxWithPDFJS || (supportsPdfMimeType && !isIOS); + }, + + /** + * Init Sidebar & TOC inner dimensions on all pages and for all schemes. + * Need for Sidebar/TOC inner scrolling if content taller then viewport. + */ + initSidebarDimension: function() { + var sidebarNav = document.querySelector('.sidebar-nav'); + var sidebarNavHeight = sidebarNav.style.display !== 'none' ? sidebarNav.offsetHeight : 0; + var sidebarOffset = CONFIG.sidebar.offset || 12; + var sidebarb2tHeight = CONFIG.back2top.enable && CONFIG.back2top.sidebar ? document.querySelector('.back-to-top').offsetHeight : 0; + var sidebarSchemePadding = (CONFIG.sidebar.padding * 2) + sidebarNavHeight + sidebarb2tHeight; + // Margin of sidebar b2t: -4px -10px -18px, brings a different of 22px. + if (CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini') sidebarSchemePadding += (sidebarOffset * 2) - 22; + // Initialize Sidebar & TOC Height. + var sidebarWrapperHeight = document.body.offsetHeight - sidebarSchemePadding + 'px'; + document.querySelector('.site-overview-wrap').style.maxHeight = sidebarWrapperHeight; + document.querySelector('.post-toc-wrap').style.maxHeight = sidebarWrapperHeight; + }, + + updateSidebarPosition: function() { + var sidebarNav = document.querySelector('.sidebar-nav'); + var hasTOC = document.querySelector('.post-toc'); + if (hasTOC) { + sidebarNav.style.display = ''; + sidebarNav.classList.add('motion-element'); + document.querySelector('.sidebar-nav-toc').click(); + } else { + sidebarNav.style.display = 'none'; + sidebarNav.classList.remove('motion-element'); + document.querySelector('.sidebar-nav-overview').click(); + } + NexT.utils.initSidebarDimension(); + if (!this.isDesktop() || CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini') return; + // Expand sidebar on post detail page by default, when post has a toc. + var display = CONFIG.page.sidebar; + if (typeof display !== 'boolean') { + // There's no definition sidebar in the page front-matter. + display = CONFIG.sidebar.display === 'always' || (CONFIG.sidebar.display === 'post' && hasTOC); + } + if (display) { + window.dispatchEvent(new Event('sidebar:show')); + } + }, + + getScript: function(url, callback, condition) { + if (condition) { + callback(); + } else { + var script = document.createElement('script'); + script.onload = script.onreadystatechange = function(_, isAbort) { + if (isAbort || !script.readyState || /loaded|complete/.test(script.readyState)) { + script.onload = script.onreadystatechange = null; + script = undefined; + if (!isAbort && callback) setTimeout(callback, 0); + } + }; + script.src = url; + document.head.appendChild(script); + } + }, + + loadComments: function(element, callback) { + if (!CONFIG.comments.lazyload || !element) { + callback(); + return; + } + let intersectionObserver = new IntersectionObserver((entries, observer) => { + let entry = entries[0]; + if (entry.isIntersecting) { + callback(); + observer.disconnect(); + } + }); + intersectionObserver.observe(element); + return intersectionObserver; + } +}; diff --git a/lib/anime.min.js b/lib/anime.min.js new file mode 100644 index 00000000..99b263aa --- /dev/null +++ b/lib/anime.min.js @@ -0,0 +1,8 @@ +/* + * anime.js v3.1.0 + * (c) 2019 Julian Garnier + * Released under the MIT license + * animejs.com + */ + +!function(n,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):n.anime=e()}(this,function(){"use strict";var n={update:null,begin:null,loopBegin:null,changeBegin:null,change:null,changeComplete:null,loopComplete:null,complete:null,loop:1,direction:"normal",autoplay:!0,timelineOffset:0},e={duration:1e3,delay:0,endDelay:0,easing:"easeOutElastic(1, .5)",round:0},r=["translateX","translateY","translateZ","rotate","rotateX","rotateY","rotateZ","scale","scaleX","scaleY","scaleZ","skew","skewX","skewY","perspective"],t={CSS:{},springs:{}};function a(n,e,r){return Math.min(Math.max(n,e),r)}function o(n,e){return n.indexOf(e)>-1}function u(n,e){return n.apply(null,e)}var i={arr:function(n){return Array.isArray(n)},obj:function(n){return o(Object.prototype.toString.call(n),"Object")},pth:function(n){return i.obj(n)&&n.hasOwnProperty("totalLength")},svg:function(n){return n instanceof SVGElement},inp:function(n){return n instanceof HTMLInputElement},dom:function(n){return n.nodeType||i.svg(n)},str:function(n){return"string"==typeof n},fnc:function(n){return"function"==typeof n},und:function(n){return void 0===n},hex:function(n){return/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(n)},rgb:function(n){return/^rgb/.test(n)},hsl:function(n){return/^hsl/.test(n)},col:function(n){return i.hex(n)||i.rgb(n)||i.hsl(n)},key:function(r){return!n.hasOwnProperty(r)&&!e.hasOwnProperty(r)&&"targets"!==r&&"keyframes"!==r}};function c(n){var e=/\(([^)]+)\)/.exec(n);return e?e[1].split(",").map(function(n){return parseFloat(n)}):[]}function s(n,e){var r=c(n),o=a(i.und(r[0])?1:r[0],.1,100),u=a(i.und(r[1])?100:r[1],.1,100),s=a(i.und(r[2])?10:r[2],.1,100),f=a(i.und(r[3])?0:r[3],.1,100),l=Math.sqrt(u/o),d=s/(2*Math.sqrt(u*o)),p=d<1?l*Math.sqrt(1-d*d):0,h=1,v=d<1?(d*l-f)/p:-f+l;function g(n){var r=e?e*n/1e3:n;return r=d<1?Math.exp(-r*d*l)*(h*Math.cos(p*r)+v*Math.sin(p*r)):(h+v*r)*Math.exp(-r*l),0===n||1===n?n:1-r}return e?g:function(){var e=t.springs[n];if(e)return e;for(var r=0,a=0;;)if(1===g(r+=1/6)){if(++a>=16)break}else a=0;var o=r*(1/6)*1e3;return t.springs[n]=o,o}}function f(n){return void 0===n&&(n=10),function(e){return Math.round(e*n)*(1/n)}}var l,d,p=function(){var n=11,e=1/(n-1);function r(n,e){return 1-3*e+3*n}function t(n,e){return 3*e-6*n}function a(n){return 3*n}function o(n,e,o){return((r(e,o)*n+t(e,o))*n+a(e))*n}function u(n,e,o){return 3*r(e,o)*n*n+2*t(e,o)*n+a(e)}return function(r,t,a,i){if(0<=r&&r<=1&&0<=a&&a<=1){var c=new Float32Array(n);if(r!==t||a!==i)for(var s=0;s=.001?function(n,e,r,t){for(var a=0;a<4;++a){var i=u(e,r,t);if(0===i)return e;e-=(o(e,r,t)-n)/i}return e}(t,l,r,a):0===d?l:function(n,e,r,t,a){for(var u,i,c=0;(u=o(i=e+(r-e)/2,t,a)-n)>0?r=i:e=i,Math.abs(u)>1e-7&&++c<10;);return i}(t,i,i+e,r,a)}}}(),h=(l={linear:function(){return function(n){return n}}},d={Sine:function(){return function(n){return 1-Math.cos(n*Math.PI/2)}},Circ:function(){return function(n){return 1-Math.sqrt(1-n*n)}},Back:function(){return function(n){return n*n*(3*n-2)}},Bounce:function(){return function(n){for(var e,r=4;n<((e=Math.pow(2,--r))-1)/11;);return 1/Math.pow(4,3-r)-7.5625*Math.pow((3*e-2)/22-n,2)}},Elastic:function(n,e){void 0===n&&(n=1),void 0===e&&(e=.5);var r=a(n,1,10),t=a(e,.1,2);return function(n){return 0===n||1===n?n:-r*Math.pow(2,10*(n-1))*Math.sin((n-1-t/(2*Math.PI)*Math.asin(1/r))*(2*Math.PI)/t)}}},["Quad","Cubic","Quart","Quint","Expo"].forEach(function(n,e){d[n]=function(){return function(n){return Math.pow(n,e+2)}}}),Object.keys(d).forEach(function(n){var e=d[n];l["easeIn"+n]=e,l["easeOut"+n]=function(n,r){return function(t){return 1-e(n,r)(1-t)}},l["easeInOut"+n]=function(n,r){return function(t){return t<.5?e(n,r)(2*t)/2:1-e(n,r)(-2*t+2)/2}}}),l);function v(n,e){if(i.fnc(n))return n;var r=n.split("(")[0],t=h[r],a=c(n);switch(r){case"spring":return s(n,e);case"cubicBezier":return u(p,a);case"steps":return u(f,a);default:return u(t,a)}}function g(n){try{return document.querySelectorAll(n)}catch(n){return}}function m(n,e){for(var r=n.length,t=arguments.length>=2?arguments[1]:void 0,a=[],o=0;o1&&(r-=1),r<1/6?n+6*(e-n)*r:r<.5?e:r<2/3?n+(e-n)*(2/3-r)*6:n}if(0==u)e=r=t=i;else{var f=i<.5?i*(1+u):i+u-i*u,l=2*i-f;e=s(l,f,o+1/3),r=s(l,f,o),t=s(l,f,o-1/3)}return"rgba("+255*e+","+255*r+","+255*t+","+c+")"}(n):void 0;var e,r,t,a}function C(n){var e=/[+-]?\d*\.?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?(%|px|pt|em|rem|in|cm|mm|ex|ch|pc|vw|vh|vmin|vmax|deg|rad|turn)?$/.exec(n);if(e)return e[1]}function B(n,e){return i.fnc(n)?n(e.target,e.id,e.total):n}function P(n,e){return n.getAttribute(e)}function I(n,e,r){if(M([r,"deg","rad","turn"],C(e)))return e;var a=t.CSS[e+r];if(!i.und(a))return a;var o=document.createElement(n.tagName),u=n.parentNode&&n.parentNode!==document?n.parentNode:document.body;u.appendChild(o),o.style.position="absolute",o.style.width=100+r;var c=100/o.offsetWidth;u.removeChild(o);var s=c*parseFloat(e);return t.CSS[e+r]=s,s}function T(n,e,r){if(e in n.style){var t=e.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase(),a=n.style[e]||getComputedStyle(n).getPropertyValue(t)||"0";return r?I(n,a,r):a}}function D(n,e){return i.dom(n)&&!i.inp(n)&&(P(n,e)||i.svg(n)&&n[e])?"attribute":i.dom(n)&&M(r,e)?"transform":i.dom(n)&&"transform"!==e&&T(n,e)?"css":null!=n[e]?"object":void 0}function E(n){if(i.dom(n)){for(var e,r=n.style.transform||"",t=/(\w+)\(([^)]*)\)/g,a=new Map;e=t.exec(r);)a.set(e[1],e[2]);return a}}function F(n,e,r,t){var a,u=o(e,"scale")?1:0+(o(a=e,"translate")||"perspective"===a?"px":o(a,"rotate")||o(a,"skew")?"deg":void 0),i=E(n).get(e)||u;return r&&(r.transforms.list.set(e,i),r.transforms.last=e),t?I(n,i,t):i}function N(n,e,r,t){switch(D(n,e)){case"transform":return F(n,e,t,r);case"css":return T(n,e,r);case"attribute":return P(n,e);default:return n[e]||0}}function A(n,e){var r=/^(\*=|\+=|-=)/.exec(n);if(!r)return n;var t=C(n)||0,a=parseFloat(e),o=parseFloat(n.replace(r[0],""));switch(r[0][0]){case"+":return a+o+t;case"-":return a-o+t;case"*":return a*o+t}}function L(n,e){if(i.col(n))return O(n);if(/\s/g.test(n))return n;var r=C(n),t=r?n.substr(0,n.length-r.length):n;return e?t+e:t}function j(n,e){return Math.sqrt(Math.pow(e.x-n.x,2)+Math.pow(e.y-n.y,2))}function S(n){for(var e,r=n.points,t=0,a=0;a0&&(t+=j(e,o)),e=o}return t}function q(n){if(n.getTotalLength)return n.getTotalLength();switch(n.tagName.toLowerCase()){case"circle":return o=n,2*Math.PI*P(o,"r");case"rect":return 2*P(a=n,"width")+2*P(a,"height");case"line":return j({x:P(t=n,"x1"),y:P(t,"y1")},{x:P(t,"x2"),y:P(t,"y2")});case"polyline":return S(n);case"polygon":return r=(e=n).points,S(e)+j(r.getItem(r.numberOfItems-1),r.getItem(0))}var e,r,t,a,o}function $(n,e){var r=e||{},t=r.el||function(n){for(var e=n.parentNode;i.svg(e)&&i.svg(e.parentNode);)e=e.parentNode;return e}(n),a=t.getBoundingClientRect(),o=P(t,"viewBox"),u=a.width,c=a.height,s=r.viewBox||(o?o.split(" "):[0,0,u,c]);return{el:t,viewBox:s,x:s[0]/1,y:s[1]/1,w:u/s[2],h:c/s[3]}}function X(n,e){function r(r){void 0===r&&(r=0);var t=e+r>=1?e+r:0;return n.el.getPointAtLength(t)}var t=$(n.el,n.svg),a=r(),o=r(-1),u=r(1);switch(n.property){case"x":return(a.x-t.x)*t.w;case"y":return(a.y-t.y)*t.h;case"angle":return 180*Math.atan2(u.y-o.y,u.x-o.x)/Math.PI}}function Y(n,e){var r=/[+-]?\d*\.?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/g,t=L(i.pth(n)?n.totalLength:n,e)+"";return{original:t,numbers:t.match(r)?t.match(r).map(Number):[0],strings:i.str(n)||e?t.split(r):[]}}function Z(n){return m(n?y(i.arr(n)?n.map(b):b(n)):[],function(n,e,r){return r.indexOf(n)===e})}function Q(n){var e=Z(n);return e.map(function(n,r){return{target:n,id:r,total:e.length,transforms:{list:E(n)}}})}function V(n,e){var r=x(e);if(/^spring/.test(r.easing)&&(r.duration=s(r.easing)),i.arr(n)){var t=n.length;2===t&&!i.obj(n[0])?n={value:n}:i.fnc(e.duration)||(r.duration=e.duration/t)}var a=i.arr(n)?n:[n];return a.map(function(n,r){var t=i.obj(n)&&!i.pth(n)?n:{value:n};return i.und(t.delay)&&(t.delay=r?0:e.delay),i.und(t.endDelay)&&(t.endDelay=r===a.length-1?e.endDelay:0),t}).map(function(n){return k(n,r)})}function z(n,e){var r=[],t=e.keyframes;for(var a in t&&(e=k(function(n){for(var e=m(y(n.map(function(n){return Object.keys(n)})),function(n){return i.key(n)}).reduce(function(n,e){return n.indexOf(e)<0&&n.push(e),n},[]),r={},t=function(t){var a=e[t];r[a]=n.map(function(n){var e={};for(var r in n)i.key(r)?r==a&&(e.value=n[r]):e[r]=n[r];return e})},a=0;a-1&&(_.splice(o,1),r=_.length)}else a.tick(e);t++}n()}else U=cancelAnimationFrame(U)}return n}();function rn(r){void 0===r&&(r={});var t,o=0,u=0,i=0,c=0,s=null;function f(n){var e=window.Promise&&new Promise(function(n){return s=n});return n.finished=e,e}var l,d,p,h,v,g,y,b,M=(d=w(n,l=r),p=w(e,l),h=z(p,l),v=Q(l.targets),g=W(v,h),y=J(g,p),b=K,K++,k(d,{id:b,children:[],animatables:v,animations:g,duration:y.duration,delay:y.delay,endDelay:y.endDelay}));f(M);function x(){var n=M.direction;"alternate"!==n&&(M.direction="normal"!==n?"normal":"reverse"),M.reversed=!M.reversed,t.forEach(function(n){return n.reversed=M.reversed})}function O(n){return M.reversed?M.duration-n:n}function C(){o=0,u=O(M.currentTime)*(1/rn.speed)}function B(n,e){e&&e.seek(n-e.timelineOffset)}function P(n){for(var e=0,r=M.animations,t=r.length;e2||(b=Math.round(b*p)/p)),h.push(b)}var k=d.length;if(k){g=d[0];for(var O=0;O0&&(M.began=!0,I("begin")),!M.loopBegan&&M.currentTime>0&&(M.loopBegan=!0,I("loopBegin")),d<=r&&0!==M.currentTime&&P(0),(d>=l&&M.currentTime!==e||!e)&&P(e),d>r&&d=e&&(u=0,M.remaining&&!0!==M.remaining&&M.remaining--,M.remaining?(o=i,I("loopComplete"),M.loopBegan=!1,"alternate"===M.direction&&x()):(M.paused=!0,M.completed||(M.completed=!0,I("loopComplete"),I("complete"),!M.passThrough&&"Promise"in window&&(s(),f(M)))))}return M.reset=function(){var n=M.direction;M.passThrough=!1,M.currentTime=0,M.progress=0,M.paused=!0,M.began=!1,M.loopBegan=!1,M.changeBegan=!1,M.completed=!1,M.changeCompleted=!1,M.reversePlayback=!1,M.reversed="reverse"===n,M.remaining=M.loop,t=M.children;for(var e=c=t.length;e--;)M.children[e].reset();(M.reversed&&!0!==M.loop||"alternate"===n&&1===M.loop)&&M.remaining++,P(M.reversed?M.duration:0)},M.set=function(n,e){return R(n,e),M},M.tick=function(n){i=n,o||(o=i),T((i+(u-o))*rn.speed)},M.seek=function(n){T(O(n))},M.pause=function(){M.paused=!0,C()},M.play=function(){M.paused&&(M.completed&&M.reset(),M.paused=!1,_.push(M),C(),U||en())},M.reverse=function(){x(),C()},M.restart=function(){M.reset(),M.play()},M.reset(),M.autoplay&&M.play(),M}function tn(n,e){for(var r=e.length;r--;)M(n,e[r].animatable.target)&&e.splice(r,1)}return"undefined"!=typeof document&&document.addEventListener("visibilitychange",function(){document.hidden?(_.forEach(function(n){return n.pause()}),nn=_.slice(0),rn.running=_=[]):nn.forEach(function(n){return n.play()})}),rn.version="3.1.0",rn.speed=1,rn.running=_,rn.remove=function(n){for(var e=Z(n),r=_.length;r--;){var t=_[r],a=t.animations,o=t.children;tn(e,a);for(var u=o.length;u--;){var i=o[u],c=i.animations;tn(e,c),c.length||i.children.length||o.splice(u,1)}a.length||o.length||t.pause()}},rn.get=N,rn.set=R,rn.convertPx=I,rn.path=function(n,e){var r=i.str(n)?g(n)[0]:n,t=e||100;return function(n){return{property:n,el:r,svg:$(r),totalLength:q(r)*(t/100)}}},rn.setDashoffset=function(n){var e=q(n);return n.setAttribute("stroke-dasharray",e),e},rn.stagger=function(n,e){void 0===e&&(e={});var r=e.direction||"normal",t=e.easing?v(e.easing):null,a=e.grid,o=e.axis,u=e.from||0,c="first"===u,s="center"===u,f="last"===u,l=i.arr(n),d=l?parseFloat(n[0]):parseFloat(n),p=l?parseFloat(n[1]):0,h=C(l?n[1]:n)||0,g=e.start||0+(l?d:0),m=[],y=0;return function(n,e,i){if(c&&(u=0),s&&(u=(i-1)/2),f&&(u=i-1),!m.length){for(var v=0;v-1&&_.splice(o,1);for(var s=0;sli{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-webkit-transform:scaleY(-1);transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1);transform:scale(-1)}:root .fa-flip-both,:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-acquisitions-incorporated:before{content:"\f6af"}.fa-ad:before{content:"\f641"}.fa-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adobe:before{content:"\f778"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-air-freshener:before{content:"\f5d0"}.fa-airbnb:before{content:"\f834"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-alipay:before{content:"\f642"}.fa-allergies:before{content:"\f461"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-amilia:before{content:"\f36d"}.fa-anchor:before{content:"\f13d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angry:before{content:"\f556"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-ankh:before{content:"\f644"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-alt:before{content:"\f5d1"}.fa-apple-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.fa-archway:before{content:"\f557"}.fa-arrow-alt-circle-down:before{content:"\f358"}.fa-arrow-alt-circle-left:before{content:"\f359"}.fa-arrow-alt-circle-right:before{content:"\f35a"}.fa-arrow-alt-circle-up:before{content:"\f35b"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrows-alt:before{content:"\f0b2"}.fa-arrows-alt-h:before{content:"\f337"}.fa-arrows-alt-v:before{content:"\f338"}.fa-artstation:before{content:"\f77a"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asterisk:before{content:"\f069"}.fa-asymmetrik:before{content:"\f372"}.fa-at:before{content:"\f1fa"}.fa-atlas:before{content:"\f558"}.fa-atlassian:before{content:"\f77b"}.fa-atom:before{content:"\f5d2"}.fa-audible:before{content:"\f373"}.fa-audio-description:before{content:"\f29e"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-award:before{content:"\f559"}.fa-aws:before{content:"\f375"}.fa-baby:before{content:"\f77c"}.fa-baby-carriage:before{content:"\f77d"}.fa-backspace:before{content:"\f55a"}.fa-backward:before{content:"\f04a"}.fa-bacon:before{content:"\f7e5"}.fa-bahai:before{content:"\f666"}.fa-balance-scale:before{content:"\f24e"}.fa-balance-scale-left:before{content:"\f515"}.fa-balance-scale-right:before{content:"\f516"}.fa-ban:before{content:"\f05e"}.fa-band-aid:before{content:"\f462"}.fa-bandcamp:before{content:"\f2d5"}.fa-barcode:before{content:"\f02a"}.fa-bars:before{content:"\f0c9"}.fa-baseball-ball:before{content:"\f433"}.fa-basketball-ball:before{content:"\f434"}.fa-bath:before{content:"\f2cd"}.fa-battery-empty:before{content:"\f244"}.fa-battery-full:before{content:"\f240"}.fa-battery-half:before{content:"\f242"}.fa-battery-quarter:before{content:"\f243"}.fa-battery-three-quarters:before{content:"\f241"}.fa-battle-net:before{content:"\f835"}.fa-bed:before{content:"\f236"}.fa-beer:before{content:"\f0fc"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bell:before{content:"\f0f3"}.fa-bell-slash:before{content:"\f1f6"}.fa-bezier-curve:before{content:"\f55b"}.fa-bible:before{content:"\f647"}.fa-bicycle:before{content:"\f206"}.fa-biking:before{content:"\f84a"}.fa-bimobject:before{content:"\f378"}.fa-binoculars:before{content:"\f1e5"}.fa-biohazard:before{content:"\f780"}.fa-birthday-cake:before{content:"\f1fd"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blender:before{content:"\f517"}.fa-blender-phone:before{content:"\f6b6"}.fa-blind:before{content:"\f29d"}.fa-blog:before{content:"\f781"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bold:before{content:"\f032"}.fa-bolt:before{content:"\f0e7"}.fa-bomb:before{content:"\f1e2"}.fa-bone:before{content:"\f5d7"}.fa-bong:before{content:"\f55c"}.fa-book:before{content:"\f02d"}.fa-book-dead:before{content:"\f6b7"}.fa-book-medical:before{content:"\f7e6"}.fa-book-open:before{content:"\f518"}.fa-book-reader:before{content:"\f5da"}.fa-bookmark:before{content:"\f02e"}.fa-bootstrap:before{content:"\f836"}.fa-border-all:before{content:"\f84c"}.fa-border-none:before{content:"\f850"}.fa-border-style:before{content:"\f853"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-box-open:before{content:"\f49e"}.fa-box-tissue:before{content:"\f95b"}.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-brain:before{content:"\f5dc"}.fa-bread-slice:before{content:"\f7ec"}.fa-briefcase:before{content:"\f0b1"}.fa-briefcase-medical:before{content:"\f469"}.fa-broadcast-tower:before{content:"\f519"}.fa-broom:before{content:"\f51a"}.fa-brush:before{content:"\f55d"}.fa-btc:before{content:"\f15a"}.fa-buffer:before{content:"\f837"}.fa-bug:before{content:"\f188"}.fa-building:before{content:"\f1ad"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-burn:before{content:"\f46a"}.fa-buromobelexperte:before{content:"\f37f"}.fa-bus:before{content:"\f207"}.fa-bus-alt:before{content:"\f55e"}.fa-business-time:before{content:"\f64a"}.fa-buy-n-large:before{content:"\f8a6"}.fa-buysellads:before{content:"\f20d"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-alt:before{content:"\f073"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-day:before{content:"\f783"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-times:before{content:"\f273"}.fa-calendar-week:before{content:"\f784"}.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-campground:before{content:"\f6bb"}.fa-canadian-maple-leaf:before{content:"\f785"}.fa-candy-cane:before{content:"\f786"}.fa-cannabis:before{content:"\f55f"}.fa-capsules:before{content:"\f46b"}.fa-car:before{content:"\f1b9"}.fa-car-alt:before{content:"\f5de"}.fa-car-battery:before{content:"\f5df"}.fa-car-crash:before{content:"\f5e1"}.fa-car-side:before{content:"\f5e4"}.fa-caravan:before{content:"\f8ff"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-square-down:before{content:"\f150"}.fa-caret-square-left:before{content:"\f191"}.fa-caret-square-right:before{content:"\f152"}.fa-caret-square-up:before{content:"\f151"}.fa-caret-up:before{content:"\f0d8"}.fa-carrot:before{content:"\f787"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-plus:before{content:"\f217"}.fa-cash-register:before{content:"\f788"}.fa-cat:before{content:"\f6be"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-centos:before{content:"\f789"}.fa-certificate:before{content:"\f0a3"}.fa-chair:before{content:"\f6c0"}.fa-chalkboard:before{content:"\f51b"}.fa-chalkboard-teacher:before{content:"\f51c"}.fa-charging-station:before{content:"\f5e7"}.fa-chart-area:before{content:"\f1fe"}.fa-chart-bar:before{content:"\f080"}.fa-chart-line:before{content:"\f201"}.fa-chart-pie:before{content:"\f200"}.fa-check:before{content:"\f00c"}.fa-check-circle:before{content:"\f058"}.fa-check-double:before{content:"\f560"}.fa-check-square:before{content:"\f14a"}.fa-cheese:before{content:"\f7ef"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-chrome:before{content:"\f268"}.fa-chromecast:before{content:"\f838"}.fa-church:before{content:"\f51d"}.fa-circle:before{content:"\f111"}.fa-circle-notch:before{content:"\f1ce"}.fa-city:before{content:"\f64f"}.fa-clinic-medical:before{content:"\f7f2"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clock:before{content:"\f017"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-download-alt:before{content:"\f381"}.fa-cloud-meatball:before{content:"\f73b"}.fa-cloud-moon:before{content:"\f6c3"}.fa-cloud-moon-rain:before{content:"\f73c"}.fa-cloud-rain:before{content:"\f73d"}.fa-cloud-showers-heavy:before{content:"\f740"}.fa-cloud-sun:before{content:"\f6c4"}.fa-cloud-sun-rain:before{content:"\f743"}.fa-cloud-upload-alt:before{content:"\f382"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-cocktail:before{content:"\f561"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-coffee:before{content:"\f0f4"}.fa-cog:before{content:"\f013"}.fa-cogs:before{content:"\f085"}.fa-coins:before{content:"\f51e"}.fa-columns:before{content:"\f0db"}.fa-comment:before{content:"\f075"}.fa-comment-alt:before{content:"\f27a"}.fa-comment-dollar:before{content:"\f651"}.fa-comment-dots:before{content:"\f4ad"}.fa-comment-medical:before{content:"\f7f5"}.fa-comment-slash:before{content:"\f4b3"}.fa-comments:before{content:"\f086"}.fa-comments-dollar:before{content:"\f653"}.fa-compact-disc:before{content:"\f51f"}.fa-compass:before{content:"\f14e"}.fa-compress:before{content:"\f066"}.fa-compress-alt:before{content:"\f422"}.fa-compress-arrows-alt:before{content:"\f78c"}.fa-concierge-bell:before{content:"\f562"}.fa-confluence:before{content:"\f78d"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-cookie:before{content:"\f563"}.fa-cookie-bite:before{content:"\f564"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-cotton-bureau:before{content:"\f89e"}.fa-couch:before{content:"\f4b8"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-creative-commons-zero:before{content:"\f4f3"}.fa-credit-card:before{content:"\f09d"}.fa-critical-role:before{content:"\f6c9"}.fa-crop:before{content:"\f125"}.fa-crop-alt:before{content:"\f565"}.fa-cross:before{content:"\f654"}.fa-crosshairs:before{content:"\f05b"}.fa-crow:before{content:"\f520"}.fa-crown:before{content:"\f521"}.fa-crutch:before{content:"\f7f7"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-cut:before{content:"\f0c4"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-d-and-d-beyond:before{content:"\f6ca"}.fa-dailymotion:before{content:"\f952"}.fa-dashcube:before{content:"\f210"}.fa-database:before{content:"\f1c0"}.fa-deaf:before{content:"\f2a4"}.fa-delicious:before{content:"\f1a5"}.fa-democrat:before{content:"\f747"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-desktop:before{content:"\f108"}.fa-dev:before{content:"\f6cc"}.fa-deviantart:before{content:"\f1bd"}.fa-dharmachakra:before{content:"\f655"}.fa-dhl:before{content:"\f790"}.fa-diagnoses:before{content:"\f470"}.fa-diaspora:before{content:"\f791"}.fa-dice:before{content:"\f522"}.fa-dice-d20:before{content:"\f6cf"}.fa-dice-d6:before{content:"\f6d1"}.fa-dice-five:before{content:"\f523"}.fa-dice-four:before{content:"\f524"}.fa-dice-one:before{content:"\f525"}.fa-dice-six:before{content:"\f526"}.fa-dice-three:before{content:"\f527"}.fa-dice-two:before{content:"\f528"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-digital-tachograph:before{content:"\f566"}.fa-directions:before{content:"\f5eb"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-disease:before{content:"\f7fa"}.fa-divide:before{content:"\f529"}.fa-dizzy:before{content:"\f567"}.fa-dna:before{content:"\f471"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-dog:before{content:"\f6d3"}.fa-dollar-sign:before{content:"\f155"}.fa-dolly:before{content:"\f472"}.fa-dolly-flatbed:before{content:"\f474"}.fa-donate:before{content:"\f4b9"}.fa-door-closed:before{content:"\f52a"}.fa-door-open:before{content:"\f52b"}.fa-dot-circle:before{content:"\f192"}.fa-dove:before{content:"\f4ba"}.fa-download:before{content:"\f019"}.fa-draft2digital:before{content:"\f396"}.fa-drafting-compass:before{content:"\f568"}.fa-dragon:before{content:"\f6d5"}.fa-draw-polygon:before{content:"\f5ee"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drum:before{content:"\f569"}.fa-drum-steelpan:before{content:"\f56a"}.fa-drumstick-bite:before{content:"\f6d7"}.fa-drupal:before{content:"\f1a9"}.fa-dumbbell:before{content:"\f44b"}.fa-dumpster:before{content:"\f793"}.fa-dumpster-fire:before{content:"\f794"}.fa-dungeon:before{content:"\f6d9"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-ebay:before{content:"\f4f4"}.fa-edge:before{content:"\f282"}.fa-edit:before{content:"\f044"}.fa-egg:before{content:"\f7fb"}.fa-eject:before{content:"\f052"}.fa-elementor:before{content:"\f430"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-ello:before{content:"\f5f1"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-text:before{content:"\f658"}.fa-envelope-square:before{content:"\f199"}.fa-envira:before{content:"\f299"}.fa-equals:before{content:"\f52c"}.fa-eraser:before{content:"\f12d"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-ethernet:before{content:"\f796"}.fa-etsy:before{content:"\f2d7"}.fa-euro-sign:before{content:"\f153"}.fa-evernote:before{content:"\f839"}.fa-exchange-alt:before{content:"\f362"}.fa-exclamation:before{content:"\f12a"}.fa-exclamation-circle:before{content:"\f06a"}.fa-exclamation-triangle:before{content:"\f071"}.fa-expand:before{content:"\f065"}.fa-expand-alt:before{content:"\f424"}.fa-expand-arrows-alt:before{content:"\f31e"}.fa-expeditedssl:before{content:"\f23e"}.fa-external-link-alt:before{content:"\f35d"}.fa-external-link-square-alt:before{content:"\f360"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper:before{content:"\f1fb"}.fa-eye-slash:before{content:"\f070"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fan:before{content:"\f863"}.fa-fantasy-flight-games:before{content:"\f6dc"}.fa-fast-backward:before{content:"\f049"}.fa-fast-forward:before{content:"\f050"}.fa-faucet:before{content:"\f905"}.fa-fax:before{content:"\f1ac"}.fa-feather:before{content:"\f52d"}.fa-feather-alt:before{content:"\f56b"}.fa-fedex:before{content:"\f797"}.fa-fedora:before{content:"\f798"}.fa-female:before{content:"\f182"}.fa-fighter-jet:before{content:"\f0fb"}.fa-figma:before{content:"\f799"}.fa-file:before{content:"\f15b"}.fa-file-alt:before{content:"\f15c"}.fa-file-archive:before{content:"\f1c6"}.fa-file-audio:before{content:"\f1c7"}.fa-file-code:before{content:"\f1c9"}.fa-file-contract:before{content:"\f56c"}.fa-file-csv:before{content:"\f6dd"}.fa-file-download:before{content:"\f56d"}.fa-file-excel:before{content:"\f1c3"}.fa-file-export:before{content:"\f56e"}.fa-file-image:before{content:"\f1c5"}.fa-file-import:before{content:"\f56f"}.fa-file-invoice:before{content:"\f570"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-file-medical:before{content:"\f477"}.fa-file-medical-alt:before{content:"\f478"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-prescription:before{content:"\f572"}.fa-file-signature:before{content:"\f573"}.fa-file-upload:before{content:"\f574"}.fa-file-video:before{content:"\f1c8"}.fa-file-word:before{content:"\f1c2"}.fa-fill:before{content:"\f575"}.fa-fill-drip:before{content:"\f576"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-fingerprint:before{content:"\f577"}.fa-fire:before{content:"\f06d"}.fa-fire-alt:before{content:"\f7e4"}.fa-fire-extinguisher:before{content:"\f134"}.fa-firefox:before{content:"\f269"}.fa-firefox-browser:before{content:"\f907"}.fa-first-aid:before{content:"\f479"}.fa-first-order:before{content:"\f2b0"}.fa-first-order-alt:before{content:"\f50a"}.fa-firstdraft:before{content:"\f3a1"}.fa-fish:before{content:"\f578"}.fa-fist-raised:before{content:"\f6de"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flag-usa:before{content:"\f74d"}.fa-flask:before{content:"\f0c3"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-flushed:before{content:"\f579"}.fa-fly:before{content:"\f417"}.fa-folder:before{content:"\f07b"}.fa-folder-minus:before{content:"\f65d"}.fa-folder-open:before{content:"\f07c"}.fa-folder-plus:before{content:"\f65e"}.fa-font:before{content:"\f031"}.fa-font-awesome:before{content:"\f2b4"}.fa-font-awesome-alt:before{content:"\f35c"}.fa-font-awesome-flag:before{content:"\f425"}.fa-font-awesome-logo-full:before{content:"\f4e6"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-football-ball:before{content:"\f44e"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-forward:before{content:"\f04e"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-frog:before{content:"\f52e"}.fa-frown:before{content:"\f119"}.fa-frown-open:before{content:"\f57a"}.fa-fulcrum:before{content:"\f50b"}.fa-funnel-dollar:before{content:"\f662"}.fa-futbol:before{content:"\f1e3"}.fa-galactic-republic:before{content:"\f50c"}.fa-galactic-senate:before{content:"\f50d"}.fa-gamepad:before{content:"\f11b"}.fa-gas-pump:before{content:"\f52f"}.fa-gavel:before{content:"\f0e3"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-ghost:before{content:"\f6e2"}.fa-gift:before{content:"\f06b"}.fa-gifts:before{content:"\f79c"}.fa-git:before{content:"\f1d3"}.fa-git-alt:before{content:"\f841"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glass-cheers:before{content:"\f79f"}.fa-glass-martini:before{content:"\f000"}.fa-glass-martini-alt:before{content:"\f57b"}.fa-glass-whiskey:before{content:"\f7a0"}.fa-glasses:before{content:"\f530"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-globe:before{content:"\f0ac"}.fa-globe-africa:before{content:"\f57c"}.fa-globe-americas:before{content:"\f57d"}.fa-globe-asia:before{content:"\f57e"}.fa-globe-europe:before{content:"\f7a2"}.fa-gofore:before{content:"\f3a7"}.fa-golf-ball:before{content:"\f450"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-gopuram:before{content:"\f664"}.fa-graduation-cap:before{content:"\f19d"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-greater-than:before{content:"\f531"}.fa-greater-than-equal:before{content:"\f532"}.fa-grimace:before{content:"\f57f"}.fa-grin:before{content:"\f580"}.fa-grin-alt:before{content:"\f581"}.fa-grin-beam:before{content:"\f582"}.fa-grin-beam-sweat:before{content:"\f583"}.fa-grin-hearts:before{content:"\f584"}.fa-grin-squint:before{content:"\f585"}.fa-grin-squint-tears:before{content:"\f586"}.fa-grin-stars:before{content:"\f587"}.fa-grin-tears:before{content:"\f588"}.fa-grin-tongue:before{content:"\f589"}.fa-grin-tongue-squint:before{content:"\f58a"}.fa-grin-tongue-wink:before{content:"\f58b"}.fa-grin-wink:before{content:"\f58c"}.fa-grip-horizontal:before{content:"\f58d"}.fa-grip-lines:before{content:"\f7a4"}.fa-grip-lines-vertical:before{content:"\f7a5"}.fa-grip-vertical:before{content:"\f58e"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-guitar:before{content:"\f7a6"}.fa-gulp:before{content:"\f3ae"}.fa-h-square:before{content:"\f0fd"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hackerrank:before{content:"\f5f7"}.fa-hamburger:before{content:"\f805"}.fa-hammer:before{content:"\f6e3"}.fa-hamsa:before{content:"\f665"}.fa-hand-holding:before{content:"\f4bd"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-hand-holding-medical:before{content:"\f95c"}.fa-hand-holding-usd:before{content:"\f4c0"}.fa-hand-holding-water:before{content:"\f4c1"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-middle-finger:before{content:"\f806"}.fa-hand-paper:before{content:"\f256"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-rock:before{content:"\f255"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-sparkles:before{content:"\f95d"}.fa-hand-spock:before{content:"\f259"}.fa-hands:before{content:"\f4c2"}.fa-hands-helping:before{content:"\f4c4"}.fa-hands-wash:before{content:"\f95e"}.fa-handshake:before{content:"\f2b5"}.fa-handshake-alt-slash:before{content:"\f95f"}.fa-handshake-slash:before{content:"\f960"}.fa-hanukiah:before{content:"\f6e6"}.fa-hard-hat:before{content:"\f807"}.fa-hashtag:before{content:"\f292"}.fa-hat-cowboy:before{content:"\f8c0"}.fa-hat-cowboy-side:before{content:"\f8c1"}.fa-hat-wizard:before{content:"\f6e8"}.fa-hdd:before{content:"\f0a0"}.fa-head-side-cough:before{content:"\f961"}.fa-head-side-cough-slash:before{content:"\f962"}.fa-head-side-mask:before{content:"\f963"}.fa-head-side-virus:before{content:"\f964"}.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-headphones-alt:before{content:"\f58f"}.fa-headset:before{content:"\f590"}.fa-heart:before{content:"\f004"}.fa-heart-broken:before{content:"\f7a9"}.fa-heartbeat:before{content:"\f21e"}.fa-helicopter:before{content:"\f533"}.fa-highlighter:before{content:"\f591"}.fa-hiking:before{content:"\f6ec"}.fa-hippo:before{content:"\f6ed"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-history:before{content:"\f1da"}.fa-hockey-puck:before{content:"\f453"}.fa-holly-berry:before{content:"\f7aa"}.fa-home:before{content:"\f015"}.fa-hooli:before{content:"\f427"}.fa-hornbill:before{content:"\f592"}.fa-horse:before{content:"\f6f0"}.fa-horse-head:before{content:"\f7ab"}.fa-hospital:before{content:"\f0f8"}.fa-hospital-alt:before{content:"\f47d"}.fa-hospital-symbol:before{content:"\f47e"}.fa-hospital-user:before{content:"\f80d"}.fa-hot-tub:before{content:"\f593"}.fa-hotdog:before{content:"\f80f"}.fa-hotel:before{content:"\f594"}.fa-hotjar:before{content:"\f3b1"}.fa-hourglass:before{content:"\f254"}.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-start:before{content:"\f251"}.fa-house-damage:before{content:"\f6f1"}.fa-house-user:before{content:"\f965"}.fa-houzz:before{content:"\f27c"}.fa-hryvnia:before{content:"\f6f2"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-i-cursor:before{content:"\f246"}.fa-ice-cream:before{content:"\f810"}.fa-icicles:before{content:"\f7ad"}.fa-icons:before{content:"\f86d"}.fa-id-badge:before{content:"\f2c1"}.fa-id-card:before{content:"\f2c2"}.fa-id-card-alt:before{content:"\f47f"}.fa-ideal:before{content:"\f913"}.fa-igloo:before{content:"\f7ae"}.fa-image:before{content:"\f03e"}.fa-images:before{content:"\f302"}.fa-imdb:before{content:"\f2d8"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-industry:before{content:"\f275"}.fa-infinity:before{content:"\f534"}.fa-info:before{content:"\f129"}.fa-info-circle:before{content:"\f05a"}.fa-instagram:before{content:"\f16d"}.fa-instagram-square:before{content:"\f955"}.fa-intercom:before{content:"\f7af"}.fa-internet-explorer:before{content:"\f26b"}.fa-invision:before{content:"\f7b0"}.fa-ioxhost:before{content:"\f208"}.fa-italic:before{content:"\f033"}.fa-itch-io:before{content:"\f83a"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jedi:before{content:"\f669"}.fa-jedi-order:before{content:"\f50e"}.fa-jenkins:before{content:"\f3b6"}.fa-jira:before{content:"\f7b1"}.fa-joget:before{content:"\f3b7"}.fa-joint:before{content:"\f595"}.fa-joomla:before{content:"\f1aa"}.fa-journal-whills:before{content:"\f66a"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-kaaba:before{content:"\f66b"}.fa-kaggle:before{content:"\f5fa"}.fa-key:before{content:"\f084"}.fa-keybase:before{content:"\f4f5"}.fa-keyboard:before{content:"\f11c"}.fa-keycdn:before{content:"\f3ba"}.fa-khanda:before{content:"\f66d"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-kiss:before{content:"\f596"}.fa-kiss-beam:before{content:"\f597"}.fa-kiss-wink-heart:before{content:"\f598"}.fa-kiwi-bird:before{content:"\f535"}.fa-korvue:before{content:"\f42f"}.fa-landmark:before{content:"\f66f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laptop-code:before{content:"\f5fc"}.fa-laptop-house:before{content:"\f966"}.fa-laptop-medical:before{content:"\f812"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-laugh:before{content:"\f599"}.fa-laugh-beam:before{content:"\f59a"}.fa-laugh-squint:before{content:"\f59b"}.fa-laugh-wink:before{content:"\f59c"}.fa-layer-group:before{content:"\f5fd"}.fa-leaf:before{content:"\f06c"}.fa-leanpub:before{content:"\f212"}.fa-lemon:before{content:"\f094"}.fa-less:before{content:"\f41d"}.fa-less-than:before{content:"\f536"}.fa-less-than-equal:before{content:"\f537"}.fa-level-down-alt:before{content:"\f3be"}.fa-level-up-alt:before{content:"\f3bf"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-line:before{content:"\f3c0"}.fa-link:before{content:"\f0c1"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lira-sign:before{content:"\f195"}.fa-list:before{content:"\f03a"}.fa-list-alt:before{content:"\f022"}.fa-list-ol:before{content:"\f0cb"}.fa-list-ul:before{content:"\f0ca"}.fa-location-arrow:before{content:"\f124"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-long-arrow-alt-down:before{content:"\f309"}.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-long-arrow-alt-right:before{content:"\f30b"}.fa-long-arrow-alt-up:before{content:"\f30c"}.fa-low-vision:before{content:"\f2a8"}.fa-luggage-cart:before{content:"\f59d"}.fa-lungs:before{content:"\f604"}.fa-lungs-virus:before{content:"\f967"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-magic:before{content:"\f0d0"}.fa-magnet:before{content:"\f076"}.fa-mail-bulk:before{content:"\f674"}.fa-mailchimp:before{content:"\f59e"}.fa-male:before{content:"\f183"}.fa-mandalorian:before{content:"\f50f"}.fa-map:before{content:"\f279"}.fa-map-marked:before{content:"\f59f"}.fa-map-marked-alt:before{content:"\f5a0"}.fa-map-marker:before{content:"\f041"}.fa-map-marker-alt:before{content:"\f3c5"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-markdown:before{content:"\f60f"}.fa-marker:before{content:"\f5a1"}.fa-mars:before{content:"\f222"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mask:before{content:"\f6fa"}.fa-mastodon:before{content:"\f4f6"}.fa-maxcdn:before{content:"\f136"}.fa-mdb:before{content:"\f8ca"}.fa-medal:before{content:"\f5a2"}.fa-medapps:before{content:"\f3c6"}.fa-medium:before{content:"\f23a"}.fa-medium-m:before{content:"\f3c7"}.fa-medkit:before{content:"\f0fa"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-megaport:before{content:"\f5a3"}.fa-meh:before{content:"\f11a"}.fa-meh-blank:before{content:"\f5a4"}.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-memory:before{content:"\f538"}.fa-mendeley:before{content:"\f7b3"}.fa-menorah:before{content:"\f676"}.fa-mercury:before{content:"\f223"}.fa-meteor:before{content:"\f753"}.fa-microblog:before{content:"\f91a"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-alt:before{content:"\f3c9"}.fa-microphone-alt-slash:before{content:"\f539"}.fa-microphone-slash:before{content:"\f131"}.fa-microscope:before{content:"\f610"}.fa-microsoft:before{content:"\f3ca"}.fa-minus:before{content:"\f068"}.fa-minus-circle:before{content:"\f056"}.fa-minus-square:before{content:"\f146"}.fa-mitten:before{content:"\f7b5"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mixer:before{content:"\f956"}.fa-mizuni:before{content:"\f3cc"}.fa-mobile:before{content:"\f10b"}.fa-mobile-alt:before{content:"\f3cd"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-money-bill:before{content:"\f0d6"}.fa-money-bill-alt:before{content:"\f3d1"}.fa-money-bill-wave:before{content:"\f53a"}.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-money-check:before{content:"\f53c"}.fa-money-check-alt:before{content:"\f53d"}.fa-monument:before{content:"\f5a6"}.fa-moon:before{content:"\f186"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-mosque:before{content:"\f678"}.fa-motorcycle:before{content:"\f21c"}.fa-mountain:before{content:"\f6fc"}.fa-mouse:before{content:"\f8cc"}.fa-mouse-pointer:before{content:"\f245"}.fa-mug-hot:before{content:"\f7b6"}.fa-music:before{content:"\f001"}.fa-napster:before{content:"\f3d2"}.fa-neos:before{content:"\f612"}.fa-network-wired:before{content:"\f6ff"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-nimblr:before{content:"\f5a8"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-not-equal:before{content:"\f53e"}.fa-notes-medical:before{content:"\f481"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-oil-can:before{content:"\f613"}.fa-old-republic:before{content:"\f510"}.fa-om:before{content:"\f679"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-orcid:before{content:"\f8d2"}.fa-osi:before{content:"\f41a"}.fa-otter:before{content:"\f700"}.fa-outdent:before{content:"\f03b"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-pager:before{content:"\f815"}.fa-paint-brush:before{content:"\f1fc"}.fa-paint-roller:before{content:"\f5aa"}.fa-palette:before{content:"\f53f"}.fa-palfed:before{content:"\f3d8"}.fa-pallet:before{content:"\f482"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-parachute-box:before{content:"\f4cd"}.fa-paragraph:before{content:"\f1dd"}.fa-parking:before{content:"\f540"}.fa-passport:before{content:"\f5ab"}.fa-pastafarianism:before{content:"\f67b"}.fa-paste:before{content:"\f0ea"}.fa-patreon:before{content:"\f3d9"}.fa-pause:before{content:"\f04c"}.fa-pause-circle:before{content:"\f28b"}.fa-paw:before{content:"\f1b0"}.fa-paypal:before{content:"\f1ed"}.fa-peace:before{content:"\f67c"}.fa-pen:before{content:"\f304"}.fa-pen-alt:before{content:"\f305"}.fa-pen-fancy:before{content:"\f5ac"}.fa-pen-nib:before{content:"\f5ad"}.fa-pen-square:before{content:"\f14b"}.fa-pencil-alt:before{content:"\f303"}.fa-pencil-ruler:before{content:"\f5ae"}.fa-penny-arcade:before{content:"\f704"}.fa-people-arrows:before{content:"\f968"}.fa-people-carry:before{content:"\f4ce"}.fa-pepper-hot:before{content:"\f816"}.fa-percent:before{content:"\f295"}.fa-percentage:before{content:"\f541"}.fa-periscope:before{content:"\f3da"}.fa-person-booth:before{content:"\f756"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phoenix-squadron:before{content:"\f511"}.fa-phone:before{content:"\f095"}.fa-phone-alt:before{content:"\f879"}.fa-phone-slash:before{content:"\f3dd"}.fa-phone-square:before{content:"\f098"}.fa-phone-square-alt:before{content:"\f87b"}.fa-phone-volume:before{content:"\f2a0"}.fa-photo-video:before{content:"\f87c"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-square:before{content:"\f91e"}.fa-piggy-bank:before{content:"\f4d3"}.fa-pills:before{content:"\f484"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-pizza-slice:before{content:"\f818"}.fa-place-of-worship:before{content:"\f67f"}.fa-plane:before{content:"\f072"}.fa-plane-arrival:before{content:"\f5af"}.fa-plane-departure:before{content:"\f5b0"}.fa-plane-slash:before{content:"\f969"}.fa-play:before{content:"\f04b"}.fa-play-circle:before{content:"\f144"}.fa-playstation:before{content:"\f3df"}.fa-plug:before{content:"\f1e6"}.fa-plus:before{content:"\f067"}.fa-plus-circle:before{content:"\f055"}.fa-plus-square:before{content:"\f0fe"}.fa-podcast:before{content:"\f2ce"}.fa-poll:before{content:"\f681"}.fa-poll-h:before{content:"\f682"}.fa-poo:before{content:"\f2fe"}.fa-poo-storm:before{content:"\f75a"}.fa-poop:before{content:"\f619"}.fa-portrait:before{content:"\f3e0"}.fa-pound-sign:before{content:"\f154"}.fa-power-off:before{content:"\f011"}.fa-pray:before{content:"\f683"}.fa-praying-hands:before{content:"\f684"}.fa-prescription:before{content:"\f5b1"}.fa-prescription-bottle:before{content:"\f485"}.fa-prescription-bottle-alt:before{content:"\f486"}.fa-print:before{content:"\f02f"}.fa-procedures:before{content:"\f487"}.fa-product-hunt:before{content:"\f288"}.fa-project-diagram:before{content:"\f542"}.fa-pump-medical:before{content:"\f96a"}.fa-pump-soap:before{content:"\f96b"}.fa-pushed:before{content:"\f3e1"}.fa-puzzle-piece:before{content:"\f12e"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\f128"}.fa-question-circle:before{content:"\f059"}.fa-quidditch:before{content:"\f458"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-quran:before{content:"\f687"}.fa-r-project:before{content:"\f4f7"}.fa-radiation:before{content:"\f7b9"}.fa-radiation-alt:before{content:"\f7ba"}.fa-rainbow:before{content:"\f75b"}.fa-random:before{content:"\f074"}.fa-raspberry-pi:before{content:"\f7bb"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-reacteurope:before{content:"\f75d"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.fa-receipt:before{content:"\f543"}.fa-record-vinyl:before{content:"\f8d9"}.fa-recycle:before{content:"\f1b8"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redhat:before{content:"\f7bc"}.fa-redo:before{content:"\f01e"}.fa-redo-alt:before{content:"\f2f9"}.fa-registered:before{content:"\f25d"}.fa-remove-format:before{content:"\f87d"}.fa-renren:before{content:"\f18b"}.fa-reply:before{content:"\f3e5"}.fa-reply-all:before{content:"\f122"}.fa-replyd:before{content:"\f3e6"}.fa-republican:before{content:"\f75e"}.fa-researchgate:before{content:"\f4f8"}.fa-resolving:before{content:"\f3e7"}.fa-restroom:before{content:"\f7bd"}.fa-retweet:before{content:"\f079"}.fa-rev:before{content:"\f5b2"}.fa-ribbon:before{content:"\f4d6"}.fa-ring:before{content:"\f70b"}.fa-road:before{content:"\f018"}.fa-robot:before{content:"\f544"}.fa-rocket:before{content:"\f135"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-route:before{content:"\f4d7"}.fa-rss:before{content:"\f09e"}.fa-rss-square:before{content:"\f143"}.fa-ruble-sign:before{content:"\f158"}.fa-ruler:before{content:"\f545"}.fa-ruler-combined:before{content:"\f546"}.fa-ruler-horizontal:before{content:"\f547"}.fa-ruler-vertical:before{content:"\f548"}.fa-running:before{content:"\f70c"}.fa-rupee-sign:before{content:"\f156"}.fa-sad-cry:before{content:"\f5b3"}.fa-sad-tear:before{content:"\f5b4"}.fa-safari:before{content:"\f267"}.fa-salesforce:before{content:"\f83b"}.fa-sass:before{content:"\f41e"}.fa-satellite:before{content:"\f7bf"}.fa-satellite-dish:before{content:"\f7c0"}.fa-save:before{content:"\f0c7"}.fa-schlix:before{content:"\f3ea"}.fa-school:before{content:"\f549"}.fa-screwdriver:before{content:"\f54a"}.fa-scribd:before{content:"\f28a"}.fa-scroll:before{content:"\f70e"}.fa-sd-card:before{content:"\f7c2"}.fa-search:before{content:"\f002"}.fa-search-dollar:before{content:"\f688"}.fa-search-location:before{content:"\f689"}.fa-search-minus:before{content:"\f010"}.fa-search-plus:before{content:"\f00e"}.fa-searchengin:before{content:"\f3eb"}.fa-seedling:before{content:"\f4d8"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-server:before{content:"\f233"}.fa-servicestack:before{content:"\f3ec"}.fa-shapes:before{content:"\f61f"}.fa-share:before{content:"\f064"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-share-square:before{content:"\f14d"}.fa-shekel-sign:before{content:"\f20b"}.fa-shield-alt:before{content:"\f3ed"}.fa-shield-virus:before{content:"\f96c"}.fa-ship:before{content:"\f21a"}.fa-shipping-fast:before{content:"\f48b"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shoe-prints:before{content:"\f54b"}.fa-shopify:before{content:"\f957"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-shopping-cart:before{content:"\f07a"}.fa-shopware:before{content:"\f5b5"}.fa-shower:before{content:"\f2cc"}.fa-shuttle-van:before{content:"\f5b6"}.fa-sign:before{content:"\f4d9"}.fa-sign-in-alt:before{content:"\f2f6"}.fa-sign-language:before{content:"\f2a7"}.fa-sign-out-alt:before{content:"\f2f5"}.fa-signal:before{content:"\f012"}.fa-signature:before{content:"\f5b7"}.fa-sim-card:before{content:"\f7c4"}.fa-simplybuilt:before{content:"\f215"}.fa-sistrix:before{content:"\f3ee"}.fa-sitemap:before{content:"\f0e8"}.fa-sith:before{content:"\f512"}.fa-skating:before{content:"\f7c5"}.fa-sketch:before{content:"\f7c6"}.fa-skiing:before{content:"\f7c9"}.fa-skiing-nordic:before{content:"\f7ca"}.fa-skull:before{content:"\f54c"}.fa-skull-crossbones:before{content:"\f714"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack:before{content:"\f198"}.fa-slack-hash:before{content:"\f3ef"}.fa-slash:before{content:"\f715"}.fa-sleigh:before{content:"\f7cc"}.fa-sliders-h:before{content:"\f1de"}.fa-slideshare:before{content:"\f1e7"}.fa-smile:before{content:"\f118"}.fa-smile-beam:before{content:"\f5b8"}.fa-smile-wink:before{content:"\f4da"}.fa-smog:before{content:"\f75f"}.fa-smoking:before{content:"\f48d"}.fa-smoking-ban:before{content:"\f54d"}.fa-sms:before{content:"\f7cd"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-snowboarding:before{content:"\f7ce"}.fa-snowflake:before{content:"\f2dc"}.fa-snowman:before{content:"\f7d0"}.fa-snowplow:before{content:"\f7d2"}.fa-soap:before{content:"\f96e"}.fa-socks:before{content:"\f696"}.fa-solar-panel:before{content:"\f5ba"}.fa-sort:before{content:"\f0dc"}.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-down-alt:before{content:"\f881"}.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-alpha-up-alt:before{content:"\f882"}.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-down-alt:before{content:"\f884"}.fa-sort-amount-up:before{content:"\f161"}.fa-sort-amount-up-alt:before{content:"\f885"}.fa-sort-down:before{content:"\f0dd"}.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-down-alt:before{content:"\f886"}.fa-sort-numeric-up:before{content:"\f163"}.fa-sort-numeric-up-alt:before{content:"\f887"}.fa-sort-up:before{content:"\f0de"}.fa-soundcloud:before{content:"\f1be"}.fa-sourcetree:before{content:"\f7d3"}.fa-spa:before{content:"\f5bb"}.fa-space-shuttle:before{content:"\f197"}.fa-speakap:before{content:"\f3f3"}.fa-speaker-deck:before{content:"\f83c"}.fa-spell-check:before{content:"\f891"}.fa-spider:before{content:"\f717"}.fa-spinner:before{content:"\f110"}.fa-splotch:before{content:"\f5bc"}.fa-spotify:before{content:"\f1bc"}.fa-spray-can:before{content:"\f5bd"}.fa-square:before{content:"\f0c8"}.fa-square-full:before{content:"\f45c"}.fa-square-root-alt:before{content:"\f698"}.fa-squarespace:before{content:"\f5be"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-stackpath:before{content:"\f842"}.fa-stamp:before{content:"\f5bf"}.fa-star:before{content:"\f005"}.fa-star-and-crescent:before{content:"\f699"}.fa-star-half:before{content:"\f089"}.fa-star-half-alt:before{content:"\f5c0"}.fa-star-of-david:before{content:"\f69a"}.fa-star-of-life:before{content:"\f621"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-step-backward:before{content:"\f048"}.fa-step-forward:before{content:"\f051"}.fa-stethoscope:before{content:"\f0f1"}.fa-sticker-mule:before{content:"\f3f7"}.fa-sticky-note:before{content:"\f249"}.fa-stop:before{content:"\f04d"}.fa-stop-circle:before{content:"\f28d"}.fa-stopwatch:before{content:"\f2f2"}.fa-stopwatch-20:before{content:"\f96f"}.fa-store:before{content:"\f54e"}.fa-store-alt:before{content:"\f54f"}.fa-store-alt-slash:before{content:"\f970"}.fa-store-slash:before{content:"\f971"}.fa-strava:before{content:"\f428"}.fa-stream:before{content:"\f550"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-stroopwafel:before{content:"\f551"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-subscript:before{content:"\f12c"}.fa-subway:before{content:"\f239"}.fa-suitcase:before{content:"\f0f2"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-sun:before{content:"\f185"}.fa-superpowers:before{content:"\f2dd"}.fa-superscript:before{content:"\f12b"}.fa-supple:before{content:"\f3f9"}.fa-surprise:before{content:"\f5c2"}.fa-suse:before{content:"\f7d6"}.fa-swatchbook:before{content:"\f5c3"}.fa-swift:before{content:"\f8e1"}.fa-swimmer:before{content:"\f5c4"}.fa-swimming-pool:before{content:"\f5c5"}.fa-symfony:before{content:"\f83d"}.fa-synagogue:before{content:"\f69b"}.fa-sync:before{content:"\f021"}.fa-sync-alt:before{content:"\f2f1"}.fa-syringe:before{content:"\f48e"}.fa-table:before{content:"\f0ce"}.fa-table-tennis:before{content:"\f45d"}.fa-tablet:before{content:"\f10a"}.fa-tablet-alt:before{content:"\f3fa"}.fa-tablets:before{content:"\f490"}.fa-tachometer-alt:before{content:"\f3fd"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tape:before{content:"\f4db"}.fa-tasks:before{content:"\f0ae"}.fa-taxi:before{content:"\f1ba"}.fa-teamspeak:before{content:"\f4f9"}.fa-teeth:before{content:"\f62e"}.fa-teeth-open:before{content:"\f62f"}.fa-telegram:before{content:"\f2c6"}.fa-telegram-plane:before{content:"\f3fe"}.fa-temperature-high:before{content:"\f769"}.fa-temperature-low:before{content:"\f76b"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-tenge:before{content:"\f7d7"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-th:before{content:"\f00a"}.fa-th-large:before{content:"\f009"}.fa-th-list:before{content:"\f00b"}.fa-the-red-yeti:before{content:"\f69d"}.fa-theater-masks:before{content:"\f630"}.fa-themeco:before{content:"\f5c6"}.fa-themeisle:before{content:"\f2b2"}.fa-thermometer:before{content:"\f491"}.fa-thermometer-empty:before{content:"\f2cb"}.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-think-peaks:before{content:"\f731"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbtack:before{content:"\f08d"}.fa-ticket-alt:before{content:"\f3ff"}.fa-times:before{content:"\f00d"}.fa-times-circle:before{content:"\f057"}.fa-tint:before{content:"\f043"}.fa-tint-slash:before{content:"\f5c7"}.fa-tired:before{content:"\f5c8"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-toilet:before{content:"\f7d8"}.fa-toilet-paper:before{content:"\f71e"}.fa-toilet-paper-slash:before{content:"\f972"}.fa-toolbox:before{content:"\f552"}.fa-tools:before{content:"\f7d9"}.fa-tooth:before{content:"\f5c9"}.fa-torah:before{content:"\f6a0"}.fa-torii-gate:before{content:"\f6a1"}.fa-tractor:before{content:"\f722"}.fa-trade-federation:before{content:"\f513"}.fa-trademark:before{content:"\f25c"}.fa-traffic-light:before{content:"\f637"}.fa-trailer:before{content:"\f941"}.fa-train:before{content:"\f238"}.fa-tram:before{content:"\f7da"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-alt:before{content:"\f2ed"}.fa-trash-restore:before{content:"\f829"}.fa-trash-restore-alt:before{content:"\f82a"}.fa-tree:before{content:"\f1bb"}.fa-trello:before{content:"\f181"}.fa-tripadvisor:before{content:"\f262"}.fa-trophy:before{content:"\f091"}.fa-truck:before{content:"\f0d1"}.fa-truck-loading:before{content:"\f4de"}.fa-truck-monster:before{content:"\f63b"}.fa-truck-moving:before{content:"\f4df"}.fa-truck-pickup:before{content:"\f63c"}.fa-tshirt:before{content:"\f553"}.fa-tty:before{content:"\f1e4"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-tv:before{content:"\f26c"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-ubuntu:before{content:"\f7df"}.fa-uikit:before{content:"\f403"}.fa-umbraco:before{content:"\f8e8"}.fa-umbrella:before{content:"\f0e9"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-underline:before{content:"\f0cd"}.fa-undo:before{content:"\f0e2"}.fa-undo-alt:before{content:"\f2ea"}.fa-uniregistry:before{content:"\f404"}.fa-unity:before{content:"\f949"}.fa-universal-access:before{content:"\f29a"}.fa-university:before{content:"\f19c"}.fa-unlink:before{content:"\f127"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before{content:"\f13e"}.fa-untappd:before{content:"\f405"}.fa-upload:before{content:"\f093"}.fa-ups:before{content:"\f7e0"}.fa-usb:before{content:"\f287"}.fa-user:before{content:"\f007"}.fa-user-alt:before{content:"\f406"}.fa-user-alt-slash:before{content:"\f4fa"}.fa-user-astronaut:before{content:"\f4fb"}.fa-user-check:before{content:"\f4fc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-clock:before{content:"\f4fd"}.fa-user-cog:before{content:"\f4fe"}.fa-user-edit:before{content:"\f4ff"}.fa-user-friends:before{content:"\f500"}.fa-user-graduate:before{content:"\f501"}.fa-user-injured:before{content:"\f728"}.fa-user-lock:before{content:"\f502"}.fa-user-md:before{content:"\f0f0"}.fa-user-minus:before{content:"\f503"}.fa-user-ninja:before{content:"\f504"}.fa-user-nurse:before{content:"\f82f"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-shield:before{content:"\f505"}.fa-user-slash:before{content:"\f506"}.fa-user-tag:before{content:"\f507"}.fa-user-tie:before{content:"\f508"}.fa-user-times:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-users-cog:before{content:"\f509"}.fa-usps:before{content:"\f7e1"}.fa-ussunnah:before{content:"\f407"}.fa-utensil-spoon:before{content:"\f2e5"}.fa-utensils:before{content:"\f2e7"}.fa-vaadin:before{content:"\f408"}.fa-vector-square:before{content:"\f5cb"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-vial:before{content:"\f492"}.fa-vials:before{content:"\f493"}.fa-viber:before{content:"\f409"}.fa-video:before{content:"\f03d"}.fa-video-slash:before{content:"\f4e2"}.fa-vihara:before{content:"\f6a7"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-virus:before{content:"\f974"}.fa-virus-slash:before{content:"\f975"}.fa-viruses:before{content:"\f976"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-voicemail:before{content:"\f897"}.fa-volleyball-ball:before{content:"\f45f"}.fa-volume-down:before{content:"\f027"}.fa-volume-mute:before{content:"\f6a9"}.fa-volume-off:before{content:"\f026"}.fa-volume-up:before{content:"\f028"}.fa-vote-yea:before{content:"\f772"}.fa-vr-cardboard:before{content:"\f729"}.fa-vuejs:before{content:"\f41f"}.fa-walking:before{content:"\f554"}.fa-wallet:before{content:"\f555"}.fa-warehouse:before{content:"\f494"}.fa-water:before{content:"\f773"}.fa-wave-square:before{content:"\f83e"}.fa-waze:before{content:"\f83f"}.fa-weebly:before{content:"\f5cc"}.fa-weibo:before{content:"\f18a"}.fa-weight:before{content:"\f496"}.fa-weight-hanging:before{content:"\f5cd"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-wheelchair:before{content:"\f193"}.fa-whmcs:before{content:"\f40d"}.fa-wifi:before{content:"\f1eb"}.fa-wikipedia-w:before{content:"\f266"}.fa-wind:before{content:"\f72e"}.fa-window-close:before{content:"\f410"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-windows:before{content:"\f17a"}.fa-wine-bottle:before{content:"\f72f"}.fa-wine-glass:before{content:"\f4e3"}.fa-wine-glass-alt:before{content:"\f5ce"}.fa-wix:before{content:"\f5cf"}.fa-wizards-of-the-coast:before{content:"\f730"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-won-sign:before{content:"\f159"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wpressr:before{content:"\f3e4"}.fa-wrench:before{content:"\f0ad"}.fa-x-ray:before{content:"\f497"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yammer:before{content:"\f840"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yarn:before{content:"\f7e3"}.fa-yelp:before{content:"\f1e9"}.fa-yen-sign:before{content:"\f157"}.fa-yin-yang:before{content:"\f6ad"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.fa-zhihu:before{content:"\f63f"}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}@font-face{font-family:"Font Awesome 5 Brands";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:"Font Awesome 5 Brands"}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-regular-400.eot);src:url(../webfonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.woff) format("woff"),url(../webfonts/fa-regular-400.ttf) format("truetype"),url(../webfonts/fa-regular-400.svg#fontawesome) format("svg")}.fab,.far{font-weight:400}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.far,.fas{font-family:"Font Awesome 5 Free"}.fa,.fas{font-weight:900} \ No newline at end of file diff --git a/lib/font-awesome/webfonts/fa-brands-400.woff2 b/lib/font-awesome/webfonts/fa-brands-400.woff2 new file mode 100644 index 00000000..141a90a9 Binary files /dev/null and b/lib/font-awesome/webfonts/fa-brands-400.woff2 differ diff --git a/lib/font-awesome/webfonts/fa-regular-400.woff2 b/lib/font-awesome/webfonts/fa-regular-400.woff2 new file mode 100644 index 00000000..7e0118e5 Binary files /dev/null and b/lib/font-awesome/webfonts/fa-regular-400.woff2 differ diff --git a/lib/font-awesome/webfonts/fa-solid-900.woff2 b/lib/font-awesome/webfonts/fa-solid-900.woff2 new file mode 100644 index 00000000..978a681a Binary files /dev/null and b/lib/font-awesome/webfonts/fa-solid-900.woff2 differ diff --git a/lib/velocity/velocity.min.js b/lib/velocity/velocity.min.js new file mode 100644 index 00000000..58244c80 --- /dev/null +++ b/lib/velocity/velocity.min.js @@ -0,0 +1,4 @@ +/*! VelocityJS.org (1.2.2). (C) 2014 Julian Shapiro. MIT @license: en.wikipedia.org/wiki/MIT_License */ +/*! VelocityJS.org jQuery Shim (1.0.1). (C) 2014 The jQuery Foundation. MIT @license: en.wikipedia.org/wiki/MIT_License. */ +!function(e){function t(e){var t=e.length,r=$.type(e);return"function"===r||$.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===r||0===t||"number"==typeof t&&t>0&&t-1 in e}if(!e.jQuery){var $=function(e,t){return new $.fn.init(e,t)};$.isWindow=function(e){return null!=e&&e==e.window},$.type=function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?a[o.call(e)]||"object":typeof e},$.isArray=Array.isArray||function(e){return"array"===$.type(e)},$.isPlainObject=function(e){var t;if(!e||"object"!==$.type(e)||e.nodeType||$.isWindow(e))return!1;try{if(e.constructor&&!n.call(e,"constructor")&&!n.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(r){return!1}for(t in e);return void 0===t||n.call(e,t)},$.each=function(e,r,a){var n,o=0,i=e.length,s=t(e);if(a){if(s)for(;i>o&&(n=r.apply(e[o],a),n!==!1);o++);else for(o in e)if(n=r.apply(e[o],a),n===!1)break}else if(s)for(;i>o&&(n=r.call(e[o],o,e[o]),n!==!1);o++);else for(o in e)if(n=r.call(e[o],o,e[o]),n===!1)break;return e},$.data=function(e,t,a){if(void 0===a){var n=e[$.expando],o=n&&r[n];if(void 0===t)return o;if(o&&t in o)return o[t]}else if(void 0!==t){var n=e[$.expando]||(e[$.expando]=++$.uuid);return r[n]=r[n]||{},r[n][t]=a,a}},$.removeData=function(e,t){var a=e[$.expando],n=a&&r[a];n&&$.each(t,function(e,t){delete n[t]})},$.extend=function(){var e,t,r,a,n,o,i=arguments[0]||{},s=1,l=arguments.length,u=!1;for("boolean"==typeof i&&(u=i,i=arguments[s]||{},s++),"object"!=typeof i&&"function"!==$.type(i)&&(i={}),s===l&&(i=this,s--);l>s;s++)if(null!=(n=arguments[s]))for(a in n)e=i[a],r=n[a],i!==r&&(u&&r&&($.isPlainObject(r)||(t=$.isArray(r)))?(t?(t=!1,o=e&&$.isArray(e)?e:[]):o=e&&$.isPlainObject(e)?e:{},i[a]=$.extend(u,o,r)):void 0!==r&&(i[a]=r));return i},$.queue=function(e,r,a){function n(e,r){var a=r||[];return null!=e&&(t(Object(e))?!function(e,t){for(var r=+t.length,a=0,n=e.length;r>a;)e[n++]=t[a++];if(r!==r)for(;void 0!==t[a];)e[n++]=t[a++];return e.length=n,e}(a,"string"==typeof e?[e]:e):[].push.call(a,e)),a}if(e){r=(r||"fx")+"queue";var o=$.data(e,r);return a?(!o||$.isArray(a)?o=$.data(e,r,n(a)):o.push(a),o):o||[]}},$.dequeue=function(e,t){$.each(e.nodeType?[e]:e,function(e,r){t=t||"fx";var a=$.queue(r,t),n=a.shift();"inprogress"===n&&(n=a.shift()),n&&("fx"===t&&a.unshift("inprogress"),n.call(r,function(){$.dequeue(r,t)}))})},$.fn=$.prototype={init:function(e){if(e.nodeType)return this[0]=e,this;throw new Error("Not a DOM node.")},offset:function(){var t=this[0].getBoundingClientRect?this[0].getBoundingClientRect():{top:0,left:0};return{top:t.top+(e.pageYOffset||document.scrollTop||0)-(document.clientTop||0),left:t.left+(e.pageXOffset||document.scrollLeft||0)-(document.clientLeft||0)}},position:function(){function e(){for(var e=this.offsetParent||document;e&&"html"===!e.nodeType.toLowerCase&&"static"===e.style.position;)e=e.offsetParent;return e||document}var t=this[0],e=e.apply(t),r=this.offset(),a=/^(?:body|html)$/i.test(e.nodeName)?{top:0,left:0}:$(e).offset();return r.top-=parseFloat(t.style.marginTop)||0,r.left-=parseFloat(t.style.marginLeft)||0,e.style&&(a.top+=parseFloat(e.style.borderTopWidth)||0,a.left+=parseFloat(e.style.borderLeftWidth)||0),{top:r.top-a.top,left:r.left-a.left}}};var r={};$.expando="velocity"+(new Date).getTime(),$.uuid=0;for(var a={},n=a.hasOwnProperty,o=a.toString,i="Boolean Number String Function Array Date RegExp Object Error".split(" "),s=0;sn;++n){var o=u(r,e,a);if(0===o)return r;var i=l(r,e,a)-t;r-=i/o}return r}function p(){for(var t=0;b>t;++t)w[t]=l(t*x,e,a)}function f(t,r,n){var o,i,s=0;do i=r+(n-r)/2,o=l(i,e,a)-t,o>0?n=i:r=i;while(Math.abs(o)>h&&++s=y?c(t,s):0==l?s:f(t,r,r+x)}function g(){V=!0,(e!=r||a!=n)&&p()}var m=4,y=.001,h=1e-7,v=10,b=11,x=1/(b-1),S="Float32Array"in t;if(4!==arguments.length)return!1;for(var P=0;4>P;++P)if("number"!=typeof arguments[P]||isNaN(arguments[P])||!isFinite(arguments[P]))return!1;e=Math.min(e,1),a=Math.min(a,1),e=Math.max(e,0),a=Math.max(a,0);var w=S?new Float32Array(b):new Array(b),V=!1,C=function(t){return V||g(),e===r&&a===n?t:0===t?0:1===t?1:l(d(t),r,n)};C.getControlPoints=function(){return[{x:e,y:r},{x:a,y:n}]};var T="generateBezier("+[e,r,a,n]+")";return C.toString=function(){return T},C}function u(e,t){var r=e;return g.isString(e)?v.Easings[e]||(r=!1):r=g.isArray(e)&&1===e.length?s.apply(null,e):g.isArray(e)&&2===e.length?b.apply(null,e.concat([t])):g.isArray(e)&&4===e.length?l.apply(null,e):!1,r===!1&&(r=v.Easings[v.defaults.easing]?v.defaults.easing:h),r}function c(e){if(e){var t=(new Date).getTime(),r=v.State.calls.length;r>1e4&&(v.State.calls=n(v.State.calls));for(var o=0;r>o;o++)if(v.State.calls[o]){var s=v.State.calls[o],l=s[0],u=s[2],f=s[3],d=!!f,m=null;f||(f=v.State.calls[o][3]=t-16);for(var y=Math.min((t-f)/u.duration,1),h=0,b=l.length;b>h;h++){var S=l[h],w=S.element;if(i(w)){var V=!1;if(u.display!==a&&null!==u.display&&"none"!==u.display){if("flex"===u.display){var C=["-webkit-box","-moz-box","-ms-flexbox","-webkit-flex"];$.each(C,function(e,t){x.setPropertyValue(w,"display",t)})}x.setPropertyValue(w,"display",u.display)}u.visibility!==a&&"hidden"!==u.visibility&&x.setPropertyValue(w,"visibility",u.visibility);for(var T in S)if("element"!==T){var k=S[T],A,F=g.isString(k.easing)?v.Easings[k.easing]:k.easing;if(1===y)A=k.endValue;else{var E=k.endValue-k.startValue;if(A=k.startValue+E*F(y,u,E),!d&&A===k.currentValue)continue}if(k.currentValue=A,"tween"===T)m=A;else{if(x.Hooks.registered[T]){var j=x.Hooks.getRoot(T),H=i(w).rootPropertyValueCache[j];H&&(k.rootPropertyValue=H)}var N=x.setPropertyValue(w,T,k.currentValue+(0===parseFloat(A)?"":k.unitType),k.rootPropertyValue,k.scrollData);x.Hooks.registered[T]&&(i(w).rootPropertyValueCache[j]=x.Normalizations.registered[j]?x.Normalizations.registered[j]("extract",null,N[1]):N[1]),"transform"===N[0]&&(V=!0)}}u.mobileHA&&i(w).transformCache.translate3d===a&&(i(w).transformCache.translate3d="(0px, 0px, 0px)",V=!0),V&&x.flushTransformCache(w)}}u.display!==a&&"none"!==u.display&&(v.State.calls[o][2].display=!1),u.visibility!==a&&"hidden"!==u.visibility&&(v.State.calls[o][2].visibility=!1),u.progress&&u.progress.call(s[1],s[1],y,Math.max(0,f+u.duration-t),f,m),1===y&&p(o)}}v.State.isTicking&&P(c)}function p(e,t){if(!v.State.calls[e])return!1;for(var r=v.State.calls[e][0],n=v.State.calls[e][1],o=v.State.calls[e][2],s=v.State.calls[e][4],l=!1,u=0,c=r.length;c>u;u++){var p=r[u].element;if(t||o.loop||("none"===o.display&&x.setPropertyValue(p,"display",o.display),"hidden"===o.visibility&&x.setPropertyValue(p,"visibility",o.visibility)),o.loop!==!0&&($.queue(p)[1]===a||!/\.velocityQueueEntryFlag/i.test($.queue(p)[1]))&&i(p)){i(p).isAnimating=!1,i(p).rootPropertyValueCache={};var f=!1;$.each(x.Lists.transforms3D,function(e,t){var r=/^scale/.test(t)?1:0,n=i(p).transformCache[t];i(p).transformCache[t]!==a&&new RegExp("^\\("+r+"[^.]").test(n)&&(f=!0,delete i(p).transformCache[t])}),o.mobileHA&&(f=!0,delete i(p).transformCache.translate3d),f&&x.flushTransformCache(p),x.Values.removeClass(p,"velocity-animating")}if(!t&&o.complete&&!o.loop&&u===c-1)try{o.complete.call(n,n)}catch(d){setTimeout(function(){throw d},1)}s&&o.loop!==!0&&s(n),i(p)&&o.loop===!0&&!t&&($.each(i(p).tweensContainer,function(e,t){/^rotate/.test(e)&&360===parseFloat(t.endValue)&&(t.endValue=0,t.startValue=360),/^backgroundPosition/.test(e)&&100===parseFloat(t.endValue)&&"%"===t.unitType&&(t.endValue=0,t.startValue=100)}),v(p,"reverse",{loop:!0,delay:o.delay})),o.queue!==!1&&$.dequeue(p,o.queue)}v.State.calls[e]=!1;for(var g=0,m=v.State.calls.length;m>g;g++)if(v.State.calls[g]!==!1){l=!0;break}l===!1&&(v.State.isTicking=!1,delete v.State.calls,v.State.calls=[])}var f=function(){if(r.documentMode)return r.documentMode;for(var e=7;e>4;e--){var t=r.createElement("div");if(t.innerHTML="",t.getElementsByTagName("span").length)return t=null,e}return a}(),d=function(){var e=0;return t.webkitRequestAnimationFrame||t.mozRequestAnimationFrame||function(t){var r=(new Date).getTime(),a;return a=Math.max(0,16-(r-e)),e=r+a,setTimeout(function(){t(r+a)},a)}}(),g={isString:function(e){return"string"==typeof e},isArray:Array.isArray||function(e){return"[object Array]"===Object.prototype.toString.call(e)},isFunction:function(e){return"[object Function]"===Object.prototype.toString.call(e)},isNode:function(e){return e&&e.nodeType},isNodeList:function(e){return"object"==typeof e&&/^\[object (HTMLCollection|NodeList|Object)\]$/.test(Object.prototype.toString.call(e))&&e.length!==a&&(0===e.length||"object"==typeof e[0]&&e[0].nodeType>0)},isWrapped:function(e){return e&&(e.jquery||t.Zepto&&t.Zepto.zepto.isZ(e))},isSVG:function(e){return t.SVGElement&&e instanceof t.SVGElement},isEmptyObject:function(e){for(var t in e)return!1;return!0}},$,m=!1;if(e.fn&&e.fn.jquery?($=e,m=!0):$=t.Velocity.Utilities,8>=f&&!m)throw new Error("Velocity: IE8 and below require jQuery to be loaded before Velocity.");if(7>=f)return void(jQuery.fn.velocity=jQuery.fn.animate);var y=400,h="swing",v={State:{isMobile:/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),isAndroid:/Android/i.test(navigator.userAgent),isGingerbread:/Android 2\.3\.[3-7]/i.test(navigator.userAgent),isChrome:t.chrome,isFirefox:/Firefox/i.test(navigator.userAgent),prefixElement:r.createElement("div"),prefixMatches:{},scrollAnchor:null,scrollPropertyLeft:null,scrollPropertyTop:null,isTicking:!1,calls:[]},CSS:{},Utilities:$,Redirects:{},Easings:{},Promise:t.Promise,defaults:{queue:"",duration:y,easing:h,begin:a,complete:a,progress:a,display:a,visibility:a,loop:!1,delay:!1,mobileHA:!0,_cacheValues:!0},init:function(e){$.data(e,"velocity",{isSVG:g.isSVG(e),isAnimating:!1,computedStyle:null,tweensContainer:null,rootPropertyValueCache:{},transformCache:{}})},hook:null,mock:!1,version:{major:1,minor:2,patch:2},debug:!1};t.pageYOffset!==a?(v.State.scrollAnchor=t,v.State.scrollPropertyLeft="pageXOffset",v.State.scrollPropertyTop="pageYOffset"):(v.State.scrollAnchor=r.documentElement||r.body.parentNode||r.body,v.State.scrollPropertyLeft="scrollLeft",v.State.scrollPropertyTop="scrollTop");var b=function(){function e(e){return-e.tension*e.x-e.friction*e.v}function t(t,r,a){var n={x:t.x+a.dx*r,v:t.v+a.dv*r,tension:t.tension,friction:t.friction};return{dx:n.v,dv:e(n)}}function r(r,a){var n={dx:r.v,dv:e(r)},o=t(r,.5*a,n),i=t(r,.5*a,o),s=t(r,a,i),l=1/6*(n.dx+2*(o.dx+i.dx)+s.dx),u=1/6*(n.dv+2*(o.dv+i.dv)+s.dv);return r.x=r.x+l*a,r.v=r.v+u*a,r}return function a(e,t,n){var o={x:-1,v:0,tension:null,friction:null},i=[0],s=0,l=1e-4,u=.016,c,p,f;for(e=parseFloat(e)||500,t=parseFloat(t)||20,n=n||null,o.tension=e,o.friction=t,c=null!==n,c?(s=a(e,t),p=s/n*u):p=u;;)if(f=r(f||o,p),i.push(1+f.x),s+=16,!(Math.abs(f.x)>l&&Math.abs(f.v)>l))break;return c?function(e){return i[e*(i.length-1)|0]}:s}}();v.Easings={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},spring:function(e){return 1-Math.cos(4.5*e*Math.PI)*Math.exp(6*-e)}},$.each([["ease",[.25,.1,.25,1]],["ease-in",[.42,0,1,1]],["ease-out",[0,0,.58,1]],["ease-in-out",[.42,0,.58,1]],["easeInSine",[.47,0,.745,.715]],["easeOutSine",[.39,.575,.565,1]],["easeInOutSine",[.445,.05,.55,.95]],["easeInQuad",[.55,.085,.68,.53]],["easeOutQuad",[.25,.46,.45,.94]],["easeInOutQuad",[.455,.03,.515,.955]],["easeInCubic",[.55,.055,.675,.19]],["easeOutCubic",[.215,.61,.355,1]],["easeInOutCubic",[.645,.045,.355,1]],["easeInQuart",[.895,.03,.685,.22]],["easeOutQuart",[.165,.84,.44,1]],["easeInOutQuart",[.77,0,.175,1]],["easeInQuint",[.755,.05,.855,.06]],["easeOutQuint",[.23,1,.32,1]],["easeInOutQuint",[.86,0,.07,1]],["easeInExpo",[.95,.05,.795,.035]],["easeOutExpo",[.19,1,.22,1]],["easeInOutExpo",[1,0,0,1]],["easeInCirc",[.6,.04,.98,.335]],["easeOutCirc",[.075,.82,.165,1]],["easeInOutCirc",[.785,.135,.15,.86]]],function(e,t){v.Easings[t[0]]=l.apply(null,t[1])});var x=v.CSS={RegEx:{isHex:/^#([A-f\d]{3}){1,2}$/i,valueUnwrap:/^[A-z]+\((.*)\)$/i,wrappedValueAlreadyExtracted:/[0-9.]+ [0-9.]+ [0-9.]+( [0-9.]+)?/,valueSplit:/([A-z]+\(.+\))|(([A-z0-9#-.]+?)(?=\s|$))/gi},Lists:{colors:["fill","stroke","stopColor","color","backgroundColor","borderColor","borderTopColor","borderRightColor","borderBottomColor","borderLeftColor","outlineColor"],transformsBase:["translateX","translateY","scale","scaleX","scaleY","skewX","skewY","rotateZ"],transforms3D:["transformPerspective","translateZ","scaleZ","rotateX","rotateY"]},Hooks:{templates:{textShadow:["Color X Y Blur","black 0px 0px 0px"],boxShadow:["Color X Y Blur Spread","black 0px 0px 0px 0px"],clip:["Top Right Bottom Left","0px 0px 0px 0px"],backgroundPosition:["X Y","0% 0%"],transformOrigin:["X Y Z","50% 50% 0px"],perspectiveOrigin:["X Y","50% 50%"]},registered:{},register:function(){for(var e=0;e=f)switch(e){case"name":return"filter";case"extract":var a=r.toString().match(/alpha\(opacity=(.*)\)/i);return r=a?a[1]/100:1;case"inject":return t.style.zoom=1,parseFloat(r)>=1?"":"alpha(opacity="+parseInt(100*parseFloat(r),10)+")"}else switch(e){case"name":return"opacity";case"extract":return r;case"inject":return r}}},register:function(){9>=f||v.State.isGingerbread||(x.Lists.transformsBase=x.Lists.transformsBase.concat(x.Lists.transforms3D));for(var e=0;en&&(n=1),o=!/(\d)$/i.test(n);break;case"skew":o=!/(deg|\d)$/i.test(n);break;case"rotate":o=!/(deg|\d)$/i.test(n)}return o||(i(r).transformCache[t]="("+n+")"),i(r).transformCache[t]}}}();for(var e=0;e=f||3!==o.split(" ").length||(o+=" 1"),o;case"inject":return 8>=f?4===n.split(" ").length&&(n=n.split(/\s+/).slice(0,3).join(" ")):3===n.split(" ").length&&(n+=" 1"),(8>=f?"rgb":"rgba")+"("+n.replace(/\s+/g,",").replace(/\.(\d)+(?=,)/g,"")+")"}}}()}},Names:{camelCase:function(e){return e.replace(/-(\w)/g,function(e,t){return t.toUpperCase()})},SVGAttribute:function(e){var t="width|height|x|y|cx|cy|r|rx|ry|x1|x2|y1|y2";return(f||v.State.isAndroid&&!v.State.isChrome)&&(t+="|transform"),new RegExp("^("+t+")$","i").test(e)},prefixCheck:function(e){if(v.State.prefixMatches[e])return[v.State.prefixMatches[e],!0];for(var t=["","Webkit","Moz","ms","O"],r=0,a=t.length;a>r;r++){var n;if(n=0===r?e:t[r]+e.replace(/^\w/,function(e){return e.toUpperCase()}),g.isString(v.State.prefixElement.style[n]))return v.State.prefixMatches[e]=n,[n,!0]}return[e,!1]}},Values:{hexToRgb:function(e){var t=/^#?([a-f\d])([a-f\d])([a-f\d])$/i,r=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i,a;return e=e.replace(t,function(e,t,r,a){return t+t+r+r+a+a}),a=r.exec(e),a?[parseInt(a[1],16),parseInt(a[2],16),parseInt(a[3],16)]:[0,0,0]},isCSSNullValue:function(e){return 0==e||/^(none|auto|transparent|(rgba\(0, ?0, ?0, ?0\)))$/i.test(e)},getUnitType:function(e){return/^(rotate|skew)/i.test(e)?"deg":/(^(scale|scaleX|scaleY|scaleZ|alpha|flexGrow|flexHeight|zIndex|fontWeight)$)|((opacity|red|green|blue|alpha)$)/i.test(e)?"":"px"},getDisplayType:function(e){var t=e&&e.tagName.toString().toLowerCase();return/^(b|big|i|small|tt|abbr|acronym|cite|code|dfn|em|kbd|strong|samp|var|a|bdo|br|img|map|object|q|script|span|sub|sup|button|input|label|select|textarea)$/i.test(t)?"inline":/^(li)$/i.test(t)?"list-item":/^(tr)$/i.test(t)?"table-row":/^(table)$/i.test(t)?"table":/^(tbody)$/i.test(t)?"table-row-group":"block"},addClass:function(e,t){e.classList?e.classList.add(t):e.className+=(e.className.length?" ":"")+t},removeClass:function(e,t){e.classList?e.classList.remove(t):e.className=e.className.toString().replace(new RegExp("(^|\\s)"+t.split(" ").join("|")+"(\\s|$)","gi")," ")}},getPropertyValue:function(e,r,n,o){function s(e,r){function n(){u&&x.setPropertyValue(e,"display","none")}var l=0;if(8>=f)l=$.css(e,r);else{var u=!1;if(/^(width|height)$/.test(r)&&0===x.getPropertyValue(e,"display")&&(u=!0,x.setPropertyValue(e,"display",x.Values.getDisplayType(e))),!o){if("height"===r&&"border-box"!==x.getPropertyValue(e,"boxSizing").toString().toLowerCase()){var c=e.offsetHeight-(parseFloat(x.getPropertyValue(e,"borderTopWidth"))||0)-(parseFloat(x.getPropertyValue(e,"borderBottomWidth"))||0)-(parseFloat(x.getPropertyValue(e,"paddingTop"))||0)-(parseFloat(x.getPropertyValue(e,"paddingBottom"))||0);return n(),c}if("width"===r&&"border-box"!==x.getPropertyValue(e,"boxSizing").toString().toLowerCase()){var p=e.offsetWidth-(parseFloat(x.getPropertyValue(e,"borderLeftWidth"))||0)-(parseFloat(x.getPropertyValue(e,"borderRightWidth"))||0)-(parseFloat(x.getPropertyValue(e,"paddingLeft"))||0)-(parseFloat(x.getPropertyValue(e,"paddingRight"))||0);return n(),p}}var d;d=i(e)===a?t.getComputedStyle(e,null):i(e).computedStyle?i(e).computedStyle:i(e).computedStyle=t.getComputedStyle(e,null),"borderColor"===r&&(r="borderTopColor"),l=9===f&&"filter"===r?d.getPropertyValue(r):d[r],(""===l||null===l)&&(l=e.style[r]),n()}if("auto"===l&&/^(top|right|bottom|left)$/i.test(r)){var g=s(e,"position");("fixed"===g||"absolute"===g&&/top|left/i.test(r))&&(l=$(e).position()[r]+"px")}return l}var l;if(x.Hooks.registered[r]){var u=r,c=x.Hooks.getRoot(u);n===a&&(n=x.getPropertyValue(e,x.Names.prefixCheck(c)[0])),x.Normalizations.registered[c]&&(n=x.Normalizations.registered[c]("extract",e,n)),l=x.Hooks.extractValue(u,n)}else if(x.Normalizations.registered[r]){var p,d;p=x.Normalizations.registered[r]("name",e),"transform"!==p&&(d=s(e,x.Names.prefixCheck(p)[0]),x.Values.isCSSNullValue(d)&&x.Hooks.templates[r]&&(d=x.Hooks.templates[r][1])),l=x.Normalizations.registered[r]("extract",e,d)}if(!/^[\d-]/.test(l))if(i(e)&&i(e).isSVG&&x.Names.SVGAttribute(r))if(/^(height|width)$/i.test(r))try{l=e.getBBox()[r]}catch(g){l=0}else l=e.getAttribute(r);else l=s(e,x.Names.prefixCheck(r)[0]);return x.Values.isCSSNullValue(l)&&(l=0),v.debug>=2&&console.log("Get "+r+": "+l),l},setPropertyValue:function(e,r,a,n,o){var s=r;if("scroll"===r)o.container?o.container["scroll"+o.direction]=a:"Left"===o.direction?t.scrollTo(a,o.alternateValue):t.scrollTo(o.alternateValue,a);else if(x.Normalizations.registered[r]&&"transform"===x.Normalizations.registered[r]("name",e))x.Normalizations.registered[r]("inject",e,a),s="transform",a=i(e).transformCache[r];else{if(x.Hooks.registered[r]){var l=r,u=x.Hooks.getRoot(r);n=n||x.getPropertyValue(e,u),a=x.Hooks.injectValue(l,a,n),r=u}if(x.Normalizations.registered[r]&&(a=x.Normalizations.registered[r]("inject",e,a),r=x.Normalizations.registered[r]("name",e)),s=x.Names.prefixCheck(r)[0],8>=f)try{e.style[s]=a}catch(c){v.debug&&console.log("Browser does not support ["+a+"] for ["+s+"]")}else i(e)&&i(e).isSVG&&x.Names.SVGAttribute(r)?e.setAttribute(r,a):e.style[s]=a;v.debug>=2&&console.log("Set "+r+" ("+s+"): "+a)}return[s,a]},flushTransformCache:function(e){function t(t){return parseFloat(x.getPropertyValue(e,t))}var r="";if((f||v.State.isAndroid&&!v.State.isChrome)&&i(e).isSVG){var a={translate:[t("translateX"),t("translateY")],skewX:[t("skewX")],skewY:[t("skewY")],scale:1!==t("scale")?[t("scale"),t("scale")]:[t("scaleX"),t("scaleY")],rotate:[t("rotateZ"),0,0]};$.each(i(e).transformCache,function(e){/^translate/i.test(e)?e="translate":/^scale/i.test(e)?e="scale":/^rotate/i.test(e)&&(e="rotate"),a[e]&&(r+=e+"("+a[e].join(" ")+") ",delete a[e])})}else{var n,o;$.each(i(e).transformCache,function(t){return n=i(e).transformCache[t],"transformPerspective"===t?(o=n,!0):(9===f&&"rotateZ"===t&&(t="rotate"),void(r+=t+n+" "))}),o&&(r="perspective"+o+" "+r)}x.setPropertyValue(e,"transform",r)}};x.Hooks.register(),x.Normalizations.register(),v.hook=function(e,t,r){var n=a;return e=o(e),$.each(e,function(e,o){if(i(o)===a&&v.init(o),r===a)n===a&&(n=v.CSS.getPropertyValue(o,t));else{var s=v.CSS.setPropertyValue(o,t,r);"transform"===s[0]&&v.CSS.flushTransformCache(o),n=s}}),n};var S=function(){function e(){return l?T.promise||null:f}function n(){function e(e){function p(e,t){var r=a,i=a,s=a;return g.isArray(e)?(r=e[0],!g.isArray(e[1])&&/^[\d-]/.test(e[1])||g.isFunction(e[1])||x.RegEx.isHex.test(e[1])?s=e[1]:(g.isString(e[1])&&!x.RegEx.isHex.test(e[1])||g.isArray(e[1]))&&(i=t?e[1]:u(e[1],o.duration),e[2]!==a&&(s=e[2]))):r=e,t||(i=i||o.easing),g.isFunction(r)&&(r=r.call(n,w,P)),g.isFunction(s)&&(s=s.call(n,w,P)),[r||0,i,s]}function f(e,t){var r,a;return a=(t||"0").toString().toLowerCase().replace(/[%A-z]+$/,function(e){return r=e,""}),r||(r=x.Values.getUnitType(e)),[a,r]}function d(){var e={myParent:n.parentNode||r.body,position:x.getPropertyValue(n,"position"),fontSize:x.getPropertyValue(n,"fontSize")},a=e.position===N.lastPosition&&e.myParent===N.lastParent,o=e.fontSize===N.lastFontSize;N.lastParent=e.myParent,N.lastPosition=e.position,N.lastFontSize=e.fontSize;var s=100,l={};if(o&&a)l.emToPx=N.lastEmToPx,l.percentToPxWidth=N.lastPercentToPxWidth,l.percentToPxHeight=N.lastPercentToPxHeight;else{var u=i(n).isSVG?r.createElementNS("http://www.w3.org/2000/svg","rect"):r.createElement("div");v.init(u),e.myParent.appendChild(u),$.each(["overflow","overflowX","overflowY"],function(e,t){v.CSS.setPropertyValue(u,t,"hidden")}),v.CSS.setPropertyValue(u,"position",e.position),v.CSS.setPropertyValue(u,"fontSize",e.fontSize),v.CSS.setPropertyValue(u,"boxSizing","content-box"),$.each(["minWidth","maxWidth","width","minHeight","maxHeight","height"],function(e,t){v.CSS.setPropertyValue(u,t,s+"%")}),v.CSS.setPropertyValue(u,"paddingLeft",s+"em"),l.percentToPxWidth=N.lastPercentToPxWidth=(parseFloat(x.getPropertyValue(u,"width",null,!0))||1)/s,l.percentToPxHeight=N.lastPercentToPxHeight=(parseFloat(x.getPropertyValue(u,"height",null,!0))||1)/s,l.emToPx=N.lastEmToPx=(parseFloat(x.getPropertyValue(u,"paddingLeft"))||1)/s,e.myParent.removeChild(u)}return null===N.remToPx&&(N.remToPx=parseFloat(x.getPropertyValue(r.body,"fontSize"))||16),null===N.vwToPx&&(N.vwToPx=parseFloat(t.innerWidth)/100,N.vhToPx=parseFloat(t.innerHeight)/100),l.remToPx=N.remToPx,l.vwToPx=N.vwToPx,l.vhToPx=N.vhToPx,v.debug>=1&&console.log("Unit ratios: "+JSON.stringify(l),n),l}if(o.begin&&0===w)try{o.begin.call(m,m)}catch(y){setTimeout(function(){throw y},1)}if("scroll"===k){var S=/^x$/i.test(o.axis)?"Left":"Top",V=parseFloat(o.offset)||0,C,A,F;o.container?g.isWrapped(o.container)||g.isNode(o.container)?(o.container=o.container[0]||o.container,C=o.container["scroll"+S],F=C+$(n).position()[S.toLowerCase()]+V):o.container=null:(C=v.State.scrollAnchor[v.State["scrollProperty"+S]],A=v.State.scrollAnchor[v.State["scrollProperty"+("Left"===S?"Top":"Left")]],F=$(n).offset()[S.toLowerCase()]+V),s={scroll:{rootPropertyValue:!1,startValue:C,currentValue:C,endValue:F,unitType:"",easing:o.easing,scrollData:{container:o.container,direction:S,alternateValue:A}},element:n},v.debug&&console.log("tweensContainer (scroll): ",s.scroll,n)}else if("reverse"===k){if(!i(n).tweensContainer)return void $.dequeue(n,o.queue);"none"===i(n).opts.display&&(i(n).opts.display="auto"),"hidden"===i(n).opts.visibility&&(i(n).opts.visibility="visible"),i(n).opts.loop=!1,i(n).opts.begin=null,i(n).opts.complete=null,b.easing||delete o.easing,b.duration||delete o.duration,o=$.extend({},i(n).opts,o);var E=$.extend(!0,{},i(n).tweensContainer);for(var j in E)if("element"!==j){var H=E[j].startValue;E[j].startValue=E[j].currentValue=E[j].endValue,E[j].endValue=H,g.isEmptyObject(b)||(E[j].easing=o.easing),v.debug&&console.log("reverse tweensContainer ("+j+"): "+JSON.stringify(E[j]),n)}s=E}else if("start"===k){var E;i(n).tweensContainer&&i(n).isAnimating===!0&&(E=i(n).tweensContainer),$.each(h,function(e,t){if(RegExp("^"+x.Lists.colors.join("$|^")+"$").test(e)){var r=p(t,!0),n=r[0],o=r[1],i=r[2];if(x.RegEx.isHex.test(n)){for(var s=["Red","Green","Blue"],l=x.Values.hexToRgb(n),u=i?x.Values.hexToRgb(i):a,c=0;cO;O++){var z={delay:F.delay,progress:F.progress};O===R-1&&(z.display=F.display,z.visibility=F.visibility,z.complete=F.complete),S(m,"reverse",z)}return e()}};v=$.extend(S,v),v.animate=S;var P=t.requestAnimationFrame||d;return v.State.isMobile||r.hidden===a||r.addEventListener("visibilitychange",function(){r.hidden?(P=function(e){return setTimeout(function(){e(!0)},16)},c()):P=t.requestAnimationFrame||d}),e.Velocity=v,e!==t&&(e.fn.velocity=S,e.fn.velocity.defaults=v.defaults),$.each(["Down","Up"],function(e,t){v.Redirects["slide"+t]=function(e,r,n,o,i,s){var l=$.extend({},r),u=l.begin,c=l.complete,p={height:"",marginTop:"",marginBottom:"",paddingTop:"",paddingBottom:""},f={};l.display===a&&(l.display="Down"===t?"inline"===v.CSS.Values.getDisplayType(e)?"inline-block":"block":"none"),l.begin=function(){u&&u.call(i,i);for(var r in p){f[r]=e.style[r];var a=v.CSS.getPropertyValue(e,r);p[r]="Down"===t?[a,0]:[0,a]}f.overflow=e.style.overflow,e.style.overflow="hidden"},l.complete=function(){for(var t in f)e.style[t]=f[t];c&&c.call(i,i),s&&s.resolver(i)},v(e,p,l)}}),$.each(["In","Out"],function(e,t){v.Redirects["fade"+t]=function(e,r,n,o,i,s){var l=$.extend({},r),u={opacity:"In"===t?1:0},c=l.complete;l.complete=n!==o-1?l.begin=null:function(){c&&c.call(i,i),s&&s.resolver(i)},l.display===a&&(l.display="In"===t?"auto":"none"),v(this,u,l)}}),v}(window.jQuery||window.Zepto||window,window,document)}); \ No newline at end of file diff --git a/lib/velocity/velocity.ui.min.js b/lib/velocity/velocity.ui.min.js new file mode 100644 index 00000000..87069453 --- /dev/null +++ b/lib/velocity/velocity.ui.min.js @@ -0,0 +1,2 @@ +/* VelocityJS.org UI Pack (5.0.4). (C) 2014 Julian Shapiro. MIT @license: en.wikipedia.org/wiki/MIT_License. Portions copyright Daniel Eden, Christian Pucci. */ +!function(t){"function"==typeof require&&"object"==typeof exports?module.exports=t():"function"==typeof define&&define.amd?define(["velocity"],t):t()}(function(){return function(t,a,e,r){function n(t,a){var e=[];return t&&a?($.each([t,a],function(t,a){var r=[];$.each(a,function(t,a){for(;a.toString().length<5;)a="0"+a;r.push(a)}),e.push(r.join(""))}),parseFloat(e[0])>parseFloat(e[1])):!1}if(!t.Velocity||!t.Velocity.Utilities)return void(a.console&&console.log("Velocity UI Pack: Velocity must be loaded first. Aborting."));var i=t.Velocity,$=i.Utilities,s=i.version,o={major:1,minor:1,patch:0};if(n(o,s)){var l="Velocity UI Pack: You need to update Velocity (jquery.velocity.js) to a newer version. Visit http://github.com/julianshapiro/velocity.";throw alert(l),new Error(l)}i.RegisterEffect=i.RegisterUI=function(t,a){function e(t,a,e,r){var n=0,s;$.each(t.nodeType?[t]:t,function(t,a){r&&(e+=t*r),s=a.parentNode,$.each(["height","paddingTop","paddingBottom","marginTop","marginBottom"],function(t,e){n+=parseFloat(i.CSS.getPropertyValue(a,e))})}),i.animate(s,{height:("In"===a?"+":"-")+"="+n},{queue:!1,easing:"ease-in-out",duration:e*("In"===a?.6:1)})}return i.Redirects[t]=function(n,s,o,l,c,u){function f(){s.display!==r&&"none"!==s.display||!/Out$/.test(t)||$.each(c.nodeType?[c]:c,function(t,a){i.CSS.setPropertyValue(a,"display","none")}),s.complete&&s.complete.call(c,c),u&&u.resolver(c||n)}var p=o===l-1;a.defaultDuration="function"==typeof a.defaultDuration?a.defaultDuration.call(c,c):parseFloat(a.defaultDuration);for(var d=0;d1&&($.each(a.reverse(),function(t,e){var r=a[t+1];if(r){var n=e.o||e.options,s=r.o||r.options,o=n&&n.sequenceQueue===!1?"begin":"complete",l=s&&s[o],c={};c[o]=function(){var t=r.e||r.elements,a=t.nodeType?[t]:t;l&&l.call(a,a),i(e)},r.o?r.o=$.extend({},s,c):r.options=$.extend({},s,c)}}),a.reverse()),i(a[0])}}(window.jQuery||window.Zepto||window,window,document)}); \ No newline at end of file diff --git a/movies/index.html b/movies/index.html new file mode 100644 index 00000000..3b168cd6 --- /dev/null +++ b/movies/index.html @@ -0,0 +1,625 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 电影 | Daily record + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    Daily record

    + +
    +

    琐记随笔

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    + +

    电影 +

    + + + +
    + + + + +
    +
    +

    +
    + + + + +
    +
    + +
    + 首页 + 上一页 + 1 / 1 + 下一页 + 尾页 +
    + + + + +
    + + + +
    + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/page/2/index.html b/page/2/index.html new file mode 100644 index 00000000..aed5e076 --- /dev/null +++ b/page/2/index.html @@ -0,0 +1,1175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Daily record + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    Daily record

    + +
    +

    琐记随笔

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    + + +
    +
    +
    + + +
    + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    Service Worker 的初衷是极致优化用户体验,带来丝滑般流畅的离线应用。但同时也可以用作站点缓存使用。它本身类似于一个介于浏览器和服务端之间的网络代理,可以拦截请求并操作响应内容。功能强大,但由于兼容性问题,更适合用作渐进增强来使用。

    + +
    + + 阅读全文 » + +
    + + + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    2019年终总结(✖️)
    2020待做清单(✔️)

    + +
    + + 阅读全文 » + +
    + + + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    年底了,总结一下上半年探索的 PWA 的离线缓存技术。顺带总结了一下前端全流程每一步中都可能遇到的缓存,大部分都是概念、名词的理解和说明。涉及到的缓存有:HTTP 缓存、Manifest 缓存、CDN 缓存、Nginx 服务器缓存、Service Worker 缓存。

    + +
    + + 阅读全文 » + +
    + + + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    Apache Flink 是一个分布式处理引擎,在有界或无界数据流上进行有状态的计算。工作时偶然接触到一点点,有些概念虽然有点抽象,但是思路却值得借鉴。本文记录用 Flink 实时求均值、水印生成、以及迟到的数据元触发计算更新等等,是一篇纯探索性文章。用笔记形式记录,以便忘记。

    + +
    + + 阅读全文 » + +
    + + + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    《计算机网络(第7版)-谢希仁》http 部分的读书小结和扩展,因为工作中最常打交道的就是这部分了。整本书都很不错,语言通俗易懂;各协议的关系、发展过程以及区别都概括的很好。
    本文主要概括 HTTP、HTTP1.0、HTTP2.0、HTTPS 的之间的差异。

    + +
    + + 阅读全文 » + +
    + + + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    当 z-index 多个规则多个层级共同作用时,展现的效果往往跟自己的想法有很大差异,论 CSS 基本功的重要性。本文总结了 CSS 层叠的特性、基本准则和创建条件,内容大多为张鑫旭大神的《CSS世界》读书小结。

    + +
    + + 阅读全文 » + +
    + + + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

       Prometheus 是一个开源的监控系统。它可以自动化的监听应用各性能指标的变化情况,并发出报警信息。了解它目的,是想把前端页面的性能指标记录到公司的 Prometheus 监控系统上,利用它监听前端页面各类异常。

    + +
    + + 阅读全文 » + +
    + + + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

      平常只在测试环境测过前端页面性能,到了真实环境用户的手机上,页面性能的具体表现却未曾了解。H5 新增的 Performance API 可以精确的测量网页性能。使开发者可以通过数据上报的方式收集线上 H5 页面的性能表现,以合理优化页面性能短板,提升用户体验。

    + +
    + + 阅读全文 » + +
    + + + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    计算机知识补全计划:ip地址、子网掩码相关笔记。

    + +
    + + 阅读全文 » + +
    + + + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    在公司制作 H5 页面的时候,有这样一个场景:在微信打开 H5 页面,已经绑定微信的用户直接免密登录,未绑定的用户使用传统账号密码的登录方式。其中免密登录的核心一环就是走一个微信授权流程,原理不难,弄懂它的流程比较重要。

    + +
    + + 阅读全文 » + +
    + + + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/page/3/index.html b/page/3/index.html new file mode 100644 index 00000000..fcec83cf --- /dev/null +++ b/page/3/index.html @@ -0,0 +1,1005 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Daily record + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    Daily record

    + +
    +

    琐记随笔

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    + + +
    +
    +
    + + +
    + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    把2017年的笔记整理一下,方便查找。记录了js正则表达式常用的概念、字符、以及方法。

    + +
    + + 阅读全文 » + +
    + + + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    目前JS模块化规范主要三种:浏览器端的 AMDCMD 规范。经常被 exports、modules.exports、export、require 绕懵,遂来探一探究竟。

    + +
    + + 阅读全文 » + +
    + + + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    2018年仿佛什么都没做,但又仿佛做了些什么;仿佛没有遗憾,但却又心有不甘;以为走到了正确的方向,但“迷茫”二字却困惑了我整整一年。

    + +
    + + 阅读全文 » + +
    + + + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    《Linux命令行与shell脚本编程大全》读书小结,熟悉一下常用的命令行操作。书籍比较基础,对熟悉Linux命令行的人来说参考意义不大。主要记录下书中提到的、没提到的常用的命令。

    + +
    + + 阅读全文 » + +
    + + + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    公司用 gitlab 存管代码,自己用 github 。懒得下班后用自己电脑提交到 github ,故学习一下如何在同一台电脑上使用两个 git 账号。在 SSH config 中为不同的域名指定不同的 SSH key,之后再将自己本地的 github 库的 git config – local 设置成自己的 github 账号。

    + +
    + + 阅读全文 » + +
    + + + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    记录一下 hexo 基础用法。

    + +
    + + 阅读全文 » + +
    + + + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    无意间逛知乎的时候发现的书籍片段,留下了很深的印象。从记者的视角看到平日里生活中接触不到的社会另一面,别有一番感触。不愧是著名记者,文笔犀利,干练不拖沓。值得一读的好书:★★★★★

    + +
    + + 阅读全文 » + +
    + + + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    第一个 hexo-next 主题的 blog,主要记录下 markdown 语法

    + +
    + + 阅读全文 » + +
    + + + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/placeholder b/placeholder deleted file mode 100644 index e69de29b..00000000 diff --git a/search.xml b/search.xml new file mode 100644 index 00000000..3cc1a1c3 --- /dev/null +++ b/search.xml @@ -0,0 +1,711 @@ + + + + + + + 2022年终总结 + + /2023/01/13/2022%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/ + + 工作是否也有五年之痒,2022 年度吐槽汇总。

    一篇比往年迟了半个月的年度总结。拖延症的魔抓终于也伸向了年终总结…

    • 一月
      • 工作 :年假见底,在公司苟到了年前最后一刻。除夕前一天 Leader 给谈了谈入职半年的工作表现;
    • 二月
      • 回家过年(一) :过年了,老妈为了准备了一桌子的零食。晚上我打开电视看花滑表演,她在我旁边嗑瓜子。或许好久没这么安逸过了,感动哭了。
      • 回家过年(二) :接下来的三四天里,天天以泪洗面,别问,问就是感动的。一年过去了,别的方面没见长,泪腺到是发达了。
      • 回家过年(二) :过年几乎每天都会出去串个门或者跟朋友吃饭。还去了家乡的海棠山爬到了山顶。在商场的娃娃机里抓出了好几只娃娃,小时候在娃娃机上的怨念,长大了算是找补回来了。
    • 三月: 工作
    • 四月、五月
      • 居家工作 :公司园区附近出现了阳性,全员居家办公。居家这段时间正是需求比较多的时期,电脑放床上,睡醒了敲,敲完了睡。此时总是会想念公司宽阔的办公桌,清晰的扩展屏,自动饮水机和温度适宜的空调,以及到点联系不到人的状态。
    • 六月、七月:工作
    • 八月
      • 云南九日游(一) :单方面宣布大理就是海拉鲁本鲁,不接受任何反驳。云南的天和云给我感觉就是,动漫诚不欺我。苍山上的云从山峰一直延绵到脚下,伴随着下午三四点明晃晃的阳光,明亮美丽,仿佛置身天堂,我当时都怀疑是不是上帝要来接我了,同行的朋友瞟了我一眼,示意我别废话了赶紧把氧气罐打开。没想到我一米六五的东北大汉居然折在高原反应上,但是同样东北出身的她却啥事没有,这是旅行中我觉得最离谱的事。
      • 云南见朋友(二) :云南之行其实是一场说走就走的旅行。我们去了丽江、大理、昆明,最后一天去见了见大学同学,我的同学到还是老样子,以前爱笑,现在也是。继 2020 年之后的又一次彻夜长谈,听起来她的 2021 也并不是一帆风顺。
      • 工作(噩梦模式) :在忙碌期抽空出去玩回来的下场就是放假回来忙成狗。跟 PM 提出了延期的想法,被驳回。着急上火加高强度开发,直接给我人中干出了三四个大痘,那段时间照镜子我感觉我就是山本小次郎。
    • 九月
      • 工作(噩梦模式) :头疼欲裂,布洛芬止痛片,每个打工人都值得拥有。
      • 环球影城一日游 :水上表演和剧场表演都非常精彩,想着什么时候能带父母也来看看。
    • 十月、十一月、十二月
      • BEC 考试取消 :想考个中级,大概备考了小 3 个月,北京突发疫情,朝阳区考点几乎全军覆没,考试被取消。
      • 工作(地狱模式) :走在去公司的路上,经常会怀疑自己开启了地狱副本。OKR 加上应接不暇的业务需求,有点让我喘不过气。好消息是按期干完了,坏消息新需求在接下来的时间里依旧骑脸输出。
      • 游戏笔记本 :游戏下好了才发现老电脑带不起来,等反应回来时发现新的笔记本已经下单了。之前本想自己搭个台式,但是租的房子实在没地方放,搬家也会是问题,故作罢。这是第二次为了游戏买设备了,买完十分后悔,后悔为啥不早点买。感谢暴雪退出中国市场,让我拓展视野去玩其他好游戏。
      • 阳了 :没想到居家一个月,大门不出二门不迈也能阳。没发烧,嗓子冒烟几天之后好了。

    关于职业

    算上今年,作为一名前端开发,已经工作了五年了。那些曾经在网页上吸引我的、跳来跳去的动画,现在也只是静静看着,心中再无波澜,只是想着,这花里胡哨的交互可不能让我们公司设计师给看到(bushi。

    有的时候很羡慕客户端的开发同学,原生语言写的页面流畅又好用,也不用受到浏览器的层层限制,可以直接调用系统级 api,他们想法和声音也能受到大家的重视,开发出的产品也可以留存。再对比于随写随弃的前端活动页,真的羡慕的不是一点半点…

    如果职业选择能读档重来,我还会选择做前端吗。当年对这份职业的热忱,也被这几年这些细碎的事情消磨的一干二净。

    呃啊,被嫌弃的切图仔的一生…

    全年总结

    虽然是忙到脚打后脑勺的一年,但是内心变得更佛系了,很多事变得无所谓了…

    看了下去年给今年TODO,是早睡早起 + 治好拖延症,嗯…今年还是未完成,留给明年继续吧…

    ]]>
    + + + + + + 年度总结 + + + +
    + + + + + 【译】Why Is WKWebView So Heavy and Why Is Leaking It So Bad? + + /2022/06/09/%E3%80%90%E8%AF%91%E3%80%91Why-Is-WKWebView-So-Heavy-and-Why-Is-Leaking-It-So-Bad/ + + 从 iOS8 开始,就引入了新的浏览器控件 WKWebView,用于取代 UIWebView。在新版本系统中使用 UIWebView 会发出警告 ⚠️ 提醒更换控件。坊间传闻 WKWebView 存在内存占用过大的问题…

    声明:这是一篇翻译水文,有用的内容不多,之前是因为好奇翻译了一半,翻译完发现并没有什么有用的知识点…

    在 Embrace 公司,我们帮助移动应用公司解决他们最困难的生产问题。其中常见的 bug 是 iOS 上对 WKWebView 的不当管理产生的。而问题是 Webview 对象在资源中占用较重。大量被占用的内存未被正确的释放则会导致系统卡顿、死机甚至崩溃。

    本文中,我们将会介绍以下内容:

    • 为什么 WKWebView 会这么重
    • 常见的 WKWebView 导致的内存泄露方式
    • 使用 WKWebView 时怎样发现内存泄露
    • 使用 WKWebView 的最佳实践

    为什么 WKWebView 会这么重

    在开始之前,我们在先前的文章中已经介绍了content process 终止导致 WebView 阻塞显示空白以及降级导致的 WebView 空包。如果你依然因为空白的 WebView 而苦恼,看看这些文章或许会有帮助。

    文本将主要探讨在加载过程中 WebView 被阻塞以及在你的 App 中存在了过多的 WebView 的问题。WebView 是可控的最重的对象之一。基本上,你可以用你的 App 来启动另一个应用并添加两个附属进程 —— content process 和 networking process。

    所以如果你的应用中有 一个 WebView ,则意味着你的应用实际运行在 三个系统进程 上:应用进程、Web Content Process 和 Web Networking Process。

    两个 WebView 则意味着有 五个进程
    三个 WenView 则意味着有 七个进程

    当示例个数成倍增加时,并没有形成一个规模经济效应(即进程越多越高效)。事实上,正相反。创建的 WebView 越多,你的 App 运行就越慢。

    常见的 WKWebView 导致的内存泄露方式

    WKWebView 致使内存泄露最常见的原因就是 新建 ,而不是复用已经创建好的实例。一些时候,工程师们以为他们已经复用了 Webview 了,但是他们并没有检查在 Xcode 已经构建的 WKWebView 实例。因为 WKWebView 是存放在 Apple 系统目录中,工程师在调试性能问题时很容易把这部分忽略掉。

    例如,你有一个轮播组件(Carousel),每当用户滑动时就会加载一个 WebView,内容如以下几种:

    • 加载一篇 新闻/杂志 网站的文章
    • 加载一个 电商 网站的产品列表页
    • 加载一段 流媒体 如视频

    对于轮播组件来说,在内存中的 WebView 数量最好永远不要超过两个。一个为用户展示当前内容,另一个用作下一个内容的承接。一旦用户滑动切换到下一个 WebView,应该清空第一个 WebView 并且使之为下一次切换做准备。这样无论用户切换多少次,你的 App 中始终就只有两个 WebView。

    对于 ScrollView,在同一时间内可能会有多个可见的 WebView 存在。这种情况下,其最大数量取决于填满屏幕大小需要的 WebView 个数外加一个用于预加载 WebView。

    另一中泄露方式是已崩溃的 WebView 一直被保留而没有得到释放。 无论用户在何时遇见白屏页面,你都应该有一个状态码来确定当前页面是应该重新加载还是应该被移除。例如,如果是付款页面出了问题,你会想去重新加载;如果是广告页面出了问题,当你不能够修复时你会选择删除它。

    使用 WKWebView 时怎样发现内存泄露

    通过 Xcode 内存图表来查看内存泄露。 用 Xcode 进行 debug 时,查看 WebView 模块,可以在展开左边侧栏中看到当前内存中的 WebView 数量。此时滑过刚刚我们创建的组件,就可以看到到内存使用情况。

    使用 WKWebView 的最佳实践

    首先最好的实践就是限制应用应用内 WKWebView 的数量。在 iOS 应用中,最繁重的操作之一就是创建新的 WKWebView。它们占用大量的内存并添加额外的进程。无论何时尝试将 WebView 用系统本身的某功能来代替,都是有意义的。

    第二个实践是复用已有的 WKWebView 而不是新建。清除现有的旧内容并将新内容加载到现有的 WKWebView 实例中,这比直接删除和创建的性能要好的多。

    第三个实践是写一些适当的测试用例来标记溢出的 WebView,代码可以严格一点。如果你仅在轮播组件中使用了 WebView,那么你很明确的知道同一时刻的内存中最多应该包含两个 WebView。当超过两个 WebView 存在时,测试用例将报错。

    同理,如果你有一个产品列表在 ScrollView 中,那么你就可以通过计算填满屏幕所需的 WebView 数量来计算最大值。测试用例也是同样的方法。利用 Xcode 的内存图和适当的测试用例来发现 WebView 的泄露是很重要的,这样就可以使你的应用程序性能更佳。

    总结一下

    iOS 应用卡顿和反应慢的问题之一是创建了太多 WKWebView 实例,对已存在的 webview 没复用也没销毁(这不卡才怪…)。

    参考文章

    https://blog.embrace.io/wkwebview-memory-leaks/

    翻译总结

    • 文章部分内容写的过于重复,并不是很干货,让我想起了国内的营销号;
    • 强烈怀疑原文是隔壁机翻成英文的,或者作者母语并非英语;
    • 机翻比自己脑子翻好用…
    ]]>
    + + + + + App + + + + + + + App + + Webview + + 翻译 + + + +
    + + + + + 网工相关 + + /2022/03/11/%E7%BD%91%E5%B7%A5%E7%9B%B8%E5%85%B3/ + + 之前网工学习部分知识点总结,主要是整理汇总下分散在不同地方的笔记。
    工作中用到的不多,仅当笔记备份。

    加密算法

    可逆算法

    字面意思,经过算法处理后的信息可通过某种逆向计算得到原信息的算法称为可逆算法。
    PS:常见的 base64 并不是一种加密算法。它的编码过程完全公开,逆向解码即可得到原来的信息。经过 base64 编码后的字符串也都是常规字符,这样在信息传递的过程中,UNICODE 字符串就不会发生不能识别或者丢失的现象了。故 base64 只是一种编码方式,并不能归类为加密算法当中。

    对称加密

    对称加密指的是加密、解密用的是同一个密钥。特点是 加密速度快,但安全性不高。
    常见的对称加密算法有:DES、3DES、AES。

    非对称加密

    非对称加密指的是加密、解密用的是不同的密钥,它们是成对出现的、称为公钥和私钥,公钥加密的内容,用私钥才可以解密。特点是 安全性高,加密速度慢。
    常见的非对称加密算法有:RSA。

    不可逆算法

    不可逆算法的特征是加密过程中不需要使用密钥,输入明文后通过算法生成密文,密文信息无法被解密。(常见的场景是:用户账密校验, 常见的算法是:哈希算法-信息摘要算法)
    常见算法:HASH 算法。

    • MD5 算法:通过不可逆的字符串变换法,产生一个唯一的 MD5 信息摘要。
      (每个文件都有一个数字指纹)
    • SHA 算法:信息摘要算法,主要用于验证数据的完整性。在传输过程中,若数据发生变化,那么就会产生不同的摘要。

    更多算法细节介绍参考:常见加密算法

    HTTPS 中用的是什么算法

    // TODO 我记得这里分成了两步 既要保证安全 又要保证速度 查一下书吧

    HTTPS 证书过期,内容是否还是加密的

    不论证书是否有效,只要用户通过浏览器确认要发起协商会话,那么就依然会从证书里拿到密钥,走一个加密通话的过程。
    (证书的作用主要是认证,去辨明浏览器的真伪)
    参考文章:如果没有有效的证书,HTTPS连接是否加密

    域名解析 A记录、CNAME、NX

    • A记录 就是把一个域名解析到一个IP地址(Address,特制数字IP地址),创建不带 www 的域名记录需要在域名前加一个 ‘@’;
    • CNAME记录 可以看做是称域名的别名。它可以将多个域名指向同一个ip(实际上都指向了一个域名);
    • MX记录 (Mail eXchanger) 设置域名邮箱用的:直接添加记录值为对应的域名邮箱,网易、腾讯、等等;
    • AAAA 记录 是一个指向 IPv6 地址的记录;
    • NS 记录 (Name Server) 用于记录域名服务器,用于指定域名由哪台服务器来解析。可以使用 nslook -qt=ns [域名] 来查询当前 ip;

    参考文章:

    ]]>
    + + + + + 计算机相关知识 + + + + + + + 计算机网络 + + + +
    + + + + + 2021年终总结 + + /2021/12/03/2021%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/ + + 2021年终总结(✖️)
    2021大师难度通关记(✔️)

    • 1月
      • 筹备团建 :疫情管控比较严,筹备工作进行的并不顺利;
      • 年终绩效(年终奖) :坚持工作不跑路的精神支柱,做好了绩效为D的准备,领导比较照顾给了C,拿了一笔钱准备回家过年;
    • 2月
      • 各部门聚餐 :春节前的两周平均每天都有聚餐,在中午,或是在晚上;
        • 团队聚餐常常在一家湘菜馆,印象最深的一道菜是红辣椒炒绿辣椒;
        • 趁着聚餐活动去玩了一把卡丁车,体验在轰鸣声中狂飙的感觉,只是车技捉急;
      • 回家过年 :应国家号召”就地过年“,有顾虑的人留下,没有顾虑的人启程返乡;
      • 整理照片 :筛选了一些从毕业到工作后的照片整理成相册。只是照片数量较多,即使到返程也没能整理完;
    • 3月
      • 加班
    • 4月
      • 加班/看工作
    • 5月
      • 加班/看工作/面试
    • 6月
      • 加班/看工作/面试
      • 好友毕业/奇葩解压馆/欢乐谷
      • 离职申请
    • 7月
      • 最后一次团建 : 团建是在一个草原上,视野开阔,景色优美;夜晚下起了雨,同事们在屋内远程联调排查 bug;
      • 离职 :HR 给了满满一页的表格需要对应的负责人签字,下午找遍了楼层的每一处,没想到居然是在这一天才将各个大佬和部门的位置熟悉了个遍。同事将我送出园区的时候,天还亮着。迎着夕阳下班,感觉真实又不真实。
      • 搬家 :又从海淀/昌平区搬回朝阳区了;
      • 入职 :那两天恰逢北京暴雨,于是入职时间往后顺移了两天,提前感受到了公司的人文主义关怀。
      • 加班
    • 8月
      • 加班
    • 9月
      • 工作
    • 10月
      • 国庆假期 :通关了积灰已久的塞尔达,终于以一副兽皮人面的样子来到了公主身前。
      • 工作
    • 11月
      • 工作
      • 海口/火星演唱会 :演唱会跟想象中的体验出入还蛮大的,如果可以重来一次,能不能把票钱还我。
    • 12月
      • 工作

    一些无关紧要的记录

    • 鼠标:换成了罗技的 Anywhere3,很香很丝滑;
    • 模玩:上半年入了从大学就开始惦记的 MARK 3,下半年为了情怀入了 MARK 85;
      (嗯,明年打算喝西北风 (  ̄ー ̄) );
    • 记录工具:印象笔记内容变多之后,不管是网页端还是客户端,用起来都很笨重,而且一言不合就进行设备限制弹广告,bug 也变多了,逐渐让人难以忍受,遂寻找新的笔记工具。期间试过 马克飞象、Notion、网易云笔记、语雀。最后选择了语雀,不管是目录分类还是工具布局,都非常合理,用起来得心应手。
      现在已经成为 dida、语雀 的重度使用用户了。

    为什么辞职

    给了自己一年的时间去适应公司,约定好若一年之后还是如刚入职般的状态,便离职。
    这一年过的并不轻松,从未知道原来安心敲代码,没有其他事打扰是这么奢侈的事。或许当时匆匆做选择,没有深思熟虑就入职等同于埋坑。

    最焦虑的时候,恰逢朋友毕业,来北京陪我两天。
    这两天我们去了减压馆,去了欢乐谷。
    但是人在乐园里,心在工作上,这份焦灼似乎并没有被化解。

    好友返程的那天上午,早会时分,我站在人群后面,听着别人的需求进度,思绪早已飘到了九霄云外。
    回到工位后,打开电脑,开始码离职申请,

    这一天晚上,下班回家走在路上,我感觉到,心底的那块石头终于被搬走了。

    关于前公司

    周围的同事们都很优秀,大家之间的氛围很棒,目前遇到的公司中无出其右。
    这里的人追求技术,敢于创新。
    只是,来来往往很多人,还没来得及再次打招呼,工位就空了。
    待了一年多,大大小小的会议,各种各样的事情,朝令夕改的要求,也将我对大厂的幻想磨的粉碎。
    公司说不上哪里好,也说不上哪里特别不好。
    只是在我漫漫找工作的路途中,为我上了重要一课,

    关于现公司

    哪怕已经入职新公司小半年了,可是这对我来说依然还是如梦如幻般的经历。
    工作的自由度很高,曾经有的束缚现在都没有了。
    同时也有些难过,失去了一些共同工作的伙伴,曾经是一群人,现在是一个人。

    但做人也不能太贪心,世上本就没有两全之事。

    一场义无反顾的海口之旅

    忙忙碌碌的下半年,一直想去的演唱会终于在年底有了消息,
    觉得无论如何也要去看一看,害怕一错过就又等不来了。

    虽然疫情防控各种措施,跨省看演唱会困难重重,好在抵达海口后一切顺利。
    海风吹拂的那一瞬间,让我觉得这一切都是值得的。

    因为一场演唱会,结识了一群小伙伴。
    我们在落日海边散步,在海大夜市吃夜宵,去逛免税店,去尝椰子鸡。
    三天短暂而又充实的海口之旅,玩的很开心。
    这次受时间限制,没来得及去三亚玩一圈,如果有机会,想去亚特兰蒂斯水世界玩一玩。

    全年总结

    受尽心理折磨终于迎来转折的一年,生活节奏和心态也在慢慢调整过来。
    努力工作是为了更好的生活,要保持本心。
    只可惜,方方面面而言,今年也是没啥进步的一年 ╮(╯▽╰)╭。
    希望新一年的自己能战胜懒惰,把拖延的毛病改掉,还有就是早点起床早点上班,不要天天睡到11点才起床收拾去公司…

    PS:不确定字迹是否跟心态有关系,上半年“草书”,下半年“行楷”。

    ]]>
    + + + + + + 年度总结 + + + +
    + + + + + 《写给大家看的设计书》- 小结 + + /2021/09/14/%E3%80%8A%E5%86%99%E7%BB%99%E5%A4%A7%E5%AE%B6%E7%9C%8B%E7%9A%84%E8%AE%BE%E8%AE%A1%E4%B9%A6%E3%80%8B-%E5%B0%8F%E7%BB%93/ + + 很好的排版设计入门书,19年年底的时候在地铁里用了不到两周的时间看完了,一边看书,一边拿出平日里设计师给的设计稿对比着看,总会有一种“我不明白,但我大受震撼”的感觉。 书很快读完了,读书小结却从19年拖到了21年,再过三个月就22年了,现在拿出来翻翻,如获新书( ̄ε(# ̄)。

    前言

    好的排版会让信息的可读性大大增加,一眼抓住重点。这本书主要介绍排版中最基础的四大设计原则 “亲密性、对齐、重复、对比”, 读完之后会发现在设计稿和 PPT 的应用中有一些规律可循。书中有大量示例,小结中只摘取最具代表性的几个,以方便查看和参考。

    亲密性(Proximity)

    概念

    物理位置的接近意味着存在关联。彼此相关的项应当组织在一起,形成一个视觉单元, 而不是多个孤立的元素。
    要有意识地注意你是怎样阅读的,你的视线怎样移动:从哪里开始;沿着怎样的路径;到哪里结束;读完之后,接下来看哪里?整个过程当是一个合理的过程,有确定的开始,而且要有确定的结束。

    示例

    1. 调整前(左) / 调整后(右)

    2. 从左到右 逐次调整

    要避免的问题

    • 避免一个页面上有太多孤立的元素;
    • 不要在元素之间留出同样大小的空白,除非各组同属于一个子集;
    • 不属于一组的元素之间不要建立关系:如果元素彼此无关,就把它们分开;
    • 不要仅仅因为有空白就把元素放在角落或中央;

    对齐(Alignment)

    思想:任何东西都不能在页面上随意安放。每个元素都应当与页面上另一个元素有某种视觉联系。这样能建立一种清晰、精巧的外观。
    如果页面上某些元素是对齐的,即使对齐的元素物理位置是彼此分离的,但在你眼里(以及你的心里),它们之间也会有一条看不见的线把彼此连接在一起。
    对齐的根本目的是 使页面统一而且有条理。

    示例

    1. 调整前视线在不同位置停留5次 调整后结构变的清晰有序

    2. 文章主题与标题采用相同的对齐方式 才不会显得杂乱

    3. 对齐线”边界的强度为布局提供了力度

    要避免的问题

    • 避免在页面上混合使用多种文本对齐方式;
      (也就是说,不要将某些文本居中,而另外一些文本右对齐)
    • 尽量不要将居中对齐作为默认选择,除非你有意识地想要想要创建一种比较正式的表示;

    重复(Repetition)

    思想:设计中视觉元素的重复可以将作品中的各部分连在一起,从而 统一 并增强整个作品。
    重复的元素可以是一种粗字体、一条粗线、某种颜色、空间关系等。
    不过 重复不只是自然的一致,而是统一设计各个部分的有意识的行为。
    如果一个作品看起来很有趣,它往往也更易阅读。
    (列表项就是一个典型应用重复的例子)

    示例

    要避免的问题

    • 避免过多地重复一个元素,重复太多会让人讨厌。要注意对比的价值;

    对比(Contrast)

    思想:对比是为页面增加视觉效果最有效的途径,也是在不同元素之间建立一种有组织的层次结构最有效的方法。
    可以通过字体、线宽、颜色、形状、大小、空间等来增加对比。但要记住一个原则:要想实现有效的对比,对比就必须强烈,如果元素不同,那就让它们截然不同。

    示例

    1. 背景色与文字颜色的强烈对比 更易阅读

    2. 一条无形的对角线以及放大的图片也增加了趣味性

    要避免的问题

    • 不要将一种粗线与一种更粗的线进行对比,不要将棕色文本与黑色标题建立对比。要避免使用两种或多种类似的字体;
    • 如果各个项不完全一样,那就让它们截然不同;

    颜色运用

    色轮基础

    • 色轮的基础是 红、黄、蓝 3种颜色,它们被称之为 三原色, 因为它们无法被创建。
    • 将相邻的三原色等量的混合 ,就会得到 三间色(secondary color)
    • 其余位置将相邻的两个颜色等量混合,就会得到 第三色

    颜色关系

    • 互补色:色轮上相对的颜色为互补色 。利用它们的对立关系,常见搭配是一种作为主色,而另一种用于强调。
    • 三色组:彼此等距的三种颜色叫做三色组 。三色组中的颜色都有基础色使其相互连接,因此看上去十分协调。
    • 分裂互补三色组:从色轮的一边选择一种颜色,再找出它的互补色,但并不直接使用这个互补色,而是使用 该互补色两侧的颜色 。这样的组合会有一种更为细致的边界。
    • 类似色:类似色由色轮上彼此相邻的颜色组成 ,它们都有相同的基础色。用不同的亮色和暗色组合一组类似色,会获得醒目的效果。
    • 单色组:单色组合由一种色调及其相应的多种亮色和暗色组成 ,如黑白照片。
    • 暗色和亮色的组合:不使用色调,而是使用这些颜的不同亮色和暗色 。这样既丰富了选择,也可以放心颜色的协调性。

    颜色变化

    • 亮色和暗色
      • 纯色就是 色调
      • 向色调增加黑色就构成一个 暗色
      • 向色调增加白色就构成一个 亮色
    • 色质:指某种颜色的明暗度、深浅度或色调。
    • 暖色与冷色
      • 暖色:其中包含红色或者黄色。暖色是趋进型的,趋于做视觉提醒;
      • 冷色:其中包含蓝色。冷色属于后退型的,更趋于做背景色;

    颜色模型

    • CMYK:由四种墨色组成,可以打印成千上万种颜色。常用于纸张打印、书籍印刷等;
    • RGB:由 Red、Green、Blue 三种颜色组成。常用于显示器、电视、手机屏幕等;

    要注意的问题

    • 避免组合中的色质过于接近。如果色质很接近,对比太过微弱,看上去就会模糊不清;
    • 不要让冷暖色过于均衡,要充分利用冷暖色的视觉特质;
    • CMYK 和 RGB 之间的转换会有数据损失,所以最好用 RGB 处理图像,最后再把它们转换为 CMYK 格式;

    文字与字体

    • 衬线体:衬线又被称为“字脚”,衬线体(Serif)就是有边角装饰的字体,如宋体;
    • 无衬线体:无衬线体(Sans-serif)则与衬线体相反,通常是机械和统一粗细的线条,没有边角的装饰,易读性更好,如黑体;

    标点符号

    • 跟随在有样式文字后的标点:如果一个单词的样式是粗体,那么跟随在文字之后的符号也应该是粗体。
    • 括号中的标点:
      • 如果若括号中的文字是整个句子的一部分,那么标点就应该在括号之外
        (就像这个例子一样)。
      • 如果括号内的文字是一个完整的句子,那么标点应该出现在括号内。
        (这就是一个标点出现在括号内的例子。)
    • (英文)标点后面一个空格,破折号两侧无空格。
    • 方框中的文字:如果你确实要把文字放进方框里,那就要在四周留出足够的空间。

    更多提示与技巧

    • 创建中心点:页面上应该有一个 最突出的主导元素。
    • 使用有对比的子标题:不仅视觉效果强烈,而且能充分地表达含义。
    • 段落缩进:第一段不要缩进。即使跟在子标题后面也如此。
      段落之间要么有额外的空间,要么缩进,但不要二者都有。
    • 主标题可采用无衬线体来增加对比。正文中可以使用衬线体。
      若使用无衬线体,则需要预留较宽的行间距,行的长度应缩短;
    • 不要把元素堆到角落里,也别总要填满空白,更不要把它们都变成一个尺寸或者类似尺寸。
    • 打破规则: 只要你清楚有哪些规则,适当地打破规则,且结果合理,那就大胆去做。
      只要让人看出来你是有意为之,而不是把页面弄的杂乱无序就好。
    ]]>
    + + + + + 闲暇读物 + + + + + + + 读书小结 + + + +
    + + + + + H5 与 App 之间的交互 + + /2021/04/12/H5%E4%B8%8EApp%E4%B9%8B%E9%97%B4%E7%9A%84%E4%BA%A4%E4%BA%92/ + + 遗留了很久的一个学习任务,最近正好在总结归纳小程序在 App 之间的交互,顺便拾起一些学过的和没学过的知识。主要涉及的知识点:URL Scheme、Webview、H5 与 App 之间的通信以及 JSBridge 的概念。

    H5 唤起 App

    唤起方式

    H5 可以通过 location.href、iframe、a 标签三种方式来调用 URL Scheme 来实现唤起 App。
    URL Scheme 是系统提供的一种机制,它可以由应用程序注册,然后其他程序通过 URL Scheme 来调用该应用程序,其基本格式为 scheme://[path][?query]

    • scheme :应用标识,已安装的APP注册在系统中的标识;
    • path :应用行为,表示应用某个页面或功能;
    • query :应用参数,标识应用页面或者应用功能所需的条件参数;
      但是此方式无法得知 App 是否唤起成功,有可能存在 App 未下载的情况。通常用计时器,监听页面是否已隐藏(监听页面 visibilityChange),若未曾隐藏则认为打开失败,再根据不同的平台跳转不同的渠道下载页。

    除此之外,还有一种链接叫:Universal Link (通用链接) , 是 Apple 在 iOS9 推出的一种能够通过 HTTPS 链接来启动 APP 的功能。当应用支持此链接时,则会无缝跳转到 APP,而不需要其他判断;但需要注意的是,这个链接是可以访问的,直接在浏览器中打开并不会跳转 App,需要跨域访问才可以。

    一个完善的唤起流程如下:

    若在小程序的 Webview 中尝试唤起 App 会怎么样?

    (以下的逻辑参照上图的流程)
    由于微信拦截了 URL Scheme,所以并不会打开 App;此时就会判断 Webview 环境,跳转对应的下载页:
    如:在 Mac 上的开发者工具中会跳转 App Store:

    而在安卓手机的微信小程序中则会跳转(腾讯的安装渠道):

    但是由于这两个地址的域名都没有在微信后台配置,所以都会被微信认成不可信的域名,只会跳转到一个空白页面,然后提示域名不可信。

    PS:主要禁止的原因是,小程序不允许将流量导出到 APP 之外;
    2021.4.12 不过网上还有一种说法就是将 App 关联到腾讯的应用宝上,到时候就会自己跳转到 App Store (参考
    2021.4.13 经验证,此方法不可行,在信任应用宝域名的前提下(sj.qq.com),依旧无法唤起下载/跳转:

    • 安卓表现为停止在当前 H5 并提示使用浏览器打开;
    • IOS 表现为卡在当前 H5,点击下载按钮无反应;

    H5 与 App 之间的通信

    关于 Webview 能力

    Webview 是 Android / IOS 操作系统的一个组件,它可以让应用程序直接显示网页内容。
    它提供了很多能力,其中最重要的一项就是 添加 JS 和执行 JS 的能力。H5 与 App 之间的通信正是依赖于此。

    还有很多通用能力如 注入 Cookie添加/移除响应头监听页面返回操作拦截 Url拦截弹窗 、获取/放置证书、监听下载事件 等等。更多 API 可以查阅 官方文档

    通信方式

    通信过程主要依赖 Webview 提供的 JS API,可以简单的看成两个方向:

    • App 调用 JS 代码
    • JS 调用 App 代码

    接下来示例的方法均以 Android API 为例。

    App 调用 JS 代码

    在 Webview 中是可以获取到 window 对象的,所以 App 可以访问挂载在全局对象上的方法。只需告知 App 方法名即可。
    Andiroid 中使用这两个方法执行 JS:

    • 使用 WebView 的 loadUrl() 方法:参数 为 js 文件路径;
    • 使用 WebView 的 evaluateJavascript() 方法:参数为 js 方法名,以及回调函数;

    JS 调用 App 代码

    JS 调用 App 代码主要有两种方式,一种是页面发起行为,App 拦截 行为解析语义响应操作;另一种是 App 提前将方法映射成 JS,注入 到 window 对象上供 JS 调用。

    • 通过 WebChromeClient 的 onJsAlert()onJsConfirm()onJsPrompt() 方法回调 拦截 JS 对话框 alert()confirm()prompt() 消息;
      • 得到消息内容后解析,再做相应的处理;
    • 通过 WebViewClient 的 shouldOverrideUrlLoading() 方法回调 拦截 url
      • 一般使用这种方法拦截事先约定好 URL Scheme 上的挂载参数,再执行不同的逻辑;
    • 通过 WebView 的 addJavascriptInterface() 进行对象映射;
      • 此方法可以将 Java 对象映射映射成 JS 对象,JS 直接调用即可。

    关于 JS Bridge

    JS Bridge 只是 Native 和 H5 交互方案的一种统称,犹如它的名字一样,Webview 和 H5 将 JS 用作沟通的桥梁。它赋予了 JavaScript 操作 Native 的能力,同时也给了 Native 调用 JavaScript 的能力。上述的通信方案都可以称之为 JS Bridge 的实现。

    联调注意事项

    1. 同一方法,若确定方法名、参数等没有问题,但是调用结果与预期不一致,注意同时对比 IOS 端和 Android 端表现是否一致,若表现不一致,则应找对应的客户端同事去修改;
    2. 注意测试 App 版本号,以及 H5 中引用的 sdk 版本号,排查问题时考虑是否是版本过旧导致的;
    3. 对于用作工具的测试页面出现问题,即时反馈,有可能是测试页面未更新。

    参考链接

    • Android:你要的WebView与 JS 交互方式 都在这里了
    • h5 与原生 app 交互的原理
    • In-depth Profiling of JSBridge
    • Android Build Version
    ]]>
    + + + + + 前端 + + + + + + + App + + Webview + + 交互 + + + +
    + + + + + 微信小程序从 0 到 1 + + /2021/03/22/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F%E4%BB%8E0%E5%88%B01/ + + 大概这一年左右的时间,都在跟小程序相关的需求。从开发到上线,流程上会跟以往的 Web 开发有些不同。此前除了大学时的一次课设,其他时间未曾接触过小程序,算是从 0 开始吧。不过得益于 Uniapp 基于 Vue.js 的语法封装,除了小程序自己的 API 之外,语法学习成本几乎没有。

    与H5相比,孰优孰劣

    对比

    • 运行环境
      • ​网页开发渲染线程和脚本线程是互斥的,这也是为什么长时间的脚本运行可能会导致页面失去响应;
      • 在小程序中渲染层和逻辑层分别运行在不同的线程中。即 双线程模型
    • 开发差异
      • 小程序原生写法很像前端框架中的 Vue,也是 MVVM 模式,但是写法上没有完全照抄,都可以用类似虚拟 DOM 的形式能保证你的数据变化自动响应到模板;
      • 小程序里不能使用任何 window 下的属性和方法;
      • 小程序不可以过虚拟 DOM 来操作 DOM,不能使用任何 DOM 和 BOM 相关API;
        • 这是因为:小程序的逻辑层和渲染层是分开的,逻辑层运行在 JSCore 中,并没有一个完整浏览器对象,因而缺少相关的 DOM API 和 BOM API;
      • 小程序提供了很多 SDK 方法,几乎涵盖了 APP 能赋予 H5 的所有能力;
      • 小程序类似于离线包,只要用户访问过,就会把主包代码下载到本地。
    • 维护成本
      • 网页开发者需要面对各式的浏览器兼容 :如 PC 端需要面对 IE、Chrome、QQ浏览器等,在移动端需要面对 Safari、Chrome 以及 iOS、Android 系统中的各式 WebView,开发时只需要常用的编辑器和浏览器即可 ;
      • 小程序开发过程中需要面对的是两大操作系统 iOS 和 Android 的 微信客户端 ,以及用于辅助开发的小程序开发者工具;小程序的 开发者需要经过申请小程序帐号、安装小程序开发者工具、配置项目 等等过程才可进行小程序开发。

    开发前准备

    账号相关权限

    开发者和测试相关的权限需要在微信后台添加;权限分为项目成员和体验成员,都有数量限制。
    一般将开发者添加为 项目成员 ,将测试人员或者 PM 添加为 体验成员

    开发上手

    相关文档

    项目目录

    一个小程序主体部分由三个文件组成,必须放在项目的根目录:

    文件必须作用
    app.js小程序逻辑:调用小程序实例、小程序生命周期 hook
    app.json全局配置:决定页面文件的路径、窗口表现、设置网络超时时间、设置多 tab 等
    app.wxss小程序公共样式表
    project.config.json项目配置文件(如:appId、编译时配置、依赖等)

    相关概念

    小程序运行机制

    冷启动

    如果用户首次打开,或小程序销毁后被用户再次打开,此时小程序需要重新加载启动,即冷启动。冷启动不保留上次的浏览场景,打开即直接进入首页(可以使用 restartStrategy 配置冷启动进入的页面)。

    热启动

    如果用户已经打开过某小程序,然后在一定时间内再次打开该小程序,此时小程序并未被销毁,只是从后台状态进入前台状态,这个过程就是热启动。热启动保留上次浏览的 path。

    小程序更新机制

    开发者在管理后台发布新版本的小程序之后,微信客户端会静默更新到新版本。但是无法立刻影响到所有现网用户,最差情况下,也在发布之后 24 小时之内下发新版本信息到用户。
    如果需要马上应用最新版本,可以使用 wx.getUpdateManager API 进行处理。

    • UpdateManager.applyUpdate():强制小程序重启并使用新版本,在小程序新版本下载完成后调用;
    • UpdateManager.onCheckForUpdate():监听向微信后台请求检查更新结果事件。微信在小程序冷启动时自动检查更新,不需由开发者主动触发;
    • UpdateManager.onUpdateReady():监听小程序有版本更新事件。客户端主动触发下载(无需开发者触发),下载成功后回调;
    • UpdateManager.onUpdateFailed():监听小程序更新失败事件;

    基础库

    小程序的能力需要微信客户端来支撑,每一个基础库都只能在对应的客户端版本上运行,高版本的基础库无法兼容低版本的微信客户端。
    参考:基础库版本分布

    分包

    某些情况下,开发者需要将小程序划分成不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载。这样做可以优化小程序首次启动的下载时间,在多团队共同开发时可以更好的解耦协作。
    在小程序启动时,默认会下载主包并启动主包内页面,当用户进入分包内某个页面时,客户端会把对应分包下载下来,下载完成后再进行展示。

    目前小程序分包大小有以下限制:

    • 整个小程序所有分包大小不超过 20M
    • 单个分包/主包大小不能超过 2M

    开发者通过在 app.json subpackages 字段声明项目分包结构:
    写成 subPackages 也支持:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    {
    "pages":[
    "pages/index",
    "pages/logs"
    ],
    "subpackages": [
    {
    "root": "packageA",
    "pages": [
    "pages/cat",
    ]
    }, {
    "root": "packageB",
    "name": "pack2",
    "pages": [
    "pages/apple",
    ]
    }
    ]
    }

    打包原则

    • 声明 subpackages 后,将按 subpackages 配置路径进行打包,subpackages 配置路径外的目录将被打包到 app(主包)中
    • app(主包)也可以有自己的 pages(即最外层的 pages 字段);
    • subpackage 的根目录不能是另外一个 subpackage 内的子目录;
    • tabBar 页面必须在 app(主包)内。

    引用原则

    • packageA 无法 require packageB JS 文件,但可以 require app、自己 package 内的 JS 文件;
    • packageA 无法 import packageB 的 template,但可以 require app、自己 package 内的 template;
    • packageA 无法使用 packageB 的资源,但可以使用 app、自己 package 内的资源。

    例如:nodemodules 包中引用的代码会打包到主包中,因为该文件路径在 subPages 之外。

    鉴权登录

    小程序鉴权登录流程图

    常用 API 及能力

    • 常用事件如 Tap、longPress 参照:WXML的冒泡事件列表

    • getApp():获取全局的应用实例,全局数据可以在 App 中设置

      1
      2
      3
      4
      5
      6
      7
      8
      // app.js
      App({
      globalData: 1
      })

      // a.js
      var app = getApp()
      app.globalData++
    • 授权相关信息

      • 获取用户手机号
        • 需要将 <button> 组件 open-type 的值设置为 getPhoneNumber,当用户点击同意之后,可以通过 bindgetphonenumber 事件回调获取到微信服务器返回的加密数据;
          1
          <button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber"></button>
      • wx.getSetting() 获取用户当前权限配置,常可以用来在调用某项系统功能时,查看用户是否授权(例如保存存图片到相册)
      • wx.authorize() 向用户发起授权请求,调用后会立即弹窗询问用户是否同意授权小程序使用某项功能( 如果用户之前已经同意授权,则不会出现弹窗
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        // 下边这段代码就是上两个 API 的应用
        wx.getSetting({
        success: (res?: any) => {
        // 判断是否已经授权
        if (!res['scope.writePhotosAlbum']) {
        wx.authorize({
        scope: 'scope.writePhotosAlbum',
        success: () => {
        // 存储图片
        wx.saveImageToPhotosAlbum(...)
        }
        })
        } else {
        // 调起客户端小程序设置界面,返回用户设置的操作结果
        wx.openSetting()
        }
        }
        })
      • 若想取消当前用户授权,可【点击小程序右上角三个点】->【设置】->【用户信息】里取消授权
    • 生成小程序二维码

      • wxacode.createQRCode:获取小程序二维码,适用于需要的码数量较少的业务场景。通过该接口生成的小程序码,永久有效,有数量限制;
    • 小程序运行版本的区分

      • __wxConfig.envVersion:会返回当前小程序运行版本
        • develop - 开发版
        • trial - 体验版
        • release - 正式版
      • 注:此方法没有在官方文档上注明,只是挂在在全局 this 下,使用时注意该对象是否存在。
      • 客户端分享的小程序链接可以指定小程序版本,需要跟客户端同学提前约定好,例如:
    • 与其他第三方应用进行交互

      • 跳转第三方小程序:需要将被调用的第三方小程序的 AppId 加入到小程序项目白名单中,正式版只能打开正式版;
        • wx.navigateToMiniProgram
        • wx.navigateBackMiniProgram
      • 小程序内关注公众号:
        • <official-account> 原生组件,只能关注与小程序主体相同的公众号(后台配置),且样式不允许自定义,使用场景受限(扫码);
      • 小程序唤起 app:
        • 直接唤起:否;
        • 由 app 直接调起小程序,然后小程序可以通过操作再调起 app;
        • 从 app 分享出去的小程序,可以调起 app:需要将 <button> 组件 open-type 的值设置为 launchApp,可通过 app-parameter 参数给 App 传参(详情
          (PS:App分享到小程序的参数,支持选择 正式版、体验版、开发板)
        • 2021.5.19 后,小程序不再支持唤起 App 的能力;
        • 无法从小程序的 webview 组件中唤起 App,微信做了 Url Scheme 拦截;
      • 小程序内 webview 访问 H5:
        • <web-view> 原生组件,个人类型的小程序暂不支持使用,需要在微信后台将域名加入白名单;
        • 在微信后台下载”校验文件“,并将校验文件上传至网站根目录,供小程序平台进行验证,验证通过了才能成功添加域名;
        • 注:若要从 webview 打开的 H5 跳转回小程序原生页,则需要提前引入 wx-js-sdk,使用 wx.miniProgram.navigateTo 方法 (官方文档)
      • 小程序内打开公众号文章:
        • 使用 <web-view> 组件即可打开相关联的公众号文章,非关联的公众号则提示“无法打开图文消息”;
        • 在微信管理后台:设置 -> 关联设置 中可以看到“关联的公众号”,(需要到公众号中关联小程序)。
    • 第三方应用与小程序的交互

      • APP 调起微信小程序(只能调用与当前APP相关联的小程序) 参考
        • 在微信管理后台:设置 -> 关联设置 中可以看到“关联的移动应用”;
        • 可跳转到指定页面
        • 限制
          • APP和小程序相同主体:如果在同一个主体下,不存在调用个数限制;
          • APP和小程序不同主体:如果不在同一个主体下,一个app最多只能关联3个小程序。也就是说,非相同主体的小程序最多拉起3个;
      • 外部 H5 调起微信小程序
        • 直接调用:否;
        • 可根原生同学协商,使用 APP 提供的 SDK 方法调用;
        • 或者使用使用微信云开发能力的托管 H5,免鉴权直接跳转任意合法的小程序;
      • 短信跳转小程序
        • 直接调用:否;
        • 微信开放能力 - 服务端接口 - 可以获取打开小程序任意页面的 URL Link。适用于从短信、邮件、网页、微信中直接打开;
        • 使用微信云开发能力,打开M页跳转小程序(待调研);
      • 公众号打开小程序(只能调用与当前公众号相关联的小程序)参考

    常见的问题

    项目测试

    可以从 H5 直接进入小程序体验版

    在移动端打开:

    https://open.weixin.qq.com/sns/getexpappinfo?appid={AppId}&path={pagesPath}.html

    即可访问小程序体验版,并跳转到对应路径(注意:此链接只能在移动端微信中打开).

    也可以,通过判断微信版本,自己写一个测试/入口构造页面来作为测试入口。

    缓存

    小程序的所有缓存数据上线为 10MB,像 storage 中的数据,除非用户主动删除或因存储空间原因被系统清理,否则数据都一直可用。
    清除缓存:

    • 发现-小程序-在列表中删除掉测试的小程序;
    • 微信-我-设置-通用-存储空间;
    • 安卓在私信聊天页输入 debugx5.qq.com ,利用腾讯的工具清理 cookie;
    • 退出登录,重新登录。

    Android

    由于安卓9的安全限制,无法信任用户自行安装的证书,正常状态连代理打开小程序会报错“获取运行环境失败”;
    将手机 root 后,解决证书信任问题后才能访问。

    IOS

    直接连代理,访问小程序即可。

    框架对比

    业内知常见小程序框架:wepy、mpvue、uni-app、taro、chameleon。
    主流框架对比:详情
    主流框架性能对比:详情
    目前使用的是 Uniapp,因为可以编译多平台的小程序,且与 Vue 的语法能无缝衔接,开发成本较低。

    个人偏见

    关于功能开发

    从一个开发者的角度,我并不希望听到 PM 说“这个功能和页面要和APP保持一致”。
    个人认为 APP 承载的功能是核心且重的,也是在用户体验上最优的一端,若将 H5 和小程序的功能完全与 APP 拉齐,不仅开发周期长,维护难度高,同时会让小程序和 H5 失去本身的轻量优势。
    小程序 和 H5 应该承载更多引流的功能,而不是一整套完备的 APP,当然了,这句话是针对公司有核心 APP 的情况;若是主要产品就是小程序方向,就看功能利弊的权衡了,只是个人认为“小程序”不应该变成一个庞然大物,对于 PM 而言应该更侧重于对于不同端的用户给出不同的产品特性,而不是一味的追求“复刻”。

    关于设计还原

    由于小程序提供的通用的原生组件有的时候,是不允许开发者更改某些样式的,此时要跟设计同学及时反馈;若要自己开发某些组件,记得增加工期。
    用现有的框架也可以:汇总9款优秀的开源小程序UI框架

    关于部署上线

    最后一点,不管是开发还是审核部署,小程序强烈依赖微信运行环境,被封禁和能否上线的话语权(例如小程序中有游戏广告之类的,通常就会被封禁)并非掌握在自己手里,需要做好被封禁时的准备,域名同理。

    参考链接

    ]]>
    + + + + + 前端 + + 微信开发相关 + + + + + + + 微信 + + 小程序 + + + +
    + + + + + 2020年终总结 + + /2020/12/31/2020%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/ + + 2020年终总结(✖️)
    2020年流水账(✔️)

    • 1.22 回家过年
    • 2.9 疫情 家里东拼西凑最好的口罩都给我拿到了北京,高铁上人很少,人们不怎么说话也不怎么吃东西
    • 3.20 被裁 P2P 行业终没能熬过政策寒冬,和之前的同事们由于疫情的原因最后也没能聚上一餐
    • 4.2 新公司 入职某二手平台,开启9点后下班的生活
    • 4.4 搬家 不搬家来公司要一个半小时
    • 5.1 购入Switch 笔记本拿去维修了,于是买了心仪已久的 Switch 和塞尔达来消磨时光
    • 8.18 同学婚礼 去了满洲里见证大学室友的婚礼,两三年不见,大家变化都很大,仿佛只有我还在原地
    • 9.27 购入Mac Pro 赶着最后一波教育优惠,买了自己的 MacBook,顺带入了 Air Pods
    • 10.15 搬家 又换房子了,房东不租了给了一个月搬家时间,自如赔付了一个月的房租
    • 10.29 转正 改了六七版述职PPT,终于成为公司正式的一员了,有时会不禁会想,如果时间能回到以前就好了
    • 11.1 欢乐谷 搭上万圣节的尾巴和公司的小伙伴们去了欢乐谷,各种失重旋转高速俯冲的过山车超级好玩,可是一起同行的小伙伴却说啥也不来第二次了
    • 12.8 取消成都之旅 一场说走就走的成都之旅最终没能逃离疫情的影响,周五买的元旦机票也只能匆匆退掉
    • 12.28 深圳&优化 一起吃饭的小伙伴有一位还在试用期,被”调“去了深圳,31号就是在公司的最后一天,有点不舍

    工作

    这一年,最大的变化就是换公司了吧。

    新公司 leader 人挺好,每月都会面谈与我们沟通,只是我每次都不知道说什么,只是每次的问题都没有什么变化。
    新公司的同事们都很年轻,也很 nice,虽然我依旧不是很健谈,饭桌上也总是沉默。

    技术

    • 之前没接触过小程序,没用过 TS,也没用过 React,今年在工作中都浅浅的用到了;
    • 公司内很多内部封装好的组件库,用起来很方便,也有专门负责维护的同学;
    • 内部技术分享很丰富,但自己的消化速度没想象那么强;

    作息
    疫情期间找工作,虽然已经做好了就算新公司是 996 也无所谓的准备,但真的接触这种作息才发现,如果可以我还是希望有自己的时间;

    购入 Mac

    在公司配备的 Windows 上设计稿总是展示不完全,要么看不清阴影,要么找不到虚线,被设计谈了两三次之后,索性就买了。
    刚开始不是很适应新版 Mac 的键盘手感,后来敲着敲着也无所谓了。
    Air Pods 着实很惊艳,开启降噪模式,专心程度 up~ up~。

    神奇的小程序(uniapp + ts)

    首次接触小程序还是在实习的时候,在学校的一个课题中用过一点,当时也没觉得什么。但是新公司小程序体积很大,改一行代码,编译要好久好久…先要等 uniapp 编译完,然后要等小程序开发者工具编译,这两段编译时间足够我接杯水顺便去上个厕所;
    6、7月份的时候想要尝试过去优化这个编译速度,却发现无从下手,小程序项目的编译工具是 uniapp 自己的,开发者工具的编译也无法介入,最后组内同事给出一个方案,就是注释掉开发中不需要的路由,然而速度依然差强人意;
    买了 Mac 之后,以为多少速度能快点,神奇的是,不仅没快,全量编译小程序一不小心就能把 Mac 搞死机,看着黑屏的电脑,我不仅发出感叹:”这,就是小程序的力量吗。“

    生活

    无法拉上拉链的伴娘服。

    刚收到大学室友结婚消息的时候,还是挺震惊的,我们还一起相聚的夜晚仿佛还在昨天,现在算算,我都已经工作两年半了,毕业一别就我们就没再见过面。
    室友是辽宁人,是我们正儿八经的东北妹砸,远嫁到了内蒙古,很佩服她的勇气,毕竟亲人和好友都不在那边,而且是一个全新的生活环境。
    也是靠着这次机会,和久别的同学见了一面。感觉大家都变了,大部分已经褪去了学生的稚气,仿佛只有我,也只有我,行为处事依旧像个没长大的学生。
    草原很美,一望无垠,很羡慕这里的生活节奏,草原上的牛羊偶尔成团,偶尔散开,都是低着头各吃各的草。
    坐了两个小时的车,从满洲里到新左旗。从乌云密布下着雨的草原一边,行驶到只有落日和霞光的另一边,如此美景,想发朋友圈却没信号。
    见了新娘,感叹时光飞逝,试了伴娘服,感叹体重为什么不飞逝;加之参加婚礼之前,去剪了剪头发,婚礼当天的我就像是个胖胖的人妖,丢人到是没有,辣眼倒是会有一点。
    要走了,拥抱一下,一转身两行热泪就下来了:”为什么哭了?“,”因为明天还要上班…“。

    身体

    虽然逢人便说”我还年轻“,虽然别人也常说”你还年轻“,但身体仿佛已经不太认同年轻这两个字了。
    越来越重的眼袋和越来越僵硬的后背,连尾椎的疼痛也变得越来越难以忍受。
    除此之外,体重也以肉眼可见的速度上涨,如此说来,今年自己都没有做过几顿饭,全靠外卖过活。
    再这样下去,怕是要早早步入 ICU 了。

    这一年

    这一年,即使有千百种不适应,也要有千百种方式去适应。

    絮叨絮叨,一年比一年能叨叨,年终总结写的像越来越像流水账。挺丧的一年,年底之前还是没能去海拉鲁城堡看公主一眼,也没能跟帮助过的我人道声谢,崩坏的查莉娅线稿依旧没能画完…

    对 2021 没有特别想立的 flag,希望做一些正确的事吧。

    PS: 不要听民谣写年终总结, (╯°□°)╯( ┻━┻ 越写越丧。

    ]]>
    + + + + + + 年度总结 + + + +
    + + + + + 【译】Can NodeJS use ES6 import syntax ? + + /2020/12/10/%E3%80%90%E8%AF%91%E3%80%91Can-NodeJS-use-ES6-import-syntax/ + + 偶然在一篇文章中看到 Node 可以使用 import 语法了,无需再使用 babel 做额外的转换,遂去了解下 Node 相关的更新。本文主要介绍在最新版本 Node(14.15.1) 中如何使用 import 语法。大部分内容翻译自官网和外网文章。关于 JS 模块机制之前已经总结过一篇文章,这里不再赘述。

    ( PS:原本是公司部门要求的 kpi 文章,现做了精简 )

    概览

    本文主要内容:

    • Node 对 ES Modules 的支持
    • 在 Node 使用 import 语法
    • Node 中 ES Modules 的现状和未来

    Node 对 ES Modules 支持

    Node 13.2.0 开始正式支持 ES Modules 特性(移除了 –experimental-modules 启动参数).

    注意:相关的 ESM 的实验性标志都虽然被移除
    (但是由于 ESM loader 还是实验性的,所以运行 ES Modules 代码依然会有警告:

    1
    (node:47324) ExperimentalWarning: The ESM module loader is experimental.

    在 NodeJS 中使用 ES Modules

    使 Node 支持 ES modules 有两种方式:

    1. 在 package.json中,增加 type: "module"配置,即可在 node 代码中使用 importexport语法:

    文件目录结构:

    1
    2
    3
    4
    5
    .
    ├── index.js
    ├── package.json
    └── utils
    └── speak.js
    1
    2
    3
    4
    5
    6
    7
    8
    // utils/speak.js
    export function speak() {
    console.log('Come from speak.')
    }

    // index.js
    import { speak } from './utils/speak.js';
    speak(); //come from speak
    1. 在 .mjs 文件中直接使用 importexport

    文件目录结构:

    1
    2
    3
    4
    5
    .
    ├── index.mjs
    ├── package.json
    └── utils
    └── sing.mjs
    1
    2
    3
    4
    5
    6
    7
    8
    // utils/sing.mjs
    export function sing() {
    console.log('Come from sing')
    }

    // index.mjs
    import { sing } from './utils/sing.mjs';
    sing(); //come from sing

    注意:

    • 若不添加上述两项中任一项,直接使用在 Node 中使用 ES modules,则会抛出警告:
      1
      Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
    • 根据ESM规范,使用import关键字并不会像 CommonJS 模块那样,在默认情况下以文件扩展名完成文件路径。因此,ES Modules 必须明确文件扩展名。

    模块作用域

    一个模块的作用域,由父级中有 type: "module" 的 package.json 文件路径定义。而使用.mjs扩展文件加载模块,则不受限于包的作用域。
    同理,package.json中没有type标志的包都会默认采用 CommonJS 模块机制,.cjs类型的扩展文件使用 CommonJS 方式加载模块同样不受限于包的作用域。

    包的入口

    定义包的入口有两种方式,在 package.json 中定义main字段或者exports字段

    1
    2
    3
    4
    {
    "main": "./main.js",
    "exports": "./main.js"
    }

    需要注意的是,当exports字段被定义后,包的所有子路径都将被封装,子路径的文件不可再被导入。例如 require(‘pkg/subpath.js’) 将会报错:

    1
    ERR_PACKAGE_PATH_NOT_EXPORTED error.

    参考官方文档:https://nodejs.org/api/packages.html#packages_main_entry_point_export

    两个模块机制在执行时机上的区别

    • ES Modules 导入的模块会被预解析,以便在代码运行前导入:
      • 根据 EMS 规范 import / export 必须位于模块顶级,不能位于作用域内;
      • 模块内的 import/export 会提升到模块顶部;
    • 在 CommonJS 中,模块将在运行时解析;

    举一个简单的例子来直观的对比下二者的差别:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // ES Modules

    // a.js
    console.log('Come from a.js.');
    import { hello } from './b.js';
    console.log(hello);

    // b.js
    console.log('Come from b.js.');
    export const hello = 'Hello from b.js';

    输出:

    1
    2
    3
    Come from b.js.
    Come from a.js.
    Hello from b.js

    同样的代码使用 CommonJS 机制:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // CommonJS

    // a.js
    console.log('Come from a.js.');
    const hello = require('./b.js');
    console.log(hello);

    // b.js
    console.log('Come from b.js.');
    module.exports = 'Hello from b.js';

    输出:

    1
    2
    3
    Come from a.js.
    Come from b.js.
    Hello from b.js

    可以看到 ES Modules 预先解析了模块代码,而 CommonJS 是代码运行的时候解析的。

    两个模块在原理上的区别

    1. CommonJS

    Node 将每个文件都视为独立的模块,它定义了一个 Module 构造函数,它代表模块自身:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function Module(id = '', parent) {
    this.id = id;
    this.path = path.dirname(id);
    this.exports = {};
    this.parent = parent;
    this.filename = null;
    this.loaded = false;
    this.children = [];
    };

    而 require 函数接收一个代表模块ID或者路径的值作为参数,它返回的是用module.exports导出的对象。在执行代码模块之前,NodeJs 将使一 个包装器对模块中的代码其进行封装:

    1
    2
    3
    (function(exports, require, module, __filename, __dirname) { 
    // Module code actually lives in here
    });

    引自 NodeJS 官网

    通过这样做,Node.js 实现了以下几点:

    • 它保持了顶层的变量(用 var、 const 或 let 定义)作用在模块范围内,而不是全局对象。
    • 它有助于提供一些看似全局的但实际上是模块特定的变量,例如:
      • 实现者可以用于从模块中导出值的 module 和 exports 对象。
      • 包含模块绝对文件名和目录路径的快捷变量 __filename 和 __dirname 。

    简言之,每个模块都有自己的函数包装器, Node 通过此种方式确保模块内的代码对它是私有的。在包装器执行之前,模块内的导出内容是不确定的。
    除此之外,第一次加载的模块会被缓存到 Module._cache中。一个完整的加载周期大致如下:

    1
    Resolution (解析) –> Loading (加载) –> Wrapping (私有化) –> Evaluation (执行) –> Caching (缓存)
    1. ES Modules

    在 ESM 中,import 语句用于在解析代码时导入模块依赖的静态链接。文件的依赖关系在编译阶段就确定了。对于 ESM,模块的加载大致分为三步:

    1
    Construction (解析) -> Instantiation (实例化、建立链接) -> Evaluation (执行)

    这些步骤是异步执行的,每一步都可以看作是相互独立的。这一点跟 CommonJS 有很大不同,对于 CommonJS 来说,每一步都是同步进行的。

    两种模块间的相互引用

    CommonJS 和 ES Modules 都支持 Dynamic import()。它可以支持两种模块机制的导入:

    在 CommonJS 文件中导入 ES Modules 模块

    由于 ES Modules 的加载、解析和执行都是异步的,而 require() 的过程是同步的、所以不能通过 require() 来引用一个 ES6 模块。ES6 提议的 import() 函数将会返回一个 Promise,它在 ES Modules 加载后标记完成。借助于此,我们可以在 CommonJS 中使用异步的方式导入 ES Modules:

    1
    2
    3
    4
    5
    6
    // 使用 then() 来进行模块导入后的操作
    import("es6-modules.mjs").then((module)=>{/*…*/}).catch((err)=>{/**…*/})
    // 或者使用 async 函数
    (async () => {
    await import('./es6-modules.mjs');
    })();

    在 ES Modules 文件中导入 CommonJS 模块

    在 ES6 模块里可以很方便地使用 import 来引用一个 CommonJS 模块,因为在 ES6 模块里异步加载并非是必须的:

    1
    2
    3
    4
    5
    6
    7
    8
    import { default as cjs } from 'cjs';

    // The following import statement is "syntax sugar" (equivalent but sweeter)
    // for `{ default as cjsSugar }` in the above import statement:
    import cjsSugar from 'cjs';

    console.log(cjs);
    console.log(cjs === cjsSugar);

    Node 中 ES Modules 的现状和未来

    在引入 ES6 标准之前,服务器端 JavaScript 代码都是依赖 CommonJS 模块机制进行包管理的。
    如今,随着 ES Modules 的引入,开发人员可以享受到与发布规范相关的许多好处。但需要注意的是,截止至当前时间(2020.11.30),在最新版 Node v15.1.0 中,该特性依然是实验性的(Stability: 1),不建议在生产环境中使用该功能。

    最后,由于两种模块格式之间存在不兼容问题,将当前项目从 CommonJS 到 ES Modules 转换将是一个很大的挑战。可以借助 Babel 相关插件实现 CommonJS 和 ES Modules 间的相互转换:

    参考链接

    翻译原文

    官方文档

    ]]>
    + + + + + 后端 + + + + + + + NodeJS + + 翻译 + + + +
    + + + + + 【译】从 ES2016 到 ES2020 的所有特性 + + /2020/07/23/%E3%80%90%E8%AF%91%E3%80%91%E4%BB%8E-ES2016-%E5%88%B0-ES2020-%E7%9A%84%E6%89%80%E6%9C%89%E7%89%B9%E6%80%A7/ + + 自 ECMA2015 (6th) 大幅更新之后, ECMA 标准变更成每年6月发布一个版本进行小幅度更新。为方便温习和查找,汇总一下近五年的所有版本特性。本文共涵盖了 ES2016、ES2017、ES2018、ES2019、ES2020 五个版本的更新内容。翻译有删改,仅供快速查找使用。

    前言:关于ECMA

    ECMA 相关stage-x 处于某个阶段,描述的是 ECMA 标准相关的内容。根据提案划分界限,stage-x 大致分为以下阶段:

    • stage-0:还是一个设想,只能由 TC39 成员或 TC39 贡献者提出。
    • stage-1:提案阶段,比较正式的提议,只能由 TC39 成员发起,这个提案要解决的问题必须有正式的书面描述。
    • stage-2:草案,有了初始规范,必须对功能语法和语义进行正式描述,包括一些实验性的实现。
    • stage-3:候选,该提议基本已经实现,需要等待实验验证,用户反馈及验收测试通过。
    • stage-4:已完成,必须通过 Test262 验收测试,下一步就纳入 ECMA 标准。

    总结起来就是数字越大,越成熟。

    ES2016 新特性

    ES2016 只更新了两个特性:

    • Array.prototype.includes()
    • 指数运算符

    Array.prototype.includes()

    该方法用于检测数组中是否包含某个值,包含则返回 true,否则返回 false。

    1
    2
    3
    4
    5
    6
    let array = [1, 2, 4, 5];

    array.includes(2);
    // true
    array.includes(3);
    // false

    结合 fromIndex 使用:

    可以为 .includes() 提供一个起始索引,默认是 0,接受负数值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    let array = [ 1, 3, 5, 7, 9, 11 ];

    array.includes(3, 1);
    // find the number 3 starting from array index 1
    // true
    array.includes(5, 4);
    //false
    array.includes(1, -1);
    // find the number 1 starting from the ending of the array going backwards
    // false
    array.includes(11, -3);
    // true

    指数操作符 (**)

    在 ES2016 前我们会这样写:

    1
    2
    3
    4
    Math.pow(2, 2);
    // 4
    Math.pow(2, 3);
    // 8

    现在,有了指数运算符之后,可以这样写:

    1
    2
    3
    4
    2 ** 2;
    // 4
    2 ** 3;
    // 8

    这在多次操作指数运算的时候很有用:

    1
    2
    3
    4
    2 ** 2 ** 2
    // 16
    Math.pow(Math.pow(2, 2), 2);
    // 16

    Math.pow() 需要连续调用,这会使代码看起来很长不宜阅读。使用指数运算符的方式更快更简洁。

    ES2017 新特性

    ES2017 介绍了更多新特性,如 String padding,Object.entries(), Object.values(), 原子性操作, 以及 Async、Await 等。

    字符串填充 ( String.padStart() 和 String.padEnd() )

    .padStart() 对字符串头部进行填充, .padEnd() 对字符串尾部进行填充:

    1
    2
    3
    4
    "hello".padStart(6);
    // " hello"
    "hello".padEnd(6);
    // "hello "

    为什么只填充1个空格而不是6个?是因为 “hello” 一共是五个字符,而 .padStart.padEnd 的入参是填充后的字符串长度,所以之只会填充一个空格。

    使用 padStart 实现文本右对齐

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const strings = ["short", "medium length", "very long string"];

    const longestString = strings.sort((s1, s2) => s2.length - s1.length).map(str => str.length)[0];

    strings.forEach(str => console.log(str.padStart(longestString)));

    // very long string
    // medium length
    // short

    第一步获取了数组中最长字符串的长度,接下来用该长度填充数组中的每个字符串,即打印出一组右对齐的字符串。

    自定义填充值

    除了默认的空格,还可以使用字符串和数字进行填充。

    1
    2
    3
    4
    5
    6
    "hello".padEnd(13," Alberto");
    // "hello Alberto"
    "1".padStart(3, 0);
    // "001"
    "99".padStart(3, 0);
    // "099"

    Object.entries() 和 Object.values()

    首先创建一个Object:

    1
    2
    3
    4
    5
    const family = {
    father: "Jonathan Kent",
    mother: "Martha Kent",
    son: "Clark Kent",
    }

    在上个版本的 javascript 中,我们可以使用如下方式获取 Object 中的值:

    1
    2
    3
    4
    Object.keys(family);
    // ["father", "mother", "son"]
    family.father;
    "Jonathan Kent"

    Object.keys() 仅会返回对象中所有的键名。

    现在又多了两种可以访问对象的方法:

    1
    2
    3
    4
    5
    6
    7
    Object.values(family);
    // ["Jonathan Kent", "Martha Kent", "Clark Kent"]

    Object.entries(family);
    // ["father", "Jonathan Kent"]
    // ["mother", "Martha Kent"]
    // ["son", "Clark Kent"]

    Object.values() 以数组形式返回对象所有值。
    Object.entries() 同样以数组形式返回对象中的键值对。

    Object.getOwnPropertyDescriptors()

    这个方法会返回对象所有自身属性的描述。描述性的字段有:valuewritable, get, set, configurableenumerable

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const myObj = {
    name: "Alberto",
    age: 25,
    greet() {
    console.log("hello");
    },
    }
    Object.getOwnPropertyDescriptors(myObj);
    // age: {value: 25, writable: true, enumerable: true, configurable: true}

    // greet: {value: ƒ, writable: true, enumerable: true, configurable: true}

    // name: {value: "Alberto", writable: true, enumerable: true, configurable: true}

    尾行逗号

    这仅仅是语法上的一个小改变。现在在写 Object 属性值时,我们可以在每个值后边加上一个逗号,不论它是否是最后一个。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // from this
    const object = {
    prop1: "prop",
    prop2: "propop"
    }

    // to this
    const object = {
    prop1: "prop",
    prop2: "propop",
    }

    注意上述第二个例子中的最后一个逗号,即使你不写它也不会报错,只是写上会更方便的开发者们协作。

    共享内存和原子性操作

    下述引自 MDN:

    多个共享内存的线程能够同时读写同一位置上的数据。原子操作会确保正在读或写的数据的值是符合预期的,即下一个原子操作一定会在上一个原子操作结束后才会开始,其操作过程不会中断。

    这些原子操作属于 Atomics 模块。与一般的全局对象不同,Atomics 不是构造函数,因此不能使用 new 操作符调用,也不能将其当作函数直接调用。Atomics 的所有属性和方法都是静态的(与 Math 对象一样)。

    方法示例:

    • add / sub
    • and / or / xor
    • load / store

    Atomics 通常和 SharedArrayBuffer 对象(通用的固定长度二进制数据缓冲区)一起使用。
    来看一下几个 Atomics方法的使用示例:

    Atomics.add(), Atomics.sub(), Atomics.load(), and Atomics.store()

    Atomics.add() 共接受三个参数:array、index、value。并返回该索引在执行操作前的值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // create a `SharedArrayBuffer`
    const buffer = new SharedArrayBuffer(16);
    const uint8 = new Uint8Array(buffer);

    // add a value at the first position
    uint8[0] = 10;

    console.log(Atomics.add(uint8, 0, 5));
    // 10

    // 10 + 5 = 15
    console.log(uint8[0])
    // 15
    console.log(Atomics.load(uint8, 0));
    // 15

    要从数组中检索特定的值,可以使用 Atomics.load() 并传递两个参数,一个数组和一个索引。
    Atomics.sub() 的使用方式与 Atomics.add() 类似,只不过它是减去某个值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // create a `SharedArrayBuffer`
    const buffer = new SharedArrayBuffer(16);
    const uint8 = new Uint8Array(buffer);

    // add a value at the first position
    uint8[0] = 10;

    console.log(Atomics.sub(uint8, 0, 5));
    // 10

    // 10 - 5 = 5
    console.log(uint8[0])
    // 5
    console.log(Atomics.store(uint8, 0, 3));
    // 3
    console.log(Atomics.load(uint8, 0));
    // 3

    上述示例调用 Atomics.sub() 方法,实现 unit8[0] - 5 ,相当于 10 - 5。如同 Atomics.add() 一样,该方法也会返回数组中该索引在执行操作前的值。

    使用 Atomics.store() 来存储一个值,使用 Atomics.load() 来加载一个值。

    Atomics.and(), Atomics.or(), Atomics.xor()

    这三个方法都在数组的给定位置执行按位的 AND、OR 和 XOR 操作。不再赘述。

    Async 和 Await

    ES2017 提供了两个操作 Promise 的新方法:”async/await”。

    回顾一下 Promise

    在介绍新语法之前,让我们快速浏览下之前我们是怎么使用 Promise 的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // fetch a user from github
    fetch('api.github.com/user/AlbertoMontalesi').then( res => {
    // return the data in json format
    return res.json();
    }).then(res => {
    // if everything went well, print the data
    console.log(res);
    }).catch( err => {
    // or print the error
    console.log(err);
    })

    上述是一个非常简单的例子:请求一个 Github 用户的数据,并打印。下面来看个复杂点的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    function walk(amount) {
    return new Promise((resolve, reject) => {
    if (amount < 500) {
    reject ("the value is too small");
    }
    setTimeout(() => resolve(`you walked for ${amount}ms`),amount);
    });
    }

    walk(1000).then(res => {
    console.log(res);
    return walk(500);
    }).then(res => {
    console.log(res);
    return walk(700);
    }).then(res => {
    console.log(res);
    return walk(800);
    }).then(res => {
    console.log(res);
    return walk(100);
    }).then(res => {
    console.log(res);
    return walk(400);
    }).then(res => {
    console.log(res);
    return walk(600);
    });

    // you walked for 1000ms
    // you walked for 500ms
    // you walked for 700ms
    // you walked for 800ms
    // uncaught exception: the value is too small

    来看下,如何用新语法 async / await 来重写 Promise

    Async 和 Await
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    function walk(amount) {
    return new Promise((resolve, reject) => {
    if (amount < 500) {
    reject ("the value is too small");
    }
    setTimeout(() => resolve(`you walked for ${amount}ms`),amount);
    });
    }

    // create an async function
    async function go() {
    // use the keyword `await` to wait for the response
    const res = await walk(500);
    console.log(res);
    const res2 = await walk(900);
    console.log(res2);
    const res3 = await walk(600);
    console.log(res3);
    const res4 = await walk(700);
    console.log(res4);
    const res5 = await walk(400);
    console.log(res5);
    console.log("finished");
    }

    go();

    // you walked for 500ms
    // you walked for 900ms
    // you walked for 600ms
    // you walked for 700ms
    // uncaught exception: the value is too small

    让我们来分解一下上述代码都做了什么:

    • 创建一个异步函数需要在 function 前面添加 async 关键词
    • 这个关键词会告诉 Javascript 返回一个 Promise
    • 如果指定 async 函数返回一个非 Promise 的值,那么这个值将会被包含在 Promise 中然后被返回
    • 顾名思义, await 会告诉 Javascript 等待 promise 返回结果
    错误处理

    通常在 promise 中,我们使用 .catch() 捕获最终的错误。现在有一点不同了:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    async function asyncFunc() {

    try {
    let response = await fetch('http:your-url');
    } catch (err) {
    console.log(err);
    }
    }

    asyncFunc();
    // TypeError: failed to fetch

    ES2018 新特性

    对象扩展运算符

    还记得 ES6 中我们可以使用扩展运算符来做什么吗:

    1
    2
    3
    4
    5
    6
    const veggie = ["tomato", "cucumber", "beans"];
    const meat = ["pork", "beef", "chicken"];

    const menu = [...veggie, "pasta", ...meat];
    console.log(menu);
    // Array [ "tomato", "cucumber", "beans", "pasta", "pork", "beef", "chicken" ]

    现在,扩展运算符同样适用于对象:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    let myObj = {
    a: 1,
    b: 3,
    c: 5,
    d: 8,
    }

    // we use the rest operator to grab everything else left in the object.
    let { a, b, ...z } = myObj;
    console.log(a); // 1
    console.log(b); // 3
    console.log(z); // {c: 5, d: 8}

    // using the spread syntax we cloned our Object
    let clone = { ...myObj };
    console.log(clone);
    // {a: 1, b: 3, c: 5, d: 8}
    myObj.e = 15;
    console.log(clone)
    // {a: 1, b: 3, c: 5, d: 8}
    console.log(myObj)
    // {a: 1, b: 3, c: 5, d: 8, e: 15}

    使用扩展运算符,我们可以轻松的复制对象(浅复制)。

    异步的迭代

    使用异步的迭代,我们可以异步的遍历数据。
    引自文档

    异步迭代器很像迭代器,只不过迭代器的 next 方法返回一对 { value, done }

    为此,我们将使用一个 for-await-of 循环,它将迭代转换成 Promise。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    const iterables = [1, 2, 3];

    async function test() {
    for await (const value of iterables) {
    console.log(value);
    }
    }

    test();
    // 1
    // 2
    // 3

    在执行过程中,[Symbol.asyncIterator]() 方法将会创造一个异步的迭代器,每次访问序列中的下一个值时,我们都会隐式地等待迭代器方法返回 Promise。

    Promise.prototype.finally()

    引自 MDN:

    finally() 方法返回一个 Promise。在 Promise 结束时,无论结果是 fulfilled 或者是 rejected,都会执行指定的回调函数。这为在 Promise 是否成功完成后都需要执行的代码提供了一种方式。避免了同样的语句需要在 then() 和 catch() 中各写一次的情况。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const myPromise = new Promise((resolve, reject) => {
    resolve();
    });

    myPromise
    .then(() => {
    console.log('still working');
    })
    .catch(() => {
    console.log('there was an error');
    })
    .finally(()=> {
    console.log('Done!');
    })

    .finally() 同样会返回一个 promise,所以我们可以继续链式调用 thencatch 方法,但是它们是基于之前的 promise 进行调用的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    const myPromise = new Promise((resolve, reject) => {
    resolve();
    });

    myPromise
    .then(() => {
    console.log('still working');
    return 'still working';
    })
    .finally(()=> {
    console.log('Done!');
    return 'Done!';
    })
    .then(res => {
    console.log(res);
    })
    // still working
    // Done!
    // still working

    从上边代码可以看到 finally 后边的 then 返回的值是由第一个 then 创建的,而不是 finally

    正则表达式的新特性

    在新版的 ECMA 中,共更新了 4 个关于正则的特性。

    • 正则表达式的 s (doAll) 标志
    • 正则表达式捕获组命名
    • 正则表达式反向断言 (Lookbehind Assertions)
    • unicode 字符转义 (Unicode property escapes)
    s (doAll) 标志

    引自MDN

    dotAll 属性表明是否在正则表达式中一起使用 “s“ 修饰符(引入 /s 修饰符,使得.可以匹配任意单个字符,包括换行符和回车符)

    1
    2
    /foo.bar/s.test('foo\nbar');
    // true
    捕获组命名

    想要引用正则匹配到的某一部分字符串可以为捕获组编号。每个捕获组的数字都是唯一的,可以对应的数字引用它们,但是这使正则表达式难以阅读和维护。例如 /(\d{4})-(\d{2})-(\d{2})/ 匹配一个日期,但如果不看上下文的代码,就无法确定哪一组对应于月份,哪一组是一天。当然,如果哪一天需要交换日期和月份的顺序,那么对应的组引用也需要更新。现在,可以使用 (?<name>...) 来为捕获组命名,以表示任何标识符名称。重写上述例子:/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u 每一个命名都是唯一且遵循 ECMA 命名规范的。命名的组可以通过匹配结果的 result 属性来访问。对组的数字引用也会被建立,就像未命名的组一样。看下边几个例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
    let result = re.exec('2015-01-02');
    // result.groups.year === '2015';
    // result.groups.month === '01';
    // result.groups.day === '02';

    // result[0] === '2015-01-02';
    // result[1] === '2015';
    // result[2] === '01';
    // result[3] === '02';

    let { groups: { one, two } } = /^(?<one>.*):(?<two>.*)$/u.exec('foo:bar');
    console.log(`one: ${one}, two: ${two}`);
    // one: foo, two: bar
    反向断言

    使用反向断言可以确保匹配之前或者之后没有其他匹配。反向断言的语法表示为 (?<=...)
    例如:匹配一个美元数值且不包含美元符号可以这样写 /(?<=$)\d+(\.\d*)?/,这个表达式会匹配 $10.53 并返回 10.53,而并不会匹配 €10.53。而 (?<!...) 匹配的规则正相反,它会匹配不存在表达式中的匹配项,例如 /(?<!$)\d+(?:\.\d*)/ 不会匹配 $10.53,但是会匹配 €10.53

    Unicode 字符转义

    Unicode 字符转义是一种新的转义序列,u 作为字符转义的标志, \p{...}\P{...} 用来添加转义符。有了这个特性,匹配 Unicode 字符可以这样写:

    1
    2
    3
    const regexGreekSymbol = /\p{Script=Greek}/u;
    regexGreekSymbol.test('π');
    // true

    解除模板字符限制

    当使用 Tagged 模板字符串时,转义字符的限制被移除了(阅读更多

    ES2019 新特性

    Array.prototype.flat() / Array.prototype.flatMap()

    Array.prototype.flat() 会递归地展平一个数组并作为新值返回,它接受一个表示递归深度的值,未传值则默认深度为1。可以用 Infinity 去展平所有嵌套的数组。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    const letters = ['a', 'b', ['c', 'd', ['e', 'f']]];
    // default depth of 1
    letters.flat();
    // ['a', 'b', 'c', 'd', ['e', 'f']]

    // depth of 2
    letters.flat(2);
    // ['a', 'b', 'c', 'd', 'e', 'f']

    // which is the same as executing flat with depth of 1 twice
    letters.flat().flat();
    // ['a', 'b', 'c', 'd', 'e', 'f']

    // Flattens recursively until the array contains no nested arrays
    letters.flat(Infinity)
    // ['a', 'b', 'c', 'd', 'e', 'f']

    Array.prototype.flatMap() 与深度值为1的 flat 几乎相同,但它并非仅仅展平数组。 flatMap 接收一个处理函数,使用 flatMap() 可以在展平的同时更改对应的值并返回一个新的数组。

    1
    2
    3
    let greeting = ["Greetings from", " ", "Vietnam"];
    greeting.flatMap(x => x.split(" "))
    // ["Greetings", "from", "", "", "Vietnam"]

    这有点类似于 map() 方法,只不过多了一次展平操作。

    Object.fromEntries()

    Object.fromEntries() 将一组键值对转换成对象。

    1
    2
    3
    4
    5
    6
    7
    const keyValueArray = [
    ['key1', 'value1'],
    ['key2', 'value2']
    ]

    const obj = Object.fromEntries(keyValueArray)
    // {key1: "value1", key2: "value2"}

    我们可以将任何可迭代的值作为 Object.entries() 方法的参数,不论它是一个 Array 还是 Map,或是其他实现了迭代协议的值。

    注:可迭代协议( Iteration Protocols )是 ES2015 提出的,通常通过常量 Symbol.iterator 访问该对象的可迭代属性。

    1
    2
    3
    4
    5
    6
    7
    8
    var someString = "hi";
    typeof someString[Symbol.iterator]; // "function"
    var iterator = someString[Symbol.iterator]();

    iterator + ""; // "[object String Iterator]"
    iterator.next() // { value: "h", done: false }
    iterator.next(); // { value: "i", done: false }
    iterator.next(); // { value: undefined, done: true }

    阅读更多关于迭代协议的内容

    String.prototype.trimStart() / .trimEnd()

    String.prototype.trimStart() 移除字符串前面的空白符,String.prototype.trimEnd() 移除字符串后面的空白符。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    let str = "    this string has a lot of whitespace   ";

    str.length;
    // 42

    str = str.trimStart();
    // "this string has a lot of whitespace "
    str.length;
    // 38

    str = str.trimEnd();
    // "this string has a lot of whitespace"
    str.length;
    // 35

    也可以使用 .trimStart()trimEnd() 的别名: .trimLeft().trimRight()

    可选的 catch 捕获参数

    在 ES2019 之前,你必须为 catch 捕获传递一个表示异常的变量,现在这个变量不是必要的了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // Before
    try {
    ...
    } catch(error) {
    ...
    }

    // ES2019
    try {
    ...
    } catch {
    ...
    }

    这在你想忽略错误参数的时候很有用。

    Function.ptototype.toString()

    .toString() 方法返回一个代表函数源码的字符串。

    1
    2
    3
    4
    5
    6
    7
    8
    function sum(a, b) {
    return a + b;
    }

    console.log(sum.toString());
    // function sum(a, b) {
    // return a + b;
    // }

    注释也会被包含其中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function sum(a, b) {
    // perform a sum
    return a + b;
    }

    console.log(sum.toString());
    // function sum(a, b) {
    // // perform a sum
    // return a + b;
    // }

    Symbol.prototype.description

    .description 返回 Symbol 对象可选描述的字符串。

    1
    2
    3
    4
    5
    6
    const me = Symbol("Alberto");
    me.description;
    // "Alberto"

    me.toString()
    // "Symbol(Alberto)"

    ES2020 特性

    BigInt 类型

    BigInt 是 JavaScript 第七个原始类型,它允许开发者操作非常大的整型。
    数字类型可以处理 2 ** 53 - 19007199254740991 以内的数。可以通过常量 MAX_SAFE_INTEGER 来访问这个值。

    1
    Number.MAX_SAFE_INTEGER; // 9007199254740991

    顾名思义,若操作的 number 值超过最大值时,运行结果就会变的奇怪。使用 BigInt 类型则没有明确的界限,因为它的界限取决于运行设备的内存。
    定义 BigInt 类型,你即可以通过给 BigInt() 构造函数传递一个字符串值来创建,也可以像平常一样使用字面量语法来创建,但是要在尾部加上一个字符 n

    1
    2
    3
    4
    const myBigInt = BigInt("999999999999999999999999999999");
    const mySecondBigInt = 999999999999999999999999999999n;

    typeof myBigInt; // "bigint"

    注意,BigInt 类型与常规类型的数字并不是完全兼容的,这意味这你确定最好仅在操作比较大的数据时使用它。

    1
    2
    3
    4
    5
    6
    7
    const bigInt = 1n; // small number, but still of BigInt type
    const num = 1;

    num === bigInt; // false -> they aren't strictly equal
    num == bigInt; // true
    num >= bigInt; // true -> they can be compared
    num + bigInt; // error -> they can't operate with one another

    总之,使用 JS 做比较复杂的数学运算时 BigInt 是个不错的选择。它在替换专门用于处理大量数字的库方面表现良好。现在至少在整型方向有所进展,而目前我们对 BigDecimal 的提案了解的还很少。

    动态导入(Dynamic imports)

    动态导入,允许在浏览器端动态地加载代码模块。使用 import() 语法来导入你的代码块。

    1
    2
    3
    4
    5
    6
    7
    8
    import("module.js").then((module) => {
    // ...
    });

    // or
    async () => {
    const module = await import("module.js");
    };

    import() 返回一个 promise,resolve 中会返回代码模块加载后的内容。可以使用 ES6 的 .then() 方法或者 async/await 来处理加载结果。

    空值合并操作符(??)

    空值合并操作符(??)是一个新的 JS 运算符,当所访问的值是 null 或者 undefined 时,它会提供一个默认值。

    1
    2
    3
    4
    5
    const basicValue = "test";
    const nullishValue = null;

    const firstExample = basicValue ?? "example"; // "test"
    const secondExample = nullishValue ?? "example"; // "example"

    但是这跟 逻辑或(||)有什么区别呢?当第一个数是虚值 (在 Boolean 上下文中认定为 false 的值),如 false, 0, 或者"",以及空值 nullundefined,那么 逻辑或 将会使用第二个操作数。而空值合并操作符仅仅是在第一个值为空值而不是虚值的时候才会使用第二个操作数。如果你的代码可以接受除了 nullundefined 以外的任何值,那么空值合并操作符就是最佳选择。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const falseValue = false;
    const zeroValue = 0;
    const emptyValue = "";
    const nullishValue = null;

    const firstExampleOR = falseValue || "example"; // "example"
    const secondExampleOR = zeroValue || "example"; // "example"
    const thirdExampleOR = emptyValue || "example"; // "example"
    const forthExampleOR = nullish || "example"; // "example"

    const firstExample = falseValue ?? "example"; // false
    const secondExample = zeroValue ?? "example"; // 0
    const thirdExample = emptyValue ?? "example"; // ""
    const forthExample = nullish ?? "example"; // "example"

    可选链(?.)

    与空值合并操作符类似,只不过可选链是处理 Object 中 nullundefined 的。鉴于直接从空值中国获取属性值会报错,现在可选链会直接将空值返回。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const obj = {
    prop: {
    subProp: {
    value: 1,
    },
    },
    };

    obj.prop.subProp.value; // 1
    obj.prop.secondSubProp.value; // error

    obj?.prop?.subProp?.value; // 1
    obj?.prop?.secondSubProp?.value; // undefined

    当然,这只是一个语法糖,但也是一个很受欢迎的补充。记住不要在代码里到处使用这些操作符,他们虽然用起来方便,但从性能角度来说,它比普通的 . 开销要大。而且,若是代码是经过 Babel 和 TypeScript 转义的,则更要谨慎使用。

    GlobalThis

    由于 JavaScript 的代码可以运行在多个不同的环境,例如 浏览器、Node.js、Web Worker 等,要实现这种交叉兼容性绝非易事,globalThis 的出现方便了这些操作。
    globalThis 是一个新的全局属性,通常它引用的是当前环境下的全局对象。就像是 self 对于 Web Workers,window 对于浏览器,global 对于 Node.js,以及其他实现了ES2020标准的运行环境。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // Hacky globalThis polyfill you had to use pre-ES2020
    const getGlobal = () => {
    if (typeof self !== "undefined") {
    return self;
    }
    if (typeof window !== "undefined") {
    return window;
    }
    if (typeof global !== "undefined") {
    return global;
    }
    throw new Error("Couldn't detect global");
    };

    getGlobal() === globalThis; // true (for browser, Web Worker and Node.js)
    globalThis === window; // true (if you're in browser)

    Promise.allSettled()

    这个新增的方法看起来有点像 Promise.all()
    Promise.all() 的参数中的 promise 若有一个失败,则此实例回调失败。而 Promise.allSettled()不论成功或者失败,都会返回处理结束后的对象数组。

    String.matchAll()

    如果你之前使用正则,那么相比于在 while 循环中使用 RegExp.exec() 并开启标志 g 来匹配,String.matchAll() 会是更好的选择。它会返回一个包含了所有匹配结果的数组,包括捕获组的匹配结果。

    1
    2
    3
    4
    5
    6
    const regexp = /t(e)(st(\d?))/g;
    const str = "test1test2";
    const resultsArr = [...str.matchAll(regexp)]; // convert iterator to an array

    resultsArr[0]; // ["test1", "e", "st1", "1"]
    resultsArr[1]; // ["test2", "e", "st2", "2"]

    原文链接

    ]]>
    + + + + + 前端 + + JavaScript + + + + + + + 翻译 + + JavaScript + + + +
    + + + + + PWA-Service Worker 小结(二)实践 + + /2020/01/02/PWA-Service-Worker-%E5%B0%8F%E7%BB%93%EF%BC%88%E4%BA%8C%EF%BC%89%E5%AE%9E%E8%B7%B5/ + + Service Worker 的初衷是极致优化用户体验,带来丝滑般流畅的离线应用。但同时也可以用作站点缓存使用。它本身类似于一个介于浏览器和服务端之间的网络代理,可以拦截请求并操作响应内容。功能强大,但由于兼容性问题,更适合用作渐进增强来使用。

    一、前言

    • Service Worker 是独立于当前页面的一段运行在浏览器后台进程里的脚本,它有自己独立的注册文件;它是 Web Worker 的一种,不能够直接操作 DOM;
    • 出于安全问题考虑,它只能在 HTTPS 域名下或者 localhost 本地运行;
    • 可以通过 postMessage 接口传递数据给其他 JS 文件;
    • Service Worker 中运行的代码不会被阻塞,也不会阻塞其他页面的 JS 文件中的代码;
    • 每个 Service Worker(注册文件)都有自己的作用域,它只会处理自己作用域下的请求,而 Service Worker 的存放位置就是它的最大作用域;
    • 缓存的资源存储在 Cache Storage 中,缓存不会过期,但是浏览器对每个网站的 Cache Storage 的大小有硬性限制,所以需要清理不必要的缓存;

    二、Service Worker 的生命周期

    1. 注册 Service worker,在网页上生效;
    2. 安装成功,激活 或者 安装失败(下次加载会尝试重新安装);
    3. 激活后,在 sw 的作用域下作用所有的页面,首次注册 sw 不会生效,下次加载页面才会生效;已经注册的 sw 不会重复注册;不会因为页面的关闭而被销毁;
    4. sw 作用页面后,处理 fetch(网络请求)和 postMessage(页面消息)事件 或者 被终止(节省内存)。

    三、Service Worker 安装注册

    注册文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // service worker 注册文件
    if ('serviceWorker' in window.navigator) {
    navigator.serviceWorker.register('./sw.js', { scope: './' })
    .then(function (reg) {
    console.log('success', reg);
    })
    .catch(function (err) {
    console.log('fail', err);
    });

    register 方法接受两个参数,第一个是 service worker 文件的路径,第二个参数是 Serivce Worker 的配置项,可选填,其中比较重要的是 scope 属性。

    拓展 Service Worker 作用域

    scope的默认值为 ./(注意,这里所有的相对路径不是相对于页面,而是相对于sw.js脚本的),因此,navigator.serviceWorker.register('/static/home/js/sw.js')代码中的 scope 实际上是/static/home/js,Service Worker也就注册在了/static/home/js路径下,显然无法在/home下生效。

    可以通过添加 Service-Worker-Allowed 响应头的方式来扩展 service worker 的作用域:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // express 扩展 service worker scope
    app.use(serveStatic(`${sourceRoot}/home`, {
    maxAge: 0,
    setHeaders: function (res, path, stat) {
    if (/\/sw\/.+\.js/.test(path)) {
    res.set({
    'Content-Type': 'application/javascript',
    'Service-Worker-Allowed': `/${sourceRoot}//home`,
    'Cache-control': 'no-store'
    });
    }
    }
    }));

    打包工具生成静态资源注册文件

    自己本地调试,可以一个个写进 Service Worker 的注册文件里调试;实际开发中可以借助 gulp / webpack 等打包工具等生成站点静态文件的 sw 注册文件;
    以 gulp 为例,使用 sw-precache 插件生成注册文件:

    1
    2
    3
    4
    5
    6
    7
    8
    gulp.task('generate-service-worker', function(callback) {

    swPrecache.write('./service-worker.js', {
    staticFileGlobs: ['./build/public' + '/**/*.{js,css,png,jpg,webp,gif,svg,eot,ttf,woff}'],
    stripPrefix: './build'
    }, callback);

    });

    四、Service Worker.js 注意事项

    1. 不要给 service-worker.js 设置不同的名字
      实际开发过程中,为了避免静态资源缓存,通常的做法是在打包压缩静态资源的时候,在文件名后边加上 MD5 后缀,让浏览器认为这是一个新文件从而重新发起请求,但是这种做法在 service-worker.js 上是不可取的;
      第一种情况:如果缓存了 html 文件,service-worker.js 的文件因为是在 html 中引入的,所以更改 service-worker.js 的名字并不会更新。
      第二种情况:只缓存了css,js 文件,未缓存 html 文件;页面引入了新的 service-worker.js ,但是旧版本的 service-worker.js 还在使用中,会导致页面状态有问题。
    2. 不要给 service-worker.js 设置缓存
      理由和第一点类似,也是为了防止在浏览器需要请求新版本的 sw 时,因为缓存的干扰而无法实现。毕竟我们不能要求用户去清除缓存。因此给 sw 及相关的 JS (例如 sw-register.js,如果独立出来的话)设置 Cache-control: no-store 是比较安全的。

    五、遇到的问题

    1. 接收不到浏览器的fetch事件:
      原因:静态资源缓存:页面路径不能大于 Service worker 的 scope (详情)
    2. public/* 无法匹配public路径下的所有文件, addCaches 时只能写fileName?
      原因:service worker 没有通配符 * 这个概念,/sw-test/ 这个 path 只是让 sw 寻找缓存时的一个入口,用以区分各个路径的缓存(详情);
      解决方案:service-worker.js 使用官方的 sw-precache 插件生成(详情);
    3. 如果 service worker 缓存的了全部的js和img 会不会导致 cacheStorage 很占用用户的系统空间?
      不会,各个浏览器分配给各站点的 cacheStorrage 的值不一样,同时也受用户设备空间影响。

    落地情况

    个人觉得 Service Worker 更适合在单页应用、文档类应用的等场景使用,才能把离线缓存的优势发挥出来。比如 Vue 的官网。



    2019.4.23
    未落地。主要原因有两点:

    1. 工作中想要使用 Service worker 提供离线缓存服务的是一个负责 APP 内嵌页面的 H5 站点,HTML都是动态渲染的,活动数据是实时的,不能离线访问;
    2. 这个站点的页面入口都是几乎都是单独的活动页,没有一个统一 sw 注册的入口;

    *2020.3.16*重新看这篇文章的时候,如果在几个主要的活动入口页引入 sw 的注册文件,那么这几个长期的活动就可以应用 sw 缓存了,但这并没有覆盖全站,所以依然不是好的解决方案。

    应用场景

    这部分总结摘录自这篇文章:Service Worker 从入门到出门

    • 网站功能趋于稳定:频繁迭代的网站似乎不方便加 Service Worker。
    • 网站需要拥有大量用户:管理后台、OA系统等场景似乎不是很有必要加 Service Worker。
    • 网站真的在追求用户体验:Bug 多多、脸不好看的网站似乎不是很有必要加 Service Worker。
    • 网站用户体验关乎用户留存:12306 似乎完全不需要加 Service Worker。

    简单总结:Service Worker 的初衷是极致优化用户体验,是用来锦上添花的,技术只是技术,但实际应用前,应考虑成本和收益。

    参考链接

    ]]>
    + + + + + 前端 + + PWA + + + + + + + Service Worker + + + +
    + + + + + 2019年终总结 + + /2019/12/31/2019%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/ + + 2019年终总结(✖️)
    2020待做清单(✔️)

    • 一月:工作
    • 二月:回家过年
    • 三月:PWA 离线缓存初步探索
    • 四月:多页应用 Service Worker 合适的落地方案持续寻找中(暂缓);前端性能指标 Performance 采集上报
    • 五月:前端性能指标尝试用 Prometheus 监控系统记录并用 Grafana 做可视化,尝试结果并不好;其他工作中过了一遍 RabbitMQ 事件队列收发流程
    • 六月:Elasticsearch 日志监控系统初探,目的是根据日志中的某些关键词,发出微信、邮件告警通知
    • 七月:elk 告警通知进行落地测试调优,微信和邮箱刚开始差点被通知给炸没了…
    • 八月:告警通知发现了很多不规范的日志,以及某些机器大清早的刷接口…持续使用中…但怎样区分业务的错误日志和代码的错误日志一直是一个问题…
    • 九月:学习计算机网络相关知识
    • 十月:Flink 实时计算性能指标探索
    • 十一月:网络工程师考试
    • 十二月:灌水

    今年是工作上探索新技术比较多的一年,前端的、不止前端的…虽然最终落地实践并产生结果的并不多,但是多种类型和方向的尝试让我开拓了不少眼界。纵观全年技术探索,leader 在带我们建立 FE 团队的性能监控和异常监控系统方向上做努力。19年没搞完的总结,就是20年的todolist…
    希望 2020 年的我对技术研究能上升个层次,不仅仅是学习新技术、了解新技术带来什么好处,还要能想到引入新技术带来的一系列后续的优化和落地。

    其他方面,就是缺少的计科知识,出来混,总要还的。报了个网工的考试督促自己系统学习下计科网络相关的知识,然后用实力证明忘的比学的快。

    二月份 回家过年无聊的时候下了个游戏叫《守望先锋》,从人机模式简单->困难,到快速游戏,再到竞技比赛:体验团队合作游戏的乐趣 + 1,一言不合口吐芬芳 + 10086 。概括起来就是,如果这局赢了,那是队友真强;如果这局输了,那是我正常发挥。不知不觉,这游戏已经玩了一年了…
    六月底 终于不堪某水果的电池续航和微信卡顿问题转战 HUAWEI 阵营,不得不说,真香…,但是安卓9下的抓包真是让我脑壳疼了好几天…
    八月份 gxTodo 总是莫名其妙的卡死崩溃,改用【滴答清单】了。印象笔记也是真的好用…
    十月份 找房换房。没想到第一次坐在电动车后面吹着微风晒着暖阳周游北京,居然是自如管家驮着我。带我看房的自如管家是位体型娇小的南方妹子,我站在她面前就像是一个五大三粗的钢铁硬汉…怪不好意思的…
    十一月份 软考北京考点在房山…(╯°□°)╯︵┻━┻)

    19年还算充实,虽然相比于18年更宅了一点…最大的希望就是在 2020 年懒癌和拖延症能治好,找到比较感兴趣的东西。还有,别迟到。

    ]]>
    + + + + + + 年度总结 + + + +
    + + + + + PWA-Service Worker 小结(一)各类缓存对比 + + /2019/12/26/PWA-Service-Worker-%E5%B0%8F%E7%BB%93%EF%BC%88%E4%B8%80%EF%BC%89%E5%90%84%E7%B1%BB%E7%BC%93%E5%AD%98%E5%AF%B9%E6%AF%94/ + + 年底了,总结一下上半年探索的 PWA 的离线缓存技术。顺带总结了一下前端全流程每一步中都可能遇到的缓存,大部分都是概念、名词的理解和说明。涉及到的缓存有:HTTP 缓存、Manifest 缓存、CDN 缓存、Nginx 服务器缓存、Service Worker 缓存。

    缓存的好处:
    存储频繁访问的数据,降低服务器压力;
    减少网络延迟,加快页面打开速度;

    一、HTTP 缓存

    浏览器缓存机制

    1. 在未设置相应头缓存字段的时候,只有用户点击“回退”按钮的时候,页面才会从缓存中读取
    2. 过期机制 :与服务器协商获取。对于浏览器来说,如何缓存一个资源是服务器端制定的策略,服务器对每个资源的 HTTP 响应头设置属性和值,自己只负责执行。常用的为以下几种:
      • Expires: 设置过期时间(单位日期),某日期之前都不再询问;浏览器再次命中这个资源,直至XXX时间前都不会发起 HTTP 请求,而是直接从缓存(在硬盘中)读取。
        • 如:200 (from cache) 这种缓存速度最快。
      • Last-Modified: 设置资源上次修改时间(单位日期),每次请求命中资源,都去询问资源是否过期;通过这种缓存方式,无论资源是否发生变更,都会发生至少一来一去的 HTTP 头传输和接收,速度比不上 Expires;
        • 如:304,若文件发生变更,则返回200。
      • Cache-Control:
        • max-age= 设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒);标准中规定 max-age 的值最大不能超过一年,且以秒为单位,所以值为 31536000;
        • no-cache  字面意义“不缓存”。实际机制是对资源仍使用缓存,但每次使用前必须(MUST)向服务器对缓存资源进行验证;
        • no-store 不使用任何缓存;
    3. 验证机制 :服务器返回资源的时候有时会在头信息中携带 __Etag(Entity Tag)__,它可作为浏览器再次请求过程的校验标识。如发现校验标识不匹配,说明资源已经修改或过期,浏览器需要重新获取资源内容。
      ETag 可以保证每一个资源是唯一的,资源变化都会导致 ETag 变化。服务器根据浏览器上送的 ETag / If-None-Match 值来判断是否命中缓存。在精准度上,Etag 优于 Last-Modified。因为 Etag 是按照内容为资源增加标识,而 Last-Modified 是根据文件最后修改时间判断。

    常用的缓存策略:

    • 对于动态生成的 HTML 页面使用 HTTP 头: Cache-Control : no-cache;
    • 对于静态 HTML 页面使用 HTTP 头: Last-Modified;
    • 其他所有文件类型都设置 Cache-Control 头,并且在文件内容有所修改都时候修改文件名。

    如何更新文件:

    按照 HTTP 规范,如果修改了请求资源的 Query String,就应该被视为一个新的文件。但是遇到运营商劫持时,会忽略 Query String,遇到这种情况只能修改文件名。

    疑问:

    给 HTML 都设置了 Cache-Control: no-cache; 对 CSS 和 JS都用了 gulp 进行了打包编译处理,每次有变化都会变更文件名;那么此种情况下,是否还需要设置 Last-Modified?
    直接设置 Cache-Control max-age 或者 Expires 难道不会节省更多 HTTP 请求吗?避免服务器为做出应答返回大量 304。

    二、Manifest 缓存

    manifest 在前端含义很多,常见的四个使用场景如下:

    1. HTML 标签的 manifest 属性,用来离线缓存 HTML 文档以及资源的;
      • 如 <html manifest=”xxx”></html>,由于坑太多,现在已经被废弃;
    2. PWA 的 manifest 功能:将 web 应用程序安装到设备的主屏幕;
      • 如 <link rel=”manifest” href=”/manifest.json”>;
      • 在 manifest.json 中配置应用的图标、名称等信息;通过一系列配置,就可以为 Web App 添加一个图标到手机上,点击图标即可打开站点;
    3. webpack 打包时会生成个 manifest.json 的文件,用来分析打包后的文件;
    4. gulp 处理静态资源时,使用 gulp 的 gulp-rev 插件生成 manifest.json,用来记录源文件与处理后的目标文件的对照

    三、CDN缓存

    即使为各类资源文件设置了 HTTP 头,当用户手动清除缓存 ,或者由于磁盘容量限制,先缓存的文件被挤出磁盘,此时依旧需要请求资源,为了快速响应用户请求,使用 CDN 加速。CDN的分流作用不仅减少了用户的访问延时,也减少了源站的负载。
    当用户手动清理本地缓存后,将去请求距离最近的 CDN 边缘节点。
    CDN 边缘节点缓存策略因服务商不同而不同,但一般会遵循 HTTP 标准协议。通过 HTTP 响应头中的 Cache-Control: max-age 的字段来设置CDN边缘节点数据缓存时间,若数据失效,则向源站发出回源请求,拉取最新的数据;当源站内容有更新的时候,源站主动把内容推到CDN节点。

    各家 CDN 缓存参考:https://segmentfault.com/a/1190000006673084

    CDN 回源原理:https://www.jianshu.com/p/e7751ecb6f21

    四、nginx 服务器缓存

    这里又牵扯到了两个地方…就像家用路由器和企业级路由器虽然都叫路由器但是功能完全不一样…
    nginx 大名 负载均衡服务器,它是服务器不是服务;CDN 加速是运营商提供的一种服务….,这俩玩意一点关系都没有。如果网站既使用了 CDN 加速,同时又使用了 Nginx 代理,那么 CDN 的位置相比于 Nginx 服务器更靠近用户。

    网站管理者可以通过为网站配置 Nginx 服务器来达到负载均衡的目的, Nginx 可以重写静态资源的 HTTP 头的缓存信息等,也可以用 Nginx 搭建自己的 CDN 节点(原理跟运营商 CDN 差不多,都是转发到合适的机器;只不过 CDN 是将静态资源存在运营商的机器上,Nginx 做 CDN 的话就缓存在自己的机器上)。具体选择时可通过银子的多少来判断是选 CDN 加速,还是 Nginx 搭建 CDN。

    综上,当 Nginx 服务器承载“CDN 加速”的功能时,可通过配置 proxy_cache 将文件缓存到本地的一个目录,缓存命中原理当与 CDN 相同;当 Nginx 服务器不充当 CDN,只是重写静态文件的响应头时,此时跟服务器写命令没差,缓存在浏览器中,原理见浏览器缓存命中机制,不再进行赘述。

    五、Service Worker 缓存

    Service Worker 是一个位于浏览器和网络之间的客户端代理,可以拦截、处理流经的 HTTP 请求,使开发者可以从缓存中向 Web 应用提供资源。可以把它看成是用户设备中的缓存提供服务器,功能十分强大。它缓存的文件同样存储在客户端(用户设备)中:

    Service Worker 是 PWA 实现离线应用的核心技术。它可以:

    • 让网页可以离线访问;
    • 让网页在弱网情况,使用缓存快速打开应用,提升体验;
    • 同时在网络正常的情况下走网络缓存减少请求的带宽;
    • 对不支持的手机没有影响;

    缓存有各自的优先级,当依次查找缓存且都没有命中的时候,才会去请求网络:

    1. Service Worker
    2. Memory Cache
    3. Disk Cache
    4. 网络请求

    参考资料:

    ]]>
    + + + + + 前端 + + PWA + + + + + + + Service Worker + + + +
    + + + + + Flink 初探 + + /2019/11/14/Flink-%E5%88%9D%E6%8E%A2/ + + Apache Flink 是一个分布式处理引擎,在有界或无界数据流上进行有状态的计算。工作时偶然接触到一点点,有些概念虽然有点抽象,但是思路却值得借鉴。本文记录用 Flink 实时求均值、水印生成、以及迟到的数据元触发计算更新等等,是一篇纯探索性文章。用笔记形式记录,以便忘记。

    https://flink.apachecn.org/docs/1.7-SNAPSHOT/#/

    Flink 是一个针对流数据和批数据的分布式处理引擎,代码主要是由 Java 实现,部分代码是 Scala。它可以处理有界的批量数据集、也可以处理无界的实时数据集。对 Flink 而言,其主要处理的场景就是流数据。

    二、流处理和批处理的区别

    批处理 特点:离线、单次处理的数据量大、处理速度慢、非实时计算。常见的批处理就是数据库深夜定时跑任务,因为批量计算会占用大量资源。
    流处理 特点:在线,单次处理数据量小、处理速度快、实时计算。常见的应用场景就是监控、统计、实时推荐等。

    三、学习目标

    用 Flink 消费已有数据源,实时计算数据均值,并允许数据元延迟到来时,重新触发计算。

    四、涉及到的名词概念

    1. 窗口 (Windows):对某段数据流进行统计,即统计区间;Windows 可以是时间驱动的(例如:每30秒)或数据驱动(例如:每100个数据元)。
    2. 时间 (Time):程序中引用的时间;Flink 支持三种时间:事件时间、摄取时间和处理时间。
    3. 算子 (Operator):Flink 内部提供的时间/数据流/数据元的处理函数。
    4. 时间戳 (TimeStamp)/水印 (WaterMark):使用数据源的时间或者系统时间为到来的数据元加上时间戳;数据流加上水印标记,为了等下个数据元到来时知道该数据元是否应该被包含在当前次计算中。
      注:Watermark 是随数据产生的,窗口时间现在处于什么位置看 Watermark,只有新产生的一条数据超出窗口长度,这个窗口才会触发计算。(当使用事件时间窗口时,可能会发生数据元迟到的情况,则必须为数据流设置时间戳和水印)

    允许迟到 allowedLateness

    只要应该属于此窗口的第一个数据元到达,就会创建一个窗口,当时间(事件或处理时间)超过其结束时间戳加上用户指定 allowed lateness 时,窗口将被完全删除。
    allowedLateness 用来设置窗口销毁时间 ,而 waterMark 是用来设置窗口激活时间。当时延迟时间超过 allowedLateness 设置的时间,这个计算窗口就会被销毁,开始下一个窗口,即使被销毁的窗口还没有触发计算。

    窗口函数

    Flink 的窗口函数会暴露出数据流不同状态时的处理函数,具体的高级操作或者运算例如聚合、求均值等函数需要我们自己去实现。
    例如聚合窗口 stream.aggregate 的参数 AggregateFunction <IN, ACC, OUT>,具有三种的类型:输入类型(IN)、累加器类型(ACC)和输出类型(OUT)。
    使用 AggregateFunction 求均值(示例代码来自官网):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    private static class AverageAggregate
    implements AggregateFunction<Tuple2<String, Long>, Tuple2<Long, Long>, Double> {
    @Override
    public Tuple2<Long, Long> createAccumulator() {
    return new Tuple2<>(0L, 0L);
    }

    @Override
    public Tuple2<Long, Long> add(Tuple2<String, Long> value, Tuple2<Long, Long> accumulator) {
    return new Tuple2<>(accumulator.f0 + value.f1, accumulator.f1 + 1L);
    }

    @Override
    public Double getResult(Tuple2<Long, Long> accumulator) {
    return ((double) accumulator.f0) / accumulator.f1;
    }

    @Override
    public Tuple2<Long, Long> merge(Tuple2<Long, Long> a, Tuple2<Long, Long> b) {
    return new Tuple2<>(a.f0 + b.f0, a.f1 + b.f1);
    }

    DataStream<Tuple2<String, Long>> input = ...;

    input
    .keyBy(<key selector>)
    .window(<window assigner>)
    .aggregate(new AverageAggregate());
    }

    五、遇到的问题

    • 数据流过滤后,只剩下被过滤的数据:
      • SingleOutputStreamOperator 旁路分支:这个分支用来获取被过滤掉的数据,并不是过滤后的数据。
    • 给数据流设置时间戳之后,迟到的数据没有被抛弃:
      • stream.assignTimestampsAndWatermarks 定期生成水印:最简单的特殊情况是给定源任务看到的时间戳按升序发生的情况。在这种情况下,当前时间戳始终可以充当水印,因为没有更早的时间戳会到达。且生成的时间戳会覆盖事件原有的,若存在迟到的数据元,用这个方法,则数据不会被抛弃。
      • BoundedOutOfOrdernessTimestampExtractor :Flink 提供此参数为固定数量的迟到者分配时间戳和水印。若有数据元可能迟到的场景,请应用此方法。
    • 设置的水印时间戳,超时告警,但是数据没有被丢弃?
    • 最新记录没有被统计,只有下一条数据写入时,之前的数据才会被触发统计?

    六、数据下沉 Data Sink

    Flink 可以自己指定数据源连接器,以及数据下沉(接收)目标。从 Flink 官网上来看连接器支持 Kalfa、Elasticsearch、HDFS、RabbitMQ 等等,公司已有 RabbitMQ 数据源,使用 RabbitMQ sink 接收数时,注意事件消费者不要和事件生产者的队列名不要相同,否则会报错。

    参考链接

    ]]>
    + + + + + 大数据 + + + + + + + 大数据 + + 流处理 + + + +
    + + + + + 《计算机网络》- http 部分读书笔记 + + /2019/10/22/%E3%80%8A%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E3%80%8B-http-%E9%83%A8%E5%88%86%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/ + + 《计算机网络(第7版)-谢希仁》http 部分的读书小结和扩展,因为工作中最常打交道的就是这部分了。整本书都很不错,语言通俗易懂;各协议的关系、发展过程以及区别都概括的很好。
    本文主要概括 HTTP、HTTP1.0、HTTP2.0、HTTPS 的之间的差异。

    万维网 WWW

    万维网(World Wide Web)是一个分布式的超媒体系统,是超文本系统的扩充。万维网使用 统一资源定位符URL(Uniform Resource Locator) 来标志万维网上的各种文档。

    URL 的格式

    URL 的一般形式由以下四个部分组成:
         <协议>://<主机>:<端口>/<路径>
    URL 的<协议>就是指出使用什么协议来获取万维网文档。现在最常用的协议就是 http,其次是 ftp。有些浏览器为方便用户,在输入 URL 时,可以把最前面的“http://”甚至把主机名最前面的“www”省略,然后浏览器替用户把省略的字补上。
    URL里面的字母不分大小写,但是为了便于阅读,有时故意使用一些大写字母。

    超文本传送协议 HTTP

    HTTP(HyperText Transfer Protocol,超文本传输协议),使用了面向连接的 TCP 作为传输层协议,监听 80 端口,信息是明文传输,其本身是无状态的。

    • HTTP1.0(1996)每次请求都会单独建立一个TCP连接,用完关闭;(缺点:每次请求都耗费时间在连接上,非持续连接 使服务器开销很重)。
    • HTTP1.1(1999)在服务器发送完响应后仍在一段时间内保持这条连接,浏览器和该服务器可以继续在该连接上传送后续的请求报文和响应报文,使用 持续连接
    • HTTP2.0(2015)使用了新的二进制格式、多路复用、以及 header 压缩,性能相对于 HTTP1.x 提升明显。改善了在 HTTP1.1 中,浏览器客户端在同一时间,针对同一域名下的请求有一定数量限制(连接数量),超过限制会被阻塞;基于 HTTPS,天生具有安全性,可以避免单纯使用 HTTPS 带来的性能下降。

    影响 HTTP 网络请求的因素

    影响因素主要有两个:带宽和延迟。

    • 带宽 :在浏览器刚流行的时候,大部分用户是通过拨号来上网,由于受当时的带宽条件的限制,无法使得用户的同时多个请求被处理。同时,当时的服务器的配置也比现在差很多,所以限制每个浏览器的连接数的大小也是有必要的。浏览器默认对同一域下的资源,只保持一定的连接数,阻塞过多的连接,以提高访问速度和解决阻塞问题。不同浏览器的默认值不一样,对于不同的 HTTP 协议其值也不一样。
    浏览器HTTP 1.1HTTP 1.0
    IE 6、724
    IE 866
    FireFox 228
    FireFox 366
    Safari 3、444

    如果说我们还停留在拨号上网的阶段,带宽可能会成为一个比较严重影响请求的问题,但是现在网络基础建设已经使得带宽得到极大的提升,我们不再会担心由带宽而影响网速,那么就只剩下延迟了。

    • 延迟
      • 浏览器阻塞(HOL blocking):浏览器会因为一些原因阻塞请求。浏览器对于同一个域名,同时只能有 4 个连接(这个根据浏览器内核不同可能会有所差异),超过浏览器最大连接数限制(见上表),后续请求就会被阻塞。
      • DNS 查询(DNS Lookup):浏览器需要知道目标服务器的 IP 才能建立连接。将域名解析为 IP 的这个系统就是 DNS。这个通常可以利用DNS缓存结果来达到减少这个时间的目的。
      • 建立连接(Initial connection):HTTP 是基于 TCP 协议的,浏览器最快也要在第三次握手时才能捎带 HTTP 请求报文,达到真正的建立连接,但是这些连接无法复用会导致每次请求都经历三次握手和慢启动。三次握手在高延迟的场景下影响较明显,慢启动则对文件类大请求影响较大。

    应用层安全协议 HTTPS

    HTTPS 是使用 SSL(Secure Socket Layer,安全套接字层)协议的 HTTP 协议。SSL 作用在 HTTP 和运输层之间,在 TCP 之上建立起一个安全通道,为通过 TCP 传输的应用层数据提供安全保障。
    HTTPS 监听 TCP 的 443 端口,信息是密文传输。

    ]]>
    + + + + + 计算机相关知识 + + + + + + + 计算机网络 + + + +
    + + + + + z-index 小结 + + /2019/08/05/z-index%E5%B0%8F%E7%BB%93/ + + 当 z-index 多个规则多个层级共同作用时,展现的效果往往跟自己的想法有很大差异,论 CSS 基本功的重要性。本文总结了 CSS 层叠的特性、基本准则和创建条件,内容大多为张鑫旭大神的《CSS世界》读书小结。

    层叠的基本概念

    • 层叠上下文(stacking context):当前元素所处的层叠规则,即元素所处的 z 轴。一个页面中,层叠上下文不止一个。
    • 层叠水平(stacking level):同一个层叠上下文中元 素在 z 轴上的显示等级。
    • 层叠顺序(stacking order):
      • background/border 指在同一层叠上下文元素的边框和背景色。
      • inline水平盒子指的是包括inline/inline-block/inline-table元素的“层叠顺序”,它们都是同等级别的。
      • 内联元素的层叠顺序要比浮动元素和块状元素都高,是因为float元素在起始时是作为布局元素存在的。由于“内容”的重要性远大于“装饰”和“布局”,所以内容元素层叠顺序比较高,详情见下图:

    z-index

    z-index 属性只有和定位元素(position 不为 static 的元素)在一起的时候才有作用,可以是正数也可以是负数。在同一层叠上下文中,数值越大层级越高。 在CSS3中,z-index 已经并非只对定位元素有效,flex 盒子的子元素 也可以设置 z-index 属性。

    层叠准则

    1. 谁大谁上:在同一个层叠上下文领域,具有明显的层叠水平标识的时候,层叠水平值大的那一个覆盖小的那一个,例如 z-index 属性值。
    2. 后来居上:当元素的层叠水平一致、层叠顺序相同的时候,在 DOM 流中处于后面的元素会覆盖前面的元素。

    层叠上下文的特性

    • 层叠上下文的层叠水平要比普通元素高。
    • 层叠上下文可以阻断元素的混合模式。
    • 层叠上下文可以嵌套,内部层叠上下文及其所有子元素均受制于外部的“层叠上下文”。
    • 每个层叠上下文和兄弟元素独立,也就是说,当进行层叠变化或渲染的时候,只需要考虑后代元素。
    • 每个层叠上下文是自成体系的,当元素发生层叠的时候,整个元素被认为是在父层叠上下文的层叠顺序中。

    页面中的层叠上下文

    • 根层叠上下文 :页面根元素具有层叠上下文,称为“根层叠上下文”。故页面中所有的元素至少处于一个层叠上下文中。
    • 定位元素与传统层叠上下文 :对于 position 值为 relative/absolute 以及 Firefox/IE 浏览器(不包括 Chrome 浏览 器)下含有 position:fixed 声明的定位元素,当其 z-index 值不是 auto 的时候,会创建层叠上下文(z-index 一旦变成数值,即使是 0,也创建一个层叠上下文)。
    • CSS3新属性的层叠上下文
      • 元素为 flex 布局元素(父元素 display:flex | inline-flex),同时 z-index 值不是 auto;
      • 元素的 opacity 值不是 1;
      • 元素的 transform 值不是 none;
      • 元素 mix-blend-mode 值不是 normal;
      • 元素的 filter 值不是 none;
      • 元素的 isolation 值是 isolate;
      • 元素的 will-change 属性值为上面 2~6 的任意一个(如 will-change:opacity、will-chang:transform 等);
      • 元素的-webkit-overflow-scrolling 设为 touch;

    CSS3 属性与 z-index 的兼容性问题

    ]]>
    + + + + + 前端 + + CSS + + + + + + + CSS + + + +
    + + + + + Prometheus 监控应用性能 + + /2019/07/15/Prometheus%E7%9B%91%E6%8E%A7%E5%BA%94%E7%94%A8%E6%80%A7%E8%83%BD/ + +    Prometheus 是一个开源的监控系统。它可以自动化的监听应用各性能指标的变化情况,并发出报警信息。了解它目的,是想把前端页面的性能指标记录到公司的 Prometheus 监控系统上,利用它监听前端页面各类异常。

    一、Prometheus 系统简介

    Prometheus 是一个开源的服务监控系统,社区资源和开发者都很活跃。其主要原理是通过 HTTP 协议从远程的机器收集数据并存储在本地的时序数据库上。Prometheus 通过安装在远程机器上的 exporter (数据暴露)插件来收集监控数据。

    Prometheus 特点

    Prometheus 本身也是一个时序数据库,它通过 HTTP 的方式获取时序数据。Prometheus 自身的查询语言 PromQL 可多维度的查询并实时计算指标的值。通过 PromQL 提供的计算方法,可以自定义数据可视化的指标,以及报警临界值。它有四种数据类型,可针对不同场景使用不同数据类型。

    Prometheus 系统的组成部分

    在监控流程中,主要由三个部分组成:被监控的应用暴露性能指标(exporter),promethues 应用采集性能指标(collector),数据可视化分析界面(web UI)。详情参照下述:

    1. Prometheus server: 用于抓取数据,并存储到时序数据库;
    2. Prometheus exporter: 安装在监控目标的上,为 Prometheus server 提供数据抓取的接口;
    3. Prometheus web UI: 提供数据可视化分析界面;
    4. Alertmanager: 用于处理警报;
    5. Pushgateway: 用于 job 推送;

    二、Prometheus 监控流程图

    Promethues server

    主要负责数据采集和存储,提供 PromQL 查询语言的支持。可以通过 Prometheus 的 .yml文件中的scrape_config (字段详情)来配置要抓取的应用指标地址。同时,Promethues 也会监控自身的健康情况,默认将指标暴露在自身的 http://localhost:9090/metrics

    Promethues exporter

    参照官方推荐的插件列表,由于本次监听的站点是 NodeJS 站点,所以选择 prom-client 作为 exporter。
    注意:被监听的应用需要暴露指标接口供 server 抓取。

    Prometheus web UI

    Grafana 可以连接多种类型的库,选择 Promethues 即可,默认监听 Promethues server 的9090/metrics 路径。

    三、NodeJS 应用性能监控

    使用 prom-clientnode-prometheus-gc-stats 收集 NodeJS 的性能指标。

    • prom-client:收集服务端性能指标
    • node-prometheus-gc-stats:垃圾回收相关指标统计

    (prom-client 相当于一个exporter,将默认的指标暴露在 /metrics 接口,之后 Promethues service会根据 .yml 配置中的采集时间定期来这个接口采集数据信息,然后 web UI Grafana 再跟 Promethues server 进行同步)

    prom-client 文档

    一共支持四种数据格式:Histogram、Summary、Gauges 、Counters:

    • Histogram(柱状图) 统计数据的分布情况(比如 Http_response_time 的时间分布)
    • Summary(摘要) 主要用于表示一段时间内数据采样结果(请求持续的时间或响应大小)
    • Gauges(仪表盘) 最简单的度量指标,监测瞬间状态(监控硬盘容量或者内存的使用量)
    • Counters(计数器) 从数据量0开始累积计算,在理想状态下只能是永远的增长不会降低

    常用的采集方法:

    收集到指标后,就可以利用 PromQL 进行计算了,计算时注意 PromQL 即时向量范围向量 两种向量的区别和转换:

    1
    2
    3
    4
    5
    // 计算每分钟垃圾回收bytes数
    delta(nodejs_gc_reclaimed_bytes_total{gctype="Scavenge"}[1m])

    // 计算个页面5min以内的DomReady均值
    delta(FE_Timing_Performance_domReady_sum[5m])/delta(FE_Timing_Performance_domReady_count[5m])

    example-prometheus-nodejs

       这个 demo 是一个完整的 prom-client + Promethues + grafana 监控示例,有助于理解整个监控流程。

    四、前端异常记录实践结论

    并不推荐使用 Prometheus 系统来记录前端页面性能等信息。

    1. 从指标上来看,应用的基本性能指标:吞吐量、内存使用量、每秒请求数、请求平均耗时等。这些几乎都是“瞬时”值(由于时间窗口小,可看做瞬时值),而前端性能指标并不是“瞬时”,它更偏向于一段时间内的表现情况(时间窗口大)。Prometheus 系统主要用于监听应用的性能,它的数据类型更多是为应用服务。
    1. 从数据类型上来看:
      • Gauges:不可以用来记录前端的性能表现。因为 Gauge 记录的某一刻的瞬时值,如果用来记录时间,则每次数据都会被最后访问的那名用户刷新;
      • Counter:计数器虽然可以记录前端某个页面的访问次数,但若页面路由中携带参数,或者结尾带时间戳,则会生成多个重复页面的 Counter,遇到爬虫还会生成大量无用路径,表现并不好;
      • Histogram、Summary:可以记录多个页面,多个指标;Histogram 和 Summary 很相似,只不过 Histogram 记录原始值,Summary 记录指标的各个占比。
    2. 从可视化 Web UI 上来看:
      公司 Prometheus 系统默认使用的可视化 UI 是 Grafana。之前尝试用 Histogram 来记录前端各页面的性能表现,在 Grafana 中用折线图可视化数据。一个指标对应一个折线图,但由于页面路由多个,导致各个折线图中折线过多难以分辨;若取所有页面该指标的均值或者最大值来展示,又不知道峰值是哪个页面产生的。

    综上可以看出,Prometheus 可以记录前端性能指标,但是受数据类型制约,它并不是最合适的。

    参考资料

    ]]>
    + + + + + 监控 + + + + + + + 性能监控 + + + +
    + + + + + 使用 Performance API 进行前端性能监控 + + /2019/07/11/%E4%BD%BF%E7%94%A8%20Performance%20APi%20%E8%BF%9B%E8%A1%8C%E5%89%8D%E7%AB%AF%E6%80%A7%E8%83%BD%E7%9B%91%E6%8E%A7/ + +   平常只在测试环境测过前端页面性能,到了真实环境用户的手机上,页面性能的具体表现却未曾了解。H5 新增的 Performance API 可以精确的测量网页性能。使开发者可以通过数据上报的方式收集线上 H5 页面的性能表现,以合理优化页面性能短板,提升用户体验。

    前端性能监控指标

    • 白屏时间: 从打开网站到有内容渲染出来的时间节点
    • 首屏时间: 首屏内容渲染完毕的时间节点
    • domReady 时间: 用户可操作的时间节点
    • onload 时间: 总下载时间

    Performance API 简介

      Performace是 HTML5 的新特性之一,该接口会返回当前页面性能相关的信息。Performance 对象一共提供了4个属性:

    • navigation: 包含页面加载、刷新、重定向情况
    • timing: 包含了各种与浏览器性能有关的时间数据
    • memory: 返回JavaScript对内存的占用
    • timeOrigin: 返回性能测量开始时的时间的高精度时间戳

    本文主要讨论 Performance 的 timing 对象以及其他几种统计指标。

    performance.timing

    timing 对象提供了各种与浏览器处理相关的时间数据(详细),各时间节点可参照下图:

    其中常用的几项计算指标如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    var timing = performance.timing;
    var times = {};

    // 请求耗时
    times.request = timing.responseEnd - timing.requestStart || 0;

    // 页面白屏时间
    times.ttfb = timing.responseStart - timing.navigationStart || 0;

    // 页面可操作时间
    times.domReady = timing.domComplete - timing.responseEnd || 0;

    //dom渲染时间
    times.domRender = timing.domContentLoadedEventEnd - timing.navigationStart || 0,

    // 总下载时间
    times.onload = timing.loadEventEnd - timing.navigationStart || 0;

    // DNS解析时间
    times.lookupDomain = timing.domainLookupEnd - timing.domainLookupStart || 0;

    // TCP建立时间
    times.tcp = timing.connectEnd - timing.connectStart || 0,

    // 首屏时间
    times.now = performance.now();

    performance.now()

    返回当前网页从performance.timing.navigationStart到当前时间之间的微秒数

    performance.getEntries()

    浏览器获取网页时,会对网页中每一个对象(脚本文件、样式表、图片文件等等)发出一个HTTP请求。performance.getEntries方法以数组形式,返回这些请求的时间统计信息,有多少个请求,返回数组就会有多少个成员。

    数据埋点及上报方式

    利用 <script> 标签的 src 属性上报

    工作中采用的埋点方式是脚本引入。该脚本负责收集浏览器性能指标信息,并生成一个 <script> 节点,将指标信息拼接成 url param 的形式,通过 <script> 标签的 src 属性发起请求,将数据上报到服务器。

    利用 <img> 标签的 src 属性上报

    谷歌和百度的都是用的1x1 像素的透明 gif 图片,其优点如下:

    • 跨域友好
    • 执行过程无阻塞
    • 使用image时,部分浏览器内页面关闭不会影响数据上报
    • gif 的最低合法体积最小(最小的 bmp 文件需要74个字节,png 需要67个字节,而合法的 gif,只需要43个字节)

    利用 HTML5 Beacon API 进行数据上报

    Beacon API 允许开发者发送少量错误分析和上报的信息,它的特点很明显:

    • 在空闲的时候异步发送统计,不影响页面诸如 JS、CSS Animation 等执行
    • 即使页面在 unload 状态下,也会异步发送统计,不影响页面过渡/跳转到下跳页
    • 可被客户端优化发送,尤其在 Mobile 环境下,可以将 Beacon 请求合并到其他请求上,一同处理

    前端性能监控系统

    在 github 上发现的比较好的工具,可以用来参考:

    参考资料

    前端性能监控-window.performance
    Performance API-ruanyifeng
    初探Performance API
    前端全(无)埋点之页面停留时长统计

    ]]>
    + + + + + 前端 + + + + + + + 性能监控 + + + +
    + + + + + IP地址和子网划分 + + /2019/06/02/IP%E5%9C%B0%E5%9D%80%E5%92%8C%E5%AD%90%E7%BD%91%E5%88%92%E5%88%86/ + + 计算机知识补全计划:ip地址、子网掩码相关笔记。

    MAC地址:决定下跳给哪个设备
    IP地址:决定数据最终到达的计算机
    子网掩码:用来判断两台机器的ip地址是否处于同一网段
    课程链接

    一、IP地址
    IP地址是由32位二进制组成的,写成十进制,每四位以逗号分隔:如192.168.30.10。IP地址分为两部分,一部分是网络部分,另一部分是主机部分;在同一网段的计算机,网络部分一样,主机部分不一样,子网掩码就是用来区分主机与网段的。

    1. 子网掩码
      两台计算机在通信之前,首先需要判断需要进行通信的设备与当前的设备是否处于同一网段之中:IP地址子网掩码与运算得出的结果就是网络部分,网络部分相同则处于同一网段。
      例如:A计算机想与B计算机通信,首先A将A的子网掩码和A的IP地址进行与运算,再将A的子网掩码和B的IP地址进行运算,若二者结果相同,则处于同一网段。
    2. IP地址的分类
      • A类:1-127 缺省子网掩码:255.0.0.0
      • B类:128-191 缺省子网掩码:255.255.0.0
      • C类:192-223 缺省子网掩码:255.255.255.0
      • D类(组播):224-239 缺省子网掩码:无
      • E类(研究):240-255 缺省子网掩码:无
        1
        [0----------128-----192---224-240-255]
        在分配IP地址时注意:
        xxx.0.0.0:全0表示这个子网的网络号,不可用;
        xxx.255.255.255:全1表示这个子网的广播地址,代表网段内所有计算机,可跨网段,不可用。(注意,若全为255,则只能发送给本网段的机器,不能跨网段)
        例如C类地址,能设置的主机号只有2-254,一般路由器的ip地址为该网段内的第一个或者最后一个,避免冲突。
    3. 保留地址
      • 保留的私网地址(不在公网上,相互之间不能通信(内网)):
        • A 10.0.0.0 – 10.255.255.255
        • B 172.16.0.0 – 172.31.255.255
        • C 192.168.0.0 – 192.168.255.255
      • 本地环回地址
        • 127.0.0.1 本机
        • 169.254.0.0 断网地址
        • 224.0.0.1 特殊的组播地址,代表所有主机地址
    ]]>
    + + + + + 计算机相关知识 + + + + + + + 计算机网络 + + + +
    + + + + + 微信授权流程 + + /2019/05/30/%E5%BE%AE%E4%BF%A1%E6%8E%88%E6%9D%83%E6%B5%81%E7%A8%8B/ + + 在公司制作 H5 页面的时候,有这样一个场景:在微信打开 H5 页面,已经绑定微信的用户直接免密登录,未绑定的用户使用传统账号密码的登录方式。其中免密登录的核心一环就是走一个微信授权流程,原理不难,弄懂它的流程比较重要。

    微信网页授权官方文档

    当用户在微信中访问第三方网页的时候,公众号可以通过微信网页授权机制来获取用户基本信息。在授权过程中,openid作为用户的唯一标识,同一个用户不同公众号的openid不同,反之亦然。

    • 在发起授权前,需要到微信公众平台开发的官网设置授权回调的域名;
    • openid: 用户唯一标识;
    • code: code作为换取access_token的票据,每次用户授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期;
    • access_token: 网页授权接口调用凭证;
    • scope:用户授权的作用域;
      • snsapi_basescope发起的网页授权,是用来获取进入页面的用户的openid的,并且是静默授权并自动跳转到回调页(往往是业务页面)。用户无感知。
      • snsapi_userinfoscope发起的网页授权,是用来获取用户的基本信息的。但这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息。

    微信免密登录流程图

    ]]>
    + + + + + 微信开发相关 + + 其他小结 + + + + + + + 微信 + + 微信授权 + + + +
    + + + + + 正则表达式学习笔记 + + /2019/04/13/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/ + + 把2017年的笔记整理一下,方便查找。记录了js正则表达式常用的概念、字符、以及方法。

    一、RegExp对象

    JavaScript通过通过内置对象RegExp支持正则表达式,有两种方法实例化RegExp对象:

    1. 字面量:var reg = /文本/g
    2. 构造函数:var reg = new RegExp("\\bis\\b\", 'g')

    二、修饰符

    修饰符含义
    g: glogbal全文搜索(默认搜索到第一个匹配停止)
    i: ignore case忽略大小写(默认大小写敏感)
    m: multiple lines多行搜索

    三、元字符

    如(*+$^.|(){}[])等,指在正则表达式中有特殊含义的非字母字符。一般情况下正则表达式的一个字符对应字符串的一个字符。
    匹配+等特殊字符,可先转义,再匹配,如string.replace(/[\+]/g, "")

    1. 普通类[]
      若要对应多个字符,可用元字符[]来构建一个简单的类,所谓类是指符合某些特性的对象,一个泛指,而不是特指某个字符。
      1
      'a1b2c3d4'.replace(/[abc]/gi, 'X') //"X1X2X3X4"
    2. 反向类[^xx]
      [^..]使用元字符^创建反向类,即不属于某类的内容。
      1
      'a1b2c3d4'.replace(/[^abc]/gi, 'X') //"aXbXcXXX"
    3. 范围类[x-x]
      使用[a-z]来连接两个字符,表示从a到z的任意字符 (包含a,z本身)。在[]组成类的内部可以连写[a-zA-Z]
    4. 预定义类
      正则表达式提供预定义类来匹配常见字符类
    字符等价类含义
    .[^\r\n]除回车符和换行符之外的所有字符
    \d[0-9]数字字符
    \D[^0-9]非数字字符
    \s[\t\n\f\r]空白符
    \S[^\t\n\f\r]非空白符
    \w[a-zA-Z_0-9]单词字符(字母、数字、下划线)
    \W[^a-zA-Z_0-9]非单词字符
    5. 边界
    常见的边界匹配字符如下
    字符含义
    ^以xxx开始(注意^要写在字符前面)
    $以xxx结束(注意$要写在字符后面)
    \b单词边界
    \B非单词边界
    1
    2
    '@123@abc@'.replace('^@', 'Q'); //"Q123@abc@" 
    '@123@abc@'.replace('@$', 'Q'); //"Q123@abcQ"
    1. 量词
      匹配连续出现多次的字符串(仅作用于紧挨着它的字符)
    字符含义
    ?出现零或一次(最多出现一次)
    +出现一次或多次(至少出现一次)
    *出现零次或多次(任意次)
    {n}出现n次
    {n,m}出现n到m次
    {n,}至少出现n次
    {0,m}至多出现m次
    • 贪婪模式
      尽可能多的匹配,直到匹配失败
      1
      '12345678'.replace(/\d{3,6}/g,'X'); //"X78"
    • 非贪婪模式(在量词后边加?
      尽可能少的匹配,一旦匹配成功,不再继续尝试;匹配前面的子表达式零次或一次,等价于 {0,1}
      1
      '12345678'.replace(/\d{3,6}?/g, 'X'); //"XX78"
    1. 分组
      使用()可以达到分组的功能,使量词作用于分组
      1
      'a1b2c3d4'.replace(/([a-z]\d{3})g/, 'X') //"Xd4"
    2. 反向引用
      使用$1、$2、$3...来表示和捕获分组后的内容
      1
      '17741881234'.replace(/(.{3})(.{4})(.{4})/, '$1****$3') //"177****1234"
    3. 忽略分组
      不希望捕获某些分组,只需要在分组内加上?:即可

    4. 使用|可以达到或的效果
    5. 前瞻
      正则表达式从文本头部向文本尾部开始解析。 文本尾部的方向,称为“前”,文本头部称为“后”。 前瞻就是在正则表达式匹配规则的时候,向前检查是否符合断言(条件),后顾/后瞻方向相反。JavaScirpt不支持后顾。符合/不符合 特定断言称为 肯定/正向匹配 和 __否定/负向匹配__。

    四、RegExp对象方法

    • RegExp.prototype.test(str)
      用于测试字符串参数中是否存在匹配正则表达式的字符串,若存在返回true,否则返回false
    • RegExp.prototype.exec(str)
      使用正则表达式模式对字符串执行搜索,并将更新全局RegExp对象的属性以反映匹配结果。如果没有匹配的文本则返回null,否则返回一个结果数组。

    五、String对象方法

    • String.prototype.search(reg)
      用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串。方法返回第一个匹配结果的index,查找不到返回-1。search()方法不执行全局匹配,它将忽略标志g,并且总是从字符串的开始进行检索。
    • String.prototype.match(reg)
      用于检索字符串,以找到一个或者多个与正则表达式匹配的文本。如果匹配到了一个或多个字符串,则返回一个数组,若没有匹配到,则返回null。它不会忽略全局标志g。
    • String.prototype.split(reg)
      使用split方法把字符串分割为字符数组。
    • String.prototype.replace(str/reg, str)
      用于替换字符串中匹配正则表达式或字符串的文本。

    六、常用的正则表达式记录

    ]]>
    + + + + + 前端 + + JavaScript + + + + + + + JavaScript + + + +
    + + + + + 前端模块化 + + /2019/01/03/%E5%89%8D%E7%AB%AF%E6%A8%A1%E5%9D%97%E5%8C%96/ + + 目前JS模块化规范主要三种:浏览器端的 AMDCMD 规范。经常被 exports、modules.exports、export、require 绕懵,遂来探一探究竟。

    AMD规范 (requireJS) 浏览器端 异步加载模块 提前执行

    AMD (Asynchronous Module Definition): 在浏览器中使用,并用 define 函数定义模块,用require引入模块;
    它是 RequireJS 在推广过程中对模块定义的规范化产出,诣在帮开发者解决各个 js 文件的依赖问题,让开发者在页面引入多个 js 时,不必考虑各个 js 的依赖关系。

    CMD规范 (SeaJS) 浏览器端 异步加载模块 延迟执行

    CMD (Common Module Definition): 在浏览器端使用,使用 define 函数定义模块,用 module.exports 暴露模块;
    它 是SeaJS 在推广过程中对模块定义的规范化产出。与 AMD 也都是异步加载模块,只是依赖加载的时间点不一样。相比于 AMD 依赖前置,CDM 加载采用就近原则(个人理解:先依赖,先加载)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    define(function(require, exports, module) {
    let val = 'module4'
    function getVal() {
    console.log(val)
    }

    // 引入module2 同步
    let module2 = require('./module2.js');
    module2()

    // 异步引入module3
    require.async('./module3.js', function(module3) {
    module3.module3.getData()
    });

    // 暴露模块
    module.exports = {getVal}
    })

    CommonJS 服务端 同步加载模块

    NodeJS 的模块机制使用的就是 commonJS 的规范,因为服务端第三方库大多已存于本地,加载速度较快,使用同步加载比较理想。它使用 module.exports 或者是 exports 来导出,使用 require 引入。

    ES6 的 export 和 import 浏览器端

    ESM (ES Modules) 是 JavaScript 从 ES6(ES2015) 开始支持的原生模块机制,使用importexport引入和导出模块。

    UMD 通用模块机制

    UMD (Universal Module Definition) 是一个通用模块的机制,它使一个模块能运行在各种环境下,不论是 CommonJS、AMD,还是非模块化的环境。代码实现原理如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    if (typeof define === 'function') { // 兼容 requireJS AMD、CMD规范   
    define(function () {
    return moduleName;
    });
    } else if (typeof exports !== 'undefined') { // 兼容 webpack 引入方式(commonJS)
    module.exports = moduleName;
    } else {
    this.moduleName = moduleName; // 普通引入,注册到全局
    }

    常见于打包/编译工具中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function webpackConfig(BASE_PATH) {
    return {
    mode: 'development',
    entry: {
    index: path.join(__dirname, '../src/index'),
    preview: path.join(__dirname, '../src/view')
    },
    output: {
    libraryTarget: 'umd'
    },
    ...
    }
    }

    参考资料

    前端模块化详解(完整版)
    关于commonjs,AMD,CMD之间的异同

    ]]>
    + + + + + 前端 + + JavaScript + + + + + + + JavaScript + + + +
    + + + + + 2018年终总结 + + /2018/12/31/2018%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/ + + 2018年仿佛什么都没做,但又仿佛做了些什么;仿佛没有遗憾,但却又心有不甘;以为走到了正确的方向,但“迷茫”二字却困惑了我整整一年。

    • 一月:实习、回校考试
    • 二月:回家过年
    • 三月:实习、回校选毕设题目
    • 四月:实习、学习毕设相关知识、投递简历
    • 五月:实习、开始码毕设、投递简历、跑面试
    • 六月:回校答辩、毕业
    • 七月:转正,回公司工作
    • 八月:工作、去当了一次漫展NPC
    • 九月:工作、去了一次上海迪士尼
    • 十月:工作、找房、换房
    • 十一月:工作
    • 十二月:工作

    18年主要完成事件就是这些。
    四、五月 大概是最忙的时候,因为要管的事情太多,忙到脚打后脑勺。
    六月 是全年最开心的阶段,因为回学校了,有室友和同学在。虽然答辩时被老师问到怀疑人生,但最后老师还是给了高分,借此拿了一次奖学金的我也是受宠若惊,以为毕业前再也没有机会拿到了。除了感谢老师以外,还得感谢公司leader,毕设题目是他建议的。
    七月 本决定给自己一周毕业旅行的时间,奈何职业方向和家里人冲突升级。取消了打算已久的假期,回公司了。工位发生很大变化,前端组的大家这次都坐在一起了。
    八月 第一次去了漫展,也是第一次当NPC,不过应该也都是最后一次了哈哈。遇见了很好的小伙伴们,临走前,没有张口要联系方式,挺后悔的。
    九月 去了趟迪士尼,事实证明,做攻略还是非常有用滴,项目都玩了,喜欢的也几刷了。遗憾的是,时间来不及,没有买到漫威周边。
    十月 相对轻松。十一没出去玩,出去看房,找到合适的就换了,室友也换了,承蒙了之前两位姐姐很多照顾,有时会怀念。十月末公司团建,挺好玩的。
    十一月 中旬心心念念的 blog 诞生了,虽然不难,但也是历史性的一步!毕竟从去年就开始惦记着…
    十二月 成长。双十二的时候,买了新水彩颜料,终于把雄狮换成鲁本斯了。

    18年看似很充实,实际一年到头可以收获的东西却寥寥无几,全年没有明确的方向,只是被时间推着做事。
    希望19年找到自己的目标和方向。

    ]]>
    + + + + + + 年度总结 + + + +
    + + + + + Linux命令行与shell脚本学习 + + /2018/11/30/Linux%E5%91%BD%E4%BB%A4%E8%A1%8C%E4%B8%8Eshell%E8%84%9A%E6%9C%AC%E5%AD%A6%E4%B9%A0/ + + 《Linux命令行与shell脚本编程大全》读书小结,熟悉一下常用的命令行操作。书籍比较基础,对熟悉Linux命令行的人来说参考意义不大。主要记录下书中提到的、没提到的常用的命令。

    基础操作

    • . 代表当前目录
    • .. 代表父级目录
    • ~ 代表根目录 表名当前工作目录位于用户home目录之下
    • man <directive> 可查看指令可使用的参数手册
    • tab 键自动补全文件名
    • cd 切换目录
    • linux 中的文件路径全部采用正斜线/,windows中的路径都是反斜线\而且带盘符
    • ls 列出当前路径下的所有文件
      • -F 在显示子目录的时候在它的文件名之后加上一个斜线(“/”)字符
      • -F -R 遍历(递归)出当前目录下的子文件夹的所有内容(可以缩写成 ls -FR )
      • -a 列出所有文件,包括隐藏文件
      • -l 列出文件的所有信息
    • pwd 查看当前所在位置的全路径
    • sudo 以 root 用户身份运行命令

    文件基础操作

    • open <fileName> 用默认程序打开文件
      • open –args <参数> 用默认参数打开某个App
    • touch <fileName> 创建一个文件 (不可在不存在的目录下新建文件)
    • mkdir <directory> 创建一个文件夹
      • -p 创建多个层级的文件夹
    • rmdir <directory> 只删除空目录
      • 在非空目录下使用 rm -r 命令
    • cp <fileName> <targetDirectory/fileName> 复制文件到目标文件夹/文件名
      • -i 强制 shell 询问是否覆盖同名文件
    • scp <fileName> <root@targetPath> 远程拷贝文件 可以跨服务器
    • mv <fileName> <directory/fileName> 用来 移动/重命名 文件
      • -i 强制 shell 询问是否覆盖同名文件
    • rm <fileName> 删除文件/文件夹中的所有内容
      • -i 强制 shell 询问是否删除文件
      • -f 强制删除,没有警告信息也没有声音提示
      • -r 递归删除目录及目录内所有文件
      • 注意:Linux 中没有回收站或垃圾箱,文件一旦删除,就无法再找回
    • ls -l <fileName> 查看文件权限
    • chmod value <fileName> 更改文件权限
      • 权限描述顺序依次是:Owner(User)、Group、Other
      • r=读取属性 //值=4
      • w=写入属性 //值=2
      • x=执行属性 //值=1
    • chown(选项)(参数) 更改文件夹所有者和所属组
      • chown -R user:group .git 将.git文件夹的权限设置为 group 下的 user
    • 获取文件路径:直接将文件拖入命令行即可

    文件内容操作

    • file <fileName/directoryName> 查看文件类型信息
    • du <fileName/directoryName> 用来查看文件或目录所占用的磁盘空间的大小
      • -h 以易于阅读的方式展示
      • -a 显示目录及其下子目录和文件占用的磁盘空间大小
      • -s 只展示当前目录占用磁盘空间大小
    • cat/more/less <fileName> 查看整个文件内容
      • cat 一次性加载完所有文件内容
      • more 一次显示一屏文本
      • less 一次显示一屏文本 可以上下页翻建
    • tail/head <fileName> 查看部分文件内容
      • tail 默认展示文件最后10行的效果
        • -n 2 只显示文件最后两行
        • -f 允许其他进程使用该文件时查看该文件的内容,tail会保持活跃状态,并不断显示添加到文件中的内容。(可用来实时监测系统日志)
      • head 默认展示文件前10行内容
        • 不支持 -f 属性
    • grep match_pattern <fileName> 强大的文本搜索工具,可以使用正则表达式搜索文本,并显示出匹配的行数
    • sed -i ‘s/被替换的内容/要替换的内容/g’ file -i 表示直接修改并保存
      • 使用 sed 命令,报错invalid command code,是因为 -i 原地替换是危险行为,需要指明一个备份的扩展名才可以,若给了空的扩展名,则不会备份源文件。
      • 如 sed -i ‘’ ‘s/被替换的内容/要替换的内容/g’ file
    • ls -> xxx.txt 将命令输出的内容保存为文件

    监控进程

    • ps 显示进程信息(瞬间占用情况)
    • top 显示进程信息(实时占用情况)
    • lsof 查看进程打开的文件
      • lsof -i:4000 查看4000端口占用情况
    • kill [PID] 杀死对应进程

    网络情况

    • ping <ip> 测试主机之间的连通性(不会自动结束,需要手动 ctrl + c 强制退出)
    • dig <url> 域名查询工具,可以用来测试域名系统工作是否正常
    • nsloopup <url> 域名查询工具,查询 DNS 相关信息

    变量

    环境变量

    • printenv/env 默认输出所有环境变量(全局)
      • printenv JAVA_HOME 输出全局设置的JAVA SDK位置
      • env $JAVA_HOME
      • echo $JAVA_HOME
    • echo $variableName 输出变量 ($用来表名它是个变量)
    • set 输出所有环境变量(全局和局部)
    • $HOME 表示的用户的主目录,与波浪线~作用一样

    普通变量

    声明时直接声明即可使用 variable=XXX,变量名区分大小写,但需要注意的是 赋值时,变量名、等号和值之间没有空格 否则会报错 command not found
    常用的书写习惯是 所有的环境变量名均使用大写字母,若是自己创建的局部变量或是shell脚本,则用小写字母,变量名区分大小写。

    vim 操作

    • vim <fileName> 以 vim 编辑器的方式查看当前文件
    • I 对文件进行 INSERT 操作
    • esc 退出当前编辑模式
    • 输入 : 切换到底线命令模式,可以在最底行输入其他命令
    • 输入 wq ,保存并退出;输入 !q,不保存直接退出
    • 输入 ggdG,删除当前全部内容;gg 为跳转到文件首行;dG为删除光标所在行以及其下所有行的内容
    • .swp 文件: 非正常关闭的 vim 编辑器会生成一个 .swp 文件

    杂项

    大小写转换

    • echo $VAR_NAME | tr ‘[:upper:]’ ‘[:lower:]’
    • echo $VAR_NAME | tr ‘[A-Z]’ ‘[a-z]’

    其他

    • alias 可用来查看当前可用的别名(内建命令)
      • alias 新的命令=’原命令 -选项/参数’ 用来定义命令别名
    • sh <fileName.sh> 执行shell文件
    • .xxxrc 可以看做是xxx启动运行时的配置文件
      • 例如 .zshrc 就是 zsh 运行前要执行配置文件
    • source <fileName> 或者 . <fileName> (bash内部命令) 加载文件
    • 文件\包查找
      • which <fileName> 查找该包编译器所在位置
      • whereis <fileName> 搜索更大范围的系统目录并输出所有包含的路径
      • find <fileName> 查找系统是否安装了某个软件包

    代理

    • 参考
    • 若想要在当前终端中生效,直接输入 export http_proxy='http://ip_address:port' 即可,注意 ip 和端口号是本机的 ip + port;
    • 想要持久化全局生效的话,可以在 .zhsrc 中配置上述命令

    常用的配置文件地址

    • Host 文件 /etc/hosts
    • 配置的 SSH Key: cat ~/.ssh/id_rsa.pub

    常见文件颜色

    • 白色:表示普通文件
    • 蓝色:表示目录
    • 绿色:表示可执行文件
    • 红色:表示压缩文件
    • 浅蓝色:链接文件
    • 红色闪烁:表示链接的文件有问题
    • 黄色:表示设备文件
    • 灰色:表示其他文件

    插件

    • homebrew 包管理器
      • brew install <packageName> 安装插件
      • brew list 查看电脑安装了哪些插件
      • 注:每次下载包之前都会进行 brew 更新检查,速度很慢,按一次 Ctrl+C 跳过更新
      • 官网上的 github 源安装总是会 443 connect timeout,推荐使用国内的镜像安装:
        1
        bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"
    • wget 下载网页常用的工具
    • curl 是用来请求 Web 服务器的命令行工具,类似于 POSTMAN,它的名字就是客户端(client)的 URL 工具的意思。curl 支持的通信协议有 FTP、FTPS、HTTP、HTTPS、TFTP、SFTP、Gopher、SCP、Telnet、DICT、FILE、LDAP、LDAPS、IMAP、POP3、SMTP 和 RTSP。curl 还支持 SSL 认证、HTTP POST、HTTP PUT、FTP上传, HTTP form based upload、proxies、HTTP/2、cookies、用户名+密码认证(Basic, Plain, Digest, CRAM-MD5, NTLM, Negotiate and Kerberos)、file transfer resume、proxy tunneling。
      • curl <url> 直接返回 url 请求结果
        • -A/--user-agent <string\> 设置用户代理发送给服务器
        • -D/--dump-header <file\> 把 header 信息写入到该文件中
        • -O/--output <file\> 把输出写到该文件中
        • -#/--progress-bar 进度条显示当前传送状态
        • 详细信息
      • 需要注意的是,做了反爬措施的网站在直接 curl 请求的时候,结果可能不如预期
    • tree 以树状图形式展示目录及其子文件
      • tree <directory> -J 以 json 形式展示文件
    • tig 将 git 命令行可视化

    其他参考:

    ]]>
    + + + + + 计算机相关知识 + + + + + + + Linux&shell + + + +
    + + + + + 同时使用两个账号分别操作Github和Gitlab + + /2018/11/17/%E5%90%8C%E6%97%B6%E4%BD%BF%E7%94%A8%E4%B8%A4%E4%B8%AA%E8%B4%A6%E5%8F%B7%E5%88%86%E5%88%AB%E6%93%8D%E4%BD%9CGithub%E5%92%8CGitlab/ + + 公司用 gitlab 存管代码,自己用 github 。懒得下班后用自己电脑提交到 github ,故学习一下如何在同一台电脑上使用两个 git 账号。在 SSH config 中为不同的域名指定不同的 SSH key,之后再将自己本地的 github 库的 git config – local 设置成自己的 github 账号。

    一、生成SSH秘钥

    分别对githubn和gitlab生成对应的密钥

    • ssh-keygen -t rsa -C "公司邮箱地址"生成对应的gitlab密钥:id_rsa和id_rsa.pub
    • 将 gitlab 公钥(id_rsa.pub)中的内容配置到公司的gitlab上
    • ssh-keygen -t rsa -C "自己邮箱地址" -f ~/.ssh/github_rsa生成对应的github密钥:github_rsa 和 github_rsa.pub
    • 生成公私钥的过程中,会提示你输入passphrase,用作每次进行 ssh 连接时的确认密码。由于电脑和账号都是个人使用所以直接按回车设置为空就可以了
    • 将 github 公钥(github_rsa.pub)中的内容配置到自己的 github 上
    • 到目前为止本地 /.ssh 中已经存在 github_rsa、github_rsa.pub、id_rsa、id_rsa.pub 四个文件了,由于 github 和 gitlab 建立连接默认查找的都是/.ssh/id_rsa,所以需要为 github 手动指明使用的私钥名称 github_rsa,否则会报错 Permission denied (publickey)
    • 进入密钥生成的位置,创建一个 config 文件,添加配置:
      1
      2
      3
      4
      5
      # githab
      Host github.com
      HostName github.com
      User kuro-p
      IdentityFile ~/.ssh/github_rsa
    • 如果为 github 中配置了两个 ssh,那么在 config 中,谁在前谁生效

    二、测试连接

    运行ssh -T git@hostName命令测试 ssh key 对 gitlab 与 github 的连接

    如果能看到一些 Welcome 信息,说明是 OK 的。

    三、配置 git 库账号

    为了使 github / gitlab 知道提交的用户是谁,需要对账户名进行配置。由于全局配置是公司的账号,所以只需要对自己想要进行操作的 github 库进行本地配置即可。

    1
    2
    git config --local user.name 'username' # github账号名称
    git config --local user.email 'username@gmail.com' # github账号邮箱

    或者直接 init 一个 git 库,配置后 github 的代码都在这个仓库下拉取。

    参考资料

    如何在同一台电脑上使用github和gitlab
    同时使用两个账号分别操作Github和Gitlab
    由于SSH配置文件的不匹配,导致的Permission denied (publickey)及其解决方法

    ]]>
    + + + + + git + + + + + + + git + + + +
    + + + + + Hexo 基础使用 + + /2018/11/17/Hexo-%E5%9F%BA%E7%A1%80%E4%BD%BF%E7%94%A8/ + + 记录一下 hexo 基础用法。

    安装

    node 环境下,全局安装 hexo-cli

    1
    npm install hexo-cli -g

    初始化

    进入到一个放置blog的空文件夹

    1
    2
    3
    4
    hexo init 
    hexo generate
    hexo server # 默认4000端口
    hexo s -p 4001 # 在自定义端口启动

    浏览器输入 localhost:4000,出现 blog 界面

    换主题

    Hexo 官网提供了一些主题 https://hexo.io/themes/

    • git clone 主题地址到 blog 目录下,将全局_condig.yml中的theme名字改为clone下来的文件夹的名字
    • 主题中有可供选择的几套样式,更改主题 _config.yml 里的 scheme
    • 设置代码高亮样式 更改主题 _condig.yml 里的 hightlight_theme
    • 切换Hexo语言 在全局 _condig.yml 里的 language 改成 zh-Hans 即为主题下的简体中文(默认为英文)
    • (更换完主题,需要重启应用,方能生效)
    • 由于主题也是一个git仓库,下载后记得删除.git文件,否则主题文件是无法提交的

    生成文章

    1
    2
    3
    4
    5
    hexo new "postName" # /source/_post/postName & .md
    hexo new page "pageName" # /source/pageName/index & index.md
    hexo generate # /source/.md -> /public/.html
    hexo server
    hexo deploy #将.deploy目录部署到GitHub

    删除文章

    1
    2
    3
    hexo clean # delete /public
    hexo generate # regenerate /public
    hexo deploy

    其他

    • 插入本地图片
      每次hexo new 'postName'时,都会创建一个与文章名相同的文件夹,将文章所需资源放入该文件夹里,引用的时候直接写文件名即可。
    • 页面增加“阅读更多”按钮
      在 .md 文件中增加<!--more-->注释,如果想自动添加“阅读更多”按钮,可在主题下的_config.yml中将auto_excerpt下的enable设置为true

    插件

    部署

    hexo d部署前,需要安装npm install hexo-deployer-git --save
    修改全局 _config.yml 中的配置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    deploy:
    type: git
    repo: <repository url>
    branch: [branch]
    message: [message]
    name: [git user]
    email: [git email]
    extend_dirs: [extend directory] #其他要提交的目录
    ignore_hidden: true #忽略隐藏文件
    ignore_pattern: regexp #忽略正则匹配的隐藏文件

    之后,只需要hexo d -g一条命令就可以生成和部署了。关于 hexo-deployer-git 这个插件的参数 hexo官方文档 介绍的并不全面,建议去 hexo-deployer-git官方文档 查看相关配置参数。

    注意:

    • 默认部署,只将生成的 HTML 相关文件(/public文件夹)推送到 github
    • 若想把本地的生成器项目相关文件也推送到 github,则要配置 extend_dirs: /
    • message、name、email 的内容要用引号括起来
    • name、email 的配置信息用来覆盖全局的 git config 中的配置,更改这两项后,需要删除根目录下的 .deploy_git ,部署时才会生效
    • master 只能放 /public 下的文件,将项目所有文件放到 master 分支下,会导致页面 build 失败。若想将本地代码全部提交,可部署在其他分支(在 _config.yml 中增加其他分支配置信息,详情参考文档)
    • 避免提交 node_modules,需在项目下新建.gitignore文件(为什么不使用 extend_dirs ?因为需要添加的文件夹太多…)
    • 若遇见 Error: EACCES: permission denied, unlink /XXX 相关的错误,大部分是由没权限引起的,使用 sudo chown -R whoami:staff /你的blog目录 即可

    搜索功能

    全局安装插件 npm install hexo-generator-search --save
    修改全局 _config.yml中的配置:

    1
    2
    3
    4
    search:
    path: search.xml
    field: post
    content: true

    修改主题 themes/next/_config.yml 中的配置:

    1
    2
    3
    local_search:
    enable: true
    trigger: auto

    生效:hexo cleanhexo ghexo s

    Hexo 目录解析

    1
    2
    3
    4
    5
    6
    7
    8
    9
    ├── node_modules # 依赖包-安装插件及所需nodejs模块。
    ├── public # 最终网页信息。即存放通过 markdown 渲染出来的 html文件。
    ├── scaffolds # 模板文件夹。即新建文章时,根据 scaffold 生成文件。
    ├── source # 资源文件夹。即存放用户资源。
    | └── _posts # 博客文章目录。
    └── themes #存放主题。Hexo根据主题生成静态页面。
    ├── _config.yml #网站的全局配置信息。标题、网站名称等。
    ├── db.json:# source 解析所得到的缓存文件。
    ├── package.json # 应用程序信息。即配置Hexo运行需要js包。

    参考资料

    利用 hexo + Gitpage 开发自己的博客
    hexo 浅析原理

    ]]>
    + + + + + 其他小结 + + + + + + + Hexo + + + +
    + + + + + 《看见》-柴静 + + /2018/09/07/%E3%80%8A%E7%9C%8B%E8%A7%81%E3%80%8B-%E6%9F%B4%E9%9D%99/ + + 无意间逛知乎的时候发现的书籍片段,留下了很深的印象。从记者的视角看到平日里生活中接触不到的社会另一面,别有一番感触。不愧是著名记者,文笔犀利,干练不拖沓。值得一读的好书:★★★★★

    第二章 那个温热的跳动就是活着

    我对非典的印象还是停留在小学时候,有那么一段时间,教室里每天清晨和下午都要喷洒消毒水,学校的走廊里弥漫着一股医院的味道。那时还小,只知道这是在预防“非典”,但它到底是什么,我并不知道。

    这就是我之前听说的天井。四周楼群间的一块空地,一 个楼与楼之间的天井,加个盖,就成了个完全封闭的空间, 成了输液室,发热的病人都集中到这里来输液。二十七张床 几乎完全挨在一起,中间只有一只拳头的距离。白天也完全靠灯光,没有通风,没有窗,只有一个中央空调的排气口, 这个排气口把病菌传到各处。
    病历胡乱地堆在桌上,像小山一样,已经发黄发脆。我 犹豫了一秒钟。朱继红几乎是凄然地一笑,说:“我来吧。” 病例被翻开,上面写的都是“肺炎”。他指给我看墙上的黑 板,上面写了二十二个人的名字,其中十九个后面都用白粉 笔写着:肺炎、肺炎、肺炎……
    “实际上都是 SARS。”他说。

    一个卫生系统的官员在这里感染,回家又把妻子儿子感染了,想尽办法要住院,只能找到一个床位,夫妇俩让儿子住了进去。两口子发烧得浑身透湿,站不住,只能顫抖着坐在 小板凳上输液。再后来连板凳都坐不住了。孩子痊愈的时候, 父母已经去世。

    明明只是在描述,却让人觉得无比震撼。

    ]]>
    + + + + + 闲暇读物 + + + + + + + 读书小结 + + + +
    + + + + + markdown 语法小记 + + /2018/09/05/MarkDown%E8%AF%AD%E6%B3%95%E5%B0%8F%E8%AE%B0/ + + 第一个 hexo-next 主题的 blog,主要记录下 markdown 语法

    测试文本样式

    测试加粗样式

    加粗

    测试斜体样式

    斜体

    测试删除线样式

    删除线

    测试引用样式

    山穷水尽疑无路,柳暗花明又一村

    测试代码样式

    测试指定代码语言代码样式

    1
    2
    3
    4
    5
    6
    var FE_developer = {
    name: 'Kuro',
    age: '22'
    };

    console.log('info', FE_developer);

    测试单行代码样式

    在 JS 中我们常用 console.log() 来输出调试信息。

    测试代码块样式

    1
    2
    3
    4
    5
    6
    function test(a, b){
    setTimeout(function () {
    console.log(a + b);
    setTimeout(arguments.callee, 500);
    }, 500)
    }

    测试连接样式

    百度一下:Baidu

    测试首行缩进样式

      markdown 语法主要考虑的是英文,中文缩进需要依赖 HTML 的空格符号

    1
    2
    半角空格: &nbsp;
    全角空格:&emsp;

    测试表格样式

    左对齐居中对齐右对齐
    Harry PotterGryffindor90
    Hermione GrangerGryffindor100
    Draco MalfoySlytherin90

    表格使用 | 来分隔不同的单元格,使用 - 来分隔表头和其他行。
    注意:表格前若有文本,需要空一行才能正常显示

    测试插入图片

    来自百度图片:

    西瓜

    测试列表

    git 常用语法

    • git status
    • git add .
    • git commit -m”XXX”
    • git stash
    • git list
    • git stash apply stash@{n}
    • git diff
    • git reset –hard
    1. 列表内容
      • 列表嵌套第一条
      • 列表嵌套第二条
    2. 列表内容
    3. 列表内容

    测试复选框样式

    • 选项一
    • 选项二
    • 选项三

    测试流程图样式

    其他注意事项

    • 在 markdown 中直接使用尖括号<something>会被文本默认为HTML标签语句而不予显示。
      • 使用转义字符&lt;代替<,用&gt;代替>
      • 或者左闭合的尖括号前加一个转义符号\,例如:“\<something>”
    ]]>
    + + + + + 其他小结 + + + + + + + markdown + + + +
    + + + + +
    diff --git a/tags/App/index.html b/tags/App/index.html new file mode 100644 index 00000000..1669d275 --- /dev/null +++ b/tags/App/index.html @@ -0,0 +1,420 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: App | Daily record + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    Daily record

    + +
    +

    琐记随笔

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    App + 标签 +

    +
    + + +
    + 2022 +
    + + +
    + 2021 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/CSS/index.html b/tags/CSS/index.html new file mode 100644 index 00000000..e9f41317 --- /dev/null +++ b/tags/CSS/index.html @@ -0,0 +1,397 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: CSS | Daily record + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    Daily record

    + +
    +

    琐记随笔

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    CSS + 标签 +

    +
    + + +
    + 2019 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/Hexo/index.html b/tags/Hexo/index.html new file mode 100644 index 00000000..e18a8abe --- /dev/null +++ b/tags/Hexo/index.html @@ -0,0 +1,397 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: Hexo | Daily record + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    Daily record

    + +
    +

    琐记随笔

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    Hexo + 标签 +

    +
    + + +
    + 2018 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/JavaScript/index.html b/tags/JavaScript/index.html new file mode 100644 index 00000000..50cec3c8 --- /dev/null +++ b/tags/JavaScript/index.html @@ -0,0 +1,440 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: JavaScript | Daily record + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    Daily record

    + +
    +

    琐记随笔

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    JavaScript + 标签 +

    +
    + + +
    + 2020 +
    + + +
    + 2019 +
    + + + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/Linux-shell/index.html b/tags/Linux-shell/index.html new file mode 100644 index 00000000..732fbf46 --- /dev/null +++ b/tags/Linux-shell/index.html @@ -0,0 +1,397 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: Linux&shell | Daily record + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    Daily record

    + +
    +

    琐记随笔

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    Linux&shell + 标签 +

    +
    + + +
    + 2018 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/NodeJS/index.html b/tags/NodeJS/index.html new file mode 100644 index 00000000..589bddb1 --- /dev/null +++ b/tags/NodeJS/index.html @@ -0,0 +1,397 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: NodeJS | Daily record + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    Daily record

    + +
    +

    琐记随笔

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    NodeJS + 标签 +

    +
    + + +
    + 2020 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/Service-Worker/index.html b/tags/Service-Worker/index.html new file mode 100644 index 00000000..e12e3506 --- /dev/null +++ b/tags/Service-Worker/index.html @@ -0,0 +1,420 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: Service Worker | Daily record + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    Daily record

    + +
    +

    琐记随笔

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    Service Worker + 标签 +

    +
    + + +
    + 2020 +
    + + +
    + 2019 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/Webview/index.html b/tags/Webview/index.html new file mode 100644 index 00000000..55cde3d8 --- /dev/null +++ b/tags/Webview/index.html @@ -0,0 +1,420 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: Webview | Daily record + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    Daily record

    + +
    +

    琐记随笔

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    Webview + 标签 +

    +
    + + +
    + 2022 +
    + + +
    + 2021 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/git/index.html b/tags/git/index.html new file mode 100644 index 00000000..b8de4cf7 --- /dev/null +++ b/tags/git/index.html @@ -0,0 +1,397 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: git | Daily record + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    Daily record

    + +
    +

    琐记随笔

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    git + 标签 +

    +
    + + +
    + 2018 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/index.html b/tags/index.html new file mode 100644 index 00000000..b4a6c8aa --- /dev/null +++ b/tags/index.html @@ -0,0 +1,397 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签 | Daily record + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    Daily record

    + +
    +

    琐记随笔

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    + + +
    +
    +
    + + + + +
    + + + + + +
    +
    + +

    +

    + + + +
    + + + + +
    + + +
    + + + +
    + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/markdown/index.html b/tags/markdown/index.html new file mode 100644 index 00000000..1912f89a --- /dev/null +++ b/tags/markdown/index.html @@ -0,0 +1,397 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: markdown | Daily record + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    Daily record

    + +
    +

    琐记随笔

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    markdown + 标签 +

    +
    + + +
    + 2018 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/tags/\344\272\244\344\272\222/index.html" "b/tags/\344\272\244\344\272\222/index.html" new file mode 100644 index 00000000..632775d5 --- /dev/null +++ "b/tags/\344\272\244\344\272\222/index.html" @@ -0,0 +1,397 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: 交互 | Daily record + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    Daily record

    + +
    +

    琐记随笔

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    交互 + 标签 +

    +
    + + +
    + 2021 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/tags/\345\244\247\346\225\260\346\215\256/index.html" "b/tags/\345\244\247\346\225\260\346\215\256/index.html" new file mode 100644 index 00000000..98add6c2 --- /dev/null +++ "b/tags/\345\244\247\346\225\260\346\215\256/index.html" @@ -0,0 +1,397 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: 大数据 | Daily record + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    Daily record

    + +
    +

    琐记随笔

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    大数据 + 标签 +

    +
    + + +
    + 2019 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/tags/\345\260\217\347\250\213\345\272\217/index.html" "b/tags/\345\260\217\347\250\213\345\272\217/index.html" new file mode 100644 index 00000000..d373807a --- /dev/null +++ "b/tags/\345\260\217\347\250\213\345\272\217/index.html" @@ -0,0 +1,397 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: 小程序 | Daily record + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    Daily record

    + +
    +

    琐记随笔

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    小程序 + 标签 +

    +
    + + +
    + 2021 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/tags/\345\271\264\345\272\246\346\200\273\347\273\223/index.html" "b/tags/\345\271\264\345\272\246\346\200\273\347\273\223/index.html" new file mode 100644 index 00000000..5c4f7826 --- /dev/null +++ "b/tags/\345\271\264\345\272\246\346\200\273\347\273\223/index.html" @@ -0,0 +1,489 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: 年度总结 | Daily record + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    Daily record

    + +
    +

    琐记随笔

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    年度总结 + 标签 +

    +
    + + +
    + 2023 +
    + + +
    + 2021 +
    + + +
    + 2020 +
    + + +
    + 2019 +
    + + +
    + 2018 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/tags/\345\276\256\344\277\241/index.html" "b/tags/\345\276\256\344\277\241/index.html" new file mode 100644 index 00000000..c5da7d89 --- /dev/null +++ "b/tags/\345\276\256\344\277\241/index.html" @@ -0,0 +1,420 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: 微信 | Daily record + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    Daily record

    + +
    +

    琐记随笔

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    微信 + 标签 +

    +
    + + +
    + 2021 +
    + + +
    + 2019 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/tags/\345\276\256\344\277\241\346\216\210\346\235\203/index.html" "b/tags/\345\276\256\344\277\241\346\216\210\346\235\203/index.html" new file mode 100644 index 00000000..04af59af --- /dev/null +++ "b/tags/\345\276\256\344\277\241\346\216\210\346\235\203/index.html" @@ -0,0 +1,397 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: 微信授权 | Daily record + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    Daily record

    + +
    +

    琐记随笔

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    微信授权 + 标签 +

    +
    + + +
    + 2019 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/tags/\346\200\247\350\203\275\347\233\221\346\216\247/index.html" "b/tags/\346\200\247\350\203\275\347\233\221\346\216\247/index.html" new file mode 100644 index 00000000..db62e26b --- /dev/null +++ "b/tags/\346\200\247\350\203\275\347\233\221\346\216\247/index.html" @@ -0,0 +1,417 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: 性能监控 | Daily record + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    Daily record

    + +
    +

    琐记随笔

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    性能监控 + 标签 +

    +
    + + +
    + 2019 +
    + + + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/tags/\346\265\201\345\244\204\347\220\206/index.html" "b/tags/\346\265\201\345\244\204\347\220\206/index.html" new file mode 100644 index 00000000..576891b1 --- /dev/null +++ "b/tags/\346\265\201\345\244\204\347\220\206/index.html" @@ -0,0 +1,397 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: 流处理 | Daily record + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    Daily record

    + +
    +

    琐记随笔

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    流处理 + 标签 +

    +
    + + +
    + 2019 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/tags/\347\277\273\350\257\221/index.html" "b/tags/\347\277\273\350\257\221/index.html" new file mode 100644 index 00000000..b8370b49 --- /dev/null +++ "b/tags/\347\277\273\350\257\221/index.html" @@ -0,0 +1,440 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: 翻译 | Daily record + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    Daily record

    + +
    +

    琐记随笔

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    翻译 + 标签 +

    +
    + + +
    + 2022 +
    + + +
    + 2020 +
    + + + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/tags/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/index.html" "b/tags/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/index.html" new file mode 100644 index 00000000..3bf0b25e --- /dev/null +++ "b/tags/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/index.html" @@ -0,0 +1,440 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: 计算机网络 | Daily record + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    Daily record

    + +
    +

    琐记随笔

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    计算机网络 + 标签 +

    +
    + + +
    + 2022 +
    + + +
    + 2019 +
    + + + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/tags/\350\257\273\344\271\246\345\260\217\347\273\223/index.html" "b/tags/\350\257\273\344\271\246\345\260\217\347\273\223/index.html" new file mode 100644 index 00000000..e84eb77a --- /dev/null +++ "b/tags/\350\257\273\344\271\246\345\260\217\347\273\223/index.html" @@ -0,0 +1,420 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: 读书小结 | Daily record + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    Daily record

    + +
    +

    琐记随笔

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    读书小结 + 标签 +

    +
    + + +
    + 2021 +
    + + +
    + 2018 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +