@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.
Files changed (157) hide show
  1. package/output/catch/catch.d.ts +9 -0
  2. package/output/catch/catch.d.ts.map +1 -0
  3. package/output/catch/catch.js +26 -0
  4. package/output/catch/catch.test.d.ts +2 -0
  5. package/output/catch/catch.test.d.ts.map +1 -0
  6. package/output/catch/catch.test.js +258 -0
  7. package/output/catch/index.d.ts +2 -0
  8. package/output/catch/index.d.ts.map +1 -0
  9. package/output/catch/index.js +1 -0
  10. package/output/color/color.d.ts +6 -0
  11. package/output/color/color.d.ts.map +1 -0
  12. package/output/color/color.js +57 -0
  13. package/output/color/color.test.d.ts +2 -0
  14. package/output/color/color.test.d.ts.map +1 -0
  15. package/output/color/color.test.js +110 -0
  16. package/output/color/index.d.ts +2 -0
  17. package/output/color/index.d.ts.map +1 -0
  18. package/output/color/index.js +1 -0
  19. package/output/debounce/debounce.d.ts +7 -0
  20. package/output/debounce/debounce.d.ts.map +1 -0
  21. package/output/debounce/debounce.js +36 -0
  22. package/output/debounce/debounce.test.d.ts +2 -0
  23. package/output/debounce/debounce.test.d.ts.map +1 -0
  24. package/output/debounce/debounce.test.js +135 -0
  25. package/output/debounce/index.d.ts +2 -0
  26. package/output/debounce/index.d.ts.map +1 -0
  27. package/output/debounce/index.js +1 -0
  28. package/output/disposer/disposer.d.ts +6 -0
  29. package/output/disposer/disposer.d.ts.map +1 -0
  30. package/output/disposer/disposer.js +19 -0
  31. package/output/disposer/disposer.test.d.ts +2 -0
  32. package/output/disposer/disposer.test.d.ts.map +1 -0
  33. package/output/disposer/disposer.test.js +192 -0
  34. package/output/disposer/index.d.ts +2 -0
  35. package/output/disposer/index.d.ts.map +1 -0
  36. package/output/disposer/index.js +1 -0
  37. package/output/error/error.d.ts +23 -0
  38. package/output/error/error.d.ts.map +1 -0
  39. package/output/error/error.js +37 -0
  40. package/output/error/error.test.d.ts +2 -0
  41. package/output/error/error.test.d.ts.map +1 -0
  42. package/output/error/error.test.js +242 -0
  43. package/output/error/index.d.ts +2 -0
  44. package/output/error/index.d.ts.map +1 -0
  45. package/output/error/index.js +1 -0
  46. package/output/event-emitter/event-emitter.d.ts +33 -0
  47. package/output/event-emitter/event-emitter.d.ts.map +1 -0
  48. package/output/event-emitter/event-emitter.js +66 -0
  49. package/output/event-emitter/event-emitter.test.d.ts +2 -0
  50. package/output/event-emitter/event-emitter.test.d.ts.map +1 -0
  51. package/output/event-emitter/event-emitter.test.js +177 -0
  52. package/output/event-emitter/index.d.ts +2 -0
  53. package/output/event-emitter/index.d.ts.map +1 -0
  54. package/output/event-emitter/index.js +1 -0
  55. package/output/events/disposer.d.ts +6 -0
  56. package/output/events/disposer.d.ts.map +1 -0
  57. package/output/events/disposer.js +19 -0
  58. package/output/events/disposer.test.d.ts +2 -0
  59. package/output/events/disposer.test.d.ts.map +1 -0
  60. package/output/events/disposer.test.js +192 -0
  61. package/output/events/event-emitter.d.ts +33 -0
  62. package/output/events/event-emitter.d.ts.map +1 -0
  63. package/output/events/event-emitter.js +66 -0
  64. package/output/events/event-emitter.test.d.ts +2 -0
  65. package/output/events/event-emitter.test.d.ts.map +1 -0
  66. package/output/events/event-emitter.test.js +213 -0
  67. package/output/events/index.d.ts +3 -0
  68. package/output/events/index.d.ts.map +1 -0
  69. package/output/events/index.js +3 -0
  70. package/output/index.d.ts +11 -0
  71. package/output/index.d.ts.map +1 -0
  72. package/output/index.js +15 -0
  73. package/output/logger/index.d.ts +18 -0
  74. package/output/logger/index.d.ts.map +1 -0
  75. package/output/logger/index.js +34 -0
  76. package/output/logger/logger.test.d.ts +2 -0
  77. package/output/logger/logger.test.d.ts.map +1 -0
  78. package/output/logger/logger.test.js +33 -0
  79. package/output/number/index.d.ts +2 -0
  80. package/output/number/index.d.ts.map +1 -0
  81. package/output/number/index.js +1 -0
  82. package/output/number/number.d.ts +2 -0
  83. package/output/number/number.d.ts.map +1 -0
  84. package/output/number/number.js +23 -0
  85. package/output/number/number.test.d.ts +2 -0
  86. package/output/number/number.test.d.ts.map +1 -0
  87. package/output/number/number.test.js +50 -0
  88. package/output/object/index.d.ts +2 -0
  89. package/output/object/index.d.ts.map +1 -0
  90. package/output/object/index.js +1 -0
  91. package/output/object/object.d.ts +7 -0
  92. package/output/object/object.d.ts.map +1 -0
  93. package/output/object/object.js +27 -0
  94. package/output/object/object.test.d.ts +2 -0
  95. package/output/object/object.test.d.ts.map +1 -0
  96. package/output/object/object.test.js +42 -0
  97. package/output/service/index.d.ts +2 -0
  98. package/output/service/index.d.ts.map +1 -0
  99. package/output/service/index.js +1 -0
  100. package/output/service/service.d.ts +6 -0
  101. package/output/service/service.d.ts.map +1 -0
  102. package/output/service/service.js +3 -0
  103. package/output/string/index.d.ts +2 -0
  104. package/output/string/index.d.ts.map +1 -0
  105. package/output/string/index.js +1 -0
  106. package/output/string/string.d.ts +3 -0
  107. package/output/string/string.d.ts.map +1 -0
  108. package/output/string/string.js +9 -0
  109. package/output/string/string.test.d.ts +2 -0
  110. package/output/string/string.test.d.ts.map +1 -0
  111. package/output/string/string.test.js +77 -0
  112. package/output/throttle/index.d.ts +2 -0
  113. package/output/throttle/index.d.ts.map +1 -0
  114. package/output/throttle/index.js +1 -0
  115. package/output/throttle/throttle.d.ts +12 -0
  116. package/output/throttle/throttle.d.ts.map +1 -0
  117. package/output/throttle/throttle.js +55 -0
  118. package/output/throttle/throttle.test.d.ts +2 -0
  119. package/output/throttle/throttle.test.d.ts.map +1 -0
  120. package/output/throttle/throttle.test.js +177 -0
  121. package/package.json +4 -1
  122. package/source/catch/catch.test.ts +0 -341
  123. package/source/catch/catch.ts +0 -35
  124. package/source/catch/index.ts +0 -1
  125. package/source/color/color.test.ts +0 -144
  126. package/source/color/color.ts +0 -73
  127. package/source/color/index.ts +0 -1
  128. package/source/debounce/debounce.test.ts +0 -179
  129. package/source/debounce/debounce.ts +0 -42
  130. package/source/debounce/index.ts +0 -1
  131. package/source/disposer/disposer.test.ts +0 -257
  132. package/source/disposer/disposer.ts +0 -20
  133. package/source/disposer/index.ts +0 -1
  134. package/source/error/error.test.ts +0 -293
  135. package/source/error/error.ts +0 -38
  136. package/source/error/index.ts +0 -1
  137. package/source/event-emitter/event-emitter.test.ts +0 -247
  138. package/source/event-emitter/event-emitter.ts +0 -79
  139. package/source/event-emitter/index.ts +0 -1
  140. package/source/index.ts +0 -19
  141. package/source/logger/index.ts +0 -43
  142. package/source/logger/logger.test.ts +0 -36
  143. package/source/number/index.ts +0 -1
  144. package/source/number/number.test.ts +0 -60
  145. package/source/number/number.ts +0 -25
  146. package/source/object/index.ts +0 -1
  147. package/source/object/object.test.ts +0 -54
  148. package/source/object/object.ts +0 -29
  149. package/source/string/index.ts +0 -1
  150. package/source/string/string.test.ts +0 -98
  151. package/source/string/string.ts +0 -9
  152. package/source/throttle/index.ts +0 -1
  153. package/source/throttle/throttle.test.ts +0 -228
  154. package/source/throttle/throttle.ts +0 -76
  155. package/test-exports.js +0 -6
  156. package/tsconfig.json +0 -98
  157. package/vitest.config.ts +0 -8
@@ -1,43 +0,0 @@
1
- // 基础 Logger 实现,支持简单的日志输出和扩展能力
2
- export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
3
-
4
- export interface LoggerOptions {
5
- level?: LogLevel;
6
- handler?: (level: LogLevel, ...args: any[]) => void;
7
- }
8
-
9
- export class Logger {
10
- private level: LogLevel;
11
- private handler: (level: LogLevel, ...args: any[]) => void;
12
-
13
- constructor(options: LoggerOptions = {}) {
14
- this.level = options.level || 'info';
15
- this.handler = options.handler || this.defaultHandler;
16
- }
17
-
18
- private defaultHandler(level: LogLevel, ...args: any[]) {
19
- const prefix = `[${level.toUpperCase()}]`;
20
- // eslint-disable-next-line no-console
21
- (console[level] || console.log)(prefix, ...args);
22
- }
23
-
24
- private shouldLog(level: LogLevel): boolean {
25
- const order: LogLevel[] = ['debug', 'info', 'warn', 'error'];
26
- return order.indexOf(level) >= order.indexOf(this.level);
27
- }
28
-
29
- debug(...args: any[]) {
30
- if (this.shouldLog('debug')) this.handler('debug', ...args);
31
- }
32
- info(...args: any[]) {
33
- if (this.shouldLog('info')) this.handler('info', ...args);
34
- }
35
- warn(...args: any[]) {
36
- if (this.shouldLog('warn')) this.handler('warn', ...args);
37
- }
38
- error(...args: any[]) {
39
- if (this.shouldLog('error')) this.handler('error', ...args);
40
- }
41
- }
42
-
43
- export const logger = new Logger();
@@ -1,36 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { Logger } from './index';
3
-
4
- describe('Logger', () => {
5
- it('应该默认记录 info 及以上级别', () => {
6
- const handler = vi.fn();
7
- const logger = new Logger({ handler });
8
- logger.debug('debug');
9
- logger.info('info');
10
- logger.warn('warn');
11
- logger.error('error');
12
- expect(handler).not.toHaveBeenCalledWith('debug', 'debug');
13
- expect(handler).toHaveBeenCalledWith('info', 'info');
14
- expect(handler).toHaveBeenCalledWith('warn', 'warn');
15
- expect(handler).toHaveBeenCalledWith('error', 'error');
16
- });
17
-
18
- it('应该遵守日志级别', () => {
19
- const handler = vi.fn();
20
- const logger = new Logger({ level: 'warn', handler });
21
- logger.info('info');
22
- logger.warn('warn');
23
- logger.error('error');
24
- expect(handler).not.toHaveBeenCalledWith('info', 'info');
25
- expect(handler).toHaveBeenCalledWith('warn', 'warn');
26
- expect(handler).toHaveBeenCalledWith('error', 'error');
27
- });
28
-
29
- it('应该允许自定义处理器', () => {
30
- const logs: any[] = [];
31
- const handler = (level: string, ...args: any[]) => logs.push([level, ...args]);
32
- const logger = new Logger({ handler });
33
- logger.error('err', 123);
34
- expect(logs).toContainEqual(['error', 'err', 123]);
35
- });
36
- });
@@ -1 +0,0 @@
1
- export * from './number'
@@ -1,60 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
- import { bigintToNumber } from './number'
3
-
4
- describe('bigintToNumber 函数', () => {
5
- it('应该将小的 bigint 转换为 number', () => {
6
- const result = bigintToNumber(123n)
7
- expect(result).toBe(123)
8
- })
9
-
10
- it('应该将负数 bigint 转换为 number', () => {
11
- const result = bigintToNumber(-456n)
12
- expect(result).toBe(-456)
13
- })
14
-
15
- it('应该将零 bigint 转换为 number', () => {
16
- const result = bigintToNumber(0n)
17
- expect(result).toBe(0)
18
- })
19
-
20
- it('应该为 null 输入返回 0', () => {
21
- const result = bigintToNumber(null)
22
- expect(result).toBe(0)
23
- })
24
-
25
- it('应该处理 MAX_SAFE_INTEGER 的 bigint', () => {
26
- const maxSafe = BigInt(Number.MAX_SAFE_INTEGER)
27
- const result = bigintToNumber(maxSafe)
28
- expect(result).toBe(Number.MAX_SAFE_INTEGER)
29
- })
30
-
31
- it('应该处理 MIN_SAFE_INTEGER 的 bigint', () => {
32
- const minSafe = BigInt(Number.MIN_SAFE_INTEGER)
33
- const result = bigintToNumber(minSafe)
34
- expect(result).toBe(Number.MIN_SAFE_INTEGER)
35
- })
36
-
37
- it('应该为 bigint 溢出(正数)返回 MAX_SAFE_INTEGER', () => {
38
- const tooBig = BigInt(Number.MAX_SAFE_INTEGER) + 1n
39
- const result = bigintToNumber(tooBig)
40
- expect(result).toBe(Number.MAX_SAFE_INTEGER)
41
- })
42
-
43
- it('应该为 bigint 溢出(负数)返回 MAX_SAFE_INTEGER', () => {
44
- const tooSmall = BigInt(Number.MIN_SAFE_INTEGER) - 1n
45
- const result = bigintToNumber(tooSmall)
46
- expect(result).toBe(Number.MAX_SAFE_INTEGER)
47
- })
48
-
49
- it('应该处理非常大的 bigint', () => {
50
- const veryLarge = 999999999999999999999n
51
- const result = bigintToNumber(veryLarge)
52
- expect(result).toBe(Number.MAX_SAFE_INTEGER)
53
- })
54
-
55
- it('应该处理从 number 创建的 bigint', () => {
56
- const bigintFromNumber = BigInt(42)
57
- const result = bigintToNumber(bigintFromNumber)
58
- expect(result).toBe(42)
59
- })
60
- })
@@ -1,25 +0,0 @@
1
- export function bigintToNumber(value: bigint | null): number {
2
- if (value === null) return 0
3
-
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
-
8
- try {
9
- // 检查输入是否为 bigint
10
- if (typeof value !== 'bigint') {
11
- throw new TypeError('Input must be a bigint')
12
- }
13
-
14
- // 检查是否溢出
15
- if (value > BigInt(MAX_SAFE_INTEGER) || value < BigInt(MIN_SAFE_INTEGER)) {
16
- return MAX_SAFE_INTEGER // 溢出,返回 NaN
17
- }
18
-
19
- // 将 bigint 转换为 number
20
- return Number(value)
21
- } catch {
22
- // 捕获任何错误并返回 NaN
23
- return 0
24
- }
25
- }
@@ -1 +0,0 @@
1
- export * from './object'
@@ -1,54 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
- import { objectMerge } from './object'
3
-
4
- describe('merge 函数', () => {
5
- it('应该合并两个简单对象', () => {
6
- const target = { a: 1 } as object
7
- const source = { b: 2 } as object
8
- const result = objectMerge(target, source)
9
-
10
- expect(result).toEqual({ a: 1, b: 2 })
11
- })
12
-
13
- it('应该合并嵌套对象', () => {
14
- const target = { a: 1, b: { c: 2 } } as object
15
- const source = { b: { d: 3 }, e: 4 } as object
16
- const result = objectMerge(target, source)
17
-
18
- expect(result).toEqual({ a: 1, b: { c: 2, d: 3 }, e: 4 })
19
- })
20
-
21
- it('应该合并多个对象', () => {
22
- const target = { a: 1 } as object
23
- const source1 = { b: 2 } as object
24
- const source2 = { c: 3 } as object
25
- const result = objectMerge(target, source1, source2)
26
-
27
- expect(result).toEqual({ a: 1, b: 2, c: 3 })
28
- })
29
-
30
- it('应该覆盖现有属性', () => {
31
- const target = { a: 1, b: 2 } as object
32
- const source = { b: 3, c: 4 } as object
33
- const result = objectMerge(target, source)
34
-
35
- expect(result).toEqual({ a: 1, b: 3, c: 4 })
36
- })
37
-
38
- it('应该处理 null 和数组值', () => {
39
- const target = { a: 1, b: null } as object
40
- const source = { b: [1, 2, 3], c: 4 }
41
- const result = objectMerge(target, source)
42
-
43
- expect(result).toEqual({ a: 1, b: [1, 2, 3], c: 4 })
44
- })
45
-
46
- it('应该不改变原始目标对象', () => {
47
- const target = { a: 1 } as object
48
- const source = { b: 2 } as object
49
- const result = objectMerge(target, source)
50
-
51
- expect(result).toEqual({ a: 1, b: 2 })
52
- expect(source).toEqual({ b: 2 }) // Target should remain unchanged
53
- })
54
- })
@@ -1,29 +0,0 @@
1
- /**
2
- * 递归合并对象,只针对对象类型的属性进行合并,其他类型后者覆盖前者
3
- */
4
- export function objectMerge<T extends object>(target: T, ...rest: Partial<T>[]): T {
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 as any)[key] = {}
11
- }
12
- objectMerge((target as any)[key], (source as any)[key])
13
- } else {
14
- (target as any)[key] = source[key]
15
- }
16
- }
17
- }
18
- }
19
-
20
- return target
21
- }
22
-
23
- export function ObjectKeys<T extends object>(obj: T): (keyof T)[] {
24
- return Object.keys(obj) as (keyof T)[]
25
- }
26
-
27
- export function ObjectValues<T extends object>(obj: T): T[keyof T][] {
28
- return Object.values(obj) as T[keyof T][]
29
- }
@@ -1 +0,0 @@
1
- export * from './string'
@@ -1,98 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest'
2
- import { randomString } from './string'
3
-
4
- describe('randomString 函数', () => {
5
- beforeEach(() => {
6
- // 重置随机数生成器的模拟
7
- vi.clearAllMocks()
8
- })
9
-
10
- it('应该生成指定长度的字符串', () => {
11
- const result = randomString(10)
12
- expect(result).toHaveLength(10)
13
- })
14
-
15
- it('应该为零长度生成空字符串', () => {
16
- const result = randomString(0)
17
- expect(result).toBe('')
18
- })
19
-
20
- it('应该在未指定时使用默认字符集', () => {
21
- const result = randomString(100)
22
- const defaultCharset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
23
-
24
- // 检查结果只包含默认字符集中的字符
25
- for (const char of result) {
26
- expect(defaultCharset).toContain(char)
27
- }
28
- })
29
-
30
- it('应该在提供时使用自定义字符集', () => {
31
- const customCharset = '123'
32
- const result = randomString(10, customCharset)
33
-
34
- // 检查结果只包含自定义字符集中的字符
35
- for (const char of result) {
36
- expect(customCharset).toContain(char)
37
- }
38
- })
39
-
40
- it('应该在多次调用时生成不同的字符串', () => {
41
- const result1 = randomString(20)
42
- const result2 = randomString(20)
43
-
44
- // 虽然理论上可能相同,但实际上几乎不可能
45
- expect(result1).not.toBe(result2)
46
- })
47
-
48
- it('应该对单字符字符集正常工作', () => {
49
- const result = randomString(5, 'A')
50
- expect(result).toBe('AAAAA')
51
- })
52
-
53
- it('应该对字符集中的特殊字符正常工作', () => {
54
- const specialCharset = '!@#$%^&*()'
55
- const result = randomString(10, specialCharset)
56
-
57
- expect(result).toHaveLength(10)
58
-
59
- // 检查结果只包含特殊字符集中的字符
60
- for (const char of result) {
61
- expect(specialCharset).toContain(char)
62
- }
63
- })
64
-
65
- it('应该对字符集中的Unicode字符正常工作', () => {
66
- const unicodeCharset = '你好世界🎉'
67
- const result = randomString(5, unicodeCharset)
68
-
69
- expect(result).toHaveLength(5)
70
-
71
- // 检查结果只包含Unicode字符集中的字符
72
- for (const char of result) {
73
- expect(unicodeCharset).toContain(char)
74
- }
75
- })
76
-
77
- it('应该处理大长度值', () => {
78
- const result = randomString(1000)
79
- expect(result).toHaveLength(1000)
80
- })
81
-
82
- it('应该对固定的Math.random具有确定性', () => {
83
- // 模拟Math.random返回固定值
84
- const mockRandom = vi.spyOn(Math, 'random')
85
- mockRandom.mockReturnValueOnce(0) // 第一个字符
86
- mockRandom.mockReturnValueOnce(0.5) // 第二个字符
87
-
88
- const charset = 'ABC'
89
- const result = randomString(2, charset)
90
-
91
- // 基于模拟的随机值,应该返回特定的字符
92
- // Math.floor(0 * 3) = 0 -> 'A'
93
- // Math.floor(0.5 * 3) = 1 -> 'B'
94
- expect(result).toBe('AB')
95
-
96
- mockRandom.mockRestore()
97
- })
98
- })
@@ -1,9 +0,0 @@
1
- /** 生成随机字符串,可以指定字符集和长度 */
2
- export function randomString(length: number, charset: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'): string {
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
- }
@@ -1 +0,0 @@
1
- export * from './throttle'
@@ -1,228 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
2
- import { throttle, throttleFn } from './throttle'
3
-
4
- describe('throttle', () => {
5
- beforeEach(() => {
6
- vi.useFakeTimers()
7
- })
8
-
9
- afterEach(() => {
10
- vi.restoreAllMocks()
11
- vi.useRealTimers()
12
- })
13
-
14
- describe('throttleFn', () => {
15
- it('应该立即执行第一次调用', () => {
16
- const mockFn = vi.fn().mockReturnValue('result')
17
- const throttledFn = throttleFn(mockFn, 100)
18
-
19
- const result = throttledFn('arg1', 'arg2')
20
-
21
- expect(mockFn).toHaveBeenCalledOnce()
22
- expect(mockFn).toHaveBeenCalledWith('arg1', 'arg2')
23
- expect(result).toBe('result')
24
- })
25
-
26
- it('应该在间隔时间内忽略后续调用', () => {
27
- const mockFn = vi.fn().mockReturnValue('result')
28
- const throttledFn = throttleFn(mockFn, 100)
29
-
30
- // 第一次调用应该立即执行
31
- throttledFn('first')
32
- expect(mockFn).toHaveBeenCalledOnce()
33
-
34
- // 在间隔时间内的调用应该被忽略
35
- throttledFn('second')
36
- throttledFn('third')
37
- expect(mockFn).toHaveBeenCalledOnce()
38
- })
39
-
40
- it('应该在间隔时间结束后允许新的调用', () => {
41
- const mockFn = vi.fn().mockReturnValue('result')
42
- const throttledFn = throttleFn(mockFn, 100)
43
-
44
- // 第一次调用
45
- throttledFn('first')
46
- expect(mockFn).toHaveBeenCalledOnce()
47
-
48
- // 推进时间超过间隔
49
- vi.advanceTimersByTime(150)
50
-
51
- // 现在应该允许新的调用
52
- throttledFn('second')
53
- expect(mockFn).toHaveBeenCalledTimes(2)
54
- expect(mockFn).toHaveBeenLastCalledWith('second')
55
- })
56
-
57
- it('应该支持 leading: false 选项', () => {
58
- const mockFn = vi.fn().mockReturnValue('result')
59
- const throttledFn = throttleFn(mockFn, 100, { leading: false })
60
-
61
- // 第一次调用不应该立即执行
62
- throttledFn('first')
63
- expect(mockFn).not.toHaveBeenCalled()
64
- })
65
-
66
- it('应该支持 trailing: true 选项', () => {
67
- const mockFn = vi.fn().mockReturnValue('result')
68
- const throttledFn = throttleFn(mockFn, 100, { trailing: true })
69
-
70
- // 第一次调用
71
- throttledFn('first')
72
- expect(mockFn).toHaveBeenCalledOnce()
73
-
74
- // 在间隔时间内的调用
75
- throttledFn('second')
76
- throttledFn('third')
77
-
78
- // 推进时间,应该执行 trailing 调用
79
- vi.advanceTimersByTime(100)
80
- expect(mockFn).toHaveBeenCalledTimes(2)
81
- expect(mockFn).toHaveBeenLastCalledWith('third')
82
- })
83
-
84
- it('应该支持 trailing: false 选项', () => {
85
- const mockFn = vi.fn().mockReturnValue('result')
86
- const throttledFn = throttleFn(mockFn, 100, { trailing: false })
87
-
88
- // 第一次调用
89
- throttledFn('first')
90
- expect(mockFn).toHaveBeenCalledOnce()
91
-
92
- // 在间隔时间内的调用
93
- throttledFn('second')
94
- throttledFn('third')
95
-
96
- // 推进时间,不应该执行 trailing 调用
97
- vi.advanceTimersByTime(100)
98
- expect(mockFn).toHaveBeenCalledOnce()
99
- })
100
-
101
- it('应该保持 this 上下文', () => {
102
- class TestClass {
103
- value = 'test-value'
104
-
105
- getValue() {
106
- return this.value
107
- }
108
- }
109
-
110
- const instance = new TestClass()
111
- const throttledMethod = throttleFn(instance.getValue, 100)
112
-
113
- const result = throttledMethod.call(instance)
114
- expect(result).toBe('test-value')
115
- })
116
-
117
- it('应该返回最后一次执行的结果', () => {
118
- const mockFn = vi.fn()
119
- .mockReturnValueOnce('first')
120
- .mockReturnValueOnce('second')
121
-
122
- const throttledFn = throttleFn(mockFn, 100)
123
-
124
- const result1 = throttledFn()
125
- expect(result1).toBe('first')
126
-
127
- // 在间隔时间内,应该返回缓存的结果
128
- const result2 = throttledFn()
129
- expect(result2).toBe('first')
130
-
131
- // 推进时间后的新调用
132
- vi.advanceTimersByTime(150)
133
- const result3 = throttledFn()
134
- expect(result3).toBe('second')
135
- })
136
- })
137
-
138
- describe('throttle decorator', () => {
139
- it('应该装饰类方法', () => {
140
- class TestClass {
141
- callCount = 0
142
-
143
- @throttle(100)
144
- increment() {
145
- this.callCount++
146
- return this.callCount
147
- }
148
- }
149
-
150
- const instance = new TestClass()
151
-
152
- // 第一次调用应该立即执行
153
- const result1 = instance.increment()
154
- expect(result1).toBe(1)
155
- expect(instance.callCount).toBe(1)
156
-
157
- // 快速连续调用应该被节流
158
- instance.increment()
159
- instance.increment()
160
- expect(instance.callCount).toBe(1)
161
- })
162
-
163
- it('应该为每个实例独立工作', () => {
164
- class TestClass {
165
- callCount = 0
166
-
167
- @throttle(100)
168
- increment() {
169
- this.callCount++
170
- return this.callCount
171
- }
172
- }
173
-
174
- const instance1 = new TestClass()
175
- const instance2 = new TestClass()
176
-
177
- instance1.increment()
178
- instance2.increment()
179
-
180
- expect(instance1.callCount).toBe(1)
181
- expect(instance2.callCount).toBe(1)
182
- })
183
-
184
- it('应该支持带选项的装饰器', () => {
185
- class TestClass {
186
- callCount = 0
187
-
188
- @throttle(100, { leading: false, trailing: true })
189
- increment() {
190
- this.callCount++
191
- return this.callCount
192
- }
193
- }
194
-
195
- const instance = new TestClass()
196
-
197
- // leading: false,第一次调用不应该立即执行
198
- instance.increment()
199
- expect(instance.callCount).toBe(0)
200
-
201
- // trailing: true,应该在延迟后执行
202
- vi.advanceTimersByTime(100)
203
- expect(instance.callCount).toBe(1)
204
- })
205
-
206
- it('应该处理带参数的方法', () => {
207
- class TestClass {
208
- lastArgs: any[] = []
209
-
210
- @throttle(100)
211
- saveArgs(...args: any[]) {
212
- this.lastArgs = args
213
- return args.length
214
- }
215
- }
216
-
217
- const instance = new TestClass()
218
-
219
- const result = instance.saveArgs(1, 2, 3)
220
- expect(result).toBe(3)
221
- expect(instance.lastArgs).toEqual([1, 2, 3])
222
-
223
- // 在间隔时间内的调用应该被忽略
224
- instance.saveArgs(4, 5, 6, 7)
225
- expect(instance.lastArgs).toEqual([1, 2, 3]) // 应该保持原值
226
- })
227
- })
228
- })
@@ -1,76 +0,0 @@
1
- /**
2
- * 节流装饰器 - 基于 TC39 Stage 3 Decorators 提案
3
- * 限制函数在指定时间内最多执行一次
4
- */
5
-
6
- interface ThrottleOptions {
7
- leading?: boolean // 是否在开始时立即执行,默认 true
8
- trailing?: boolean // 是否在结束时执行最后一次,默认 true
9
- }
10
-
11
- // 通用节流函数
12
- export function throttleFn<T extends (...args: any[]) => any>(
13
- fn: T,
14
- wait: number,
15
- options: ThrottleOptions = {}
16
- ): T {
17
- const { leading = true, trailing = true } = options
18
- let timeout: ReturnType<typeof setTimeout> | null = null
19
- let lastCallTime: number = 0
20
- let lastArgs: any[]
21
- let lastThis: any
22
- let result: any
23
-
24
- function throttled(this: any, ...args: any[]) {
25
- const now = Date.now()
26
-
27
- // 如果是第一次调用且不允许 leading
28
- if (lastCallTime === 0 && !leading) {
29
- lastCallTime = now
30
- }
31
-
32
- const remaining = wait - (now - lastCallTime)
33
- lastArgs = args
34
- lastThis = this
35
-
36
- if (remaining <= 0 || remaining > wait) {
37
- if (timeout) {
38
- clearTimeout(timeout)
39
- timeout = null
40
- }
41
- lastCallTime = now
42
- result = fn.apply(this, args)
43
- } else if (!timeout && trailing) {
44
- timeout = setTimeout(() => {
45
- lastCallTime = leading ? Date.now() : 0
46
- timeout = null
47
- result = fn.apply(lastThis, lastArgs)
48
- }, remaining)
49
- }
50
-
51
- return result
52
- }
53
-
54
- return throttled as T
55
- }
56
-
57
- // TC39 Stage 3 装饰器实现
58
- export function throttle<T extends (...args: any[]) => any>(
59
- wait: number,
60
- options?: ThrottleOptions
61
- ) {
62
- return function (originalMethod: T, context: ClassMethodDecoratorContext): T {
63
- let methodKey = context.name
64
- context.addInitializer(function () {
65
- const self = this as any
66
- // 只 patch 原型上的原始方法,避免多层嵌套
67
- const descriptor = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(self), methodKey)
68
- const method = descriptor?.value ?? originalMethod
69
- const throttledMethod = throttleFn(method.bind(self), wait, options)
70
- self[methodKey] = throttledMethod
71
- })
72
- return originalMethod
73
- }
74
- }
75
-
76
- export type { ThrottleOptions }
package/test-exports.js DELETED
@@ -1,6 +0,0 @@
1
- // 测试 exports 配置
2
- import { catchIt } from '@taicode/common-base/catch'
3
- import { logger } from '@taicode/common-base/logger'
4
- import { debounce } from '@taicode/common-base/debounce'
5
-
6
- console.log('Exports configuration test passed!')