Merge branch 'master' into backup

This commit is contained in:
DefectingCat
2021-03-09 21:31:10 +08:00
26 changed files with 19187 additions and 4065 deletions

View File

@ -1,2 +1,5 @@
## 小破站
[![Netlify Status](https://api.netlify.com/api/v1/badges/ebec33ee-9828-4a31-905d-f9ea0f15556d/deploy-status)](https://app.netlify.com/sites/brave-nobel-bc5790/deploys)
[xfy.plus](https://xfy.plus/)

View File

@ -198,6 +198,10 @@ custom_js: /js/xfy.js
# The usage is the same as custom_js
custom_css: /css/xfy.css
# 自定义 <head> 节点中的 HTML 内容
# Customize <head> HTML content
custom_head: ''
# 自定义底部 HTML 内容(位于 footer 上方),注意不要和 `post: custom` 配置冲突
# Customize the HTML content at the bottom (located above the footer), be careful not to conflict with `post: custom`
custom_html: ''
@ -684,6 +688,11 @@ gitalk:
pagerDirection: last
distractionFreeMode: false
createIssueManually: true
# 默认 proxy 已失效,解决方法请见下方链接
# The default proxy is invalid, please see the links for the solution
# https://github.com/gitalk/gitalk/issues/429
# https://github.com/Zibri/cloudflare-cors-anywhere
proxy: <your own proxy>/https://github.com/login/oauth/access_token
# Valine
# 基于 LeanCloud
@ -899,6 +908,25 @@ links:
image: 'https://cdn.defectink.com/images/20200924163805.png'
}
# 当成员头像加载失败时,替换为指定图片
# When the member avatar fails to load, replace the specified image
onerror_avatar: images/img/avatar.webp
# 友链下方自定义区域,支持 HTML可插入例如申请友链的文字
# Custom content at the bottom of the links
custom:
enable: false
content: '<hr><p>在下方留言申请加入我的友链,按如下格式提供信息:</p><ul><li>博客名Fluid</li><li>简介Fluid 主题官方博客</li><li>链接https://hexo.fluid-dev.com</li><li>图片https://hexo.fluid-dev.com/img/favicon.png</li></ul>'
# 评论插件
# Comment plugin
comments:
enable: true
# 指定的插件,需要同时设置对应插件的必要参数
# The specified plugin needs to set the necessary parameters at the same time
# Options: utterances | disqus | gitalk | valine | waline | changyan | livere | remark42 | twikoo
type: valine
#---------------------------
# 以下是配置 JS CSS 等静态资源的 URL 前缀,可以自定义成 CDN 地址,

12308
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,11 +14,11 @@
"cl": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0"
},
"hexo": {
"version": "5.3.0"
"version": "5.4.0"
},
"dependencies": {
"babel": "^6.23.0",
"commitizen": "^4.2.2",
"commitizen": "^4.2.3",
"conventional-changelog-cli": "^2.1.1",
"gulp": "^4.0.2",
"gulp-clean-css": "^4.3.0",
@ -26,9 +26,9 @@
"gulp-htmlmin": "^5.0.1",
"gulp-uglify": "^3.0.2",
"gulp-uglify-es": "^2.0.0",
"hexo": "^5.3.0",
"hexo": "^5.4.0",
"hexo-cli": "^4.2.0",
"hexo-deployer-git": "^2.1.0",
"hexo-deployer-git": "^3.0.0",
"hexo-generator-archive": "^1.0.0",
"hexo-generator-category": "^1.0.0",
"hexo-generator-feed": "^3.0.0",
@ -36,13 +36,15 @@
"hexo-generator-sitemap": "^2.1.0",
"hexo-generator-tag": "^1.0.0",
"hexo-renderer-ejs": "^1.0.0",
"hexo-renderer-marked": "^3.3.0",
"hexo-renderer-marked": "^4.0.0",
"hexo-renderer-stylus": "^2.0.1",
"hexo-server": "^2.0.0",
"hexo-theme-fluid": "^1.8.7"
"hexo-theme-fluid": "^1.8.8",
"npm-check-updates": "^11.2.0",
"nunjucks": "^3.2.3"
},
"devDependencies": {
"@babel/core": "^7.12.10",
"@babel/preset-env": "^7.12.11"
"@babel/core": "^7.13.10",
"@babel/preset-env": "^7.13.10"
}
}

View File

@ -493,3 +493,7 @@ for (let i = 0; i < 5; i++) {
frag.append(ul);
```
### Attr 类型
## 操作 DOM

View File

@ -0,0 +1,169 @@
JavaScript 和 HTML 直接的交互是通过事件实现的。当文档或者浏览器发生交互时,使用侦听器(处理程序)来预定事件,以便事件发生时执行相应的代码。在传统软件工程中被称之为观察员模式。
事件最早是在 IE3 和 Netscape Navigator 2 中出现的。
## 事件流
经常在一些文章中看到的事件冒泡或者捕获就是用于描述事件流的两种方式。在浏览器发展到 IE4 的时代时,人们开始考虑:如何确定页面的哪一部分拥有某个特定的事件?
要理解这个问题很简单,在 Web 文档中,需要绝大部分元素进行嵌套。如果我们单击了嵌套中的某个元素,那么也相当于同时单击了它的父元素。
就像将手指指向在一张纸上的同心圆最中心的那个圆上,相当于同时也指向了纸上所有的圆。
而我们的 HTML 结构也是同理,如同下发被弯曲成三个同心圆的`<div>`元素一样。当单击最中心的粉色圆时,外层的绿色和蓝色的圆圈都同时被点击了。因为他们是父级元素,包裹着中心的粉色圆圈。
![](../images/JavaScript中的事件/concentirc-circles.png)
<p class="codepen" data-height="265" data-theme-id="light" data-default-tab="css,result" data-user="Defectink" data-slug-hash="ZEBjowz" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="同心圆">
<span>See the Pen <a href="https://codepen.io/Defectink/pen/ZEBjowz">
同心圆</a> by Defectink (<a href="https://codepen.io/Defectink">@Defectink</a>)
on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>
```html
<div class="wrapper">
<div class="w1">
<span>1</span>
<div class="w2">
<span>2</span>
<div class="w3">
<span>3</span>
</div>
</div>
</div>
</div>
```
**事件流**就是描述从页面接受事件的顺序。有意思的是,历史的竞争者居然提出了几乎相反的事件流概念。软软提出的是事件冒泡流,而 Netscape 提出的是事件捕获流。
### 事件冒泡
IE 的事件叫做**事件冒泡**event bubbling即事件最开始时由最具体的元素接受然后逐级向上层父级元素传播直到根 document。
拿上述同心圆结构来说:
```html
<div class="wrapper">
<div class="w1">
<span>1</span>
<div class="w2">
<span>2</span>
<div class="w3">
<span>3</span>
</div>
</div>
</div>
</div>
```
如果单击最里面的`<div>` w3那么事件冒泡流按照如下顺序传播
1. div.w3
2. div.w2
3. div.w1
4. div.wrapper
5. body
6. html
7. document
![](../images/JavaScript中的事件/event-bubbling.svg)
当在传播顺序中的某个元素上注册了对应事件的监听器,那么就会在当前元素上触发对应的事件:
1. div.w3 <---点击的是 w3 元素
2. div.w2
3. div.w1
4. div.wrapper <---在 wrapper 上注册了监听器
5. body
6. html
7. document
wrapper 上的监听器
```js
wrapper.addEventListener('click', (e) => {
console.log('wrapper');
console.log(e.target); // div.w3
})
```
> `addEventListener`默认在事件冒泡时触发事件,见后续。
事件代理的原理就是由事件流来实现的点击的事件会一层一层的传播当传播到了有监听器的那个元素时就会触发对应的方法不过`event.target`依然是点击的目标元素
所有的现代浏览器都支持事件冒泡在一些老版本中的实现上可能会有一些差距
### 事件捕获
Netscape Communicator 提出的另一种事件流叫做**事件捕获**event capturing)。事件捕获传递事件的方式基本上与事件冒泡相反它的用意在于事件到达预定的目标之前捕获它
还是上述同心圆的例子点击了`<div>` w3 事件捕获的传播顺序为
1. document
2. html
3. body
4. div.wrapper
5. div.w1
6. div.w2
7. div.w3
> ~~我就是想和你反着来~~
触发对应的事件也是同理也是在传播顺序当中有某个外层元素注册了对应的事件就会触发该事件
1. document
2. html
3. body
4. div.wrapper <---在 wrapper 上注册了监听器
5. div.w1
6. div.w2
7. div.w3 <---点击的是 w3 元素
wrapper 上的监听器
```js
wrapper.addEventListener('click', (e) => {
console.log('wrapper');
console.log(e.target); // div.w3
}, false)
```
> `addEventListener`第三个参数就是控制事件是冒泡还是捕获,见后续。
### DOM 事件流
上述俩家整出了几乎完全想法的概念好在 DOM 规定将其整合到了一起。“DOM2 级事件规定事件为三个阶段
1. 事件捕获阶段
2. 处于目标阶段
3. 事件冒泡阶段
首先发生的是事件捕获为截获事件提供了机会然后是实际的目标接受到的事件最后一个阶段是冒泡阶段可以在这个阶段对事件做出响应
在事件捕获阶段实际的目标不会接受到事件这意味着事件捕获阶段在实际目标之前就停止了图例中到`<body>`停止)。接下来就是处于目标阶段如果在目标元素上监听了对应的事件那么这个事件的触发被看成冒泡阶段的一部分
## 事件处理程序
事件就是用户或者浏览器自身执行的某种动作诸如 clickload mouseover等这些都是对应的事件名称而对某个事件做出响应的函数就叫做**事件处理程序**。有多种规定规定了为事件指定处理程序的方式
### HTML 事件处理程序
HTML 元素支持使用一个与事件名称同名的 Attribute 来监听对应的事件这个特性的值就是可执行的 JavaScript 代码
```html
<input type="button" value="Click Me!" onclick="alert('嘤嘤嘤')">
```
当然也可以定义一个函数来处理
```html
<input type="button" value="Click Me!" onclick="showMessage()">
```
现在的前端编程方式推荐解耦合即专门的语言处理专门的事情这种在 HTML 元素上定义事件的方式也会导致很多的问题
1. 如果函数定义在按钮下发那么在函数还未加载完成时用户就点击了对应的按钮就会导致一个错误
2. 使用 with 拓展作用域时会在不同浏览器中导致不同的结果
3. HTML JavaScript 代码紧密耦合

View File

@ -2,7 +2,27 @@
在很久以前,曾今尝试过将自己当时的小破站[所有的服务都使用 Docker 来部署](https://www.defectink.com/defect/docker-container-all.html),在未来迁移也会更加方便。也就是那时,正真的用上了 Docker。
不过这次水的部分不同,没想到过直接把小的配置很是复杂,并且多个应用在一起端口也不方便分配。于是就想到了使用 Nginx 来做反代。
不过这次水的部分不同,没想到过直接把小破站迁移到了 Hexo。这次准备搭建一些其他的服务而有些东西对于 SSL 的配置很是复杂,并且多个应用在一起端口也不方便分配。于是就想到了使用 Nginx 来做反代。
## 使用 certbot 获取证书
certbot 可以使用 webroot 模式来验证域名,在启动时添加参数`--webroot`即可。
而 certbot 的 challenge 根目录则映射出来,交给解析了对应域名的 Nginx。同时证书目录也需要映射出来再证书申请完成后也交给 Nginx。
```dockerfile
volumes:
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
```
certbot 也可以同时为多个域名申请证书,只需要在后面继续添加`-d`参数即可。
certbot 默认是交互模式,由于需要在 Docker 中使用,可以使用`--non-interactive`来关闭交互模式。
```dockerfile
command: certonly --webroot -w /var/www/certbot -d git.defectink.com --non-interactive --agree-tos -m xfy@xfy.plus
```
## Nginx 的配置
@ -16,12 +36,93 @@ docker run --rm nginx:alpine cat /etc/nginx/nginx.conf > nginx.conf
docker run --rm nginx:alpine cat /etc/nginx/conf.d/ > conf.d/
```
### 修改配置文件破站迁移到了 Hexo。这次准备搭建一些其他的服务而有些东西对于 SSL
### 响应 challenge
之后的 compose 文件中可以再将其映射回去。
certbot 映射出来对应的证书和 webroot 目录后Nginx 也需要映射到自己的容器内:
## 使用 certbot 获取证书
```dockerfile
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/conf.d/:/etc/nginx/conf.d:ro
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
```
为了使用 certbot 的 webroot 模式Nginx 需要在其默认的配置文件上添加对应 challenge 相应的配置。
这里在`conf.d`目录内新建了一个`gitea.conf`的配置文件,默认情况下`nginx.conf`会导入`conf.d`内的配置文件:`include /etc/nginx/conf.d/*.conf;`
```nginx
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
```
现在的目录结构:
```
.
├── certbot
│ ├── conf
│ └── www
├── docker-compose.yml
├── gitea
│ ├── git
│ ├── gitea
│ └── ssh
├── nginx
│ ├── conf.d
│ │ ├── default.conf
│ │ └── gitea.conf
│ └── nginx.conf
├── vlmcsd
│ └── Dockerfile
└──docker-compose.yml
```
### 为 Nginx 配置 SSL
### 自动续期
在配置 certbot 时就将其证书目录映射到宿主机了,同时也在 Nginx 中映射到容器内了,接下来要做的就是在配置文件中添加证书。
Nginx 需要再添加一个 server 字段,并使用 443 端口。证书在`ssl_certificate`填上对应映射的目录。
```nginx
server {
listen 443 ssl http2;
server_name git.defectink.com;
ssl_certificate /etc/letsencrypt/live/git.defectink.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/git.defectink.com/privkey.pem;
}
```
同时也可以在 80 端口内添加一个 301 跳转到 443
```nginx
location / {
return 301 https://$host$request_uri;
}
```
### 反代
```nginx
location / {
set $upstream "site_upstream";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-Port $server_port;
proxy_set_header X-Real-Scheme $scheme;
proxy_set_header X-NginX-Proxy true;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Ssl on;
proxy_pass http://gitea:3000;
}
```
### 自动续期
letsencrypt 获取免费的证书很是方便,但是必须要三个月续期一次。

View File

@ -0,0 +1,20 @@
## 平均空间
使用 flex 布局,在左右设置等距的 padding然后使用 space-arorund。
```css
.goods {
display: flex;
flex-flow: row wrap;
padding: 0px 6px 0 6px;
justify-content: space-around;
}
```
内部的单个项目需要指定宽度小于 50%,为中间留空白的空间
```html
style="width: 48%; margin-top: 10px"
```
![](../images/第一个SPA的总结/2021-03-01-12-15-31.png)

View File

@ -0,0 +1,395 @@
---
title: JavaScript 装饰器模式🎊
date: 2021-03-09 20:36:26
tags: JavaScript
categories: 笔记
url: javascript-decorator
index_img: /images/JavaScript装饰器模式/logo.webp
---
JavaScript 的函数非常灵活,它们可以被传递,用作对象。除了 this 难以捉摸以外。
## 装饰器
装饰decorate将原函数作为一个参数传递给装饰器。利用函数闭包的特性在父作用域中保存一些执行后的数据缓存或执行一些特殊操作。再将其返回出去在这个返回的函数根据条件来执行原函数。
在实际工作中常见到的函数防抖和节流就是装饰器的工作原理。
## 缓存装饰器
一个简单的透明缓存装饰器可以明确的让我们了解到装饰器的工作方式:
```js
// 一个工作缓慢的 slow 函数
function slow(ds) {
return `${ds}`;
}
function cachingDcorator(fn) {
// 创建一个 map 用于缓存
let cache = new Map();
// 返回一个闭包
return function(ds) {
// 检查缓存中是否有结果
if (cache.has(ds)) {
return cache.get(ds);
}
// 没有缓存时,执行原函数,并记录缓存
let res = fn(ds);
cache.set(ds, res);
return res;
}
}
// 装饰
cacheSlow = cachingDcorator(slow);
console.log(cacheSlow('124'));
```
在装饰器 cachingDcorator 内,父作用域中创建了一个名为 cache 的 map 结构,利用闭包的特性就能够访问这个缓存对象。
在每次执行时,都将检查是否有对应的缓存。如果有,则跳过执行原函数,直接返回缓存的数据,以减少函数的执行工作。
上述简单的透明缓存装饰器还有一个问题:带有 this 时会失效。
```js
let worker = {
foo() {
return '嘤嘤嘤';
},
slow(ds) {
return `${ds} ${this.foo()}`;
}
}
function cachingDcorator(fn) {
'use strict'
let cache = new Map();
return function(ds) {
if (cache.has(ds)) {
return cache.get(ds);
}
let res = fn(ds);
cache.set(ds, res);
return res;
}
}
let worker.slow = cachingDcorator(worker.slow);
// undefined
console.log(worker.slow('xfy'));
```
当一个函数被传递到其他变量中,将丢失其上下文 this。
```js
let func = worker.slow;
func('xfy')
```
同理,缓存装饰器也是将其函数体作为参数传入到方法中,导致了其丢失了上下文 this。所以这样的装饰器在对象中的方法是用不了的。
## 传递 this
### 使用 call
使用 call 可以轻松解决 this 的问题。
```js
let worker = {
foo() {
return '嘤嘤嘤';
},
slow(ds) {
return `${ds} ${this.foo()}`;
}
}
function cachingDcorator(fn) {
let cache = new Map();
return function(ds) {
if (cache.has(ds)) {
return cache.get(ds);
}
let res = fn.call(this, ds);
cache.set(ds, res);
return res;
}
}
worker.slow = cachingDcorator(worker.slow);
console.log(worker.slow('xfy'));
```
在将函数体传回对象时`worker.slow = cachingDcorator(worker.slow);`,在装饰器内部使用了 call 来执行传递的参数`fn.call(worker, ds);`。详细的传递步骤为:
1. worker.slow 被传递为包装器`function (ds) { ... }`
2. 函数作为对象属性执行时,`this=worker`。(它是点符号 . 之前的对象);
3. 在包装器内部,假设结果尚未缓存,`func.call(this, ds)`将当前的 `this``=worker`)和当前的参数(`=xfy`)传递给原始方法。
## 传递多个参数
原生的 Map 仅将单个值作为键key。当然可以使用其他的类似 map 的数据结构来存储缓存的值,或者在 map 中嵌套 map。
还有一种解决方法就是使用一个 hash 函数,来将两个参数做个简单的运算,将其做为一个值保存在 map 的 key 中,并对应结果缓存。
```js
let worker = {
slow(x, y) {
return x + y;
},
};
function cachingDcorator(fn, hash) {
let map = new Map();
return function () {
let key = hash(arguments);
if (map.has(key)) {
return map.get(key);
}
let res = fn.call(this, ...arguments);
map.set(key, res);
return res;
};
}
function hash(args) {
return `${args[0]}${args[1]}`;
}
worker.slow = cachingDcorator(worker.slow, hash);
console.log(worker.slow(2, 3));
```
在 JavaScript 中,形参不是必要的。只要传递了实际参数,那么在函数中就能使用`arguments`这个类数组来获取到所有的参数。
所以在这里返回的闭包里使用`let key = hash(arguments);`来获取两个参数做 hash 运算。
由于是一个类数组所以它也是可迭代的。在传给原函数参数时使用了展开Spread语法`let res = fn.call(this, ...arguments);`来将类数组迭代开来。
### 使用 apply
在传递 this 时,我们使用了 call 来传递参数。call 接受参数为逐个传递,在接受多个参数时,我们使用了展开语法来将可迭代对象的参数传递给 call。
call 与 apply 的唯一区别就是参数的传递方式不同。apply 接受剩余的参数为一个类数组
所以这里两个调用是等价的:
* `fn.call(this, ...arguments)`
* `fn.apply(this, arguments)`
而对于即可迭代又是类数组的对象,例如一个真正的数组,我们使用 call 或 apply 均可,但是 apply 可能会更快,因为大多数 JavaScript 引擎在内部对其进行了优化。
将所有参数连同上下文一起传递给另一个函数被称为“呼叫转移call forwarding”。
### 方法借用
能接受的参数还不够多。
上述 hash 方法一次只能处理两个参数`return '${args[0]}${args[1]}'`,将其作为 map 的一个 key 来使用。当遇到两个以上参数时就无能为力了。
所以还需要对 hash 方法做一个小改进,使其能够使用数组的`join()`方法来将所有的参数合并为一个字符串。
由于接受的参数`arguments`是一个类数组,所以它并没有数组的`join()`方法。这里就需要利用 call 来改变 this 的指向从而“借用”一下数组的`join()`方法:
```js
function hash(args) {
// return Array.prototype.join.call(args);
return [].join.call(args);
}
```
这种方式称之为**方法借用**。
最常见到的方法借用就是判断数据类型。我们都知道 typeof 在判断引用值时有那么一点不准确,这时候有一位大佬却能够准确的判断所有的类型:`Object.prototype.toString()`方法。
这个方法原本是用来转换为字符串的,但通过借用它还能 [判断数据类型](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/toString#%E4%BD%BF%E7%94%A8_tostring()_%E6%A3%80%E6%B5%8B%E5%AF%B9%E8%B1%A1%E7%B1%BB%E5%9E%8B)。
```js
Object.prototype.toString.call(new Array())
// "[object Array]"
typeof new Array()
// "object"
```
> 还有一种判断数据类型的方法是:`xxx.construtor.name`
## 函数属性
通常,函数装饰器是安全的。不过当遇到原函数拥有自身的属性时,通过装饰器返回的函数就会导致其丢失自身的属性。例如:`foo.count`
一些包装器可能也会包含自身的属性,例如记录函数被调用的次数或者其他的信息。
可以使用 Proxy 对象来包装函数来保留函数属性的访问权。Proxy 非常强大Vue 3 就是使用 Proxy 来创建响应式对象的。从而解决了 Vue 2 不能监听到对象新增属性的问题等。
## 实例
一些装饰器的实例,这段内容也是 javascript.info 的 [任务](https://zh.javascript.info/call-apply-decorators#tasks)
### 间谍装饰器
间谍装饰器保存每次函数调用时传递的参数为一个数组。这种装饰器有时对于单元测试很有用。它的高级形式是 [Sinon.JS](https://sinonjs.org/) 库中的`sinon.spy`
```js
function work(x, y) {
console.log(x, y);
}
function spy(fn) {
function ret() {
// 每次调用原函数时push 参数到 calls 属性上
ret.calls.push(`calls:${[].join.call(arguments)}`);
fn.apply(this, arguments);
}
// 在返回的函数上定义一个属性 calls
ret.calls = [];
return ret;
}
work = spy(work);
work(1, 2);
work(3, 4);
console.log(work.calls);
```
### 延时装饰器
这是个非常简单的装饰器,用于为函数设置一个延时后执行。
```js
function foo(x) {
console.log(x);
}
function delayRun(fn, ms) {
return function() {
setTimeout(() => {
// 保持 this 的指向
fn.apply(this, arguments);
}, ms);
}
}
foo = delayRun(foo, 500);
foo('xfy');
```
### 防抖装饰器
防抖装饰器 debounce 是一个常用的方法。它的主要目的是保证原函数在一定时间内的连续调用只生效一次。
通常在实际中的作用是:假设用户输入了一些内容,我们想要在用户输入完成时向服务器发送一个请求。
我们没有必要为每一个字符的输入都发送请求。相反,我们想要等一段时间,然后处理整个结果。
在 Web 浏览器中,我们可以设置一个事件处理程序 —— 一个在每次输入内容发生改动时都会调用的函数。通常,监听所有按键输入的事件的处理程序会被调用的非常频繁。但如果我们为这个处理程序做一个 1000ms 的 debounce 处理,它仅会在最后一次输入后的 1000ms 后被调用一次。
```js
function foo(x) {
console.log(x);
}
function debounce(fn, ms) {
let timer = null;
return function () {
// 如果延时内频繁被调用,则取消延时,不执行
if (timer) clearTimeout(timer);
// 延时执行
timer = setTimeout(() => {
fn.apply(this, arguments);
}, ms);
};
}
foo = debounce(foo, 1000);
foo(1);
foo(1);
foo(1);
```
### 节流装饰器
节流装饰器 throttle 和防抖装饰器很相似,但是它要复杂一点。与防抖不同的是,节流的主要作用是:让函数保持一定的**时间间隔**被调用执行。
* `debounce`会在“冷却cooldown”期后运行函数一次。适用于处理最终结果。
* `throttle`运行函数的频率不会大于所给定的时间 ms 毫秒。适用于不应该经常进行的定期更新。
```js
function foo(x) {
console.log(x);
}
function throttle(fn, ms) {
// 初始状态为不节流
let isThrottle = false,
savedArgs,
savedCont;
function wrapper() {
// 如果节流是打开的,则保存当前运行的上下文和参数
if (isThrottle) {
savedArgs = arguments;
savedCont = this;
return;
}
// 第一次直接运行原函数
fn.apply(this, arguments);
// 第一次运行完后打开节流
isThrottle = true;
setTimeout(() => {
// 在节流时间后关闭节流阀
isThrottle = false;
if (savedArgs) {
// 调用 wrapper 自身,并传递保存的上下文
wrapper.apply(savedCont, savedArgs);
savedArgs = savedCont = null;
}
}, ms);
}
return wrapper;
}
let f1000 = throttle(foo, 1000);
f1000(1);
f1000(2);
f1000(3);
f1000(4);
f1000(5);
f1000(6);
setTimeout(() => {
f1000(7);
}, 500);
setTimeout(() => {
f1000(8);
}, 1000);
setTimeout(() => {
f1000(9);
}, 1520);
setTimeout(() => {
f1000('a');
}, 2000);
setTimeout(() => {
f1000('b');
}, 2500);
setTimeout(() => {
f1000('c');
}, 3000);
```
## 参考
* [装饰器模式和转发call/apply](https://zh.javascript.info/call-apply-decorators)
* [Object.prototype.toString()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/toString)

View File

@ -0,0 +1,396 @@
---
title: 移动端触摸轮播图
date: 2021-02-23 08:57:10
tags: [Vue, JavaScript]
categories: 实践
url: mobile-slidershow
index_img: /images/移动端触摸轮播图/logo.webp
---
## 迫害移动端
继上次的[经典轮播图的实现方案](/defect/classic-slider-show.html),这次配合了`TouchEvent`来实现了移动端的触摸轮播图。
这次原生的实现方式和 Vue 基本相同,由于需要实现触摸滑动,仅仅靠`absolute`定位和 Vue 的过渡动画是不够的。所以使用了经典的图片队列,然后克隆两张图片来调换队列。
## 封装组件
这次把整个轮播图图都尝试封装成一个单独的组件,图片的队列和动画延时都由父组件传递 props 过来。
```html
<TouchSlider :images="images" :animeTime="'500'" />
```
组件内:
```js
props: {
// 图片队列
images: Array,
// 动画时间
animeTime: String
},
```
## 经典方案的实现
虽然是用 Vue 来实现的方案,但本质上还是将经典方案封装成一个组件,然后配合上`TouchEvent`来实现了移动端的触摸轮播图。
所以第一步还是先使用 Vue 的方式实现经典的布局方案。
### 布局
组件内的布局和之前的经典方案一模一样,外部的 div 使用`relative`定位,内部的 ul 通过`transform`来进行位移。
```html
<div class="slider">
<ul class="wrapper">
<!-- v-for 循环的图片 -->
<li></li>
</ul>
</div>
```
与上次不同的地方是这次 ul 使用的`flex`布局,而不是针对 li 使用`flot`。相对于使用浮动`flot`来说,`flex`布局更加好控制,也更先进。
```css
.slider {
position: relative;
overflow: hidden;
}
.wrapper {
display: flex;
}
```
### 克隆图片
在 Vue 里就不那么依赖操作 DOM 了,克隆第一张和最后一张图片也很方便,,对传过来的图片数组取第一个和最后一个项目就可以了。
整个图片的队列通过`v-for`来循环生成。
```html
<!-- 克隆最后一张图片 -->
<li>
<img :src="images[images.length - 1].src" alt="" class="roll-img" />
</li>
<li v-for="item in images" :key="'img' + item.id">
<img :src="item.src" alt="" class="roll-img" ref="img" />
</li>
<!-- 克隆第一张图片 -->
<li>
<img :src="images[0].src" alt="" class="roll-img" />
</li>
```
### 队列宽度
经典方案还有个重点就是整个 ul 队列的宽度,它配额偏移量才能准确实现图片的移动。
这里给图片`<img>`都上了一个样式,用来决定图片的宽度:
```css
.roll-img {
width: 600px;
}
```
同时还用了个媒体查询,虽然不能做到实时的响应式,但是根据不同的设备还是能做的刷新后适配不同的设备端的。
```css
@media only screen and (max-width: 376px) {
.roll-img {
width: 375px;
}
}
```
既然图片的宽度确定了,那整个 ul 的宽度就是`单张图片的宽度 * 图片的数量`了。
```html
<ul
class="wrapper"
:style="{
width: imgWidth * images.length + 'px'
}"
></ul>
```
图片的数量`images.length`很容易确定,单张图片的宽度需要使用 HTMLElement 上的`HTMLElement.offsetWidth`方法。它能返回一个元素的布局宽度,这里使用它来获取单个`<img>`的宽度。获取的时机就在组件被挂载时:
```js
mounted() {
// 计算单个图片的宽度,做移动端适配
this.imgWidth = this.$refs.img[0].offsetWidth;
}
```
现在在`data()`中还只保存了单张图片宽度`imgWidth`,慢慢来,它会越来越多的。
```js
data() {
return {
// 单张图片的宽度
imgWidth: 0,
};
```
### 移动方法
这次依然使用`transition`过渡来实现移动的动画,控制位置使用`translateX`
第一步先封装一个移动的方法,用了前两次的经验,这次修改了移动图片的方法,并且它接受一个参数,用于判断是否开启过渡动画:
```js
move(anime) {
// 移动方法,添加过渡动画,根据图片序列移动图片
if (anime) {
this.transitionX = `all ${this.animeTime}ms`;
} else {
this.transitionX = `none`;
}
this.translateX = -(this.imgWidth + this.imgWidth * this.imgIndex);
}
```
图片移动的距离也使用了一种更好的公式:`-(单张图片的宽度 + 单张图片的宽度 * 当前图片索引)`
这个公式的好处就是队列的位移由图片的索引进行驱动,当需要修改队列的位移的位置时,只需要相应的修改索引值,然后调用移动方法内的公式即可。
![](../images/移动端触摸轮播图/2021-02-23-11-23-15.webp)
### 默认位置
上述在`mounted()`挂载后获取了图片的宽度,现在还需要增加一个在挂载后修改下队列的位置。当克隆了两张图片分别在队列的前后时,队列的默认位置需要向左偏移一个图片的宽度。
现在在`data()`中还要再保存一个图片的索引。
```js
data() {
return {
// 单张图片的宽度
imgWidth: 0,
// 图片索引,用于控制位置
imgIndex: 0,
};
```
所以在挂载后图片的索引为0调用移动方法即可将队列设置为默认的位置
```js
mounted() {
// 计算单个图片的宽度,做移动端适配
this.imgWidth = this.$refs.img[0].offsetWidth;
this.move(false);
}
```
### 小圆点
按钮封装的非常基本,就是普通的按钮添加两个点击事件,分别用于操作图片索引加减。这里就不再多说了。
按钮的方法:
```js
previous() {
if (Date.now() - this.flag > Number(this.animeTime) + 10) {
this.imgIndex--;
this.move(true);
this.flag = Date.now();
}
},
next() {
if (Date.now() - this.flag > Number(this.animeTime) + 10) {
this.imgIndex++;
this.move(true);
this.flag = Date.now();
}
},
```
小圆点也不是非常的复杂,简单封装一个`<ol><li></li></ol>`的结构即可。在父组件中通过`v-for`来循环生成 li顺便绑定 images 的 index 到 li 的 id 上,用于点击事件。
```html
<IndexPoint
class="index-point"
:num="images.length"
@pointClick="pointClick"
>
<template>
<li
v-for="(item, index) in images"
:key="'point' + item.id"
:class="{ active: index == pointIndex }"
:id="index"
></li>
</template>
</IndexPoint>
```
在点击事件里,将点击的小圆点的 id 赋值到图片的索引,然后再调用`move()`方法就可以正确的移动到对于的图片上。
```js
pointClick(e) {
this.imgIndex = e.target.id;
this.move(true);
},
```
### 无限循环
轮播图的重点就在于能够无限轮播,这里使用的还是和往期一样的方案:当移动到克隆的图片时,偷偷替换图片队列。不同的是,这次没有使用`setTimeOut`来进行延时调换,而是使用了`transitionend`事件。该事件就是监听过渡动画完成后调用方法。
监听的方法还是和以前一样,通过判断图片的索引来确定是否是克隆的图片,然后偷偷调换队列。
```js
transEnd() {
if (this.imgIndex == -1) {
// 如果当前序列等于 -1也就是克隆的图片则偷偷调整图片队列
this.imgIndex = this.images.length - 1;
this.move(false);
} else if (this.imgIndex == this.images.length) {
// 反之亦然
this.imgIndex = 0;
this.move(false);
}
},
```
### 自动播放
自动播放实现的也很简单,定时操作图片的索引即可。
不过这里遇到一个小 Bug当 Chrome 在后台时,`transitionend`事件不会被监听。这就会导致使用了自动播放在后台放一会之后,所有的图片都消失了。
后来研究了下可以通过[`document.visibilityState`](https://developer.mozilla.org/zh-CN/docs/Web/API/Document/visibilityState)判断浏览器是否处于后台,当其值不等于`hidden`时,就继续播放。
```js
autoPlay() {
this.timer = setInterval(() => {
// 当页面处于后台时transEnd会失效
if (document.visibilityState != 'hidden') {
this.imgIndex++;
this.move(true);
}
console.log(document.visibilityState);
}, 3000);
},
```
```
visible
5 hidden
visible
```
## 触摸事件
上述方法都是将以前实现的经典方案封装到 Vue 组件里,本次的重点还是触摸的滑动方法。
触摸由三个事件组成:从 touchstart 到 touchmove 到 touchend。他们分别对应动作触摸开始、触摸移动和触摸结束。每一次的触摸都会触发这三个事件。
[触摸事件](https://developer.mozilla.org/zh-CN/docs/Web/API/Touch_events)有个事件对象`touches`,它就是实现图片移动的主要原理。`touches`内保存这触摸时的坐标,在不同的触摸事件里可以根据坐标来移动图片。
所以这里现在 ul 上绑定对应的三个事件:
```html
<ul
class="wrapper"
:style="{
width: imgWidth * images.length + 'px',
}"
@touchstart="touchStart"
@touchmove="touchMove"
@touchend="touchEnd"
>
```
### 触摸开始
第一个是触摸开始的阶段,当第一次触摸到 ul 时,就会触发这个事件。这个事件非常简单,就保存了两个关键的数据:点击时的 X 坐标和当前的图片位置。方便在移动时判断对应的移动位置。
```js
touchStart(e) {
this.pausePlay();
// 触摸开始
if (Date.now() - this.flag > Number(this.animeTime) + 10) {
// 获取点击时的 X 坐标
this.startX = e.touches[0].clientX;
// 点击开始时保存当前图片的位置
this.lastX = this.translateX;
}
},
```
### 触摸移动
图片移动的位置就是`原先的位置 + 滑动的距离`。首先判断滑动的距离,使用当前`touchMove`的 X 坐标减去`touchStart`所保存的 X 坐标,就能轻松得出移动了多少距离。
然后再将图片的过渡动画取消`this.transitionX = 'none';`,再给图片添加上移动的距离`this.translateX = this.lastX + this.moveX;`
这里还做了一个简单的判断,当滑动的距离大于单张图片的宽度时`this.moveX >= this.imgWidth`,就不允许再移动了`this.moveX = this.imgWidth;`。同样使用正负值来判断左右移动的方向`this.moveX < 0 && this.moveX <= -this.imgWidth`
```js
touchMove(e) {
// 防止在一定时间内过渡滑动
if (Date.now() - this.flag > Number(this.animeTime) + 10) {
// 移动时的坐标减去点击时的坐标等于移动的距离
this.moveX = e.touches[0].clientX - this.startX;
// 移动图片
this.transitionX = `none`;
// 防止滑动过渡
if (this.moveX >= this.imgWidth) {
this.moveX = this.imgWidth;
// 滑动位置等于上次的位置加上手指移动的距离
this.translateX = this.lastX + this.moveX;
} else if (this.moveX < 0 && this.moveX <= -this.imgWidth) {
this.moveX = -this.imgWidth;
this.translateX = this.lastX + this.moveX;
} else {
this.translateX = this.lastX + this.moveX;
}
}
},
```
### 触摸结束
当图片正确的移动后,触摸结束的事件就是对图片做一些判断,保证队列是预期的状态。
第一个判断就是当滑动了整张图片时,不需要再调用`move()`方法了,直接对左右方向进行判断,确定图片的索引加减`this.moveX > 0 ? this.imgIndex-- : this.imgIndex++;`。不过还得再调用一次` this.transEnd()`方法来偷偷调换图片队列。
```js
touchEnd() {
if (Date.now() - this.flag > Number(this.animeTime) + 10) {
// 防止滑动过渡
if (this.moveX == this.imgWidth || this.moveX == -this.imgWidth) {
this.moveX > 0 ? this.imgIndex-- : this.imgIndex++;
// 当越界时,调用恢复队列
this.transEnd();
// 当触摸大于 70 像素,触发移动动画,移动完整图片
} else if (this.moveX > 70) {
this.imgIndex--;
this.move(true);
// 当触摸小于 -70 像素,触发移动动画,移动完整图片
} else if (this.moveX < -70) {
this.imgIndex++;
this.move(true);
// 当在二者之间时,图片归位
} else {
this.move(true);
}
this.startX = 0;
this.moveX = 0;
this.flag = Date.now();
}
this.autoPlay();
},
```
## 🚀
这就是在之前的轮播图上加以实现的移动端轮播图方案,还有一些小方法就没有多嘴了。
几个方法写的比较冗余,感觉可以再简单一点。主要的是对这个组件封装的很烂,整个`TouchSlider.vue`一大串。
[项目地址](https://git.defectink.com/xfy/vue-touch-slider)

View File

@ -3,7 +3,7 @@ title: 经典轮播图的实现方案
date: 2021-02-09 13:37:00
tags: [Vue, JavaScript]
categories: 实践
url: classic-slider-show
url: classic-slidershow
index_img: /images/经典轮播图的实现方案/logo.webp
---

View File

@ -0,0 +1,24 @@
---
title: 踩坑记录-Win10远程桌面密码错误
date: 2021-02-28 12:03:30
tags: Windows
categories: 踩坑
url: win10-remote-desktop-password-incorrect
index_img: /images/踩坑记录-Win10远程桌面密码错误/logo.webp
---
## 触发条件
Windows 10 20H2版当只登录了一个 Microsoft 账户时。在登录时或系统默认将“**只允许使用 Windows Hello 登录**”打开了,导致无法使用传统密码登入系统。也就直接导致了远程桌面无法登录。
![](../images/踩坑记录-Win10远程桌面密码错误/2021-03-02-17-49-06.webp)
## 可能的其他问题
上述是最新的问题,相比较现在,以前也还有一些老问题会导致无法登录远程桌面。
* [如果凭据未在本地更新则Windows 10远程桌面登录失败](https://www.dell.com/support/kbdoc/zh-cn/000134994/%e5%a6%82%e6%9e%9c%e5%87%ad%e6%8d%ae%e6%9c%aa%e5%9c%a8%e6%9c%ac%e5%9c%b0%e6%9b%b4%e6%96%b0-%e5%88%99windows-10%e8%bf%9c%e7%a8%8b%e6%a1%8c%e9%9d%a2%e7%99%bb%e5%bd%95%e5%a4%b1%e8%b4%a5)
* 修改注册表
![](../images/踩坑记录-Win10远程桌面密码错误/2021-03-02-17-55-18.webp)

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
<svg id="图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" width="252.18" height="369" viewBox="0 0 252.18 369"><defs><style>.cls-1{fill:#666;}.cls-2,.cls-4{fill:#fff;}.cls-3{font-size:15px;font-family:Consolas;}.cls-4{stroke:#00a99d;}.cls-4,.cls-5{stroke-miterlimit:10;stroke-width:2px;}.cls-5{fill:none;stroke:#000;}</style></defs><rect class="cls-1" x="72.52" y="48" width="2" height="60"/><rect class="cls-2" x="1" y="1" width="148" height="48" rx="9"/><path class="cls-1" d="M901.48,182a8,8,0,0,1,8,8v30a8,8,0,0,1-8,8h-130a8,8,0,0,1-8-8V190a8,8,0,0,1,8-8h130m0-2h-130a10,10,0,0,0-10,10v30a10,10,0,0,0,10,10h130a10,10,0,0,0,10-10V190a10,10,0,0,0-10-10Z" transform="translate(-761.48 -180)"/><text class="cls-3" transform="translate(42.01 28.88)">Document</text><rect class="cls-2" x="1" y="107" width="148" height="48" rx="9"/><path class="cls-1" d="M901.48,288a8,8,0,0,1,8,8v30a8,8,0,0,1-8,8h-130a8,8,0,0,1-8-8V296a8,8,0,0,1,8-8h130m0-2h-130a10,10,0,0,0-10,10v30a10,10,0,0,0,10,10h130a10,10,0,0,0,10-10V296a10,10,0,0,0-10-10Z" transform="translate(-761.48 -180)"/><text class="cls-3" transform="translate(25.52 134.88)">Element <tspan class="cls-1" x="65.98" y="0">html</tspan></text><rect class="cls-2" x="1" y="213" width="148" height="48" rx="9"/><path class="cls-1" d="M901.48,394a8,8,0,0,1,8,8v30a8,8,0,0,1-8,8h-130a8,8,0,0,1-8-8V402a8,8,0,0,1,8-8h130m0-2h-130a10,10,0,0,0-10,10v30a10,10,0,0,0,10,10h130a10,10,0,0,0,10-10V402a10,10,0,0,0-10-10Z" transform="translate(-761.48 -180)"/><text class="cls-3" transform="translate(25.52 239.88)">Element <tspan class="cls-1" x="65.98" y="0">body</tspan></text><rect class="cls-2" x="1" y="320" width="148" height="48" rx="9"/><path class="cls-1" d="M901.48,501a8,8,0,0,1,8,8v30a8,8,0,0,1-8,8h-130a8,8,0,0,1-8-8V509a8,8,0,0,1,8-8h130m0-2h-130a10,10,0,0,0-10,10v30a10,10,0,0,0,10,10h130a10,10,0,0,0,10-10V509a10,10,0,0,0-10-10Z" transform="translate(-761.48 -180)"/><text class="cls-3" transform="translate(29.64 347.88)">Element <tspan class="cls-1" x="65.98" y="0">div</tspan></text><rect class="cls-1" x="72.52" y="154" width="2" height="60"/><rect class="cls-1" x="72.52" y="261" width="2" height="60"/><circle class="cls-4" cx="187.52" cy="346" r="20"/><text class="cls-3" transform="translate(183.39 349.88)">1</text><circle class="cls-4" cx="187.52" cy="237" r="20"/><text class="cls-3" transform="translate(183.39 240.88)">2</text><circle class="cls-4" cx="187.52" cy="133" r="20"/><text class="cls-3" transform="translate(183.39 136.88)">3</text><circle class="cls-4" cx="187.52" cy="25" r="20"/><text class="cls-3" transform="translate(183.39 28.88)">4</text><path class="cls-5" d="M973.67,532s78.85-55.35,12.31-98.08" transform="translate(-761.48 -180)"/><polygon points="212.18 247 233.49 249.85 225.35 254.55 225.4 263.95 212.18 247"/><path class="cls-5" d="M973.67,424s78.85-55.35,12.31-98.08" transform="translate(-761.48 -180)"/><polygon points="212.18 139 233.49 141.85 225.35 146.55 225.4 155.95 212.18 139"/><path class="cls-5" d="M973.67,313s78.85-55.35,12.31-98.08" transform="translate(-761.48 -180)"/><polygon points="212.18 28 233.49 30.85 225.35 35.55 225.4 44.95 212.18 28"/></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -1,7 +1,7 @@
---
title: pgp
date: 2020-02-23 17:03:44
comment: true
comment: 'valine'
---
<div class="markdown-body">

7379
yarn.lock Normal file

File diff suppressed because it is too large Load Diff