更新文章&更新主题
1. 踩坑记录 2. 装饰器模式 3. 更新主题至1.8.8
@ -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 地址,
|
||||
|
@ -39,12 +39,12 @@
|
||||
"hexo-renderer-marked": "^4.0.0",
|
||||
"hexo-renderer-stylus": "^2.0.1",
|
||||
"hexo-server": "^2.0.0",
|
||||
"hexo-theme-fluid": "^1.8.7",
|
||||
"npm-check-updates": "^11.1.9",
|
||||
"hexo-theme-fluid": "^1.8.8",
|
||||
"npm-check-updates": "^11.2.0",
|
||||
"nunjucks": "^3.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.13.8",
|
||||
"@babel/preset-env": "^7.13.8"
|
||||
"@babel/core": "^7.13.10",
|
||||
"@babel/preset-env": "^7.13.10"
|
||||
}
|
||||
}
|
||||
|
@ -493,3 +493,7 @@ for (let i = 0; i < 5; i++) {
|
||||
frag.append(ul);
|
||||
```
|
||||
|
||||
### Attr 类型
|
||||
|
||||
## 操作 DOM
|
||||
|
||||
|
169
source/_md/JavaScript中的事件.md
Normal file
@ -0,0 +1,169 @@
|
||||
JavaScript 和 HTML 直接的交互是通过事件实现的。当文档或者浏览器发生交互时,使用侦听器(处理程序)来预定事件,以便事件发生时执行相应的代码。在传统软件工程中被称之为观察员模式。
|
||||
|
||||
事件最早是在 IE3 和 Netscape Navigator 2 中出现的。
|
||||
|
||||
## 事件流
|
||||
|
||||
经常在一些文章中看到的事件冒泡或者捕获就是用于描述事件流的两种方式。在浏览器发展到 IE4 的时代时,人们开始考虑:如何确定页面的哪一部分拥有某个特定的事件?
|
||||
|
||||
要理解这个问题很简单,在 Web 文档中,需要绝大部分元素进行嵌套。如果我们单击了嵌套中的某个元素,那么也相当于同时单击了它的父元素。
|
||||
|
||||
就像将手指指向在一张纸上的同心圆最中心的那个圆上,相当于同时也指向了纸上所有的圆。
|
||||
|
||||
而我们的 HTML 结构也是同理,如同下发被弯曲成三个同心圆的`<div>`元素一样。当单击最中心的粉色圆时,外层的绿色和蓝色的圆圈都同时被点击了。因为他们是父级元素,包裹着中心的粉色圆圈。
|
||||
|
||||

|
||||
|
||||
<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
|
||||
|
||||

|
||||
|
||||
当在传播顺序中的某个元素上注册了对应事件的监听器,那么就会在当前元素上触发对应的事件:
|
||||
|
||||
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>`停止)。接下来就是“处于目标”阶段,如果在目标元素上监听了对应的事件,那么这个事件的触发被看成冒泡阶段的一部分。
|
||||
|
||||
## 事件处理程序
|
||||
|
||||
事件就是用户或者浏览器自身执行的某种动作。诸如 click、load 和 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 代码紧密耦合。
|
||||
|
@ -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 获取免费的证书很是方便,但是必须要三个月续期一次。
|
20
source/_md/第一个SPA的总结.md
Normal 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"
|
||||
```
|
||||
|
||||

|
395
source/_posts/JavaScript装饰器模式.md
Normal 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)
|
24
source/_posts/踩坑记录-Win10远程桌面密码错误.md
Normal 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 登录**”打开了,导致无法使用传统密码登入系统。也就直接导致了远程桌面无法登录。
|
||||
|
||||

|
||||
|
||||
## 可能的其他问题
|
||||
|
||||
上述是最新的问题,相比较现在,以前也还有一些老问题会导致无法登录远程桌面。
|
||||
|
||||
* [如果凭据未在本地更新,则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)
|
||||
* 修改注册表
|
||||
|
||||

|
||||
|
875
source/images/JavaScript中的事件/concentirc-circles.ai
Normal file
BIN
source/images/JavaScript中的事件/concentirc-circles.png
Normal file
After Width: | Height: | Size: 31 KiB |
1517
source/images/JavaScript中的事件/event-bubbling.ai
Normal file
1
source/images/JavaScript中的事件/event-bubbling.svg
Normal 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 |
BIN
source/images/JavaScript装饰器模式/logo.webp
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
source/images/第一个SPA的总结/2021-03-01-12-15-31.png
Normal file
After Width: | Height: | Size: 314 KiB |
BIN
source/images/踩坑记录-Win10远程桌面密码错误/2021-03-02-17-49-06.webp
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
source/images/踩坑记录-Win10远程桌面密码错误/2021-03-02-17-55-18.webp
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
source/images/踩坑记录-Win10远程桌面密码错误/logo.webp
Normal file
After Width: | Height: | Size: 4.2 KiB |