Git 提交规范

良好的代码风格很重要,但是良好的 commit 记录同样重要。我们提交代码的时候都要提交 commit message,否则就不允许提交。

$ git commit -m "hello world"
1

# commit message 格式

目前最流行的提交规范是Conventional Commits (opens new window),格式如下:

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]
1
2
3
4
5

Header 有三个字段,type(必须),scope(可选) 和 description(必须)。

type 用于说明 commit 的类别,有以下几个选项:build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test

build: 对构建系统或者外部依赖项进行了修改
chore: 构建过程或辅助工具的变动
ci: 对CI配置文件或脚本进行了修改
docs: 对文档进行了修改
feat: 增加新功能
fix: 修复BUG
perf: 优化相关,比如提升性能,体验
refactor: 重构代码
revert: 回滚到上一个版本
style: 修改代码格式
test: 添加测试代码
1
2
3
4
5
6
7
8
9
10
11

scope 用于说明 commit 影响的范围,比如某个组件的修改。

description 是 commit 目的的简短描述,不超过 50 个字符。最好以动词开头,首字母小写,末尾不加句号(.)

# Body

Body 是可选的,用来对本次提交进行详细描述, 可以写成多行,例如:

fix: prevent racing of requests

Introduce a request id and a reference to latest request. Dismiss
incoming responses other than from latest request.
1
2
3
4

Footer 也是可选的,主要有两个使用情况。一种是不兼容性更新:

chore!: drop support for Node 6

BREAKING CHANGE: use JavaScript features not available in Node 6.
1
2
3

一种是引用提交的问题,例如本次提交是修正某个 Issue 的问题。

feat(lang): add Polish language

Closes #234
1
2
3

# 总结

良好的 commit 提交规范和代码规范同样重要。

# 参考

Commit message 和 Change log 编写指南 (opens new window)

TypeScript中应该禁止使用enum

TypeScript 添加了一个 enum 的数据类型结构,但是这个数据类型在 JavaScript 中并不存在,在编译过程中会被转换成 Object。并且 enum 类型可以完全被 union 类型替代。 所以我们推荐使用 union 来代替 enum。接下来我们使用一个例子来解释下 enum 如何被替换成 union。

# enum 定义数据类型

我们来封装一个常见的 fetch 函数,使用 enum 定义的代码如下。

enum HTTPRequestMethod {
  GET = 'GET',
  POST = 'POST'
}

function fetchJSON(url: string, method: HTTPRequestMethod) {
  return fetch(url, { method }).then(response => response.json());
}
1
2
3
4
5
6
7
8

编译成 JS 代码如下:

'use strict';
var HTTPRequestMethod;
(function(HTTPRequestMethod) {
  HTTPRequestMethod['GET'] = 'GET';
  HTTPRequestMethod['POST'] = 'POST';
})(HTTPRequestMethod || (HTTPRequestMethod = {}));
function fetchJSON(url, method) {
  return fetch(url, { method }).then(response => response.json());
}
1
2
3
4
5
6
7
8
9

这样编译的 JS 代码,定义了 HTTPRequestMethod 对象,但并不是 const 常量,有被更改的可能性,非常不优雅。

# union 定义数据类型

const HTTPRequestMethod = {
  GET: 'GET',
  POST: 'POST'
} as const;

type ValuesOf<T> = T[keyof T];
type HTTPRequestMethodType = ValuesOf<typeof HTTPRequestMethod>;

function fetchJSON(url: string, method: HTTPRequestMethodType) {
  return fetch(url, { method }).then(response => response.json());
}
1
2
3
4
5
6
7
8
9
10
11

编译成 js 代码之后是:

'use strict';
const HTTPRequestMethod = {
  GET: 'GET',
  POST: 'POST'
};
function fetchJSON(url, method) {
  return fetch(url, { method }).then(response => response.json());
}
1
2
3
4
5
6
7
8

# 总结

对比 enum 和 union 定义生成的代码,我们能明显感受到 union 类型生成的代码更优雅。而且 enum 还有其他缺点,大家可以查看下参考链接。

# 参考

さようなら、TypeScript enum (opens new window)

Why it is not good to use enums? (opens new window)

Const Assertions in Literal Expressions in TypeScript (opens new window)

JS中关于深拷贝和浅拷贝

JS 有基本数据类型和复合数据类型,基本数据类型包括 null,undefined,boolean,number,string,symbol。针对这些数据类型都是值拷贝。复合数据类型包括 object,以及衍生的内置数据类型,Array, Map, Set 等等。默认对复合数据类型的赋值操作都是引用拷贝,就是浅拷贝。

# JSON.stringify 实现深拷贝

一种常用的方式是使用 JSON 接口先转换成字符串再解析成对象。

const myDeepCopy = JSON.parse(JSON.stringify(myOriginal));
1

虽然 V8 引擎优化了 JSON 的解析速度,但是这种方式还是有一些限制条件的,如果数据中存在 Map, Date 等 JS 内置数据结构就没法正常复制。

# 手动实现 deepClone

export const isObject = (obj: any): boolean => {
  return obj !== null && typeof obj === 'object';
};

export const objectToString = Object.prototype.toString;
export const toTypeString = (value: unknown): string =>
  objectToString.call(value);

export const isMap = (val: unknown): val is Map<any, any> =>
  toTypeString(val) === '[object Map]';
export const isSet = (val: unknown): val is Set<any> =>
  toTypeString(val) === '[object Set]';
export const isDate = (val: unknown): val is Date =>
  toTypeString(val) === '[object Date]';
export const isRegExp = (val: unknown): val is RegExp =>
  toTypeString(val) === '[object RegExp]';

export function deepClone<T>(val: T): T {
  if (!isObject(val)) {
    return val;
  }
  if (Array.isArray(val)) {
    return val.map(deepClone) as typeof val;
  }
  if (isDate(val)) {
    return new Date(val) as typeof val;
  }
  if (isMap(val)) {
    return new Map(val) as typeof val;
  }
  if (isSet(val)) {
    return new Set(val) as typeof val;
  }
  if (isRegExp(val)) {
    return new RegExp(val.source, val.flags) as typeof val;
  }
  return Object.keys(val).reduce((acc, key) => {
    acc[key as keyof T] = deepClone(val[key as keyof T]);
    return acc;
  }, {} as T);
}
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

手动实现 deepClone 可以处理 Date, Map, Set, RegExp 这些数据结构,但这也不是完美的解决方案,例如二进制的数据结构 ArrayBuffer 就没在处理范围,但对于绝大多数场景也足够用了。

# 使用 structuredClone

const myDeepCopy = structuredClone(myOriginal);
1

最新的浏览器标准添加了 structuredClone 函数,可以实现常见数据结构的复制,但是有些数据结构也是不能复制的,例如 Function。不过 Function 也没有复制的必要。 由于这个函数最近才添加,实际使用中可能还需要搭配 polyfill (opens new window)

# 总结

实际开发工作中大家可以根据自己的需要选择合适的深拷贝解决方案。

# 参考

structuredClone (opens new window)

Deep-copying in JavaScript using structuredClone (opens new window)

cloneDeep (opens new window)