Merge branch 'master' into backup
@ -273,13 +273,6 @@ lazyload:
|
||||
# Icon library, which includes many social icons, does not include those theme dependent, so your can modify link by yourself. See: https://hexo.fluid-dev.com/docs/en/icon/
|
||||
iconfont: //at.alicdn.com/t/font_1736178_kmeydafke9r.css
|
||||
|
||||
# 主题版本相关
|
||||
# Theme version
|
||||
version:
|
||||
# 每次生成页面后,检测主题是否为最新版本
|
||||
# If true, check whether Fluid is the latest version after hexo generate
|
||||
check: false
|
||||
|
||||
|
||||
#---------------------------
|
||||
# 页头
|
||||
@ -395,7 +388,7 @@ footer:
|
||||
statistics:
|
||||
enable: false
|
||||
|
||||
# 统计数据来源,如果使用 leancloud 需要设置 `web_analytics: leancloud` 中的参数;如果使用 busuanzi 可能会有请求失败的情况
|
||||
# 统计数据来源,使用 leancloud 需要设置 `web_analytics: leancloud` 中的参数;使用 busuanzi 不需要额外设置,但是有时不稳定,另外本地运行时 busuanzi 显示统计数据很大属于正常现象,部署后会正常
|
||||
# Data source. If use leancloud, you need to set the parameter in `web_analytics: leancloud`
|
||||
# Options: busuanzi | leancloud
|
||||
source: "busuanzi"
|
||||
@ -595,6 +588,9 @@ post:
|
||||
# Zoom feature of images
|
||||
image_zoom:
|
||||
enable: true
|
||||
# 放大后图片链接替换规则,可用于将压缩图片链接替换为原图片链接,如 ['-slim', ''] 是将链接中 `-slim` 移除;如果想使用正则请使用 `re:` 前缀,如 ['re:\\d{3,4}\\/\\d{3,4}\\/', '']
|
||||
# The image url replacement when zooming, the feature can be used to replace the compressed image to the original image, eg: ['-slim', ''] removes `-slim` from the image url when zooming; if you want to use regular, use prefix `re:`, eg: ['re:\\d{3,4}\\/\\d{3,4}\\/','']
|
||||
img_url_replace: ['', '']
|
||||
|
||||
# 脚注语法,会在文章底部生成脚注,如果 Markdown 渲染器本身支持,则建议关闭,否则可能会冲突
|
||||
# Support footnote syntax, footnotes will be generated at the bottom of the post page. If the Markdown renderer itself supports it, please disable it, otherwise it may conflict
|
||||
@ -690,16 +686,16 @@ gitalk:
|
||||
pagerDirection: last
|
||||
distractionFreeMode: false
|
||||
createIssueManually: true
|
||||
# 默认 proxy 已失效,解决方法请见下方链接
|
||||
# The default proxy is invalid, please see the links for the solution
|
||||
# 默认 proxy 可能会失效,解决方法请见下方链接
|
||||
# The default proxy may be invalid, refer to the links for solutions
|
||||
# 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
|
||||
proxy: https://cors-anywhere.azm.workers.dev/https://github.com/login/oauth/access_token
|
||||
|
||||
# Valine
|
||||
# 基于 LeanCloud
|
||||
# Based on LeanCloud
|
||||
# See: https://valine.js.org/configuration.html
|
||||
# See: https://valine.js.org/
|
||||
valine:
|
||||
appid: dD9t7mcIBVzJWag5ez6GPy2v-MdYXbMMI
|
||||
appkey: bWG6pmKsEscrH4JjrpNNAAy6
|
||||
@ -711,11 +707,15 @@ valine:
|
||||
lang: zh-CN
|
||||
highlight: true
|
||||
recordIP: false
|
||||
serverURLs:
|
||||
serverURLs: ''
|
||||
emojiCDN:
|
||||
emojiMaps:
|
||||
enableQQ: false
|
||||
requiredFields: []
|
||||
|
||||
# Waline
|
||||
# 一款从 Valine 衍生的带后端的评论插件
|
||||
# A comment plugin with backend derived from Valine
|
||||
# 从 Valine 衍生而来,额外增加了服务端和多种功能
|
||||
# Derived from Valine, with self-hosted service and new features
|
||||
# See: https://waline.js.org/
|
||||
waline:
|
||||
serverURL: ''
|
||||
@ -726,11 +726,11 @@ waline:
|
||||
pageSize: 10
|
||||
lang: zh-CN
|
||||
highlight: true
|
||||
avatarCDN:
|
||||
avatarCDN: ''
|
||||
avatarForce: false
|
||||
requiredFields: []
|
||||
emojiCDN: ''
|
||||
emojiMaps: {}
|
||||
emojiCDN:
|
||||
emojiMaps:
|
||||
anonymous:
|
||||
|
||||
# 畅言 Changyan
|
||||
@ -749,14 +749,15 @@ livere:
|
||||
uid: ''
|
||||
|
||||
# Remark42
|
||||
# 需要自己运行后端服务
|
||||
# Need to run the backend service yourself
|
||||
# 需要自托管服务端
|
||||
# Based on self-hosted service
|
||||
# See: https://remark42.com
|
||||
remark42:
|
||||
host:
|
||||
site_id:
|
||||
max_shown_comments: 10
|
||||
locale: zh
|
||||
components: ['embed']
|
||||
|
||||
# Twikoo
|
||||
# 基于腾讯云开发
|
||||
@ -767,6 +768,15 @@ twikoo:
|
||||
region: ap-shanghai
|
||||
path: window.location.pathname
|
||||
|
||||
# Cusdis
|
||||
# 基于第三方服务或自托管服务
|
||||
# Based on third-party or self-hosted service
|
||||
# See https://cusdis.com
|
||||
cusdis:
|
||||
host:
|
||||
app_id:
|
||||
lang: zh-cn
|
||||
|
||||
#---------------------------
|
||||
# 归档页
|
||||
# Archive Page
|
||||
@ -955,44 +965,44 @@ static_prefix:
|
||||
internal_css: /css
|
||||
internal_img: /images/img
|
||||
|
||||
anchor: https://cdn.jsdelivr.net/npm/anchor-js@4.3.0/
|
||||
anchor: https://cdn.jsdelivr.net/npm/anchor-js@4.3.1/
|
||||
|
||||
github_markdown: https://cdn.jsdelivr.net/npm/github-markdown-css@4.0.0/
|
||||
|
||||
jquery: https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/
|
||||
jquery: https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/
|
||||
|
||||
bootstrap: https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/
|
||||
bootstrap: https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/
|
||||
|
||||
highlightjs: https://cdn.jsdelivr.net/npm/highlight.js@10.4.0/
|
||||
highlightjs: https://cdn.jsdelivr.net/npm/highlight.js@10.7.2/
|
||||
|
||||
prismjs: https://cdn.jsdelivr.net/npm/prismjs@1.22.0/
|
||||
prismjs: https://cdn.jsdelivr.net/npm/prismjs@1.23.0/
|
||||
|
||||
tocbot: https://cdn.jsdelivr.net/npm/tocbot@4.12.0/dist/
|
||||
tocbot: https://cdn.jsdelivr.net/npm/tocbot@4.12.3/dist/
|
||||
|
||||
typed: https://cdn.jsdelivr.net/npm/typed.js@2.0.11/lib/
|
||||
typed: https://cdn.jsdelivr.net/npm/typed.js@2.0.12/lib/
|
||||
|
||||
fancybox: https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@3.5.7/dist/
|
||||
|
||||
nprogress: https://cdn.jsdelivr.net/npm/nprogress@0.2.0/
|
||||
|
||||
mathjax: https://cdn.jsdelivr.net/npm/mathjax@3.1.2/es5/
|
||||
mathjax: https://cdn.jsdelivr.net/npm/mathjax@3.1.4/es5/
|
||||
|
||||
katex: https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/
|
||||
katex: https://cdn.jsdelivr.net/npm/katex@0.13.10/dist/
|
||||
|
||||
busuanzi: https://busuanzi.ibruce.info/busuanzi/2.3/
|
||||
|
||||
clipboard: https://cdn.jsdelivr.net/npm/clipboard@2.0.6/dist/
|
||||
clipboard: https://cdn.jsdelivr.net/npm/clipboard@2.0.8/dist/
|
||||
|
||||
mermaid: https://cdn.jsdelivr.net/npm/mermaid@8.8.3/dist/
|
||||
mermaid: https://cdn.jsdelivr.net/npm/mermaid@8.10.1/dist/
|
||||
|
||||
valine: https://cdn.jsdelivr.net/npm/valine@1.4.14/dist/
|
||||
|
||||
waline: https://cdn.jsdelivr.net/npm/@waline/client@0.14.8/dist/
|
||||
waline: https://cdn.jsdelivr.net/npm/@waline/client@0.16.2/dist/
|
||||
|
||||
gitalk: https://cdn.jsdelivr.net/npm/gitalk@1.7.0/dist/
|
||||
gitalk: https://cdn.jsdelivr.net/npm/gitalk@1.7.2/dist/
|
||||
|
||||
disqusjs: https://cdn.jsdelivr.net/npm/disqusjs@1.0/dist/
|
||||
disqusjs: https://cdn.jsdelivr.net/npm/disqusjs@1.3.0/dist/
|
||||
|
||||
twikoo: https://cdn.jsdelivr.net/npm/twikoo@1.3.0/dist/
|
||||
twikoo: https://cdn.jsdelivr.net/npm/twikoo@1.3.1/dist/
|
||||
|
||||
hint: /lib/hint/
|
||||
|
@ -38,13 +38,13 @@
|
||||
"hexo-renderer-ejs": "^1.0.0",
|
||||
"hexo-renderer-marked": "^4.0.0",
|
||||
"hexo-renderer-stylus": "^2.0.1",
|
||||
"hexo-theme-fluid": "^1.8.10",
|
||||
"hexo-theme-fluid": "^1.8.11",
|
||||
"nunjucks": "^3.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.2",
|
||||
"@babel/preset-env": "^7.14.2",
|
||||
"npm-check-updates": "^11.5.11",
|
||||
"hexo-server": "^2.0.0"
|
||||
"hexo-server": "^2.0.0",
|
||||
"npm-check-updates": "^11.5.11"
|
||||
}
|
||||
}
|
||||
|
116
source/_md/C Primer Plus.md
Normal file
@ -0,0 +1,116 @@
|
||||
## 数据类型
|
||||
|
||||
### 整型
|
||||
|
||||
通常`int`占用 16 位或 32 位。现在常见的设置是`long long`占 64 位,`long`占 32 位,`short`占 16 位。
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
/* 64位整数 */
|
||||
long long int decNum;
|
||||
|
||||
printf("input a dec number: ");
|
||||
scanf("%lld", &decNum);
|
||||
/* 转换对应的进制 */
|
||||
printf("dec = %lld; octal = %llo, hex = %llx\n", decNum, decNum, decNum);
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### char 类型
|
||||
|
||||
char 类型用于存储字符(如,字母或标点符号),从技术层面看,char 是整型。因为它存储的是整数,用来编码成对应的字符。
|
||||
|
||||
char 类型与字符串不同,char 使用单引号。
|
||||
|
||||
```c
|
||||
/* 单个字符使用单引号 */
|
||||
char itable = 'a';
|
||||
/* 不能存储字符串 */
|
||||
char name = "xfy";
|
||||
```
|
||||
|
||||
字符串是以空字符`\0`结尾的 char 类型数组。
|
||||
|
||||
## 数组
|
||||
|
||||
### 指向多维数组的指针
|
||||
|
||||
指向多维数组的指针必须指向包含多个特定类型的值:
|
||||
|
||||
```c
|
||||
int (*pz) [2]; // pz 指向一个内含两个 int 类型值的数组
|
||||
```
|
||||
|
||||
数组的名称指向第一个元素的地址,当指针被赋值数组之后,访问指针与使用数组名相同。
|
||||
|
||||
```c
|
||||
pz[0][0];
|
||||
*pz[0];
|
||||
**pz;
|
||||
```
|
||||
|
||||
### 数组与指针
|
||||
|
||||
通常,字符串都作为可执行文件的一部分存储在数据段中。当把程序载入内存时,也载入了程序中的字符串。字符串储存在静态存储区(static memory)中。但是,程序在开始运行时才会为字符串数组分配内存。此时,才将字符串从拷贝到数组中。而此时,字符串有两个副本。一个是在静态内存中的字符串字面量,另一个是存储在数组中的字符串。
|
||||
|
||||
在数组形式中,数组名称是常量。不能更改数组名`ch`,如果改变了`ch`,则意味着改变了数组的存储位置(即地址)。可以进行类似于`ch + 1`这样的操作,标识数组的下一个元素。但是不允许进行`++ch`这样的操作。
|
||||
|
||||
执行形式`char* pt`也使得编译器为字符串在静态储存区预留了对应的元素空间。另外,一旦开始执行程序,它会为指针变量`pt`留出一个储存位置,并把字符串的地址存储在指针变量中。该变量最初指向该字符串的首字符,但是它的值可以改变。因此,可以对指针变量使用递增运算符。例如,使用`++pt`将指向第 2 个字符。
|
||||
|
||||
```c
|
||||
/* addresses.c -- 打印字符串数组与指针字符串的地址 */
|
||||
#include <stdio.h>
|
||||
#define MSG "I am special"
|
||||
int main(void)
|
||||
{
|
||||
char ch[] = MSG;
|
||||
const char* pt = MSG;
|
||||
printf("addresse of MSG: %p \n", MSG);
|
||||
printf("addresse of \"I am special\": %p \n", "I am special");
|
||||
printf("addresse of ch: %p \n", ch);
|
||||
printf("addresse of pt: %p \n", pt);
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
在这段程序中,分别打印了字符串常量、数组以及指针字符串的地址。在 gcc 编译的结果后,可以看到,指针以及字符串字面量的地址都相同,而数组字符串使用了另一个地址。字符串字面量出现多次时,编译器只用了一个存储位置,而且与 MSG 相同。
|
||||
|
||||
```
|
||||
addresse of MSG: 0x5618ff84a008
|
||||
addresse of "I am special": 0x5618ff84a008
|
||||
addresse of ch: 0x7ffe41c8a5fb
|
||||
addresse of pt: 0x5618ff84a008
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 字符串输入
|
||||
|
||||
如果想读取一段字符串,就必须先储存该字符串的空间,然后利用字符串获取函数来获取字符串。
|
||||
|
||||
### 分配空间
|
||||
|
||||
这段代码虽然可能会通过编译,但指针 name 是未初始化的指针,name 可能指向任何地方,甚至擦写程序中的数据或代码。
|
||||
|
||||
```c
|
||||
char* name;
|
||||
scanf("%s", &name);
|
||||
```
|
||||
|
||||
最简单的方法是声明时指定数组大小:
|
||||
|
||||
```c
|
||||
char words[50];
|
||||
```
|
||||
|
||||
### 不幸的`gets()`
|
||||
|
||||
`gets()`函数不会检查数组的长度,也就是它不知道数组何时会结束。如果输入的字符过长,就会导致缓冲区溢出(buffer overflow)。
|
||||
|
||||
在类 UNIX 系统中,会提示`Segmentation fault`。
|
223
source/_md/JavaScript代理与反射.md
Normal file
@ -0,0 +1,223 @@
|
||||
ECMAScript 6 新增的代理和反射为开发者提供了拦截并向基本操作嵌入额外行为的能力。
|
||||
|
||||
## 反射 API
|
||||
|
||||
在研究代理之前,应该先看下反射。与 Math 类似,Reflect 对象不是类,尽管他们都是大写开头的。它的属性只是定义了一组相关的方法。这些 ES6 添加的函数为“反射”对象及其属性定义了一套 API。
|
||||
|
||||
Reflect 对象在同一个命名空间里定义了一组边界函数,这些函数可以模拟核心语言语法的行为,复制各种既有对象功能的特性。这组 Reflect 函数一一对应后续的 Proxy 处理器方法。 这些方法基本上都对应了语言的常规语法,利用在 Proxy 处理器上,可以提供更好的嵌入行为的能力。
|
||||
|
||||
反射 API 所包含的方法可以在 MDN 所查询到:[Reflect - JavaScript | MDN (mozilla.org)](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect)
|
||||
|
||||
## 代理
|
||||
|
||||
代理对象是目标对象的抽象。利用代理对象,可以修改 JavaScript 对象的基础行为。上述所介绍的反射 API 可以直接对 JavaScript 对象持续基础操作,而 Proxy 则提供了另一种途径,只要在代理上调用,所有捕获器都会拦截他们对应的反射 API 操作。使得我们能创建普通对象无法企及能力的代理对象。
|
||||
|
||||
Proxy 是一个类,创建一个代理对象通过 Proxy 类来构造。同时它还接收两个必须的参数,即目标对象`target`与处理对象`handler`。缺少任何一个参数都会抛出 TypeError。
|
||||
|
||||
```js
|
||||
const proxyObj = new Proxy(target, handler);
|
||||
```
|
||||
|
||||
### 无操作转发代理
|
||||
|
||||
无操作转发代理,也就是最简单的空代理,即除了作为一个抽象的目标对象,什么也不做。默认情况下,在代理对象上执行的所有操作都会无障碍地传播到目标对象。
|
||||
|
||||
要创建空代理,可传入一个简单的空对象作为处理器对象,从而让所有操作都畅通无阻地抵达目标对象。
|
||||
|
||||
```js
|
||||
const obj = {
|
||||
name: 'xfy',
|
||||
};
|
||||
|
||||
const proxyObj = new Proxy(obj, {});
|
||||
|
||||
proxyObj.name
|
||||
// "xfy"
|
||||
proxyObj.name = '123'
|
||||
// "123"
|
||||
obj
|
||||
// {name: "123"}
|
||||
```
|
||||
|
||||
### 捕获与反射 API
|
||||
|
||||
代理的主要目的是定义**捕获器**(trap)。捕获器就是在处理器对象中定义的“基本操作的拦截器”。每个处理器都可以包含零个或多个捕获器,每个捕获器都对应一种基本操作,可以直接或间接在代理对象上调用。每次在代理对象上调用这些基本操作时,代理可以在这些操作转发到目标对象前先调用捕获器函数,从而拦截并修改相应的行为。
|
||||
|
||||
例如,可以定义一个`get()`捕获器,在 ECMAScript 操作`[[Get]]`算法时触发:
|
||||
|
||||
```js
|
||||
const obj = {
|
||||
name: 'xfy',
|
||||
};
|
||||
|
||||
const handler = {
|
||||
get(target, property, receiver) {
|
||||
return [target, property, receiver];
|
||||
},
|
||||
};
|
||||
|
||||
const proxyObj = new Proxy(obj, handler);
|
||||
|
||||
proxyObj.name // [{…}, "name", Proxy]
|
||||
```
|
||||
|
||||
这里的`get()`捕获器看上去和属性访问器很类似,事实上对应的`set()`操作也很类似。但他们并不是属性访问器,最简单的判断就是属性访问器对象的`get/set()`操作需要一个属性名称,而代理对象的捕获器会拦截所有对应的操作,并通过参数的形式访问名称、属性等。
|
||||
|
||||
这是不是看上去和反射 API 有点相似?没错,反射 API 就是这种用法:
|
||||
|
||||
```js
|
||||
// Object
|
||||
var obj = { x: 1, y: 2 };
|
||||
Reflect.get(obj, "x"); // 1
|
||||
```
|
||||
|
||||
事实上,捕获器和反射 API 提供的方法一一对应。这些方法与捕获器拦截的方法具有相同的名称和函数签名,而且也具有被拦截方法相同的行为。
|
||||
|
||||
也就是说可以这样创建一个空代理对象:
|
||||
|
||||
```js
|
||||
const obj = {
|
||||
name: 'xfy',
|
||||
};
|
||||
|
||||
const handler = {
|
||||
get(target, property, receiver) {
|
||||
return Reflect.get(...arguments);
|
||||
},
|
||||
};
|
||||
|
||||
const proxyObj = new Proxy(obj, handler);
|
||||
```
|
||||
|
||||
甚至更直接一点
|
||||
|
||||
```js
|
||||
const handler = {
|
||||
get: Reflect.get,
|
||||
};
|
||||
```
|
||||
|
||||
所有捕获器都可以基于自己的参数重建原始操作,但并非所有捕获器都像`get()`操作一样简单。所以,通过手写所有捕获器来如法炮制的想法是不现实的。好在,反射 API 为我们提供了便捷,我们不需要手动重建原始行为,而是可以通过调用全局 Reflect 对象上同名的方法来轻松创建。
|
||||
|
||||
```js
|
||||
const obj = {
|
||||
name: 'xfy',
|
||||
};
|
||||
|
||||
const handler = {
|
||||
get(target, property, receiver) {
|
||||
let decoration = '';
|
||||
property === 'name' ? (decoration = '! yyds!') : void 0;
|
||||
return Reflect.get(...arguments) + decoration;
|
||||
},
|
||||
};
|
||||
|
||||
const proxyObj = new Proxy(obj, handler);
|
||||
```
|
||||
|
||||
### 捕获不变式
|
||||
|
||||
虽然使用捕获器几乎可以改变所有基本方法的行为,但也不是没有限制。根据 ECMAScript 规范,每个捕获的方法都知道目标对象上下文、捕获函数签名,而捕获器的行为必须遵循“捕获器不变式”(trap invariant)。捕获器不变式因方法不同而异,但通常都会防止捕获器定义出现过于反常的行为。
|
||||
|
||||
例如,目标对象有一个不可写且不可配置的属性,那么捕获器在返回一个与该属性不同的值,会抛出 TypeError。
|
||||
|
||||
```js
|
||||
const target = {};
|
||||
|
||||
Object.defineProperty(target, 'foo', {
|
||||
configurable: false,
|
||||
writable: false,
|
||||
value: 'bar',
|
||||
});
|
||||
|
||||
const handler = {
|
||||
get() {
|
||||
return 'bazzz';
|
||||
},
|
||||
};
|
||||
|
||||
const proxyObj = new Proxy(target, handler);
|
||||
```
|
||||
|
||||
### 可撤销代理
|
||||
|
||||
使用 new 关键字创建的普通代理对象与目标对象之间会在声明周期内一直存在联系。Proxy 暴露了`revocable()`静态方法,使其可以撤销代理对象与目标对象的关联。
|
||||
|
||||
```js
|
||||
const target = {
|
||||
foo: 'bar',
|
||||
};
|
||||
|
||||
const handler = {
|
||||
get(target, property, recevier) {
|
||||
let decoration = '';
|
||||
property === 'foo' ? (decoration = '!!!') : void 0;
|
||||
return Reflect.get(...arguments) + decoration;
|
||||
},
|
||||
};
|
||||
|
||||
const { proxy, revoke } = Proxy.revocable(target, handler);
|
||||
|
||||
console.log(proxy.foo);
|
||||
console.log(target.foo);
|
||||
|
||||
revoke();
|
||||
|
||||
console.log(proxy.foo);
|
||||
```
|
||||
|
||||
### 代理另一个代理
|
||||
|
||||
代理允许多层嵌套,可以创建一个代理,通过它去代理另一个代理。这样就可以在目标对象之上构建多层拦截网络。
|
||||
|
||||
```js
|
||||
const target = {
|
||||
foo: 'bar',
|
||||
};
|
||||
|
||||
const firstProxy = new Proxy(target, {
|
||||
get() {
|
||||
return Reflect.get(...arguments) + 'first proxy!';
|
||||
},
|
||||
});
|
||||
|
||||
const secondProxy = new Proxy(firstProxy, {
|
||||
get() {
|
||||
return Reflect.get(...arguments) + 'second proxy!';
|
||||
},
|
||||
});
|
||||
|
||||
console.log(firstProxy.foo);
|
||||
console.log(secondProxy.foo);
|
||||
```
|
||||
|
||||
### 代理的问题与不足
|
||||
|
||||
代理是在 ECMAScript 现有基础上构建起来的一套新 API,因此其实已经尽力做到最好了。很大程度上,代理作为对象的虚拟层可以正常使用。但在某些情况下,代理也不能与现在的 ECMAScript 机制很好的协同。
|
||||
|
||||
#### 代理中的 this
|
||||
|
||||
方法中的 this 通常指向调用这个方法的对象:
|
||||
|
||||
```js
|
||||
const target = {
|
||||
showThis() {
|
||||
console.log(this);
|
||||
console.log(this.foo);
|
||||
console.log(this === proxy);
|
||||
},
|
||||
foo: 'bar',
|
||||
};
|
||||
|
||||
const proxy = new Proxy(target, {});
|
||||
|
||||
proxy.showThis()
|
||||
// Proxy {foo: "bar", showThis: ƒ}
|
||||
// bar
|
||||
// true
|
||||
```
|
||||
|
||||
在代理中亦是如此,符合预期行为。
|
||||
|
||||
#### 代理与内部槽位
|
||||
|
@ -40,8 +40,6 @@ db.names.insertOne({name:'xfy'})
|
||||
|
||||
id 不可修改。
|
||||
|
||||

|
||||
|
||||
## 关系型数据库
|
||||
|
||||
### ACID 设计模式
|
||||
|
@ -1,120 +0,0 @@
|
||||
## 函数
|
||||
|
||||
### 重载
|
||||
|
||||
重载函数:有多个调用签名的函数。
|
||||
|
||||
在多数编程语言中,声明函数时一旦指定了特定的参数和返回类型,就只能使用相应的参数调用参数。但 JavaScript 是一门动态语言,势必需要以多种方式调用一个函数的方法。不仅如此,而且有时输出的类型取决于参数的类型。
|
||||
|
||||
TypeScript 也支持动态重载函数声明,而且函数的输出类型却决于输入类型。
|
||||
|
||||
一个普通的函数类型注解:
|
||||
|
||||
```ts
|
||||
type Reserve = {
|
||||
(from: Date, to: Date, destination: string): string;
|
||||
};
|
||||
|
||||
const reserve: Reserve = (from,toOrDest, destination) => {
|
||||
const cost = Math.floor(Math.random() * (998 - 199) + 198);
|
||||
return `To ${destination} need ${cost} ${from.toLocaleString()}`
|
||||
};
|
||||
console.log(reserve(new Date(), new Date(), 'bali'));
|
||||
```
|
||||
|
||||
函数的重载需要在注解时定义多个类型,在函数声明时需要手动组合两个签名:
|
||||
|
||||
```ts
|
||||
type Reserve = {
|
||||
(from: Date, to: Date, destination: string): string;
|
||||
(fr om: Date, destination: string): string;
|
||||
};
|
||||
|
||||
const reserve: Reserve = (
|
||||
// 参数需要手动在注解并组合两个签名
|
||||
from: Date,
|
||||
toOrDest: Date | string,
|
||||
destination?: string
|
||||
) => {
|
||||
const cost = Math.floor(Math.random() * (998 - 199) + 198);
|
||||
if (toOrDest instanceof Date && destination !== undefined) {
|
||||
return `To ${destination} need ${cost} ${from.toLocaleString()} ${toOrDest.toLocaleString()}`;
|
||||
} else {
|
||||
return `To ${toOrDest} need ${cost} ${from.toLocaleString()}`;
|
||||
}
|
||||
};
|
||||
console.log(reserve(new Date(), new Date(), 'bali'));
|
||||
console.log(reserve(new Date(), 'bali'));
|
||||
```
|
||||
|
||||
### 多态
|
||||
|
||||
使用具体类型的前提是明确知道需要什么类型,并且想确认传入的确实是那个类型。但是,有时事先并不知道需要什么类型,不想限制函数只能接受某个类型。
|
||||
|
||||
这种情况可以使用函数泛型(generic type)参数。
|
||||
|
||||
> 在类型层面施加约束的占位类型,也称多态类型参数。
|
||||
|
||||
例如我们重写一个简单的数组 filter 方法,在我们老朋友 JavaScript 中,这是一个很基本的函数。
|
||||
|
||||
```js
|
||||
const filter = (arr, fn) => {
|
||||
let result = [];
|
||||
for (const i of arr) {
|
||||
if (fn(i)) {
|
||||
result.push(i);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
```
|
||||
|
||||
但在 TypeScript 中,为了类型保护我们就需要为其添加类型注解。而这个函数的特点就是他可以(需要)接受多种类型的参数,且函数的返回值也是跟着参数的类型变化的。
|
||||
|
||||
也许利用重载为其注解所有类型也是可以的:
|
||||
|
||||
```ts
|
||||
type Filter = {
|
||||
(arr: number[], fn: (item: number) => boolean): number[];
|
||||
(arr: string[], fn: (item: string) => boolean): string[];
|
||||
};
|
||||
```
|
||||
|
||||
但是如果遇到了对象数组呢:
|
||||
|
||||
```ts
|
||||
type Filter = {
|
||||
(arr: number[], fn: (item: number) => boolean): number[];
|
||||
(arr: string[], fn: (item: string) => boolean): string[];
|
||||
(arr: Object[], fn: (item: Object) => boolean): Object[];
|
||||
};
|
||||
```
|
||||
|
||||
Object 无法描述对象结构,它可以代表所有类似对象的结构。
|
||||
|
||||
这时候就需要使用泛型了:
|
||||
|
||||
```ts
|
||||
type Filter = {
|
||||
<T>(arr: T[], fn: (item: T) => boolean): T[];
|
||||
};
|
||||
```
|
||||
|
||||
这里泛型的意思是:filter 函数使用一个泛型参数 T,在事先我们不知道其具体的类型。TypeScript 从传入的 arr 参数种推导出 T 的类型。调用 filter 时,T 的类型被推导出后,将把 T 出现的每一处替换为推导出的类型。T 就像是一个占位类型,类型检查器会根据上下文填充具体的类型。T 把 Filter 的类型参数化了,因此才称其为泛型参数。
|
||||
|
||||
#### 泛型作用域
|
||||
|
||||
泛型声明的位置不仅限定了泛型的作用域,还决定了 TypeScript 什么时候为泛型绑定具体的类型。
|
||||
|
||||
例如之前 Filter 函数的类型:
|
||||
|
||||
```ts
|
||||
type Filter = {
|
||||
<T>(arr: T[], fn: (item: T) => boolean): T[];
|
||||
};
|
||||
const filter:Filter = (arr, fn) // ...
|
||||
```
|
||||
|
||||
`<T>`在调用签名中声明(位于签名开始的括号前),TypeScript 将在调用 Filter 类型的函数时为 T 绑定具体类型。
|
||||
|
||||
而如果把 T 的作用域
|
@ -1,7 +1,7 @@
|
||||
# P181
|
||||
|
||||
## 注入
|
||||
|
||||
|
||||
|
||||
配置对象中的部分内容会被提取到 Vue 实例中:
|
||||
|
||||
* `data`
|
||||
@ -1399,10 +1399,10 @@ Promise 被 resolve 或 reject 时称为:settled。
|
||||
|
||||
与回调函数相比:
|
||||
|
||||
| Promises | Callbacks |
|
||||
| :----------------------------------------------------------- | :----------------------------------------------------------- |
|
||||
| Promises 允许我们按照自然顺序进行编码。首先,我们运行 `loadScript` 和 `.then` 来处理结果。 | 在调用 `loadScript(script, callback)` 时,在我们处理的地方(disposal)必须有一个 `callback` 函数。换句话说,在调用 `loadScript` **之前**,我们必须知道如何处理结果。 |
|
||||
| 我们可以根据需要,在 promise 上多次调用 `.then`。每次调用,我们都会在“订阅列表”中添加一个新的“分析”,一个新的订阅函数。在下一章将对此内容进行详细介绍:[Promise 链](https://zh.javascript.info/promise-chaining)。 | 只能有一个回调。 |
|
||||
| Promises | Callbacks |
|
||||
| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Promises 允许我们按照自然顺序进行编码。首先,我们运行 `loadScript` 和 `.then` 来处理结果。 | 在调用 `loadScript(script, callback)` 时,在我们处理的地方(disposal)必须有一个 `callback` 函数。换句话说,在调用 `loadScript` **之前**,我们必须知道如何处理结果。 |
|
||||
| 我们可以根据需要,在 promise 上多次调用 `.then`。每次调用,我们都会在“订阅列表”中添加一个新的“分析”,一个新的订阅函数。在下一章将对此内容进行详细介绍:[Promise 链](https://zh.javascript.info/promise-chaining)。 | 只能有一个回调。 |
|
||||
|
||||
### Promise 的三种状态
|
||||
|
||||
|
38
source/_md/Vue电商管理系统.md
Normal file
@ -0,0 +1,38 @@
|
||||
## 登录概述
|
||||
|
||||
登录业务流程
|
||||
|
||||
1. 在登陆页面输入用户名和密码
|
||||
2. 调用后台接口进行验证
|
||||
3. 通过验证后,根据后台的相应状态跳转到项目主页
|
||||
|
||||
登录业务相关技术点
|
||||
|
||||
* HTTP 是无状态的
|
||||
* 通过 cookie 在客户端记录状态
|
||||
* 通过 session 在服务端记录状态
|
||||
* 通过 token 方式维持状态
|
||||
|
||||
如果前端与服务器不存在跨域,则使用 cookie + session 方式维持状态,如果存在跨域,则使用 token 的方式。
|
||||
|
||||
### token 原理分析
|
||||
|
||||
当客户端尝试登陆后,会触发如下阶段:
|
||||
|
||||
1. 登录页面输入用户名和密码进行登录;
|
||||
2. 服务器验证通过之后生产该用户的 token 并返回;
|
||||
3. 客户端接收到后存储该 token;
|
||||
4. 后续客户端所有请求都携带该 token;
|
||||
5. 服务器收到 token 后验证是否通过;
|
||||
|
||||

|
||||
|
||||
## 登录页面
|
||||
|
||||
使用 element-plus 来进行布局,使用到了如下的组件:
|
||||
|
||||
* `el-form`
|
||||
* `el-form-item`
|
||||
* `el-input`
|
||||
* `el-button`
|
||||
* 字体图标
|
113
source/_md/你不知道的JavaScript-中.md
Normal file
@ -0,0 +1,113 @@
|
||||
## 值与引用
|
||||
|
||||
在许多编程语言中,赋值和参数传递可以通过值赋值(value-copy)或者引用复制(reference-copy)来完成。
|
||||
|
||||
例如在 C 中,传递一个引用值可以通过声明类似于这样的`int* num`参数来按引用传递,如果传递一个变量 x,那么`num`就是指向 x 的引用。引用就是指向变量的指针,如果不声明为引用的话,参数值总是通过值来传递的。即便是复杂的对象值也是如此(C++)。
|
||||
|
||||
与 C/C++ 不同的是,JavaScript 没有指针这一概念,值的传递方式完全由值来决定。JavaScript 中变量不可能成为指向另一个变量的指针。
|
||||
|
||||
基本类型(简单类型)的值总是通过以值复制的方式来赋值/传递,这些类型包括:`null`、`undefined`、字符串、数字、布尔和`symbol`。
|
||||
|
||||
而复合值,也就是对象(以及对象的子类型,数组、包装对象等)和函数,则总是以引用复制的方式来赋值/传递。
|
||||
|
||||
在了解了基本类型和引用类型的值之后,先来看下他们传递有什么不同:
|
||||
|
||||
基本类型:
|
||||
|
||||
由于基本类型是按值传递的,所以 a 与 b 是分别在内存中两处保存了自己的值。a 有在内存中有自己的空间,b 也有自己单独的空间,他们互不影响。
|
||||
|
||||
```js
|
||||
let a = 123;
|
||||
let b = a; // 按值进行传递
|
||||
|
||||
a += 1; // 修改 a
|
||||
console.log(a); // 124
|
||||
console.log(b); // 123 b 不受影响
|
||||
```
|
||||
|
||||
引用类型:
|
||||
|
||||
引用值的情况正好相反,所谓按引用传递,就是`arr1`与`arr2`指向的是内存中的同一块地址,**修改**任何一个变量的值,都会立即反应到另一个变量上。因为他们对应的是同一块内存。
|
||||
|
||||
```js
|
||||
let arr1 = [1, 2, 3];
|
||||
let arr2 = arr1;
|
||||
|
||||
arr1.push(99); // 修改 aar1
|
||||
console.log(arr1); // [ 1, 2, 3, 99 ]
|
||||
console.log(arr2); // [ 1, 2, 3, 99 ]
|
||||
```
|
||||
|
||||
但是引用值还有个特性容易犯错,那就是修改:
|
||||
|
||||
这里咋一看是修改了`arr1`的值,但为什么没有反应到`arr2`身上呢?说好的一起变呢?
|
||||
|
||||
仔细回想一下引用值的定义,他们是因为指向同一块内存地址,所以修改这段地址中的值时,就会同时反应在两个变量上。但是这里的`arr1 = { name: 'xfy' }`并不是修改内存中的值,而是修改了`arr1`的指向,使其指向一块新的内存地址。而`arr2`还是指向以前的地址,所以`arr2`没有改变。
|
||||
|
||||
```js
|
||||
let arr1 = [1, 2, 3];
|
||||
let arr2 = arr1;
|
||||
|
||||
arr1 = { name: 'xfy' };
|
||||
console.log(arr1); // { name: 'xfy' }
|
||||
console.log(arr2); // [ 1, 2, 3, 99 ]
|
||||
```
|
||||
|
||||
### 使用函数修改值
|
||||
|
||||
由于上述值的传递特性,这也会导致在传递给函数参数时发生个中问题。
|
||||
|
||||
修改引用值:
|
||||
|
||||
```js
|
||||
function changeValue(value) {
|
||||
// 按引用传递,可以直接修改
|
||||
value.push(99);
|
||||
// 重新赋值,并没有修改内存中的值
|
||||
value = { name: 'xfy' };
|
||||
}
|
||||
|
||||
let arr = [1, 2, 3];
|
||||
changeValue(arr);
|
||||
console.log(arr); // [ 1, 2, 3, 99 ]
|
||||
```
|
||||
|
||||
修改基本值:
|
||||
|
||||
```js
|
||||
function changeValue(value) {
|
||||
// 按值传递,value 获取到 num 的值
|
||||
// 但是他们分别保存在两个内存中
|
||||
value++;
|
||||
}
|
||||
|
||||
let num = 123;
|
||||
changeValue(num);
|
||||
console.log(num); // 123
|
||||
```
|
||||
|
||||
这也就是为什么在 Vue3 的 Composition API 中使用 ref 封装的响应式变量必须要有`.value`属性。
|
||||
|
||||

|
||||
|
||||
## 强制类型转换
|
||||
|
||||
将值从一种类型转换为另一种类型通常称为类型转换(type casting),这是显式的情况;隐式的情况称为强制类型转换(coercion)。
|
||||
|
||||
也可以这样来区分:类型转换发生在静态类型语言的编译阶段,而强制类型转换则发生在动态类型语言的运行时(runtime)。
|
||||
|
||||
### 抽象值操作
|
||||
|
||||
在了解强制类型之前,我们需要先掌握类型之间转换的基本规则。ES5 规范第 9 节定义了一些“抽象操作”和转换规则。
|
||||
|
||||
#### ToString
|
||||
|
||||
ToString 负责非字符串到字符串的强制类型转换操作。
|
||||
|
||||
基本值转换为字符串的规则为直接添加双引号:null 转换为`"null"`,true 转换为`"true"`等。数字也遵循这种规则,不过极大或极小的数字使用指数形式。
|
||||
|
||||
```js
|
||||
(1.07 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000).toString()
|
||||
// "1.07e+21"
|
||||
```
|
||||
|
@ -4,6 +4,38 @@
|
||||
|
||||
* [How to watch and reload ts-node when TypeScript files change](https://stackoverflow.com/questions/37979489/how-to-watch-and-reload-ts-node-when-typescript-files-change)
|
||||
|
||||
```bash
|
||||
yarn add eslint prettier eslint-plugin-prettier eslint-config-prettier -D
|
||||
```
|
||||
|
||||
```bash
|
||||
yarn add @typescript-eslint/parser @typescript-eslint/eslint-plugin -D
|
||||
```
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
node: true,
|
||||
},
|
||||
parser: '@typescript-eslint/parser',
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'prettier',
|
||||
],
|
||||
plugins: ['@typescript-eslint', 'prettier'],
|
||||
parserOptions: {
|
||||
ecmaVersion: 12,
|
||||
sourceType: 'module',
|
||||
},
|
||||
rules: {},
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 参数
|
||||
|
||||
参数`ctx`是由koa传入的封装了request和response的变量,我们可以通过它访问request和response,`next`是koa传入的将要处理的下一个异步函数。
|
||||
@ -265,5 +297,3 @@ app.use(async (ctx, next) => {
|
||||
});
|
||||
```
|
||||
|
||||
## mongodb
|
||||
|
||||
|
96
source/_md/尾调用优化.md
Normal file
@ -0,0 +1,96 @@
|
||||
ECMAScript 规范新增了一项内存管理优化机制,让 JavaScript 引擎再满足条件时可以重用函数栈帧。这项优化非常适合使用“尾调用”,即外部函数的返回值时一个内部函数的返回值。
|
||||
|
||||
```js
|
||||
function outerFunc() {
|
||||
return innerFunc(); // 尾调用
|
||||
}
|
||||
```
|
||||
|
||||
这和递归有点类似,区别就是通常的递归都是递归函数本身,而尾调用再递归其他外部函数时才会触发优化。
|
||||
|
||||
上述和递归类似的尾调用函数在 ES6 优化之前的调用栈类似于这样:
|
||||
|
||||
1. 执行到`outerFunc`,第一个栈帧被推到栈上;
|
||||
2. 执行`outerFunc`函数体,直到 return 语句。计算返回值必须先计算`innerFunc`;
|
||||
3. 执行到`innerFunc`,第二个栈帧被推到栈上;
|
||||
4. 执行`innerFunc`函数体,计算返回值;
|
||||
5. 如果`innerFunc`内部返回值还是递归,则按照同样的方式推入到栈中;
|
||||
6. 待到栈内最后一个函数返回完后,返回值会随着栈一层一层的往回返回;
|
||||
7. 直到返回到`outerFunc`函数,然后`outerFunc`再返回值;
|
||||
8. 将栈帧弹出栈外。
|
||||
|
||||
## 调用栈
|
||||
|
||||
在考虑尾调用优化之前,先来看下调用栈。调用栈是解释器追踪函数执行流的一种机制。当执行环境中调用了多个[函数](https://developer.mozilla.org/zh-CN/docs/Glossary/Function)时,通过这种机制,我们能够追踪到哪个函数正在执行,执行的函数体中又调用了哪个函数。
|
||||
|
||||
来自[Call stack(调用栈) - 术语表 | MDN (mozilla.org)](https://developer.mozilla.org/zh-CN/docs/Glossary/Call_stack)的描述:
|
||||
|
||||
- 每调用一个函数,解释器就会把该函数添加进调用栈并开始执行。
|
||||
- 正在调用栈中执行的函数还调用了其它函数,那么新函数也将会被添加进调用栈,一旦这个函数被调用,便会立即执行。
|
||||
- 当前函数执行完毕后,解释器将其清出调用栈,继续执行当前执行环境下的剩余的代码。
|
||||
- 当分配的调用栈空间被占满时,会引发“堆栈溢出”错误。
|
||||
|
||||
上述对递归调用栈简单的描述也是同理,这也说明了为什么使用递归来计算斐波那契数列过大时会发生“堆栈溢出”的错误。
|
||||
|
||||
### 递归
|
||||
|
||||
这种传统使用递归来计算斐波那契数列的方法非常常见,因为调用栈,它有个致命的缺点,太慢。
|
||||
|
||||
```js
|
||||
function fibonacci(n) {
|
||||
if (n < 2) {
|
||||
return n;
|
||||
}
|
||||
return fibonacci(n - 1) + fibonacci(n - 2);
|
||||
}
|
||||
|
||||
console.time('a');
|
||||
console.log('Calculate first 40 fibonacci numbers: ', fibonacci(40));
|
||||
console.timeEnd('a');
|
||||
```
|
||||
|
||||
因为它每次返回值时都需要等待栈内递归的函数体计算完毕,然后一层一层直到返回给最外层的函数,最终返回出结果。
|
||||
|
||||
这种一层一层的顺着栈返回的计算方式不仅难以理解,而且有时还会有不必要的栈调用。
|
||||
|
||||
### 不必要的递归
|
||||
|
||||
先来看一个简单的例子,先不考虑这段函数的意义以及问题,它的存在是为了帮助我们理解不必要的递归:
|
||||
|
||||
```js
|
||||
function testN(n) {
|
||||
if (n === 0) return n;
|
||||
return testN(--n);
|
||||
}
|
||||
```
|
||||
|
||||
这段递归非常简单,当参数 n 为 0 时,返回它;如果不为 0 则调用函数自身递减。这是一段普通的递归函数,因此它也按照上述的调用栈来递归。
|
||||
|
||||
假设 n 为 1:
|
||||
|
||||
1. 执行到`testN`函数,第一个栈帧被推到栈上;
|
||||
2. 执行`testN`函数体,直到 return 语句。计算返回值必须先计算`testN(n - 1)`;
|
||||
3. 执行`testN(n - 1)`,这时第二个栈帧被推到栈上;
|
||||
4. 执行`testN(n - 1)`函数体,通过判断,直接返回 n;
|
||||
5. 由于递归的栈调用,`testN(n - 1)`返回的值会被返回到外层`testN`,这时的`testN(n - 1)`才被弹出栈;
|
||||
6. `testN`等到了递归的返回值后,将其返回出去;
|
||||
7. 将栈帧弹出栈外。
|
||||
|
||||
这段递归只递归了一次,但已经很容易看出这样工作的问题所在了。那就是在执行到第四步的时候,函数所返回的 n 就已经是我们需要的结果了。但是它还是需要顺着调用栈一直返回到最外层,然后由它返回出去。于上述斐波那契不同的是,在返回的期间,n 的值没有经过任何计算,也就是说,这段路程根本没有必要。
|
||||
|
||||
## 尾调用优化
|
||||
|
||||
了解了递归的调用栈运作方式后,就可以来一探 ES6 的尾调用优化的究竟了。
|
||||
|
||||
```js
|
||||
function fibonacci(n, a = 1, b = 1) {
|
||||
if (n === 0) {
|
||||
return a;
|
||||
}
|
||||
return fibonacci(--n, b, a + b);
|
||||
}
|
||||
console.time('a');
|
||||
console.log('Calculate first 40 fibonacci numbers: ', fibonacci(100));
|
||||
console.timeEnd('a');
|
||||
```
|
||||
|
@ -1,28 +0,0 @@
|
||||
正则,听到这个词大脑就会油然而生一阵疼痛。它是那么的令人头疼,却又是那么强大。无论在什么语言环境下都离不开正则表达式的匹配,而学习它的最佳办法就是多尝试,记住了它的语法就能轻松掌握用法了。
|
||||
|
||||
匹配字符:`[abc]`,匹配`[...]`中的所有字符。
|
||||
|
||||
排除字符:`[^abc]`,匹配除了`[...]`中字符的所有字符。
|
||||
|
||||
区间匹配:`-`表示一个区间,`[0-9]`匹配0到9,`[a-z]`匹配a到z,区分大小写。
|
||||
|
||||
`.`:匹配除换行符(\n、\r)之外的任何单个字符,相等于`[^\n\r]`。
|
||||
|
||||
`\w`:匹配字母、数字、下划线。等价于`[A-Za-z0-9_]`。
|
||||
|
||||
用`\d`可以匹配一个数字,`\w`可以匹配一个字母或数字
|
||||
|
||||
用`*`表示任意个字符(包括0个),用`+`表示至少一个字符,用`?`表示0个或1个字符,用`{n}`表示n个字符,用`{n,m}`表示n-m个字符
|
||||
|
||||
- `[0-9a-zA-Z\_]`可以匹配一个数字、字母或者下划线;
|
||||
- `[0-9a-zA-Z\_]+`可以匹配至少由一个数字、字母或者下划线组成的字符串,比如`'a100'`,`'0_Z'`,`'Py3000'`等等;
|
||||
- `[a-zA-Z\_][0-9a-zA-Z\_]*`可以匹配由字母或下划线开头,后接任意个由一个数字、字母或者下划线组成的字符串,也就是Python合法的变量;
|
||||
- `[a-zA-Z\_][0-9a-zA-Z\_]{0, 19}`更精确地限制了变量的长度是1-20个字符(前面1个字符+后面最多19个字符)。
|
||||
|
||||
`A|B`可以匹配A或B,所以`(P|p)ython`可以匹配`'Python'`或者`'python'`。
|
||||
|
||||
`^`表示行的开头,`^\d`表示必须以数字开头。
|
||||
|
||||
`$`表示行的结束,`\d$`表示必须以数字结束。
|
||||
|
||||
你可能注意到了,`py`也可以匹配`'python'`,但是加上`^py$`就变成了整行匹配,就只能匹配`'py'`了。
|
@ -66,7 +66,7 @@ HTML 和 XHTML 有什么区别?
|
||||
|
||||
什么属性能让浏览器直接使用ES6 Module
|
||||
|
||||
CSS
|
||||
## CSS
|
||||
|
||||
两种盒模型分别说一下。
|
||||
如何垂直居中?
|
||||
@ -194,7 +194,6 @@ VueRouter如何做登录跳转
|
||||
Vuex的原理,有哪些概念
|
||||
Vue3用过吗,有哪些让你觉得好用的变化
|
||||
|
||||
|
||||
React
|
||||
什么是虚拟DOM
|
||||
setState更新的原理
|
||||
@ -225,7 +224,6 @@ px、em、rem、vw、百分比的区别
|
||||
300ms延时的原因和解决
|
||||
fastclick是什么原理
|
||||
|
||||
|
||||
性能优化
|
||||
前端性能优化经验
|
||||
如何做首屏渲染优化
|
||||
|
@ -1,3 +1,12 @@
|
||||
---
|
||||
title: Nodejs多进程
|
||||
date: 2021-06-01 13:32:29
|
||||
tags: [JavaScript, Nodejs]
|
||||
categories: 实践
|
||||
url: nodejs-multi-process
|
||||
index_img: /images/Nodejs%E5%A4%9A%E8%BF%9B%E7%A8%8B/logo.webp
|
||||
---
|
||||
|
||||
众所周知,JavaScript 是一门单线程的语言。但它的实现环境可以帮助我们创建多进程甚至是多线程,这样在遇到高压力的性能计算时,可以更好的利用多核 CPU 资源。
|
||||
|
||||
## 基本概念
|
||||
@ -362,4 +371,4 @@ Percentage of the requests served within a certain time (ms)
|
||||
100% 1154 (longest request)
|
||||
```
|
||||
|
||||
发了 100 个并发,3000 个请求,耗时 9 秒,吞吐量 318。但这是 3000 个请求全部都请求同一个数据时的结果,实际情况提升估计没有这么大,但还是有一点的。如果连续访问同一条数据比较多的话,那么这中缓存还是能带来一些性能提升的,至少心里安慰是有了
|
||||
发了 100 个并发,3000 个请求,耗时 9 秒,吞吐量 318。但这是 3000 个请求全部都请求同一个数据时的结果,实际情况提升估计没有这么大,但还是有一点的。如果连续访问同一条数据比较多的话,那么这中缓存还是能带来一些性能提升的,至少心里安慰是有了
|
@ -1,3 +1,12 @@
|
||||
---
|
||||
title: TypeScript临碎笔记
|
||||
date: 2021-06-02 12:13:06
|
||||
tags: TypeScript
|
||||
categories: 笔记
|
||||
url: typescript-pieces
|
||||
index_img: /images/TypeScript%E4%B8%B4%E7%A2%8E%E7%AC%94%E8%AE%B0/logo.svg
|
||||
---
|
||||
|
||||
## 类型
|
||||
|
||||
```ts
|
||||
@ -646,7 +655,7 @@ function trainAnimal(animal: Bird | Dog) {
|
||||
}
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
### 类型保护
|
||||
|
818
source/_posts/TypeScript编程.md
Normal file
@ -0,0 +1,818 @@
|
||||
---
|
||||
title: TypeScript编程
|
||||
date: 2021-05-20 21:03:19
|
||||
tags: TypeScript
|
||||
categories: 笔记
|
||||
url: programming-typescript
|
||||
index_img: /images/TypeScript%E7%BC%96%E7%A8%8B/logo.webp
|
||||
---
|
||||
|
||||
> 《Programming TypeScript》笔记。
|
||||
|
||||
## 函数
|
||||
|
||||
### 重载
|
||||
|
||||
重载函数:有多个调用签名的函数。
|
||||
|
||||
在多数编程语言中,声明函数时一旦指定了特定的参数和返回类型,就只能使用相应的参数调用参数。但 JavaScript 是一门动态语言,势必需要以多种方式调用一个函数的方法。不仅如此,而且有时输出的类型取决于参数的类型。
|
||||
|
||||
TypeScript 也支持动态重载函数声明,而且函数的输出类型却决于输入类型。
|
||||
|
||||
一个普通的函数类型注解:
|
||||
|
||||
```ts
|
||||
type Reserve = {
|
||||
(from: Date, to: Date, destination: string): string;
|
||||
};
|
||||
|
||||
const reserve: Reserve = (from,toOrDest, destination) => {
|
||||
const cost = Math.floor(Math.random() * (998 - 199) + 198);
|
||||
return `To ${destination} need ${cost} ${from.toLocaleString()}`
|
||||
};
|
||||
console.log(reserve(new Date(), new Date(), 'bali'));
|
||||
```
|
||||
|
||||
函数的重载需要在注解时定义多个类型,在函数声明时需要手动组合两个签名:
|
||||
|
||||
```ts
|
||||
type Reserve = {
|
||||
(from: Date, to: Date, destination: string): string;
|
||||
(from: Date, destination: string): string;
|
||||
};
|
||||
|
||||
const reserve: Reserve = (
|
||||
// 参数需要手动在注解并组合两个签名
|
||||
from: Date,
|
||||
toOrDest: Date | string,
|
||||
destination?: string
|
||||
) => {
|
||||
const cost = Math.floor(Math.random() * (998 - 199) + 198);
|
||||
if (toOrDest instanceof Date && destination !== undefined) {
|
||||
return `To ${destination} need ${cost} ${from.toLocaleString()} ${toOrDest.toLocaleString()}`;
|
||||
} else {
|
||||
return `To ${toOrDest} need ${cost} ${from.toLocaleString()}`;
|
||||
}
|
||||
};
|
||||
console.log(reserve(new Date(), new Date(), 'bali'));
|
||||
console.log(reserve(new Date(), 'bali'));
|
||||
```
|
||||
|
||||
### 多态
|
||||
|
||||
使用具体类型的前提是明确知道需要什么类型,并且想确认传入的确实是那个类型。但是,有时事先并不知道需要什么类型,不想限制函数只能接受某个类型。
|
||||
|
||||
这种情况可以使用函数泛型(generic type)参数。
|
||||
|
||||
> 在类型层面施加约束的占位类型,也称多态类型参数。
|
||||
|
||||
例如我们重写一个简单的数组 filter 方法,在我们老朋友 JavaScript 中,这是一个很基本的函数。
|
||||
|
||||
```js
|
||||
const filter = (arr, fn) => {
|
||||
let result = [];
|
||||
for (const i of arr) {
|
||||
if (fn(i)) {
|
||||
result.push(i);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
```
|
||||
|
||||
但在 TypeScript 中,为了类型保护我们就需要为其添加类型注解。而这个函数的特点就是他可以(需要)接受多种类型的参数,且函数的返回值也是跟着参数的类型变化的。
|
||||
|
||||
也许利用重载为其注解所有类型也是可以的:
|
||||
|
||||
```ts
|
||||
type Filter = {
|
||||
(arr: number[], fn: (item: number) => boolean): number[];
|
||||
(arr: string[], fn: (item: string) => boolean): string[];
|
||||
};
|
||||
```
|
||||
|
||||
但是如果遇到了对象数组呢:
|
||||
|
||||
```ts
|
||||
type Filter = {
|
||||
(arr: number[], fn: (item: number) => boolean): number[];
|
||||
(arr: string[], fn: (item: string) => boolean): string[];
|
||||
(arr: Object[], fn: (item: Object) => boolean): Object[];
|
||||
};
|
||||
```
|
||||
|
||||
Object 无法描述对象结构,它可以代表所有类似对象的结构。
|
||||
|
||||
这时候就需要使用泛型了:
|
||||
|
||||
```ts
|
||||
type Filter = {
|
||||
<T>(arr: T[], fn: (item: T) => boolean): T[];
|
||||
};
|
||||
```
|
||||
|
||||
这里泛型的意思是:filter 函数使用一个泛型参数 T,在事先我们不知道其具体的类型。TypeScript 从传入的 arr 参数种推导出 T 的类型。调用 filter 时,T 的类型被推导出后,将把 T 出现的每一处替换为推导出的类型。T 就像是一个占位类型,类型检查器会根据上下文填充具体的类型。T 把 Filter 的类型参数化了,因此才称其为泛型参数。
|
||||
|
||||
#### 泛型作用域
|
||||
|
||||
泛型声明的位置不仅限定了泛型的作用域,还决定了 TypeScript 什么时候为泛型绑定具体的类型。
|
||||
|
||||
例如之前 Filter 函数的类型:
|
||||
|
||||
```ts
|
||||
type Filter = {
|
||||
<T>(arr: T[], fn: (item: T) => boolean): T[];
|
||||
};
|
||||
const filter:Filter = (arr, fn) // ...
|
||||
```
|
||||
|
||||
`<T>`在调用签名中声明(位于签名开始的括号前),TypeScript 将在调用 Filter 类型的函数时为 T 绑定具体类型。
|
||||
|
||||
而如果把 T 写在类型别名上,TypeScript 则要求在使用 Filter 时显式绑定类型:
|
||||
|
||||
```ts
|
||||
type Filter<T> = {
|
||||
(arr: T[], fn: (item: T) => boolean): T[];
|
||||
};
|
||||
const filter:Filter<number> = (arr, fn) // ...
|
||||
```
|
||||
|
||||
一般来说,TypeScript 在使用泛型时为泛型绑定具体类:对函数来说,在调用函数时;对类来说,在实例化类时;对类型别名和接口来说,在使用别名和接口时。
|
||||
|
||||
#### 函数泛型的多种写法
|
||||
|
||||
```ts
|
||||
type Filter = {
|
||||
<T>(arr: T[], fn: (item: T) => boolean): T[];
|
||||
};
|
||||
const filter: Filter = (arr, fn); // ...
|
||||
|
||||
type Filter<T> = {
|
||||
(arr: T[], fn: (item: T) => boolean): T[];
|
||||
};
|
||||
const filter:Filter<number> = (arr, fn) // ...
|
||||
|
||||
type Filter = <T>(arr: T[], fn: (item: T) => boolean) => T[];
|
||||
const filter: Filter = (arr, fn); // ...
|
||||
|
||||
type Filter<T> = (arr: T[], fn: (item: T) => boolean) => T[];
|
||||
const filter:Filter<number> = (arr, fn) // ...
|
||||
```
|
||||
|
||||
#### 约束受限多态
|
||||
|
||||
施加一个类型约束很简单,语法有点类似于子类继承:
|
||||
|
||||
```ts
|
||||
function test<T extends Filter> { //... }
|
||||
```
|
||||
|
||||
当然,也可以有多个约束,这时候就要使用到交集了:
|
||||
|
||||
```ts
|
||||
function test<T extends Filter & Map> { //... }
|
||||
```
|
||||
|
||||
除此之外还能借助受限的多态来模拟变长参数函数(可接受任意个参数的函数),例如之前一直困扰我的防抖函数:
|
||||
|
||||
```ts
|
||||
type Debounce = {
|
||||
<T extends unknown[]>(fn: (...arg: T) => void | unknown, ms: number): (
|
||||
this: unknown,
|
||||
...arg: T
|
||||
) => void | unknown;
|
||||
};
|
||||
|
||||
const debounce: Debounce = (fn, ms) => {
|
||||
let timer = 0;
|
||||
return function (...arg) {
|
||||
if (timer) clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
fn.apply(this, arg);
|
||||
}, ms);
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
`<T extends unknown[]>`表示 T 是`unknown[]`的子类型,即 T 是任意类型的数组或元组。
|
||||
|
||||
## 类
|
||||
|
||||
类是组织和规划代码的方式,是封装的基本单位。众所周知,JavaScript 的类是语法上的,在 ES6 时支持了 class 关键字。TypeScript 的类大量的借用了 C# 的理论,支持属性初始化语句、多态、装饰器和接口等。
|
||||
|
||||
### 基本语法
|
||||
|
||||
public 或 private 关键字在 constructor 中将为我们创建对应的属性:
|
||||
|
||||
```ts
|
||||
class Person {
|
||||
constructor(public name: string, public age: number) {}
|
||||
}
|
||||
|
||||
const xfy = new Person('xfy', 18);
|
||||
console.log(xfy);
|
||||
|
||||
class alotherPerson extends Person {
|
||||
protected static country: number;
|
||||
constructor(
|
||||
name: string,
|
||||
age: number,
|
||||
public sex: string,
|
||||
private nickname: string
|
||||
) {
|
||||
super(name, age);
|
||||
}
|
||||
}
|
||||
const dfy = new alotherPerson('dfy', 18, 'female', 'xfy');
|
||||
console.log(dfy);
|
||||
```
|
||||
|
||||
### 以 this 作为返回类型
|
||||
|
||||
this 可以用作值,也可以用作类型。对类来说,this 类型还可以用于注解方法的返回类型。
|
||||
|
||||
例如我们需要实现一个简单的 Set 数据结构,他有一个 add 方法,每次返回的就是一个 Set 实例。我们可以直接注解为 Set 类。
|
||||
|
||||
```ts
|
||||
class MySet {
|
||||
has(value: number):boolean {}
|
||||
add(value: number): MySet {}
|
||||
}
|
||||
```
|
||||
|
||||
但这样的缺点就是,当子类需要继承自父类时,子类的同名方法需要重新注解为返回对应的子类:
|
||||
|
||||
```ts
|
||||
class MyOtherSet extends MySet {
|
||||
add(value: number): MyOtherSet {}
|
||||
}
|
||||
```
|
||||
|
||||
这样在拓展其他类时,要把返回的 this 的每个方法的签名覆盖掉,就显得十分麻烦。如果只是为了让类型检查器满意,这样做就失去了继承基类的意义。
|
||||
|
||||
正确的方法是将 this 作为注解的返回类型,把相关工作交给 TypeScript:
|
||||
|
||||
```ts
|
||||
class MySet {
|
||||
has(value: number): boolean {}
|
||||
add(value: number): this {}
|
||||
}
|
||||
|
||||
class MyOtherSet extends MySet {
|
||||
add(value: number): this {}
|
||||
}
|
||||
```
|
||||
|
||||
### 接口
|
||||
|
||||
类经常被当作接口使用。
|
||||
|
||||
接口是一种命名类型的方式,它和类型别名类似,不过二者还有一些细微的差别:
|
||||
|
||||
第一,类型别名更为通用,右边可以是任何类型,包括类型表达式;而接口声明中,右边必须为结构。
|
||||
|
||||
```ts
|
||||
type A = number;
|
||||
type B = A | string;
|
||||
```
|
||||
|
||||
第二个区别是,扩展接口时,TypeScript 将检查扩展的接口是否可赋值给被扩展的接口。
|
||||
|
||||
```ts
|
||||
interface Animal {
|
||||
good(x: string): string;
|
||||
}
|
||||
interface Dog extends Animal {
|
||||
good(x: number): void;
|
||||
}
|
||||
```
|
||||
|
||||
而使用类型别名使用交集运算符`&`来扩展时,TypeScript 将尽自己所能,把扩展和被扩展的类型组合在一起,最终是重载冲突的签名。
|
||||
|
||||
```ts
|
||||
type C = {
|
||||
(x: string): string;
|
||||
};
|
||||
type D = C & {
|
||||
(x: number): string;
|
||||
};
|
||||
|
||||
const ef: D = (x) => {
|
||||
return 'xfy';
|
||||
};
|
||||
ef('123');
|
||||
```
|
||||
|
||||
第三个区别是,同一作用域中多个同名接口将自动合并;而同一作用域中的多个类型别名将导致编译时错误。这个特性称之为声明合并。
|
||||
|
||||
### 实现
|
||||
|
||||
可以为类添加类型层面约束。
|
||||
|
||||
```ts
|
||||
interface Animal {
|
||||
eat(food: string): void;
|
||||
sleep(hours: number): void;
|
||||
}
|
||||
|
||||
class Cat implements Animal {
|
||||
constructor(public name: string) {}
|
||||
eat(food: string) {
|
||||
console.log(`eating ${food}`);
|
||||
}
|
||||
sleep(hours: number) {
|
||||
console.log(`slept for ${hours} hours`);
|
||||
}
|
||||
}
|
||||
const myCat = new Cat('xfy');
|
||||
myCat.sleep(10);
|
||||
```
|
||||
|
||||
### 接口还是抽象类
|
||||
|
||||
TypeScript 可以使用`abstract`关键字来实现抽象类,实现接口其实于抽象类差不多。区别是,接口更通用,更轻量,而抽象类的作用更具体,功能更丰富。
|
||||
|
||||
接口是对结构建模的方式。在值层面可表示对象、数组、函数、类或类的实例。接口不生成 JavaScript 代码,只存在于编译时。
|
||||
|
||||
抽象类只能对类建模,而且生成运行时代码,即 JavaScript 类。抽象类可以有构造方法,可以提供默认实现,还能为属性和方法设置访问修饰符。这些在接口都中做不到。
|
||||
|
||||
具体使用哪一个,取决于实际用途。如果多个类共用一个实现,使用抽象类。如果需要一种轻量的方式表示“这个类是 T 型”,使用接口。
|
||||
|
||||
### 类是结构化类型
|
||||
|
||||
TypeScript 根据类的结构来比较类,与类的名称无关。类与其他类是否兼容,要看结构。
|
||||
|
||||
```ts
|
||||
class Cat {
|
||||
sleep(hours: number) {}
|
||||
}
|
||||
class Dog {
|
||||
sleep(hours: number) {}
|
||||
}
|
||||
const checkCat = (animal: Cat) => {
|
||||
animal.sleep(10);
|
||||
};
|
||||
|
||||
/* 这里传入狗狗也是可以的 */
|
||||
checkCat(new Dog());
|
||||
```
|
||||
|
||||
### 类既声明值也声明类型
|
||||
|
||||
类和枚举比较特殊,它们即在类型命名中生成类型,也在值命名空间中生成值。
|
||||
|
||||
```ts
|
||||
class G {}
|
||||
// 声明值的同时也声明了类型
|
||||
let g: G = new G();
|
||||
|
||||
enum H {
|
||||
J,
|
||||
K,
|
||||
}
|
||||
let h: H = H.J;
|
||||
```
|
||||
|
||||
### 模拟 final 类
|
||||
|
||||
在某些面向对象的语言中,可以使用 final 关键字来吧类标记为不可拓展,或者把方法标记为不可覆盖。
|
||||
|
||||
在 TypeScript 中可以使用私有的 constructor 来轻松的模拟 final 类:
|
||||
|
||||
```ts
|
||||
class Message {
|
||||
private constructor(private msg: string) {}
|
||||
}
|
||||
// Cannot extend a class 'Message'. Class constructor is marked as private.
|
||||
class NewMessage extends Message {}
|
||||
```
|
||||
|
||||
但将 constructor 标记为 private 后,同时也不能使用 new 来实例化类了。我们仅仅需要不能拓展即可,可以简单修改一下使其能够实例化:
|
||||
|
||||
```ts
|
||||
class Message {
|
||||
private constructor(private msg: string) {}
|
||||
static create(msg: string) {
|
||||
return new Message(msg);
|
||||
}
|
||||
}
|
||||
|
||||
// Constructor of class 'Message' is private and only accessible within the class declaration.
|
||||
new Message('test');
|
||||
// Ok
|
||||
Message.create('test');
|
||||
```
|
||||
|
||||
如果将 private 换成 protect 之后,就实现了完全相反的效果。由于 protect 可以被子类所使用,所有可以拓展。而外部无法使用,所以就无法实例化该类。
|
||||
|
||||
```ts
|
||||
class Message {
|
||||
protected constructor(protected msg: string) {}
|
||||
}
|
||||
|
||||
class NewMessage extends Message {}
|
||||
```
|
||||
|
||||
### 工厂模式
|
||||
|
||||
工厂模式是创建某种类型的对象的一种方式,这种方式把创建哪种具体对象给创建该对象的工厂决定。
|
||||
|
||||
这里实现了一个创建 Shoe 的工厂,Shoe 对象有一个`crarete()`方法,它根据传入的值来决定创建哪个鞋子。同时参数 type 使用了并集而不是 string 来进一步保证类型的安全。
|
||||
|
||||
```ts
|
||||
type Shoe = {
|
||||
purpose: string;
|
||||
};
|
||||
class Boot implements Shoe {
|
||||
purpose = 'woodcutting';
|
||||
}
|
||||
class BalletFlat implements Shoe {
|
||||
purpose = 'dancing';
|
||||
}
|
||||
class Sneaker implements Shoe {
|
||||
purpose = 'walking';
|
||||
}
|
||||
|
||||
const Shoe = {
|
||||
crarete(type: 'boot' | 'balletflat' | 'sneaker'): Shoe {
|
||||
switch (type) {
|
||||
case 'boot':
|
||||
return new Boot();
|
||||
case 'balletflat':
|
||||
return new BalletFlat();
|
||||
case 'sneaker':
|
||||
return new Sneaker();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const myShoe = Shoe.crarete('sneaker');
|
||||
console.log(myShoe.purpose);
|
||||
```
|
||||
|
||||
当然也可以牺牲一点抽象性,使用函数重载来明确返回类型:
|
||||
|
||||
```ts
|
||||
type Shoe = {
|
||||
purpose: string;
|
||||
};
|
||||
class Boot implements Shoe {
|
||||
purpose = 'woodcutting';
|
||||
}
|
||||
class BalletFlat implements Shoe {
|
||||
purpose = 'dancing';
|
||||
}
|
||||
class Sneaker implements Shoe {
|
||||
purpose = 'walking';
|
||||
}
|
||||
|
||||
type CreateShoe = {
|
||||
crarete(type: 'boot'): Boot;
|
||||
crarete(type: 'balletflat'): BalletFlat;
|
||||
crarete(type: 'sneaker'): Sneaker;
|
||||
};
|
||||
|
||||
const Shoe: CreateShoe = {
|
||||
crarete(type: 'boot' | 'balletflat' | 'sneaker'): Shoe {
|
||||
switch (type) {
|
||||
case 'boot':
|
||||
return new Boot();
|
||||
case 'balletflat':
|
||||
return new BalletFlat();
|
||||
case 'sneaker':
|
||||
return new Sneaker();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const myShoe = Shoe.crarete('sneaker');
|
||||
console.log(myShoe.purpose);
|
||||
```
|
||||
|
||||
## 类型进阶
|
||||
|
||||
TypeScript 拥有一流的类型系统支持强大的类型层面编程特性。不仅具有极强的表现力,易于使用,而且可以通过简介明了的方式声明类型约束和关系,并且多数时候能为我们自动推导类型。
|
||||
|
||||
### 超类型和子类型
|
||||
|
||||
如同类一样,类型也有超类型与其子类型。并且在需要超类型的地方都可以安全的使用其子类型。类型的关系很常见,例如:
|
||||
|
||||
* Cat 类拓展自 Animal 类,那么 Cat 是 Animal 的子类型;
|
||||
* Array 是 Object 的子类型;
|
||||
* Tuple 是 Array 的子类型;
|
||||
* 所有类型都是 any 的子类型;
|
||||
* never 是所有类型的子类型;
|
||||
|
||||
反过来也是同样的,Animal 就是 Cat 的超类型。
|
||||
|
||||
### 函数型变
|
||||
|
||||
如果 A 函数的参数数量小于或等于 B 函数的参数数量,且满足如下条件,那么函数 A 是函数 B 的子类型:
|
||||
|
||||
1. 函数 A 的 this 类型未指定,或者 this 是函数 B this 的**超类型**;
|
||||
2. 函数 A 的各个参数的类型为函数 B 相应参数的**超类型**;
|
||||
3. 函数 A 的返回类型为函数 B 返回类型的**子类型**;
|
||||
|
||||
仔细研究就会发现,虽然函数 A 是函数 B 的子类型,但是它的 this 和参数缺都是函数 B 的超类型。
|
||||
|
||||
来看一个简单的示例,这里通过几个类之间的继承,很好的描述了函数之间的型变:
|
||||
|
||||
```ts
|
||||
class Animal {}
|
||||
class Cat extends Animal {
|
||||
miao() {}
|
||||
}
|
||||
class Lion extends Cat {
|
||||
wawu() {}
|
||||
}
|
||||
|
||||
function miaomiao(cat: Cat) {
|
||||
cat.miao();
|
||||
return cat;
|
||||
}
|
||||
|
||||
// 需要超类型的地方也可以使用子类型
|
||||
miaomiao(new Animal());
|
||||
miaomiao(new Cat());
|
||||
miaomiao(new Lion());
|
||||
|
||||
// 回调函数 fn 为超类型
|
||||
function clone(fn: (cat: Cat) => Cat): void {
|
||||
const parent = new Cat();
|
||||
const baby = fn(parent);
|
||||
baby.miao();
|
||||
}
|
||||
|
||||
// catToLion 即是回调函数 fn 的子类型
|
||||
// 满足返回值是其子类型
|
||||
function catToLion(c: Cat): Lion {
|
||||
return new Lion();
|
||||
}
|
||||
clone(catToLion);
|
||||
|
||||
// catToAnimal 返回值是其超类型,所以无法调用
|
||||
function catToAnimal(c: Cat): Animal {
|
||||
return new Animal();
|
||||
}
|
||||
clone(catToAnimal);
|
||||
|
||||
// animalToCat 即是回调函数 fn 的子类型
|
||||
// 满足参数是其超类型
|
||||
function animalToCat(a: Animal): Cat {
|
||||
return new Cat();
|
||||
}
|
||||
clone(animalToCat);
|
||||
|
||||
// animalToLion 参数是其子类型,所以无法调用
|
||||
function animalToLion(l: Lion): Cat {
|
||||
return new Cat();
|
||||
}
|
||||
clone(animalToLion);
|
||||
```
|
||||
|
||||
值得高兴的是,我们并不需要记诵这套规则。只需要了解一下就好了,剩下的 TypeScript 都帮我们做了(我们的老朋友,红色波浪线会告诉我们传递是否正确的)。
|
||||
|
||||
### 可赋值性
|
||||
|
||||
可赋值性之的就是判断需要 B 类型的地方是否可用 A 类型时 TypeScript 采用的规则。
|
||||
|
||||
在非枚举类型来说,A 类型是否可赋值给 B 类型有两个规则:
|
||||
|
||||
1. A 是 B 的子类型;
|
||||
2. A 是 any;
|
||||
|
||||
也就是说能用到超类型的地方,都能可以用其子类型。
|
||||
|
||||
对于枚举类型来说,也是有两个规则:
|
||||
|
||||
1. A 是枚举 B 的成员;
|
||||
2. B 至少有一个成员是 number 类型,且 A 是数字;
|
||||
|
||||
### 类型拓宽
|
||||
|
||||
TypeScript 在一般情况下推导类型时会放宽要求,故意推导出一个更宽泛的类型,而不是限定为一个具体的类型。不过在使用`const`关键字声明标识符的时候会严格限定类型。
|
||||
|
||||
```ts
|
||||
// a: string
|
||||
let a = 'xfy';
|
||||
// b: 'xfy'
|
||||
const b = 'xfy';
|
||||
```
|
||||
|
||||
可以显式注解,防止类型拓宽。
|
||||
|
||||
#### const 类型
|
||||
|
||||
TypeScript 中有一个特殊的类型`const`类型,可以禁止类型拓宽并且还递归的将成员设置为`readonly`,不管嵌套有多深。这个类型作用与类型断言。
|
||||
|
||||
```ts
|
||||
let c = { x: 123 } as const;
|
||||
// d: { readonly x: 1; readonly y: { readonly z: 3; }; }
|
||||
let d = { x: 1, y: { z: 3 } } as const;
|
||||
```
|
||||
|
||||
#### 多余属性检查
|
||||
|
||||
TypeScript 在检查一个对象是否可赋值给另一个对象时,也涉及到类型拓宽。
|
||||
|
||||
传递一个对象字面量时,TypeScript 会对所有属性进行检查,也就是多余属性检查。
|
||||
|
||||
```ts
|
||||
type Option = {
|
||||
baseUrl: string;
|
||||
cacheSize?: number;
|
||||
};
|
||||
|
||||
class API {
|
||||
constructor(option: Option) {}
|
||||
}
|
||||
|
||||
new API({
|
||||
baseUrl: 'https://xfy.plus',
|
||||
cacheSizee: 123, // 错误
|
||||
});
|
||||
```
|
||||
|
||||
但多余属性检查在将对象赋值给一个变量时则不会详细检查:
|
||||
|
||||
```ts
|
||||
let option = {
|
||||
baseUrl: 'https://xfy.plus',
|
||||
cacheSizee: 123,
|
||||
};
|
||||
new API(option); // 没有错误
|
||||
```
|
||||
|
||||
### 对象类型进阶
|
||||
|
||||
对象是 JavaScript 语言的核心,为了以安全的方式描述和处理对象,TypeScript 提供了一系列方式。
|
||||
|
||||
#### 对象类型运算符
|
||||
|
||||
**键入运算符**
|
||||
|
||||
键入运算符提供了类似与对象字面量的方式来对类型的访问,在对顶层类型做访问时,能提供更简便的方法:
|
||||
|
||||
```ts
|
||||
type APIResponse = {
|
||||
user: {
|
||||
userId: number;
|
||||
frientList: {
|
||||
count: number;
|
||||
friends: {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}[];
|
||||
};
|
||||
};
|
||||
};
|
||||
type FriendList = APIResponse['user']['frientList'];
|
||||
```
|
||||
|
||||
**`keyof`运算符**
|
||||
|
||||
与`Object.keys`类似,不过`keyof`取的是作为类型的键。
|
||||
|
||||
```ts
|
||||
const Obj = {
|
||||
a: 1,
|
||||
name: 'xfy',
|
||||
};
|
||||
|
||||
type PickOne = {
|
||||
// T 是一个对象,K 是 T 中的一个键
|
||||
<T extends object, K extends keyof T>(obj: T, key: K): T[K];
|
||||
};
|
||||
|
||||
const pickOne: PickOne = (obj, key) => {
|
||||
return obj[key];
|
||||
};
|
||||
|
||||
const t1 = pickOne(Obj, 'a');
|
||||
```
|
||||
|
||||
#### 映射类型
|
||||
|
||||
TypeScript 还提供了一种更强大的方式,即映射类型(mapped type)。一个对象最多有一个映射类型
|
||||
|
||||
```ts
|
||||
const nextDay: { [key in Day]: string } = {
|
||||
Mon: 'Thu',
|
||||
};
|
||||
```
|
||||
|
||||
内置的映射类型
|
||||
|
||||
* `Record<Keys, Values>`:键的的类型为 Keys、值的类型为 Values 的对象。
|
||||
* `Partial<Object>`:把 Object 中的每个字段都标记为可选的。
|
||||
* `Required<Object>`:把 Object 中的每个字段都标记为必须的。
|
||||
* `Readonly<Object>`:把 Object 中的每个字段都标记为只读的。
|
||||
* `Pick<Object, Keys>`:返回 Object 的子类型,只包含指定的 Keys。
|
||||
|
||||
### 函数类型进阶
|
||||
|
||||
函数类型常用的几种高级技术。
|
||||
|
||||
#### 改善数组的类型推导
|
||||
|
||||
TypeScript 在推导元组类型的时候会放宽要求,推导出的结果尽量宽泛,不在乎元组的长度和各位置的类型。
|
||||
|
||||
```ts
|
||||
const tuple1 = [1, 'xfy'] as const;
|
||||
// tuple1: (string | number)[]
|
||||
```
|
||||
|
||||
有些时候我们可能希望更加严格一点,而不是当作数组来使用。当然可以使用类型断言来更加严格:
|
||||
|
||||
```ts
|
||||
const tuple2 = [1, 'xfy'] as const;
|
||||
// tuple2: readonly [1, "xfy"]
|
||||
```
|
||||
|
||||
但有时候我们可能不想使用断言,或者不想标记为只读。这时候可以利用 TypeScript 推导剩余参数的类型的方式:
|
||||
|
||||
```ts
|
||||
const tuple = <T extends unknown[]>(...ts: T): T => {
|
||||
return ts;
|
||||
};
|
||||
const myTuple = tuple(1, 'xfy');
|
||||
// myTuple: [number, string]
|
||||
```
|
||||
|
||||
这个函数返回传入的参数,神奇之处全在类型中。当代码中使用到了大量的元组时,而又不想全部断言时,可以尝试这种技术。
|
||||
|
||||
#### 用户定义的类型防护措施
|
||||
|
||||
TypeScript 支持类型的细化。但它不不是任何条件都有用的:
|
||||
|
||||
```ts
|
||||
function isString(str: unknown): boolean {
|
||||
return typeof str === 'string';
|
||||
}
|
||||
console.log(isString('123')); // true
|
||||
console.log(isString(123)); // false
|
||||
|
||||
function parseInput(input: string | number) {
|
||||
if (isString(input)) {
|
||||
input.toUpperCase(); // Property 'toUpperCase' does not exist on type 'number'.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在这种情况下,类型细化就不再为我们工作了。类型细化的能力有限,只能细化当前作用域中变量的类型,一旦离开作用域,类型细化能力不会随之转移到新作用域中。在新的函数内,TypeScript 只知道`isString`返回一个布尔值。
|
||||
|
||||
`isString`返回的确实是一个布尔值,但我们要让类型检查器知道,当返回是真值时,表明其参数是一个字符串。为此,这里需要使用用户定义的类型防护措施:
|
||||
|
||||
```ts
|
||||
function isString(str: unknown): str is string {
|
||||
return typeof str === 'string';
|
||||
}
|
||||
console.log(isString('123')); // true
|
||||
console.log(isString(123)); // false
|
||||
|
||||
function parseInput(input: string | number) {
|
||||
if (isString(input)) {
|
||||
input.toUpperCase(); // worked!
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
类型防护措施是 TypeScript 内置的特性,is 运算符就起这个作用。如果函数细化了参数的类型,而且返回一个布尔值,我们可以使用用户定义的类型防护措施确保类型的细化能在作用域之间转移。用户定义的类型防护措施只限于一个参数,但是不限定于简单的类型。
|
||||
|
||||
### 条件类型
|
||||
|
||||
TypeScript 提供了和条件运算类似的方式来运算类型。
|
||||
|
||||
```ts
|
||||
type IsString<T> = T extends string ? true : false;
|
||||
|
||||
type A = IsString<string>; // true
|
||||
type B = IsString<number>; // false
|
||||
```
|
||||
|
||||
#### infer 关键字
|
||||
|
||||
条件类型的另一个更强大的特新故事可以在条件中声明泛型。这个声明方式不是使用尖括号`<>`,而是使用 infer 关键字。
|
||||
|
||||
这里的意思是:当泛型 T 继承自另一个泛型 U 组成的数组时,返回 U 类型。
|
||||
|
||||
```ts
|
||||
type SomeArr<T> = T extends (infer U)[] ? U : T;
|
||||
type C = SomeArr<string[]>; // string
|
||||
type D = SomeArr<number[]>; // number
|
||||
```
|
||||
|
||||
这还不是最强大的地方,对于数组来说我们可以使用键入运算符`[]`。更强大的地方在于它还可以获取到函数参数类型:
|
||||
|
||||
这里的意思是:当泛型 F 继承自一个接收两个参数的函数,并且两个参数中的第二个参数为泛型 U 类型时,返回对应的 U 类型。
|
||||
|
||||
```ts
|
||||
type SecondArg<F> = F extends (argA: any, argB: infer U) => any ? U : never;
|
||||
|
||||
type F = typeof Array['prototype']['slice']; // F = (start?: number, end?: number) => any[]
|
||||
type Arg = SecondArg<F>; // Arg = number
|
||||
```
|
||||
|
||||
可见`[].slice`的第二个参数是 number 类型,而且在编译时便可知晓这一点。Java 能做到吗?
|
||||
|
||||
> ↑《Programming TypeScript》中文版 6.5.2 章原话。
|
@ -1,3 +1,16 @@
|
||||
---
|
||||
title: webpack静态网站开发
|
||||
date: 2021-05-29 21:03:20
|
||||
tags: [JavaScript, Webpack]
|
||||
categories: 实践
|
||||
url: webpack-web-develop
|
||||
index_img: /images/webpack%E9%9D%99%E6%80%81%E7%BD%91%E7%AB%99%E5%BC%80%E5%8F%91/logo.webp
|
||||
---
|
||||
|
||||
## webpack
|
||||
|
||||
模块 loader 可以链式调用。链中的每个 loader 都将对资源进行转换。链会逆序执行。第一个 loader 将其结果(被转换后的资源)传递给下一个 loader,依此类推。最后,webpack 期望链中的最后的 loader 返回 JavaScript。
|
||||
|
||||
### 初始化环境
|
||||
|
||||
```bash
|
||||
@ -5,10 +18,6 @@ yarn init
|
||||
yarn add webpack webpack-cli -D
|
||||
```
|
||||
|
||||
## webpack
|
||||
|
||||
模块 loader 可以链式调用。链中的每个 loader 都将对资源进行转换。链会逆序执行。第一个 loader 将其结果(被转换后的资源)传递给下一个 loader,依此类推。最后,webpack 期望链中的最后的 loader 返回 JavaScript。
|
||||
|
||||
### 配置文件
|
||||
|
||||
配置文件中需要一个入口文件,即为被打包的 js 文件。同时需要一个输出的目录,与打包后的文件名。
|
||||
@ -372,5 +381,4 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
```
|
BIN
source/images/Nodejs多进程/logo.webp
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
source/images/TypeScript临碎笔记/image-20210329172943170.png
Normal file
After Width: | Height: | Size: 44 KiB |
6
source/images/TypeScript临碎笔记/logo.svg
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
source/images/TypeScript编程/logo.webp
Normal file
After Width: | Height: | Size: 9.5 KiB |
BIN
source/images/Vue电商管理系统/image-20210517203706971.png
Normal file
After Width: | Height: | Size: 128 KiB |
BIN
source/images/webpack静态网站开发/logo.webp
Normal file
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 116 KiB |
BIN
source/images/尾调用优化/image-20210530185737448.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
BIN
source/images/尾调用优化/image-20210530190004138.png
Normal file
After Width: | Height: | Size: 9.4 KiB |