CSS colors

CSS colors

css 支持使用多种语法描述颜色

  • keywords
  • hex codes
  • 使用 16 进制表示 #RRGGBB
  • color function
    • rgb/rgba
      • rgb() 前三个参数分别指定 red, green, blue 通道,最后一个参数指定 alpha 通道
    • hsl/hsla
      • hsl() 的三个参数分别是色调、饱和度和明度,最后一个参数指定 alpha 通道
    • hwb
      • hwb() 的三个参数分别是色调、白度和黑度,最后一个参数指定 alpha 通道
    • lch
    • lab
    • color
      • 指定预定义的颜色空间
      • color(srgb …)
      • color(display-p3 …)

sRGB 色域可以用 16 进制, rgb/rgba, hsl/hsla, hwb 直接指定

1
2
3
4
5
// 不同用法表示同一个颜色
color: #ff0000;
color: rgb(255 0 0 / 0.5);
color: hsl(0 100% 50%);
color: hwb(0 0% 0%);

将 HSL 颜色转为 sRGB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* @param {number} hue - Hue as degrees 0..360
* @param {number} sat - Saturation in reference range [0,100]
* @param {number} light - Lightness in reference range [0,100]
* @return {number[]} Array of RGB components 0..1
*/
function hslToRgb(hue, sat, light) {
hue = hue % 360;

if (hue < 0) {
hue += 360;
}

sat /= 100;
light /= 100;

function f(n) {
let k = (n + hue / 30) % 12;
let a = sat * Math.min(light, 1 - light);
return light - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
}

return [f(0), f(8), f(4)];
}

将 sRGB 颜色转为 HSL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* @param {number} red - Red component 0..1
* @param {number} green - Green component 0..1
* @param {number} blue - Blue component 0..1
* @return {number[]} Array of HSL values: Hue as degrees 0..360, Saturation and Lightness in reference range [0,100]
*/
function rgbToHsl(red, green, blue) {
let max = Math.max(red, green, blue);
let min = Math.min(red, green, blue);
let [hue, sat, light] = [NaN, 0, (min + max) / 2];
let d = max - min;

if (d !== 0) {
sat = light === 0 || light === 1 ? 0 : (max - light) / Math.min(light, 1 - light);

switch (max) {
case red:
hue = (green - blue) / d + (green < blue ? 6 : 0);
break;
case green:
hue = (blue - red) / d + 2;
break;
case blue:
hue = (red - green) / d + 4;
}

hue = hue * 60;
}

return [hue, sat * 100, light * 100];
}

monorepo & turbo

将多个项目存放在同一个 git 仓库中,将项目放入不同的工作区,各个工作区之间互相引用模块,实现代码共享。

monorepo 文件结构

前端项目使用包管理器管理 monorepo。

1
2
3
4
5
6
7
8
9
my-monorepo
├─ docs
├─ apps
│ ├─ pc
│ └─ mobile
├─ packages
│ ├─ tsconfig
│ └─ shared-utils
└─ root-files

如果将docsappspackages 下的所有目录设为工作区,需要在根目录如下设置

npm

在根目录的 package.json 文件中配置 workspaces 字段。在workspaces 中配置工作区列表。

1
2
3
4
5
{
"name": "my-monorepo",
"version": "1.0.0",
"workspaces": ["docs", "apps/*", "packages/*"]
}

yarn

在根目录的 package.json 文件中配置 workspaces 字段。在workspaces 中配置工作区列表。

1
2
3
4
5
{
"name": "my-monorepo",
"version": "1.0.0",
"workspaces": ["docs", "apps/*", "packages/*"]
}

pnpm

在根目录的 pnpm-workspace.yaml 文件中配置工作区列表。

1
2
3
4
packages:
- 'docs'
- 'apps/*'
- 'packages/*'

添加工作区依赖

在某个工作区将另一个工作区作为依赖项,需要在其package.json 指明:

npm

1
2
3
4
5
{
"dependencies": {
"shared-utils": "*"
}
}

yarn

1
2
3
4
5
{
"dependencies": {
"shared-utils": "*"
}
}

pnpm

1
2
3
4
5
{
"dependencies": {
"shared-utils": "workspace:*"
}
}

原理解释

在根目录执行npm install 后,npm 会在根目录和每个工作区下载依赖包到 node_modules , 当遇到工作区依赖后,会将工作区符号链接node_modules, 所以可以将其当做普通依赖一样正常导入。

使用 Turborepo 管理 monorepo

Turborepo 是一个在包管理器提供的monorepo 管理功能之上,提供了更方便并且性能更好的构建系统。

使用 Turborepo只要在根目录安装turbo, 同时在根目录下添加turbo.json 即可开始使用

1
2
3
4
5
6
7
8
{
"pipeline": {
"build": {
"dependsOn": ["^build"]
},
"lint": {}
}
}

当运行  turbo lint  时,Turborepo 会查看每个工作区中的每个  lint  脚本并运行它。

turbo.json 主要配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
// 全局环境变量
"globalEnv": ["GITHUB_TOKEN"],
// 任务管道
"pipeline": {
// 任务名称,会去匹配工作区的同名任务
"build": {
// 此任务所依赖的任务列表, 执行此任务前,会先执行依赖的任务
// ^ 前缀表示,此任务取决于工作区的依赖关系
// ^build 表示依赖的工作区都执行完 build 之后,再执行此任务
// 下面的配置表示,执行 turbo build 时,会先执行 turbo lint
// 和依赖项中的 build 任务
"dependsOn": ["lint", "^build"],
// 缓存输出文件
"outputs":["dist/**"],
// 是否缓存 outputs, 默认 true
"cache": true,
// 只在匹配的文件发生更改时重新运行任务
"inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts"],
// 标记长时间运行的任务,比如 dev
"persistent": true,
}
}
}

浏览器存储

HTTP/1.1 200 OK
Content-type: text/html
Set-Cookie: name=value; expires=Mon, 22-Jan-07 07:10:24 GMT; domain=.wrox.com; path=/; secure
Other-header: other-header-value

这里创建的 cookie 对所有 wrox.com 的子域及该域中的所有页面有效(通过 path=/ 指定)。不过,这个 cookie 只能在 SSL 连接上发送,因为设置了 secure 标志。 要知道,域、路径、过期时间和 secure 标志用于告诉浏览器什么情况下应该在请求中包含 cookie。 这些参数并不会随请求发送给服务器,实际发送的只有 cookie 的名/值对。

SameSite

  • Non**e**。浏览器会在同站请求、跨站请求下继续发送 cookies,不区分大小写。

  • **Strict****。**浏览器将只在访问相同站点时发送 cookie。(在原有 Cookies 的限制条件上的加强,如上文 “Cookie 的作用域” 所述)。

  • **Lax**与 **Strict** 类似,但用户从外部站点导航至 URL 时(例如通过链接)除外。 在新版本浏览器中,为默认选项,Same-site cookies 将会为一些跨站子请求保留,如图片加载或者 frames 的调用,但只有当用户从外部站点导航到 URL 时才会发送。如 link 链接。

未设置 SameSite, 主流浏览器默认设为 Lax。

JS 想要操作 cookie 只能通过 BOM 接口 document.cookie 操作,并且需要 URL 编码(可以用 decodeURIComponent),一般都是封装下方法再使用。

如果 cookie 设置了 HttpOnly, 则前端无法操作。

限制

  • 不超过 300 个 cookie;

  • 每个 cookie 不超过 4096 字节;

  • 每个域不超过 20 个 cookie;

  • 每个域不超过 81920 字节。

Storage

方法

  • clear():删除所有值。

  • getItem(name):取得给定 name 的值。

  • key(index):取得给定数值位置的名称。

  • removeItem(name):删除给定 name 的名/值对。

  • setItem(name, value):设置给定 name 的值。

sessionStorage

使用 sessionStorage 存储的数据只在当前会话有效。有效的页面:

  • 设置 sessionStorage 的页面页面1
  • 页面1打开的页面

如果在地址栏敲入新的页面,即使是同一个页面,也无法获取到 sessionStorage。

localStorage

满足域、端口和协议都相同的页面可以获取到同一个 localSrtorage。

区别

localStorage: 存储在 localStorage 中的数据会保留到通过 JavaScript 删除或者用户清除浏览器缓存。localStorage 数据不受页面刷新影响,也不会因关闭窗口、标签页或重新启动浏览器而丢失。

sessionStorage: sessionStorage 中的数据不受页面刷新影响,会在关闭标签页后丢失。

IndexedDB

todo

Cache Storage API

Cache 这个 API 是针对 Request Response 的。Cache 一般结合 Service Worker 使用,因为请求级别的缓存与具有页面拦截功能的 Service Worker 最配。

创建或打开一个命名空间

// 不存在则创建
const myCache = await caches.open(‘myCache’);

添加缓存

通过 add/addAll 方法添加,调用 add 会类似 fetch 一样发送请求,并将响应放到 Cache Storage 里面。

在 put 方法里传一个 Response 也可以实现添加缓存。

// 参数同 fetch
myCache.add(‘/test-url’);

// 使用 put
fetch(url).then(function (response) {
if (!response.ok) {
throw new TypeError(‘bad response status’);
}
return cache.put(url, response);
})

读取

通过 match 或 matchAll 方法读取。

// 参数同 fetch, 可以是 URL 地址,也可以是 Request 对象
const res = await myCache.match(“/subscribe”);
// const res = await myCache.matchAll(“/subscribe”);

更新

通过 add 或 put 方法更新。

const request = new Request(“/subscribe”);
const fetchResponse = await fetch(request);
myCache.put(request, fetchResponse);

销毁

使用 delete 方法删除某个路径的缓存或者直接删除整个命名空间。

myChache.delete(‘/subscribe’);
caches.delete(‘myChache’);

使用场景

在 service worker (在全局环境里也能用) 中用来缓存 js/css/img 文件或者不经常更新的接口,加快请求或者提供离线访问能力。

babel plugin-transform-runtime 配置和编译结果差异

准备源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// module.js
const foo = () => {
return new Promise(resolve => resolve(1));
};
export const bar = async () => {
await foo();
};
export const far = (...rest) => {
console.log(rest.map(item => item?.aa));
};

// index.js
import { bar, far } from './module';

function* gen() {
const y1 = yield 1;
return y1;
}
const bar1 = async () => {
await far();
};
const p1 = (...rest) => {
return new Promise(resolve => {
resolve(...rest);
});
};

配置 1 : 仅使用 @babel/env

1
2
3
4
5
6
7
8
9
10
11
{
"presets": [
[
"@babel/env",
{
"useBuiltIns": "usage",
"corejs": "3.6.5"
}
]
]
}

编译结果 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// module.js
"use strict";

Object.defineProperty(exports, "__esModule", {
value: true
});
exports.far = exports.bar = void 0;

require("regenerator-runtime/runtime.js");

require("core-js/modules/es.object.to-string.js");

require("core-js/modules/es.promise.js");

require("core-js/modules/es.array.map.js");

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }

function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }

var foo = function foo() {
...
};

var bar = /*#__PURE__*/function () {
...
}();

exports.bar = bar;

var far = function far() {
...
};

exports.far = far;

// index.js
"use strict";

require("core-js/modules/es.object.to-string.js");

require("core-js/modules/es.promise.js");

require("regenerator-runtime/runtime.js");

var _module = require("./module");

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }

function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }

var _marked = /*#__PURE__*/regeneratorRuntime.mark(gen);

function gen() {
...
}

var bar1 = /*#__PURE__*/function () {
...
}();

var p1 = function p1() {
...
};

从结果来看,module.js 和 index.js 都从 core-js 引入了自身需要的 polyfill (全局变量),针对 async/await 分别生成了同样的 asyncGeneratorStep 和 _asyncToGenerator

配置 2 : 使用 @babel/env 和 @babel/plugin-transform-runtime (默认配置)

1
2
3
4
5
6
7
8
9
10
11
12
{
"presets": [
[
"@babel/env",
{
"useBuiltIns": "usage",
"corejs": "3.6.5"
}
]
],
"plugins": [["@babel/plugin-transform-runtime"]]
}

编译结果 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// module.js
"use strict";

var \_interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

Object.defineProperty(exports, "\_\_esModule", {
value: true
});
exports.far = exports.bar = void 0;

var \_regenerator = \_interopRequireDefault(require("@babel/runtime/regenerator"));

var \_asyncToGenerator2 = \_interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
require("core-js/modules/es.object.to-string.js");

require("core-js/modules/es.promise.js");

require("core-js/modules/es.array.map.js");

var foo = function foo() {
...
};

var bar = /_#**PURE**_/function () {
...
}();

exports.bar = bar;

var far = function far() {
};

exports.far = far;

// index.js
"use strict";

var \_interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

require("core-js/modules/es.object.to-string.js");

require("core-js/modules/es.promise.js");

var \_asyncToGenerator2 = \_interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));

var \_regenerator = \_interopRequireDefault(require("@babel/runtime/regenerator"));

var \_module = require("./module");

var \_marked = /_#**PURE**_/\_regenerator.default.mark(gen);

function gen() {
...
}

var bar1 = /_#**PURE**_/function () {
...
}();

var p1 = function p1() {
...
};

添加 @babel/plugin-transform-runtime 后,对于 async/await 不再重复生成 asyncGeneratorStep 和 _asyncToGenerator,改为从 @babel/runtime 中引入, 其余 polyfill 依然从 core-js 引入

配置 3 使用 @babel/env 和 @babel/plugin-transform-runtime (配置 corejs, 需要安装 @babel/runtime-corejs3 依赖)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"presets": [
[
"@babel/env",
{
"useBuiltIns": "usage",
"corejs": "3.6.5"
}
]
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": 3
}
]
]
}

编译结果 3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// module.js
"use strict";

var \_interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");

Object.defineProperty(exports, "\_\_esModule", {
value: true
});
exports.far = exports.bar = void 0;

var \_regenerator = \_interopRequireDefault(require("@babel/runtime-corejs3/regenerator"));

var \_asyncToGenerator2 = \_interopRequireDefault(require("@babel/runtime-corejs3/helpers/asyncToGenerator"));

var \_promise = \_interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));

var \_map = \_interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/map"));

var foo = function foo() {
return new \_promise.default(function (resolve) {
return resolve(1);
});
};

var bar = /_#**PURE**_/function () {
...
}();

exports.bar = bar;

var far = function far() {
...
};

exports.far = far;

// index.js
"use strict";

var \_interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");

var \_promise = \_interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));

var \_asyncToGenerator2 = \_interopRequireDefault(require("@babel/runtime-corejs3/helpers/asyncToGenerator"));

var \_regenerator = \_interopRequireDefault(require("@babel/runtime-corejs3/regenerator"));

var \_module = require("./module");

var \_marked = /_#**PURE**_/\_regenerator.default.mark(gen);

function gen() {
...
}

var bar1 = /_#**PURE**_/function () {
...
}();

var p1 = function p1() {
...
};

@babel/plugin-transform-runtime 添加配置 corejs: 3 后,所有的 polyfill 都改为从 @babel/runtime-corejs3 导入,同时由全局覆盖变为了局部变量,不会污染全局

结论:

@babel/presets 的 『”useBuiltIns”: “usage”』会引入模块中需要的 polyfill 并且是全局覆盖的,但是每个模块都会重复生成辅助函数
添加插件 『@babel/plugin-transform-runtime』 后,辅助函数不会在每个模块中重复生成,而是从 『@babel/runtime』引入
@babel/plugin-transform-runtime 进一步配置 『 “corejs”: 3 』后,所有的 polyfill 都从 『@babel/runtime-corejs3』引入,并且是局部变量,不会污染全局

HTTP 速览

HTTP 的标准是由 IETF 组织制定,跟它相关的标准:

HTTP Documentation (httpwg.org)

HTTP/1.1 相关

中文翻译: duoani/HTTP-RFCs.zh-cn: 翻译 HTTP 相关的 RFC (中英文对照) (github.com)

HTTP/2 相关

中文翻译: abbshr/rfc7540-translation-zh_cn: RFC 7540 - HTTP/2 中文翻译版 (github.com)

HTTP 协议是基于 TCP 协议实现的,在 TCP 的基础上规定了一个 Request-Response 的模式。这个模式就是客户端发送消息给服务端,服务端才能返回消息给客户端,服务端不能主动发送消息。

HTTP 协议格式

HTTP 协议大概可以划分成 Request 和 Response。

Request

Request 是客户端需要发送到服务端的数据,可以分成下面这三部分:

  • Request-Line(请求行)
  • method(方法)
  • URL
  • version(版本)
  • request-header(头)
  • message-body(实体)

具体的数据格式:

GET /hello.txt HTTP/1.1
User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3
Host: www.example.com
Accept-Language: en, mi

sp 是空格,cr 是 \r , lf 是 \n

Response

Response 是服务端返回给客户端的数据,对应的也可以分成三部分:

  • Response Line(响应行)

  • version(版本)

  • status code(状态码)

  • status text(状态文本)

  • Response Header(响应头)

  • message-body(实体)

HTTP/1.1 200 OK
Date: Mon, 27 Jul 2009 12:28:53 GMT
Server: Apache
Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT
ETag: “34aa387-d-1568eb00”
Accept-Ranges: bytes
Content-Length: 51
Vary: Accept-Encoding
Content-Type: text/plain

Hello World! My payload includes a trailing CRLF.

响应状态码

HTTP 2

HTTP2 是 HTTP1.1 的升级版,HTTP2 相对于 HTTP1.1 来说主要有下面几点的改进:

  1. 对 HTTP 的头进行压缩,对于相同的头只需要发送索引表中的索引。解决了 HTTP1.1 每次都要将 header 来回传送。
  2. 支持服务端推送。能够在客户端发送第一个请求时,提前把一部分内容推送给客户端。这可以避免客户端请求顺序带来的并行度不高,从而导致的问题。
  3. 支持 TCP 的连接复用。使用同一个 TCP 连接来传输多个 HTTP 请求,避免了 TCP 连接时的三次握手开销,和初建 TCP 连接时传输窗口小的问题。

webpack的模块化实现

源文件:a.js, b.js, c.js, d.js, index.js

展开源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// a.js
const a = {
x: 1
}
module.exports = a

// b.js
const b = {
x: 1
}
export default b

// c.js
export default {
x: 1
}

// d.js
const d = {
x: 1
}
export { d }

// index.js
import b from './b'
import c from './c'
import { d } from './d'
const a = require('./a')

console.log('a:', a)
console.log('b:', b)
console.log('c:', c)
console.log('d:', d)

就是在 index.js  中引入 [a, b, c, d]  等使用不同模块化方式的模块, 来看看 webpack 是怎么处理模块引用的

下面是 webpack 打包出来的 bundle.js 文件(省略了多余注释和没用到的声明)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
(function (modules) {
// 代码模块缓存, 将执行过的代码模块缓存起来
var installedModules = {};

// 实现模块化代码的主要函数, 通过以文件名为模块id(moduleId)递归调用
function __webpack_require__(moduleId) {
// 若模块已缓存, 则直接调用
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// 创建新的模块代码, 并把模块缓存
var module = (installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {},
});

// 执行对应模块id的代码, 并将模块对象和方法绑定到当前module上
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

// 将模块已加载标识置为true
module.l = true;

// 返回模块对外暴露方法
return module.exports;
}

// 模块化处理函数: 用于 export { aaa, bbb } , import { aaa } from './*
__webpack_require__.d = function (exports, name, getter) {
if (!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true, get: getter });
}
};

// 挂载 _esModule 属性到 exports 上
__webpack_require__.r = function (exports) {
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
Object.defineProperty(exports, '__esModule', { value: true });
};

// 封装了 Object.prototype.hasOwnProperty.call
__webpack_require__.o = function (object, property) {
return Object.prototype.hasOwnProperty.call(object, property);
};

// 代码启动, 传入入口文件的模块id
return __webpack_require__((__webpack_require__.s = './src/index.js'));
})({
'./src/a.js': function (module, exports) {
/**
* commonjs 模块
* 直接将导出的对象赋值为 module.exports
*/
eval('const a = {\n x: 1\n}\n\nmodule.exports = a\n\n//# sourceURL=webpack:///./src/a.js?');
},

'./src/b.js': function (module, __webpack_exports__, __webpack_require__) {
/**
* es6 默认导出模块 export default
* 会在 module.exports 属性挂载属性 _esModule = true
* 然后将导出对象挂载到 module.exports.default 上
*/
'use strict';
eval(
'__webpack_require__.r(__webpack_exports__);\nconst b = {\n x: 1\n}\n\n/* harmony default export */ __webpack_exports__["default"] = (b);\n\n//# sourceURL=webpack:///./src/b.js?'
);
},

'./src/c.js': function (module, __webpack_exports__, __webpack_require__) {
/**
* es6 模块
* 同 模块b
*/
'use strict';
eval(
'__webpack_require__.r(__webpack_exports__);\n/* harmony default export */ __webpack_exports__["default"] = ({\n x: 1\n});\n\n//# sourceURL=webpack:///./src/c.js?'
);
},

'./src/d.js': function (module, __webpack_exports__, __webpack_require__) {
/**
* es6 命名导出模块 export const d = {}
* 会在 module.exports 属性挂载属性 _esModule = true
* 通过 getter 在访问 module.exports[name] 时返回导出对象
*/
'use strict';
eval(
'__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "d", function() { return d; });\nconst d = {\n x: 1\n}\n\n\n\n//# sourceURL=webpack:///./src/d.js?'
);
},

'./src/index.js': function (module, __webpack_exports__, __webpack_require__) {
/**
* 入口文件
* 通过 _webpack_require__() 得到导入对象,即 编译后的 module.exports 属性
* 对不同的模块化方式做不同的引入处理
*/
'use strict';
eval(
'__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _b__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./b */ "./src/b.js");\n/* harmony import */ var _c__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./c */ "./src/c.js");\n/* harmony import */ var _d__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./d */ "./src/d.js");\n\n\n\nconst a = __webpack_require__(/*! ./a */ "./src/a.js")\n\nconsole.log(\'a\', a)\nconsole.log(\'b\', _b__WEBPACK_IMPORTED_MODULE_0__["default"], _b__WEBPACK_IMPORTED_MODULE_0__)\nconsole.log(\'c\', _c__WEBPACK_IMPORTED_MODULE_1__["default"], _c__WEBPACK_IMPORTED_MODULE_1__)\nconsole.log(\'d\', _d__WEBPACK_IMPORTED_MODULE_2__["d"], _d__WEBPACK_IMPORTED_MODULE_2__)\n\n//# sourceURL=webpack:///./src/index.js?'
);
},
});

整体代码流程就是创建一个自执行函数, 根据文件名称递归调用内部函数 __webpack_require__(moduleId) , 从而把代码模块化

node常用模块

fs(文件系统) 模块

fs 模块提供了一个 API,用于以模仿标准 POSIX 函数的方式与文件系统进行交互。

所有文件系统操作都具有同步和异步的形式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
读取文件;
const fs = require('fs');

// 异步读取
fs.readFile('./index.txt', 'utf8', (err, data) => {
console.log(data); // Hello Nodejs
});

// 同步读取
const data = fs.readFileSync('./index.txt', 'utf8');

console.log(data); // Hello Nodejs

// 创建读取流
const stream = fs.createReadStream('./index.txt', 'utf8');

// 这里fs.createReadStream用到了前面介绍的events eventEmitter.on() 方法来监听事件
stream.on('data', data => {
console.log(data); // Hello Nodejs
});

写入/修改文件
写入文件时,如果文件不存在,则会创建并写入,如果文件存在,会覆盖文件内容.

1
2
3
4
5
6
7
8
9
10
11
const fs = require('fs');
// 异步写入
fs.writeFile('./write.txt', 'Hello Nodejs', 'utf8', err => {
if (err) throw err;
});
// 同步写入
fs.writeFileSync('./writeSync.txt', 'Hello Nodejs');
// 文件流写入
const ws = fs.createWriteStream('./writeStream.txt', 'utf8');
ws.write('Hello Nodejs');
ws.end();

删除文件/文件夹
删除文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// 异步删除文件
fs.unlink('./delete.txt', err => {
if (err) throw err;
});

// 同步删除文件
fs.unlinkSync('./deleteSync.txt');
删除文件夹;
// 异步删除文件夹
fs.rmdir('./rmdir', err => {
if (err) throw err;
});

// 同步删除文件夹
fs.rmdirSync('./rmdirSync');
创建文件夹;
// 异步创建文件夹
fs.mkdir('./mkdir', err => {
if (err) throw err;
});

// 同步创建文件夹
fs.mkdirSync('./mkdirSync');
重命名文件 / 文件夹;
const fs = require('fs');

// 异步重命名文件
fs.rename('./rename.txt', './rename-r.txt', err => {
if (err) throw err;
});

// 同步重命名文件夹
fs.renameSync('./renameSync', './renameSync-r');
复制文件 / 文件夹;
const fs = require('fs');

// 异步复制文件
fs.copyFile('./copy.txt', './copy-c.txt', (err, copyFiles) => {
if (err) throw err;
});

// 同步复制文件夹
fs.copyFileSync('./null', 'null-c');
文件夹状态 - 文件 / 文件夹;
const fs = require('fs');

// 异步获取文件状态
fs.stat('./dir', (err, stats) => {
if (err) throw err;
// 是否是文件类型
console.log(stats.isFile()); // false
// 是否是文件夹类型
console.log(stats.isDirectory()); // true
});

// 同步获取文件状态
const stats = fs.statSync('./stats.txt');

// 是否是文件类型
console.log(stats.isFile()); // true
// 是否是文件夹类型
console.log(stats.isDirectory()); // false

events 模块

Node 是基于事件驱动的,其就是通过核心模块 Events 实现的。 Events 模块定义了 EventEmitter 类。实际上就是发布订阅模式。所有可能触发事件的对象都是继承自 EventEmitter 类的,其主要包含下面方法:

  • addEventListener
  • on
  • once
  • removeListener
  • removeAllListeners
  • setMaxListeners
  • emit

使用 EventEmitter
其他类需要触发事件等,都需要自行继承这个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
let util = require('util')
let EventEmitter = require('events')

function OtherClass(path) {
EventEmitter.call(this)
}
util.inherits(OtherClass, EventEmitter)
newListener 事件
EventEmitter 实例还可以监听一个 newListener 事件,即每次给这个实例添加新的监听函数时,都会触发 newListener 事件。

this.on('newListener', function (type, listener) {
// 每当监听一个事件时,就执行某些行为
})

stream 模块

流(stream)是 Node.js 中处理流式数据的抽象接口。 stream 模块用于构建实现了流接口的对象。

使用 stream 的场景:

使用 fs.readFile 读取文件时会将数据整个读取到内存中再处理,当处理大文件时,会极大的展示资源,这时就是使用 stream 的时候了。

Stream 不会一次性的读取文件,而是分批次的读取适量的内容到缓存区进行操作,这样对内存的占用会少很多。

流的四种基本类型

Writable - 可写入数据的流(例如 fs.createWriteStream())。

Readable - 可读取数据的流(例如 fs.createReadStream())。

Duplex - 可读又可写的流(例如 net.Socket)。

Transform - 在读写过程中可以修改或转换数据的 Duplex 流(例如 zlib.createDeflate())。

缓冲

缓冲区有大小限制,由 highWaterMark 指定字节总数(对象模式下指定对象总数)。调用 stream.push(chunk)时,数据缓冲在可读流中,等待被消费,如果缓冲区的数据大小达到了 highWaterMark 指定的阈值,流会停止读取数据,直到当前缓冲区的数据被消费。

用于消费的 api

使用继承自 events 的 emitter api 来通讯状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const file = fs.createReadStream('./test.txt', {
highWaterMark: 2,
flags: 'r',
encoding: 'utf8',
});

file.on('open', a => {
console.log('open', a);
});

file.on('data', chunk => {
console.log('data', chunk);
});

file.on('end', chunk => {
console.log('end', chunk);
});

file.on('close', a => {
console.log('close', a);
});
// open -> data -> data -> ... -> end -> close

(异步)事件触发顺序 open -> data -> data -> … -> end -> close

URL 模块

url 模块用于处理与解析 URL。

URL 字符串是结构化的字符串,包含多个含义不同的组成部分。 解析字符串后返回的 URL 对象,每个属性对应字符串的各个组成部分。

url 模块提供两种 api 来处理 url。(两种 api 的处理结果不同,详细)

const url = require(‘url’)
// WHATWG 标准
const myUrl = new url(‘https://baidu.com:8080/a/b/c?q=str#hash')
// 遗留 api
const myUrl2 = url.parse(‘https://baidu.com:8080/a/b/c?q=str#hash')
以下介绍的都是 WHATWG 标准解析出的对象

URL 对象的属性
const url = require(“url”);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const url = require("url");

const myURL = new url("https://github.com/webfansplz#hello");
console.log(myURL);
{
href: 'https://github.com/webfansplz#hello', // 序列化的 URL
origin: 'https://github.com', // 序列化的 URL 的 origin
protocol: 'https:', // URL 的协议
username: '', // URL 的用户名
password: '', // URL 的密码
host: 'github.com', // URL 的主机
hostname: 'github.com', // URL 的主机名
port: '', // URL 的端口
pathname: '/webfansplz', // URL 的路径
search: '', // URL 的序列化查询参数
searchParams: URLSearchParams {}, // URL 查询参数的 URLSearchParams 对象
hash: '#hello' // URL 的片段
}

URL 对象属性 除了 origin 和 searchParams 是只读的,其他都是可写的.

  1. 序列化 URL
1
2
3
4
5
6
7
8
9
const { URL } = require('url');

const myURL = new URL('https://github.com/webfansplz#hello');

console.log(myURL.href); // https://github.com/webfansplz#hello

console.log(myURL.toString()); // https://github.com/webfansplz#hello

console.log(myURL.toJSON()); // https://github.com/webfansplz#hello

初识MongoDB

数据库分类

关系型

SQL Server MySQL Access ORACLE

数据库 –> 表 –> 记录

非关系型

MongoDb 文档型的非关系数据库

安装

官网下载合适版本
不停的下一步,下一步
增加环境变量,以便直接在 shell 中使用
数据库 –> 集合 –> 文档 database –> collection –> document

存储格式 BSON 类似于 JSON

每一个 {}称为一条文档

{ ​ “name” : “小明”, ​ “age” : 18, ​ “hobby”: [“睡觉”, “吃饭”] }

命令操作

创建数据库

mongod:
mongod --dbpath dir # 打开或新建一个数据库 # mongod --dbpath E:\mongodb\mydb
mongo:
use dbname # 新建叫做 dbname 的数据库, 同时进入 dbname
初步操作

1
2
3
4
5
6
7
show dbs # 查看所有的数据库
show collections # 查看当前库下所有的 集合

## 在叫做 collectionName 的集合中插入一条文档,如果集合不存在,则新建该集合
db.collectionName.insert(obj)
## 查找名为 collectionName 集合的所有文档
db.collectionName.find()

导入数据

假设 C:\user\db\test.json 是 1000 条文档

1
2
3
4
5
6
7
8
mongoimport --db test --collection user --drop --file C:\user\db\test.json

## 以上操作会把 test.json 里的文档全部导入到 数据库 test -> 集合 user 中

--db 导入到哪个库
--collection 导入到哪个集合
--drop 清空集合中原有文档
--file 要导入的文件路径

CRUD

假设集合名叫 user

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
## 查询所有文档
db.user.find()

# 查询 k 的值为 v 的所有文档
db.user.find({k: v})

# and 操作
# 查询 k1 的值为 v1 且 k2 的值为 v2 的所有文档
db.user.find({k1: v1, k2: v2})

# or 操作
# 查询 k1 的值为 v1 或 k2 的值为 v2 的所有文档
db.user.find({$or: [{k1: v1}, {k2: v2}]})

# 比较(大于,小于)
db.user.find({k: {$gt: v}}) # 查询 k 的值大于 v 的文档
db.user.find({k: {$lt: v}}) # 查询 k 的值小于 v 的文档
# $gt 大于
# $lt 小于

# 组合
db.user.find({k: {$gt: v1, $lt: v2}}) # 查询 k 的值大于 v1 同时小于 v2 的文档
# 更新
db.user.update(
{k: v1}, # 查询条件
{
$set: {k: v2} # 把查到的 k 改为 v2
}
)
# 更新详细语法
db.user.update(
<query>, # 查询条件
<update>, # 更新方式,如 $set, $inc 等
{
multi: <boolean>, # (可选)默认为 false,只匹配找到的第一条, 如果为 true, 所有满足条件的都匹配
}
)
# 删除
db.dropDatabase() # 删除当前所在数据库
db.user.drop() # 删除名为 user 的集合
db.user.remove({k: v}) # 删除匹配到的所有 k 值为 v 的文档
db.user.remove({k: v}, {justOne: true}) # 删除第一个匹配到的所有 k 值为 v 的文档
db.user.remove({}) # 清空 user 集合
排序
db.user.find({查询条件}).sort({k1: 1}, {k2: -1})

# 按照 k1 来排序, 如果 k1 的值相同,按照 k2 来排序

# 1 升序, -1 降序

node 里简单封装增删改查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
// node mongodb 版本 v3.1.13 适用
const MongoClient = require('mongodb').MongoClient;
const log = console.log.bind(console);

class Dao {
/**
* 构造函数
* @param {string} url
* @param {string} dbname
* @param {string} colname
*/
constructor(url, dbname, colname) {
this.url = url;
this.dbname = dbname;
this.colname = colname;
}

/**
* 连接数据库
*/
_connect() {
return new Promise((resolve, reject) => {
MongoClient.connect(this.url, { useNewUrlParser: true }, (err, client) => {
if (err) {
reject(err);
} else {
resolve(client);
}
});
});
}

/**
* 插入文档
* @param {arr || object} documents
* @param {boolean} insertMany
* 使用 insertMany(arr), 在 arr === [] 时,会报错
*/
insert(documents, insertMany = false) {
return new Promise((resolve, reject) => {
this._connect()
.then(client => {
let col = client.db(this.dbname).collection(this.colname);
if (insertMany) {
col
.insertMany(documents)
.then(res => {
resolve(res);
})
.catch(err => {
log(err);
});
client.close();
} else {
col
.insertOne(documents)
.then(res => {
resolve(res);
})
.catch(err => {
log(err);
});
client.close();
}
})
.catch(err => {
reject(err);
});
});
}

/**
* 查询
* @param {object} document
*/
query(document, pageConfig) {
document = document || {};
pageConfig = pageConfig || {};
let page = pageConfig.page;
let amount = pageConfig.amount;
const resData = [];

return new Promise((resolve, reject) => {
this._connect()
.then(client => {
let col = client.db(this.dbname).collection(this.colname);
let cursor = col
.find(document)
.limit(amount)
.skip((page - 1) * amount);
cursor.each((err, data) => {
if (err) {
reject(err);
client.close();
} else if (data !== null) {
resData.push(data);
} else {
resolve(resData);
client.close();
}
});
})
.catch(err => {
log(err);
});
});
}

/**
* 删除集合中的数据
* @param {object} query
* @param {boolean} deleteMany
*/
delete(query, deleteMany = false) {
return new Promise((resolve, reject) => {
this._connect()
.then(client => {
let col = client.db(this.dbname).collection(this.colname);
if (deleteMany) {
col.deleteMany(query).then(res => {
resolve(res);
client.close();
});
} else {
col.deleteOne(query).then(res => {
resolve(res);
client.close();
});
}
})
.catch(err => {
log(err);
});
});
}

/**
* 更新
* @param {obj} filter
* @param {obj} updater
*/
update(filter, updater) {
let uptaterCpy = { $set: updater };

return new Promise((resolve, reject) => {
this._connect()
.then(client => {
let col = client.db(this.dbname).collection(this.colname);
col.updateMany(filter, uptaterCpy).then(res => {
resolve(res);
client.close();
});
})
.catch(err => {
log(err);
});
});
}
}

//
let url = 'mongodb://localhost:27017';
let dbname = 'test';
let colname = 'user';
let dao = new Dao(url, dbname, colname);

let arr = [];
for (let i = 0; i < 20; i++) {
arr.push({
userid: '234',
age: i,
});
}

type 和 interface 的异同

相同点

1
2
3
4
5
6
7
interface IAnimal {
name: string;
}

type Animal = {
name: string,
};

泛型

1
2
3
4
5
6
7
interface IAnimal<p = string> {
name: p;
}

type Animal<p = string> = {
name: p,
};

交叉继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type Robot = {
power: number,
};

interface IRobot {
name: string;
}

interface IRobotAnimal1 extends IAnimal, IRobot {}
interface IRobotAnimal2 extends IAnimal, Robot {}
interface IRobotAnimal3 extends Animal, IRobot {}
interface IRobotAnimal4 extends Animal, Robot {}

type RobotAnimal1 = IAnimal & IRobot;
type RobotAnimal2 = IAnimal & Robot;
type RobotAnimal3 = Animal & IRobot;
type RobotAnimal4 = Animal & Robot;

实现

1
2
3
4
5
6
7
class Dog implements IAnimal {
name: string = 'doge';
}

class Cat implements Animal {
name: string = 'cat cat';
}

继承类

1
2
3
4
5
6
7
8
9
10
11
class Control {
private state: any;
}

interface ISelectableControl extends Control {
select(): void;
}

type SelectableControl = Control & {
select(): () => void;
};

函数

1
2
3
4
5
6
7
8
9
10
11
12
type Bark = (x: string) => void;

interface IBark {
(x: string): void;
}

// 函数 泛型
type Bark1 = <T = Animal>(x: T) => void;

interface IBark1 {
<T = Animal>(x: T): void;
}

递归声明

1
2
3
4
5
6
7
8
9
type Tree<P> = {
node: P,
leafs: Tree<P>[],
};

interface ITree<P> {
node: P;
leafs: Tree<P>[];
}

可索引的

1
2
3
4
5
6
7
type StringRecord = {
[index: string]: number,
};

interface IStringRecord {
[index: string]: number;
}

不同点
只能使用 type 来别名基本类型

1
2
3
4
5
6
7
type NewNumber = number;

interface INewNumber extends number {}
// 'number' only refers to a type, but is being used as a value here.

interface INewNumber extends Number {}
// 这样写是可以的,但是不要忘记 1 instanceof Number === false

元组
不能使用 interface 声明元组

1
2
3
4
5
6
7
8
9
10
11
type Tuples = [number, number];

interface ITuples {
0: number;
1: number;
}

[1, 2, 3] as Tuples;
// Conversion of type '[number, number, number]' to type 'Tuples' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. Types of property 'length' are incompatible.

[1, 2, 3] as ITuples;

不相关的合集
只有 type 可以做不相关合集

1
type SomeAnimal = { type: Dog } | { type: Cat };

且不能对 不相关集合不能使用 extends 关键字

1
2
interface ISomeAnimal extends SomeAnimal {}
// An interface can only extend an object type or intersection of object types with statically known members

每个作用域只能声明一次类型

1
2
3
type Once = { a: string };
type Once = { b: string };
// Duplicate identifier 'Once'

可以在每个作用域中多次声明接口(会进行声明合并)

1
2
3
4
5
6
interface IOnce {
a: string;
}
interface IOnce {
b: string;
}

react 中的常用类型

React.FC | React.FunctionComponent

表示函数组件的类型

1
const MyComponent: React.FC<Props> = function(p) {...}

React.Component

表示类组件的类型

1
class MyComponent extends React.Component<Props, State> { ...

React.ComponentType

表示(React.FC | React. Component)的联合的类型-在 HOC 中使用

1
2
3
const withState = <P extends WrappedComponentProps>(
WrappedComponent: React.ComponentType<P>,
) => { ...

React.ComponentProps

获取指定组件 XXX 的 Props 类型(警告: 不能使用静态声明的默认道具和通用道具)

1
type MyComponentProps = React.ComponentProps<typeof MyComponent>;

React.ReactElement | JSX.Element

类型表示 DOM 组件(例如

)或自定义的组件(例如 )

1
const elementOnly: React.ReactElement = <div /> || <MyComponent />;

React.ReactNode

表示任何可能类型的 React 节点( ReactElement + 基本 JS 类型)

1
2
const elementOrPrimitive: React.ReactNode = 'string' || 0 || false || null || undefined || <div /> || <MyComponent />;
const Component = ({ children: React.ReactNode }) => ...

React.CSSProperties

JSX 中输入表示样式对象的类型

1
2
const styles: React.CSSProperties = { flexDirection: 'row', ...
const element = <div style={styles} ...

React.HTMLProps

表示指定 HTML 元素的类型-用于扩展 HTML 元素

1
2
3
const Input: React.FC<Props & React.HTMLProps<HTMLInputElement>> = props => { ... }

<Input about={...} accept={...} alt={...} ... />

React.ReactEventHandler

表示泛型事件处理函数-用于声明处理事件的函数

1
2
3
const handleChange: React.ReactEventHandler<HTMLInputElement> = (ev) => { ... }

<input onChange={handleChange} ... />

React.XXXEvent

表示更具体的事件。 常见的事件: ChangeEvent, FormEvent, FocusEvent, KeyboardEvent, MouseEvent, DragEvent, PointerEvent, WheelEvent, TouchEvent.

1
2
3
const handleChange = (ev: React.MouseEvent<HTMLDivElement>) => { ... }

<div onMouseMove={handleChange} ... />

在上面的代码中反应。 鼠标事件  React.MouseEvent  是鼠标事件的类型,这个事件发生在  HTMLDivElement  上