@taicode/common-base 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/output/catch/catch.d.ts +9 -0
- package/output/catch/catch.d.ts.map +1 -0
- package/output/catch/catch.js +26 -0
- package/output/catch/catch.test.d.ts +2 -0
- package/output/catch/catch.test.d.ts.map +1 -0
- package/output/catch/catch.test.js +258 -0
- package/output/catch/index.d.ts +2 -0
- package/output/catch/index.d.ts.map +1 -0
- package/output/catch/index.js +1 -0
- package/output/color/color.d.ts +6 -0
- package/output/color/color.d.ts.map +1 -0
- package/output/color/color.js +57 -0
- package/output/color/color.test.d.ts +2 -0
- package/output/color/color.test.d.ts.map +1 -0
- package/output/color/color.test.js +110 -0
- package/output/color/index.d.ts +2 -0
- package/output/color/index.d.ts.map +1 -0
- package/output/color/index.js +1 -0
- package/output/debounce/debounce.d.ts +7 -0
- package/output/debounce/debounce.d.ts.map +1 -0
- package/output/debounce/debounce.js +36 -0
- package/output/debounce/debounce.test.d.ts +2 -0
- package/output/debounce/debounce.test.d.ts.map +1 -0
- package/output/debounce/debounce.test.js +135 -0
- package/output/debounce/index.d.ts +2 -0
- package/output/debounce/index.d.ts.map +1 -0
- package/output/debounce/index.js +1 -0
- package/output/disposer/disposer.d.ts +6 -0
- package/output/disposer/disposer.d.ts.map +1 -0
- package/output/disposer/disposer.js +19 -0
- package/output/disposer/disposer.test.d.ts +2 -0
- package/output/disposer/disposer.test.d.ts.map +1 -0
- package/output/disposer/disposer.test.js +192 -0
- package/output/disposer/index.d.ts +2 -0
- package/output/disposer/index.d.ts.map +1 -0
- package/output/disposer/index.js +1 -0
- package/output/error/error.d.ts +23 -0
- package/output/error/error.d.ts.map +1 -0
- package/output/error/error.js +37 -0
- package/output/error/error.test.d.ts +2 -0
- package/output/error/error.test.d.ts.map +1 -0
- package/output/error/error.test.js +242 -0
- package/output/error/index.d.ts +2 -0
- package/output/error/index.d.ts.map +1 -0
- package/output/error/index.js +1 -0
- package/output/event-emitter/event-emitter.d.ts +33 -0
- package/output/event-emitter/event-emitter.d.ts.map +1 -0
- package/output/event-emitter/event-emitter.js +66 -0
- package/output/event-emitter/event-emitter.test.d.ts +2 -0
- package/output/event-emitter/event-emitter.test.d.ts.map +1 -0
- package/output/event-emitter/event-emitter.test.js +177 -0
- package/output/event-emitter/index.d.ts +2 -0
- package/output/event-emitter/index.d.ts.map +1 -0
- package/output/event-emitter/index.js +1 -0
- package/output/events/disposer.d.ts +6 -0
- package/output/events/disposer.d.ts.map +1 -0
- package/output/events/disposer.js +19 -0
- package/output/events/disposer.test.d.ts +2 -0
- package/output/events/disposer.test.d.ts.map +1 -0
- package/output/events/disposer.test.js +192 -0
- package/output/events/event-emitter.d.ts +33 -0
- package/output/events/event-emitter.d.ts.map +1 -0
- package/output/events/event-emitter.js +66 -0
- package/output/events/event-emitter.test.d.ts +2 -0
- package/output/events/event-emitter.test.d.ts.map +1 -0
- package/output/events/event-emitter.test.js +213 -0
- package/output/events/index.d.ts +3 -0
- package/output/events/index.d.ts.map +1 -0
- package/output/events/index.js +3 -0
- package/output/index.d.ts +11 -0
- package/output/index.d.ts.map +1 -0
- package/output/index.js +15 -0
- package/output/logger/index.d.ts +18 -0
- package/output/logger/index.d.ts.map +1 -0
- package/output/logger/index.js +34 -0
- package/output/logger/logger.test.d.ts +2 -0
- package/output/logger/logger.test.d.ts.map +1 -0
- package/output/logger/logger.test.js +33 -0
- package/output/number/index.d.ts +2 -0
- package/output/number/index.d.ts.map +1 -0
- package/output/number/index.js +1 -0
- package/output/number/number.d.ts +2 -0
- package/output/number/number.d.ts.map +1 -0
- package/output/number/number.js +23 -0
- package/output/number/number.test.d.ts +2 -0
- package/output/number/number.test.d.ts.map +1 -0
- package/output/number/number.test.js +50 -0
- package/output/object/index.d.ts +2 -0
- package/output/object/index.d.ts.map +1 -0
- package/output/object/index.js +1 -0
- package/output/object/object.d.ts +7 -0
- package/output/object/object.d.ts.map +1 -0
- package/output/object/object.js +27 -0
- package/output/object/object.test.d.ts +2 -0
- package/output/object/object.test.d.ts.map +1 -0
- package/output/object/object.test.js +42 -0
- package/output/service/index.d.ts +2 -0
- package/output/service/index.d.ts.map +1 -0
- package/output/service/index.js +1 -0
- package/output/service/service.d.ts +6 -0
- package/output/service/service.d.ts.map +1 -0
- package/output/service/service.js +3 -0
- package/output/string/index.d.ts +2 -0
- package/output/string/index.d.ts.map +1 -0
- package/output/string/index.js +1 -0
- package/output/string/string.d.ts +3 -0
- package/output/string/string.d.ts.map +1 -0
- package/output/string/string.js +9 -0
- package/output/string/string.test.d.ts +2 -0
- package/output/string/string.test.d.ts.map +1 -0
- package/output/string/string.test.js +77 -0
- package/output/throttle/index.d.ts +2 -0
- package/output/throttle/index.d.ts.map +1 -0
- package/output/throttle/index.js +1 -0
- package/output/throttle/throttle.d.ts +12 -0
- package/output/throttle/throttle.d.ts.map +1 -0
- package/output/throttle/throttle.js +55 -0
- package/output/throttle/throttle.test.d.ts +2 -0
- package/output/throttle/throttle.test.d.ts.map +1 -0
- package/output/throttle/throttle.test.js +177 -0
- package/package.json +4 -1
- package/source/catch/catch.test.ts +0 -341
- package/source/catch/catch.ts +0 -35
- package/source/catch/index.ts +0 -1
- package/source/color/color.test.ts +0 -144
- package/source/color/color.ts +0 -73
- package/source/color/index.ts +0 -1
- package/source/debounce/debounce.test.ts +0 -179
- package/source/debounce/debounce.ts +0 -42
- package/source/debounce/index.ts +0 -1
- package/source/disposer/disposer.test.ts +0 -257
- package/source/disposer/disposer.ts +0 -20
- package/source/disposer/index.ts +0 -1
- package/source/error/error.test.ts +0 -293
- package/source/error/error.ts +0 -38
- package/source/error/index.ts +0 -1
- package/source/event-emitter/event-emitter.test.ts +0 -247
- package/source/event-emitter/event-emitter.ts +0 -79
- package/source/event-emitter/index.ts +0 -1
- package/source/index.ts +0 -19
- package/source/logger/index.ts +0 -43
- package/source/logger/logger.test.ts +0 -36
- package/source/number/index.ts +0 -1
- package/source/number/number.test.ts +0 -60
- package/source/number/number.ts +0 -25
- package/source/object/index.ts +0 -1
- package/source/object/object.test.ts +0 -54
- package/source/object/object.ts +0 -29
- package/source/string/index.ts +0 -1
- package/source/string/string.test.ts +0 -98
- package/source/string/string.ts +0 -9
- package/source/throttle/index.ts +0 -1
- package/source/throttle/throttle.test.ts +0 -228
- package/source/throttle/throttle.ts +0 -76
- package/test-exports.js +0 -6
- package/tsconfig.json +0 -98
- package/vitest.config.ts +0 -8
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './number';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"number.d.ts","sourceRoot":"","sources":["../../source/number/number.ts"],"names":[],"mappings":"AAAA,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAwB3D"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function bigintToNumber(value) {
|
|
2
|
+
if (value === null)
|
|
3
|
+
return 0;
|
|
4
|
+
// JavaScript 的 number 类型最大值
|
|
5
|
+
const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER; // 2^53 - 1
|
|
6
|
+
const MIN_SAFE_INTEGER = Number.MIN_SAFE_INTEGER; // -(2^53 - 1)
|
|
7
|
+
try {
|
|
8
|
+
// 检查输入是否为 bigint
|
|
9
|
+
if (typeof value !== 'bigint') {
|
|
10
|
+
throw new TypeError('Input must be a bigint');
|
|
11
|
+
}
|
|
12
|
+
// 检查是否溢出
|
|
13
|
+
if (value > BigInt(MAX_SAFE_INTEGER) || value < BigInt(MIN_SAFE_INTEGER)) {
|
|
14
|
+
return MAX_SAFE_INTEGER; // 溢出,返回 NaN
|
|
15
|
+
}
|
|
16
|
+
// 将 bigint 转换为 number
|
|
17
|
+
return Number(value);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// 捕获任何错误并返回 NaN
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"number.test.d.ts","sourceRoot":"","sources":["../../source/number/number.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { bigintToNumber } from './number';
|
|
3
|
+
describe('bigintToNumber 函数', () => {
|
|
4
|
+
it('应该将小的 bigint 转换为 number', () => {
|
|
5
|
+
const result = bigintToNumber(123n);
|
|
6
|
+
expect(result).toBe(123);
|
|
7
|
+
});
|
|
8
|
+
it('应该将负数 bigint 转换为 number', () => {
|
|
9
|
+
const result = bigintToNumber(-456n);
|
|
10
|
+
expect(result).toBe(-456);
|
|
11
|
+
});
|
|
12
|
+
it('应该将零 bigint 转换为 number', () => {
|
|
13
|
+
const result = bigintToNumber(0n);
|
|
14
|
+
expect(result).toBe(0);
|
|
15
|
+
});
|
|
16
|
+
it('应该为 null 输入返回 0', () => {
|
|
17
|
+
const result = bigintToNumber(null);
|
|
18
|
+
expect(result).toBe(0);
|
|
19
|
+
});
|
|
20
|
+
it('应该处理 MAX_SAFE_INTEGER 的 bigint', () => {
|
|
21
|
+
const maxSafe = BigInt(Number.MAX_SAFE_INTEGER);
|
|
22
|
+
const result = bigintToNumber(maxSafe);
|
|
23
|
+
expect(result).toBe(Number.MAX_SAFE_INTEGER);
|
|
24
|
+
});
|
|
25
|
+
it('应该处理 MIN_SAFE_INTEGER 的 bigint', () => {
|
|
26
|
+
const minSafe = BigInt(Number.MIN_SAFE_INTEGER);
|
|
27
|
+
const result = bigintToNumber(minSafe);
|
|
28
|
+
expect(result).toBe(Number.MIN_SAFE_INTEGER);
|
|
29
|
+
});
|
|
30
|
+
it('应该为 bigint 溢出(正数)返回 MAX_SAFE_INTEGER', () => {
|
|
31
|
+
const tooBig = BigInt(Number.MAX_SAFE_INTEGER) + 1n;
|
|
32
|
+
const result = bigintToNumber(tooBig);
|
|
33
|
+
expect(result).toBe(Number.MAX_SAFE_INTEGER);
|
|
34
|
+
});
|
|
35
|
+
it('应该为 bigint 溢出(负数)返回 MAX_SAFE_INTEGER', () => {
|
|
36
|
+
const tooSmall = BigInt(Number.MIN_SAFE_INTEGER) - 1n;
|
|
37
|
+
const result = bigintToNumber(tooSmall);
|
|
38
|
+
expect(result).toBe(Number.MAX_SAFE_INTEGER);
|
|
39
|
+
});
|
|
40
|
+
it('应该处理非常大的 bigint', () => {
|
|
41
|
+
const veryLarge = 999999999999999999999n;
|
|
42
|
+
const result = bigintToNumber(veryLarge);
|
|
43
|
+
expect(result).toBe(Number.MAX_SAFE_INTEGER);
|
|
44
|
+
});
|
|
45
|
+
it('应该处理从 number 创建的 bigint', () => {
|
|
46
|
+
const bigintFromNumber = BigInt(42);
|
|
47
|
+
const result = bigintToNumber(bigintFromNumber);
|
|
48
|
+
expect(result).toBe(42);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../source/object/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './object';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 递归合并对象,只针对对象类型的属性进行合并,其他类型后者覆盖前者
|
|
3
|
+
*/
|
|
4
|
+
export declare function objectMerge<T extends object>(target: T, ...rest: Partial<T>[]): T;
|
|
5
|
+
export declare function ObjectKeys<T extends object>(obj: T): (keyof T)[];
|
|
6
|
+
export declare function ObjectValues<T extends object>(obj: T): T[keyof T][];
|
|
7
|
+
//# sourceMappingURL=object.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"object.d.ts","sourceRoot":"","sources":["../../source/object/object.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAiBjF;AAED,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,CAEhE;AAED,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,CAEnE"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 递归合并对象,只针对对象类型的属性进行合并,其他类型后者覆盖前者
|
|
3
|
+
*/
|
|
4
|
+
export function objectMerge(target, ...rest) {
|
|
5
|
+
for (const source of rest) {
|
|
6
|
+
for (const key in source) {
|
|
7
|
+
if (source.hasOwnProperty(key)) {
|
|
8
|
+
if (typeof source[key] === 'object' && source[key] !== null && !Array.isArray(source[key])) {
|
|
9
|
+
if (!target.hasOwnProperty(key)) {
|
|
10
|
+
target[key] = {};
|
|
11
|
+
}
|
|
12
|
+
objectMerge(target[key], source[key]);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
target[key] = source[key];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return target;
|
|
21
|
+
}
|
|
22
|
+
export function ObjectKeys(obj) {
|
|
23
|
+
return Object.keys(obj);
|
|
24
|
+
}
|
|
25
|
+
export function ObjectValues(obj) {
|
|
26
|
+
return Object.values(obj);
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"object.test.d.ts","sourceRoot":"","sources":["../../source/object/object.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { objectMerge } from './object';
|
|
3
|
+
describe('merge 函数', () => {
|
|
4
|
+
it('应该合并两个简单对象', () => {
|
|
5
|
+
const target = { a: 1 };
|
|
6
|
+
const source = { b: 2 };
|
|
7
|
+
const result = objectMerge(target, source);
|
|
8
|
+
expect(result).toEqual({ a: 1, b: 2 });
|
|
9
|
+
});
|
|
10
|
+
it('应该合并嵌套对象', () => {
|
|
11
|
+
const target = { a: 1, b: { c: 2 } };
|
|
12
|
+
const source = { b: { d: 3 }, e: 4 };
|
|
13
|
+
const result = objectMerge(target, source);
|
|
14
|
+
expect(result).toEqual({ a: 1, b: { c: 2, d: 3 }, e: 4 });
|
|
15
|
+
});
|
|
16
|
+
it('应该合并多个对象', () => {
|
|
17
|
+
const target = { a: 1 };
|
|
18
|
+
const source1 = { b: 2 };
|
|
19
|
+
const source2 = { c: 3 };
|
|
20
|
+
const result = objectMerge(target, source1, source2);
|
|
21
|
+
expect(result).toEqual({ a: 1, b: 2, c: 3 });
|
|
22
|
+
});
|
|
23
|
+
it('应该覆盖现有属性', () => {
|
|
24
|
+
const target = { a: 1, b: 2 };
|
|
25
|
+
const source = { b: 3, c: 4 };
|
|
26
|
+
const result = objectMerge(target, source);
|
|
27
|
+
expect(result).toEqual({ a: 1, b: 3, c: 4 });
|
|
28
|
+
});
|
|
29
|
+
it('应该处理 null 和数组值', () => {
|
|
30
|
+
const target = { a: 1, b: null };
|
|
31
|
+
const source = { b: [1, 2, 3], c: 4 };
|
|
32
|
+
const result = objectMerge(target, source);
|
|
33
|
+
expect(result).toEqual({ a: 1, b: [1, 2, 3], c: 4 });
|
|
34
|
+
});
|
|
35
|
+
it('应该不改变原始目标对象', () => {
|
|
36
|
+
const target = { a: 1 };
|
|
37
|
+
const source = { b: 2 };
|
|
38
|
+
const result = objectMerge(target, source);
|
|
39
|
+
expect(result).toEqual({ a: 1, b: 2 });
|
|
40
|
+
expect(source).toEqual({ b: 2 }); // Target should remain unchanged
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../source/service/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Service } from './service';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../source/service/service.ts"],"names":[],"mappings":"AAAA,MAAM;AACN,8BAAsB,OAAO;IAC3B,SAAgB,MAAM,EAAE,OAAO,CAAA;aACf,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC;CACzC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../source/string/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './string';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"string.d.ts","sourceRoot":"","sources":["../../source/string/string.ts"],"names":[],"mappings":"AAAA,yBAAyB;AACzB,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,MAAyE,GAAG,MAAM,CAOvI"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** 生成随机字符串,可以指定字符集和长度 */
|
|
2
|
+
export function randomString(length, charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789') {
|
|
3
|
+
let result = '';
|
|
4
|
+
const charsetLength = charset.length;
|
|
5
|
+
for (let i = 0; i < length; i++) {
|
|
6
|
+
result += charset.charAt(Math.floor(Math.random() * charsetLength));
|
|
7
|
+
}
|
|
8
|
+
return result;
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"string.test.d.ts","sourceRoot":"","sources":["../../source/string/string.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { randomString } from './string';
|
|
3
|
+
describe('randomString 函数', () => {
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
// 重置随机数生成器的模拟
|
|
6
|
+
vi.clearAllMocks();
|
|
7
|
+
});
|
|
8
|
+
it('应该生成指定长度的字符串', () => {
|
|
9
|
+
const result = randomString(10);
|
|
10
|
+
expect(result).toHaveLength(10);
|
|
11
|
+
});
|
|
12
|
+
it('应该为零长度生成空字符串', () => {
|
|
13
|
+
const result = randomString(0);
|
|
14
|
+
expect(result).toBe('');
|
|
15
|
+
});
|
|
16
|
+
it('应该在未指定时使用默认字符集', () => {
|
|
17
|
+
const result = randomString(100);
|
|
18
|
+
const defaultCharset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
19
|
+
// 检查结果只包含默认字符集中的字符
|
|
20
|
+
for (const char of result) {
|
|
21
|
+
expect(defaultCharset).toContain(char);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
it('应该在提供时使用自定义字符集', () => {
|
|
25
|
+
const customCharset = '123';
|
|
26
|
+
const result = randomString(10, customCharset);
|
|
27
|
+
// 检查结果只包含自定义字符集中的字符
|
|
28
|
+
for (const char of result) {
|
|
29
|
+
expect(customCharset).toContain(char);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
it('应该在多次调用时生成不同的字符串', () => {
|
|
33
|
+
const result1 = randomString(20);
|
|
34
|
+
const result2 = randomString(20);
|
|
35
|
+
// 虽然理论上可能相同,但实际上几乎不可能
|
|
36
|
+
expect(result1).not.toBe(result2);
|
|
37
|
+
});
|
|
38
|
+
it('应该对单字符字符集正常工作', () => {
|
|
39
|
+
const result = randomString(5, 'A');
|
|
40
|
+
expect(result).toBe('AAAAA');
|
|
41
|
+
});
|
|
42
|
+
it('应该对字符集中的特殊字符正常工作', () => {
|
|
43
|
+
const specialCharset = '!@#$%^&*()';
|
|
44
|
+
const result = randomString(10, specialCharset);
|
|
45
|
+
expect(result).toHaveLength(10);
|
|
46
|
+
// 检查结果只包含特殊字符集中的字符
|
|
47
|
+
for (const char of result) {
|
|
48
|
+
expect(specialCharset).toContain(char);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
it('应该对字符集中的Unicode字符正常工作', () => {
|
|
52
|
+
const unicodeCharset = '你好世界🎉';
|
|
53
|
+
const result = randomString(5, unicodeCharset);
|
|
54
|
+
expect(result).toHaveLength(5);
|
|
55
|
+
// 检查结果只包含Unicode字符集中的字符
|
|
56
|
+
for (const char of result) {
|
|
57
|
+
expect(unicodeCharset).toContain(char);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
it('应该处理大长度值', () => {
|
|
61
|
+
const result = randomString(1000);
|
|
62
|
+
expect(result).toHaveLength(1000);
|
|
63
|
+
});
|
|
64
|
+
it('应该对固定的Math.random具有确定性', () => {
|
|
65
|
+
// 模拟Math.random返回固定值
|
|
66
|
+
const mockRandom = vi.spyOn(Math, 'random');
|
|
67
|
+
mockRandom.mockReturnValueOnce(0); // 第一个字符
|
|
68
|
+
mockRandom.mockReturnValueOnce(0.5); // 第二个字符
|
|
69
|
+
const charset = 'ABC';
|
|
70
|
+
const result = randomString(2, charset);
|
|
71
|
+
// 基于模拟的随机值,应该返回特定的字符
|
|
72
|
+
// Math.floor(0 * 3) = 0 -> 'A'
|
|
73
|
+
// Math.floor(0.5 * 3) = 1 -> 'B'
|
|
74
|
+
expect(result).toBe('AB');
|
|
75
|
+
mockRandom.mockRestore();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../source/throttle/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './throttle';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 节流装饰器 - 基于 TC39 Stage 3 Decorators 提案
|
|
3
|
+
* 限制函数在指定时间内最多执行一次
|
|
4
|
+
*/
|
|
5
|
+
interface ThrottleOptions {
|
|
6
|
+
leading?: boolean;
|
|
7
|
+
trailing?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function throttleFn<T extends (...args: any[]) => any>(fn: T, wait: number, options?: ThrottleOptions): T;
|
|
10
|
+
export declare function throttle<T extends (...args: any[]) => any>(wait: number, options?: ThrottleOptions): (originalMethod: T, context: ClassMethodDecoratorContext) => T;
|
|
11
|
+
export type { ThrottleOptions };
|
|
12
|
+
//# sourceMappingURL=throttle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"throttle.d.ts","sourceRoot":"","sources":["../../source/throttle/throttle.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,UAAU,eAAe;IACvB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAGD,wBAAgB,UAAU,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EAC1D,EAAE,EAAE,CAAC,EACL,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,eAAoB,GAC5B,CAAC,CAuCH;AAGD,wBAAgB,QAAQ,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EACxD,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,eAAe,IAER,gBAAgB,CAAC,EAAE,SAAS,2BAA2B,KAAG,CAAC,CAY7E;AAED,YAAY,EAAE,eAAe,EAAE,CAAA"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 节流装饰器 - 基于 TC39 Stage 3 Decorators 提案
|
|
3
|
+
* 限制函数在指定时间内最多执行一次
|
|
4
|
+
*/
|
|
5
|
+
// 通用节流函数
|
|
6
|
+
export function throttleFn(fn, wait, options = {}) {
|
|
7
|
+
const { leading = true, trailing = true } = options;
|
|
8
|
+
let timeout = null;
|
|
9
|
+
let lastCallTime = 0;
|
|
10
|
+
let lastArgs;
|
|
11
|
+
let lastThis;
|
|
12
|
+
let result;
|
|
13
|
+
function throttled(...args) {
|
|
14
|
+
const now = Date.now();
|
|
15
|
+
// 如果是第一次调用且不允许 leading
|
|
16
|
+
if (lastCallTime === 0 && !leading) {
|
|
17
|
+
lastCallTime = now;
|
|
18
|
+
}
|
|
19
|
+
const remaining = wait - (now - lastCallTime);
|
|
20
|
+
lastArgs = args;
|
|
21
|
+
lastThis = this;
|
|
22
|
+
if (remaining <= 0 || remaining > wait) {
|
|
23
|
+
if (timeout) {
|
|
24
|
+
clearTimeout(timeout);
|
|
25
|
+
timeout = null;
|
|
26
|
+
}
|
|
27
|
+
lastCallTime = now;
|
|
28
|
+
result = fn.apply(this, args);
|
|
29
|
+
}
|
|
30
|
+
else if (!timeout && trailing) {
|
|
31
|
+
timeout = setTimeout(() => {
|
|
32
|
+
lastCallTime = leading ? Date.now() : 0;
|
|
33
|
+
timeout = null;
|
|
34
|
+
result = fn.apply(lastThis, lastArgs);
|
|
35
|
+
}, remaining);
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
return throttled;
|
|
40
|
+
}
|
|
41
|
+
// TC39 Stage 3 装饰器实现
|
|
42
|
+
export function throttle(wait, options) {
|
|
43
|
+
return function (originalMethod, context) {
|
|
44
|
+
let methodKey = context.name;
|
|
45
|
+
context.addInitializer(function () {
|
|
46
|
+
const self = this;
|
|
47
|
+
// 只 patch 原型上的原始方法,避免多层嵌套
|
|
48
|
+
const descriptor = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(self), methodKey);
|
|
49
|
+
const method = descriptor?.value ?? originalMethod;
|
|
50
|
+
const throttledMethod = throttleFn(method.bind(self), wait, options);
|
|
51
|
+
self[methodKey] = throttledMethod;
|
|
52
|
+
});
|
|
53
|
+
return originalMethod;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"throttle.test.d.ts","sourceRoot":"","sources":["../../source/throttle/throttle.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { throttle, throttleFn } from './throttle';
|
|
3
|
+
describe('throttle', () => {
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
vi.useFakeTimers();
|
|
6
|
+
});
|
|
7
|
+
afterEach(() => {
|
|
8
|
+
vi.restoreAllMocks();
|
|
9
|
+
vi.useRealTimers();
|
|
10
|
+
});
|
|
11
|
+
describe('throttleFn', () => {
|
|
12
|
+
it('应该立即执行第一次调用', () => {
|
|
13
|
+
const mockFn = vi.fn().mockReturnValue('result');
|
|
14
|
+
const throttledFn = throttleFn(mockFn, 100);
|
|
15
|
+
const result = throttledFn('arg1', 'arg2');
|
|
16
|
+
expect(mockFn).toHaveBeenCalledOnce();
|
|
17
|
+
expect(mockFn).toHaveBeenCalledWith('arg1', 'arg2');
|
|
18
|
+
expect(result).toBe('result');
|
|
19
|
+
});
|
|
20
|
+
it('应该在间隔时间内忽略后续调用', () => {
|
|
21
|
+
const mockFn = vi.fn().mockReturnValue('result');
|
|
22
|
+
const throttledFn = throttleFn(mockFn, 100);
|
|
23
|
+
// 第一次调用应该立即执行
|
|
24
|
+
throttledFn('first');
|
|
25
|
+
expect(mockFn).toHaveBeenCalledOnce();
|
|
26
|
+
// 在间隔时间内的调用应该被忽略
|
|
27
|
+
throttledFn('second');
|
|
28
|
+
throttledFn('third');
|
|
29
|
+
expect(mockFn).toHaveBeenCalledOnce();
|
|
30
|
+
});
|
|
31
|
+
it('应该在间隔时间结束后允许新的调用', () => {
|
|
32
|
+
const mockFn = vi.fn().mockReturnValue('result');
|
|
33
|
+
const throttledFn = throttleFn(mockFn, 100);
|
|
34
|
+
// 第一次调用
|
|
35
|
+
throttledFn('first');
|
|
36
|
+
expect(mockFn).toHaveBeenCalledOnce();
|
|
37
|
+
// 推进时间超过间隔
|
|
38
|
+
vi.advanceTimersByTime(150);
|
|
39
|
+
// 现在应该允许新的调用
|
|
40
|
+
throttledFn('second');
|
|
41
|
+
expect(mockFn).toHaveBeenCalledTimes(2);
|
|
42
|
+
expect(mockFn).toHaveBeenLastCalledWith('second');
|
|
43
|
+
});
|
|
44
|
+
it('应该支持 leading: false 选项', () => {
|
|
45
|
+
const mockFn = vi.fn().mockReturnValue('result');
|
|
46
|
+
const throttledFn = throttleFn(mockFn, 100, { leading: false });
|
|
47
|
+
// 第一次调用不应该立即执行
|
|
48
|
+
throttledFn('first');
|
|
49
|
+
expect(mockFn).not.toHaveBeenCalled();
|
|
50
|
+
});
|
|
51
|
+
it('应该支持 trailing: true 选项', () => {
|
|
52
|
+
const mockFn = vi.fn().mockReturnValue('result');
|
|
53
|
+
const throttledFn = throttleFn(mockFn, 100, { trailing: true });
|
|
54
|
+
// 第一次调用
|
|
55
|
+
throttledFn('first');
|
|
56
|
+
expect(mockFn).toHaveBeenCalledOnce();
|
|
57
|
+
// 在间隔时间内的调用
|
|
58
|
+
throttledFn('second');
|
|
59
|
+
throttledFn('third');
|
|
60
|
+
// 推进时间,应该执行 trailing 调用
|
|
61
|
+
vi.advanceTimersByTime(100);
|
|
62
|
+
expect(mockFn).toHaveBeenCalledTimes(2);
|
|
63
|
+
expect(mockFn).toHaveBeenLastCalledWith('third');
|
|
64
|
+
});
|
|
65
|
+
it('应该支持 trailing: false 选项', () => {
|
|
66
|
+
const mockFn = vi.fn().mockReturnValue('result');
|
|
67
|
+
const throttledFn = throttleFn(mockFn, 100, { trailing: false });
|
|
68
|
+
// 第一次调用
|
|
69
|
+
throttledFn('first');
|
|
70
|
+
expect(mockFn).toHaveBeenCalledOnce();
|
|
71
|
+
// 在间隔时间内的调用
|
|
72
|
+
throttledFn('second');
|
|
73
|
+
throttledFn('third');
|
|
74
|
+
// 推进时间,不应该执行 trailing 调用
|
|
75
|
+
vi.advanceTimersByTime(100);
|
|
76
|
+
expect(mockFn).toHaveBeenCalledOnce();
|
|
77
|
+
});
|
|
78
|
+
it('应该保持 this 上下文', () => {
|
|
79
|
+
class TestClass {
|
|
80
|
+
value = 'test-value';
|
|
81
|
+
getValue() {
|
|
82
|
+
return this.value;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const instance = new TestClass();
|
|
86
|
+
const throttledMethod = throttleFn(instance.getValue, 100);
|
|
87
|
+
const result = throttledMethod.call(instance);
|
|
88
|
+
expect(result).toBe('test-value');
|
|
89
|
+
});
|
|
90
|
+
it('应该返回最后一次执行的结果', () => {
|
|
91
|
+
const mockFn = vi.fn()
|
|
92
|
+
.mockReturnValueOnce('first')
|
|
93
|
+
.mockReturnValueOnce('second');
|
|
94
|
+
const throttledFn = throttleFn(mockFn, 100);
|
|
95
|
+
const result1 = throttledFn();
|
|
96
|
+
expect(result1).toBe('first');
|
|
97
|
+
// 在间隔时间内,应该返回缓存的结果
|
|
98
|
+
const result2 = throttledFn();
|
|
99
|
+
expect(result2).toBe('first');
|
|
100
|
+
// 推进时间后的新调用
|
|
101
|
+
vi.advanceTimersByTime(150);
|
|
102
|
+
const result3 = throttledFn();
|
|
103
|
+
expect(result3).toBe('second');
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
describe('throttle decorator', () => {
|
|
107
|
+
it('应该装饰类方法', () => {
|
|
108
|
+
class TestClass {
|
|
109
|
+
callCount = 0;
|
|
110
|
+
@throttle(100)
|
|
111
|
+
increment() {
|
|
112
|
+
this.callCount++;
|
|
113
|
+
return this.callCount;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const instance = new TestClass();
|
|
117
|
+
// 第一次调用应该立即执行
|
|
118
|
+
const result1 = instance.increment();
|
|
119
|
+
expect(result1).toBe(1);
|
|
120
|
+
expect(instance.callCount).toBe(1);
|
|
121
|
+
// 快速连续调用应该被节流
|
|
122
|
+
instance.increment();
|
|
123
|
+
instance.increment();
|
|
124
|
+
expect(instance.callCount).toBe(1);
|
|
125
|
+
});
|
|
126
|
+
it('应该为每个实例独立工作', () => {
|
|
127
|
+
class TestClass {
|
|
128
|
+
callCount = 0;
|
|
129
|
+
@throttle(100)
|
|
130
|
+
increment() {
|
|
131
|
+
this.callCount++;
|
|
132
|
+
return this.callCount;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const instance1 = new TestClass();
|
|
136
|
+
const instance2 = new TestClass();
|
|
137
|
+
instance1.increment();
|
|
138
|
+
instance2.increment();
|
|
139
|
+
expect(instance1.callCount).toBe(1);
|
|
140
|
+
expect(instance2.callCount).toBe(1);
|
|
141
|
+
});
|
|
142
|
+
it('应该支持带选项的装饰器', () => {
|
|
143
|
+
class TestClass {
|
|
144
|
+
callCount = 0;
|
|
145
|
+
@throttle(100, { leading: false, trailing: true })
|
|
146
|
+
increment() {
|
|
147
|
+
this.callCount++;
|
|
148
|
+
return this.callCount;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const instance = new TestClass();
|
|
152
|
+
// leading: false,第一次调用不应该立即执行
|
|
153
|
+
instance.increment();
|
|
154
|
+
expect(instance.callCount).toBe(0);
|
|
155
|
+
// trailing: true,应该在延迟后执行
|
|
156
|
+
vi.advanceTimersByTime(100);
|
|
157
|
+
expect(instance.callCount).toBe(1);
|
|
158
|
+
});
|
|
159
|
+
it('应该处理带参数的方法', () => {
|
|
160
|
+
class TestClass {
|
|
161
|
+
lastArgs = [];
|
|
162
|
+
@throttle(100)
|
|
163
|
+
saveArgs(...args) {
|
|
164
|
+
this.lastArgs = args;
|
|
165
|
+
return args.length;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const instance = new TestClass();
|
|
169
|
+
const result = instance.saveArgs(1, 2, 3);
|
|
170
|
+
expect(result).toBe(3);
|
|
171
|
+
expect(instance.lastArgs).toEqual([1, 2, 3]);
|
|
172
|
+
// 在间隔时间内的调用应该被忽略
|
|
173
|
+
instance.saveArgs(4, 5, 6, 7);
|
|
174
|
+
expect(instance.lastArgs).toEqual([1, 2, 3]); // 应该保持原值
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taicode/common-base",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"author": "Alain",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"description": "",
|
|
@@ -23,6 +23,9 @@
|
|
|
23
23
|
"build": "tsc -p tsconfig.json",
|
|
24
24
|
"dev": "tsc -p tsconfig.json --watch"
|
|
25
25
|
},
|
|
26
|
+
"files": [
|
|
27
|
+
"output"
|
|
28
|
+
],
|
|
26
29
|
"devDependencies": {
|
|
27
30
|
"vitest": "^3.2.4"
|
|
28
31
|
}
|