From cc2909effb40e579f8c5f2d24231751d9fbcff01 Mon Sep 17 00:00:00 2001 From: Aaron Date: Thu, 19 Sep 2024 17:22:17 +0800 Subject: [PATCH 1/7] refactor: adjust utils of lodash --- src/lodash/is-array.ts | 12 +++-- src/lodash/is-date.ts | 14 ++--- src/lodash/is-decimal.ts | 10 ++-- src/lodash/is-element.ts | 11 ++-- src/lodash/is-even.ts | 10 ++-- src/lodash/is-finite.ts | 10 ++-- src/lodash/is-function.ts | 7 +-- src/lodash/is-integer.ts | 14 ++--- src/lodash/is-negative.ts | 10 ++-- src/lodash/is-nil.ts | 15 +++--- src/lodash/is-null.ts | 10 ++-- src/lodash/is-number-equal.ts | 6 ++- src/lodash/is-number.ts | 13 ++--- src/lodash/is-odd.ts | 10 ++-- src/lodash/is-string.ts | 12 +++-- src/lodash/max.ts | 24 +++++---- src/lodash/memoize.ts | 98 ++++++++++++++++++----------------- 17 files changed, 153 insertions(+), 133 deletions(-) diff --git a/src/lodash/is-array.ts b/src/lodash/is-array.ts index 8ca494a..92daaa6 100644 --- a/src/lodash/is-array.ts +++ b/src/lodash/is-array.ts @@ -1,5 +1,7 @@ -import isType from './is-type'; - -export default (value: any): value is Array => { - return Array.isArray ? Array.isArray(value) : isType(value, 'Array'); -}; +/** + * 判断值是否为数组 + * @return 是否为数组 + */ +export default function isArray(value: unknown): value is Array { + return Array.isArray(value); +} diff --git a/src/lodash/is-date.ts b/src/lodash/is-date.ts index a8e747e..8f81df5 100644 --- a/src/lodash/is-date.ts +++ b/src/lodash/is-date.ts @@ -1,7 +1,7 @@ -import isType from './is-type'; - -const isDate = function (value: any): value is Date { - return isType(value, 'Date'); -}; - -export default isDate; +/** + * 判断值是否为 Date + * @return 是否为 Date + */ +export default function isDate(value: unknown): value is Date { + return value instanceof Date; +} diff --git a/src/lodash/is-decimal.ts b/src/lodash/is-decimal.ts index 28f41c0..09c1d2d 100644 --- a/src/lodash/is-decimal.ts +++ b/src/lodash/is-decimal.ts @@ -1,7 +1,9 @@ import isNumber from './is-number'; -const isDecimal = function (num: any): boolean { +/** + * 判断值是否为小数 + * @return 是否为小数 + */ +export default function isDecimal(num: unknown): boolean { return isNumber(num) && num % 1 !== 0; -}; - -export default isDecimal; +} diff --git a/src/lodash/is-element.ts b/src/lodash/is-element.ts index 2449879..6d26d8f 100644 --- a/src/lodash/is-element.ts +++ b/src/lodash/is-element.ts @@ -1,8 +1,7 @@ /** - * 判断是否HTML元素 - * @return {Boolean} 是否HTML元素 + * 判断值是否为 HTML Element 或 Document + * @return 是否为 HTML Element 或 Document */ -const isElement = function (o: any): boolean { - return o instanceof Element || o instanceof Document; -}; -export default isElement; +export default function isElement(value: unknown): value is Element | Document { + return value instanceof Element || value instanceof Document; +} diff --git a/src/lodash/is-even.ts b/src/lodash/is-even.ts index 2eb6c32..43eda52 100644 --- a/src/lodash/is-even.ts +++ b/src/lodash/is-even.ts @@ -1,7 +1,9 @@ import isNumber from './is-number'; -const isEven = function (num: any): boolean { +/** + * 判断值是否为偶数 + * @return 是否为偶数 + */ +export default function isEven(num: number): boolean { return isNumber(num) && num % 2 === 0; -}; - -export default isEven; +} diff --git a/src/lodash/is-finite.ts b/src/lodash/is-finite.ts index cbde9f9..2ccae46 100644 --- a/src/lodash/is-finite.ts +++ b/src/lodash/is-finite.ts @@ -1,9 +1,9 @@ -/** - * 判断是否为有限数 - * @return {Boolean} - */ import isNumber from './is-number'; -export default function (value: any): value is number { +/** + * 判断值是否为有限数 + * @return 是否为有限数 + */ +export default function (value: number): boolean { return isNumber(value) && isFinite(value); } diff --git a/src/lodash/is-function.ts b/src/lodash/is-function.ts index 460291b..dc4f796 100644 --- a/src/lodash/is-function.ts +++ b/src/lodash/is-function.ts @@ -1,6 +1,7 @@ /** - * @see https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_isfunction + * 判断值是否为函数 + * @return 是否为函数 */ -export default (value: any): value is Function => { +export default function isFunction(value: unknown): value is Function { return typeof value === 'function'; -}; +} diff --git a/src/lodash/is-integer.ts b/src/lodash/is-integer.ts index cc7eb0f..6b9a9f7 100644 --- a/src/lodash/is-integer.ts +++ b/src/lodash/is-integer.ts @@ -1,9 +1,9 @@ import isNumber from './is-number'; -const isInteger = Number.isInteger - ? Number.isInteger - : function (num: any): boolean { - return isNumber(num) && num % 1 === 0; - }; - -export default isInteger; +/** + * 判断值是否为整数 + * @return 是否为整数 + */ +export default function isInteger(value: number): boolean { + return isNumber(value) && value % 1 === 0; +} diff --git a/src/lodash/is-negative.ts b/src/lodash/is-negative.ts index 74e6193..b433cb9 100644 --- a/src/lodash/is-negative.ts +++ b/src/lodash/is-negative.ts @@ -1,7 +1,9 @@ import isNumber from './is-number'; -const isNegative = function (num: any): boolean { +/** + * 判断值是否为负数 + * @return 是否为负数 + */ +export default function isNegative(num: number): boolean { return isNumber(num) && num < 0; -}; - -export default isNegative; +} diff --git a/src/lodash/is-nil.ts b/src/lodash/is-nil.ts index 8eda347..7633c3b 100644 --- a/src/lodash/is-nil.ts +++ b/src/lodash/is-nil.ts @@ -1,10 +1,7 @@ -// isFinite, -const isNil = function (value: any): value is null | undefined { - /** - * isNil(null) => true - * isNil() => true - */ +/** + * 判断值是否为 null 或 undefined + * @return 是否为 null 或 undefined + */ +export default function isNil(value: unknown): value is null | undefined { return value === null || value === undefined; -}; - -export default isNil; +} diff --git a/src/lodash/is-null.ts b/src/lodash/is-null.ts index 52eb65a..8be0983 100644 --- a/src/lodash/is-null.ts +++ b/src/lodash/is-null.ts @@ -1,5 +1,7 @@ -const isNull = function (value): value is null { +/** + * 判断值是否为 null + * @return 是否为 null + */ +export default function isNull(value: unknown): value is null { return value === null; -}; - -export default isNull; +} diff --git a/src/lodash/is-number-equal.ts b/src/lodash/is-number-equal.ts index 9d3ab04..a3d0411 100644 --- a/src/lodash/is-number-equal.ts +++ b/src/lodash/is-number-equal.ts @@ -1,5 +1,9 @@ const PRECISION = 0.00001; // numbers less than this is considered as 0 +/** + * 判断两个数是否相等 + * @return 是否相等 + */ export default function isNumberEqual(a: number, b: number, precision: number = PRECISION): boolean { - return Math.abs(a - b) < precision; + return a === b || Math.abs(a - b) < precision; } diff --git a/src/lodash/is-number.ts b/src/lodash/is-number.ts index e621c1f..ba11eb9 100644 --- a/src/lodash/is-number.ts +++ b/src/lodash/is-number.ts @@ -1,10 +1,7 @@ /** - * 判断是否数字 - * @return {Boolean} 是否数字 + * 判断值是否为数字 + * @return 是否为数字 */ -import isType from './is-type'; - -const isNumber = function (value: any): value is number { - return isType(value, 'Number'); -}; -export default isNumber; +export default function isNumber(value: unknown): value is number { + return typeof value === 'number' || value instanceof Number; +} diff --git a/src/lodash/is-odd.ts b/src/lodash/is-odd.ts index 87ea27b..beae6be 100644 --- a/src/lodash/is-odd.ts +++ b/src/lodash/is-odd.ts @@ -1,7 +1,9 @@ import isNumber from './is-number'; -const isOdd = function (num: any): boolean { +/** + * 判断值是否为奇数 + * @return 是否为奇数 + */ +export default function isOdd(num: number): boolean { return isNumber(num) && num % 2 !== 0; -}; - -export default isOdd; +} diff --git a/src/lodash/is-string.ts b/src/lodash/is-string.ts index dc34cf1..a786a0d 100644 --- a/src/lodash/is-string.ts +++ b/src/lodash/is-string.ts @@ -1,5 +1,7 @@ -import isType from './is-type'; - -export default (str: any): str is string => { - return isType(str, 'String'); -}; +/** + * 判断值是否为字符串 + * @return 是否为字符串 + */ +export default function isString(value: unknown): value is string { + return typeof value === 'string' || value instanceof String; +} diff --git a/src/lodash/max.ts b/src/lodash/max.ts index f80534f..817c81b 100644 --- a/src/lodash/max.ts +++ b/src/lodash/max.ts @@ -1,5 +1,4 @@ -import each from './each'; -import isArray from './is-array'; +import isNil from './is-nil'; /** * @param {Array} arr The array to iterate over. @@ -18,11 +17,18 @@ import isArray from './is-array'; * // => 1250010 * // Math.max(...data) will encounter "Maximum call stack size exceeded" error */ -export default (arr: number[]): number | undefined => { - if (!isArray(arr)) { - return undefined; +export default function max(arr: number[]): number | undefined { + const length = arr.length; + if (length === 0) return undefined; + let max = arr[0]; + + const isNonNumber = (value: number) => isNil(value) || isNaN(value); + if (isNonNumber(max)) return undefined; + + for (let i = 1; i < length; i++) { + if (isNonNumber(arr[i])) return undefined; + if (arr[i] > max) max = arr[i]; } - return arr.reduce((prev, curr) => { - return Math.max(prev, curr); - }, arr[0]); -}; + + return max; +} diff --git a/src/lodash/memoize.ts b/src/lodash/memoize.ts index 97a0ef6..5c052b5 100644 --- a/src/lodash/memoize.ts +++ b/src/lodash/memoize.ts @@ -1,61 +1,64 @@ -import isFunction from './is-function'; +class FLRUCache { + private cache: Map; + private capacity: number; -function flru(max: number) { - let num, curr, prev; - const limit = max || 1; + constructor(capacity: number) { + this.capacity = Math.max(capacity, 1); + this.cache = new Map(); + } - function keep(key, value) { - if (++num > limit) { - prev = curr; - reset(1); - ++num; + // 获取缓存中的值,并更新使用顺序 + get(key: K): V | undefined { + if (!this.cache.has(key)) { + return undefined; } - curr[key] = value; + + // 先删除再插入,以便更新顺序 + const value = this.cache.get(key)!; + this.cache.delete(key); + this.cache.set(key, value); + return value; } - function reset(isPartial?: number) { - num = 0; - curr = Object.create(null); - isPartial || (prev = Object.create(null)); + // 设置缓存值,更新使用顺序,并维护缓存大小 + set(key: K, value: V): void { + if (this.cache.has(key)) { + // 如果 key 已存在,更新值并重新设置顺序 + this.cache.delete(key); + } else if (this.cache.size >= this.capacity) { + // 容量已满,移除最久未使用的键值对 + const firstKey = this.cache.keys().next().value; + this.cache.delete(firstKey); + } + this.cache.set(key, value); } - reset(); + // 检查是否存在指定的 key + has(key: K): boolean { + return this.cache.has(key); + } - return { - clear: reset, - has: function (key) { - return curr[key] !== void 0 || prev[key] !== void 0; - }, - get: function (key) { - var val = curr[key]; - if (val !== void 0) return val; - if ((val = prev[key]) !== void 0) { - keep(key, val); - return val; - } - }, - set: function (key, value) { - if (curr[key] !== void 0) { - curr[key] = value; - } else { - keep(key, value); - } - }, - }; + // 获取当前缓存大小 + size(): number { + return this.cache.size; + } + + // 清空缓存 + clear(): void { + this.cache.clear(); + } } /** + * 缓存函数的计算结果,避免重复计算 + * @example * _.memoize(calColor); * _.memoize(calColor, (...args) => args[0]); - * @param f - * @param resolver - * @param maxSize lru maxSize + * @param fn 缓存的函数 + * @param resolver 生成缓存 key 的函数 + * @param maxSize lru 缓存的大小 */ -export default (f: Function, resolver?: (...args: any[]) => string, maxSize = 128) => { - if (!isFunction(f)) { - throw new TypeError('Expected a function'); - } - +export default function memoize(fn: Function, resolver?: (...args: any[]) => string, maxSize = 128) { const memoized = function (...args) { // 使用方法构造 key,如果不存在 resolver,则直接取第一个参数作为 key const key = resolver ? resolver.apply(this, args) : args[0]; @@ -64,13 +67,12 @@ export default (f: Function, resolver?: (...args: any[]) => string, maxSize = 12 if (cache.has(key)) { return cache.get(key); } - const result = f.apply(this, args); - // 缓存起来 + const result = fn.apply(this, args); cache.set(key, result); return result; }; - memoized.cache = flru(maxSize); + memoized.cache = new FLRUCache(maxSize); return memoized; -}; +} From 8589e4d1e900d7269a6c554a7c888b35061c1d0f Mon Sep 17 00:00:00 2001 From: Aaron Date: Thu, 19 Sep 2024 17:52:03 +0800 Subject: [PATCH 2/7] test: add test case --- __tests__/unit/lodash/is-array.spec.ts | 23 +++++++++++ __tests__/unit/lodash/is-date.spec.ts | 19 +++++++++ __tests__/unit/lodash/is-decimal.spec.ts | 23 +++++++++++ __tests__/unit/lodash/is-element.spec.ts | 22 ++++++++++ __tests__/unit/lodash/is-even.spec.ts | 31 ++++++++++++++ __tests__/unit/lodash/is-finite.spec.ts | 20 ++++++++++ __tests__/unit/lodash/is-function.spec.ts | 20 ++++++++++ __tests__/unit/lodash/is-integer.spec.ts | 21 ++++++++++ __tests__/unit/lodash/is-negative.spec.ts | 24 +++++++++++ __tests__/unit/lodash/is-nil.spec.ts | 17 ++++++++ __tests__/unit/lodash/is-null.spec.ts | 17 ++++++++ __tests__/unit/lodash/is-number-equal.spec.ts | 24 +++++++++++ __tests__/unit/lodash/is-number.spec.ts | 23 +++++++++++ __tests__/unit/lodash/is-odd.spec.ts | 30 ++++++++++++++ __tests__/unit/lodash/is-string.spec.ts | 24 +++++++++++ __tests__/unit/lodash/max.spec.ts | 18 +++++++++ __tests__/unit/lodash/memoize.spec.ts | 40 +++++++++++++++++++ 17 files changed, 396 insertions(+) create mode 100644 __tests__/unit/lodash/is-array.spec.ts create mode 100644 __tests__/unit/lodash/is-date.spec.ts create mode 100644 __tests__/unit/lodash/is-decimal.spec.ts create mode 100644 __tests__/unit/lodash/is-element.spec.ts create mode 100644 __tests__/unit/lodash/is-even.spec.ts create mode 100644 __tests__/unit/lodash/is-finite.spec.ts create mode 100644 __tests__/unit/lodash/is-function.spec.ts create mode 100644 __tests__/unit/lodash/is-integer.spec.ts create mode 100644 __tests__/unit/lodash/is-negative.spec.ts create mode 100644 __tests__/unit/lodash/is-nil.spec.ts create mode 100644 __tests__/unit/lodash/is-null.spec.ts create mode 100644 __tests__/unit/lodash/is-number-equal.spec.ts create mode 100644 __tests__/unit/lodash/is-number.spec.ts create mode 100644 __tests__/unit/lodash/is-odd.spec.ts create mode 100644 __tests__/unit/lodash/is-string.spec.ts create mode 100644 __tests__/unit/lodash/max.spec.ts create mode 100644 __tests__/unit/lodash/memoize.spec.ts diff --git a/__tests__/unit/lodash/is-array.spec.ts b/__tests__/unit/lodash/is-array.spec.ts new file mode 100644 index 0000000..2bdfe8c --- /dev/null +++ b/__tests__/unit/lodash/is-array.spec.ts @@ -0,0 +1,23 @@ +import { isArray } from '../../../src/lodash'; + +describe('isArray', () => { + it('array literal', () => { + expect(isArray([])).toBe(true); + expect(isArray([1, 2, 3])).toBe(true); + }); + + it('array object', () => { + expect(isArray(new Array(10))).toBe(true); + expect(isArray(new Array(10).fill(0))).toBe(true); + }); + + it('not array', () => { + expect(isArray(0)).toBe(false); + expect(isArray(123)).toBe(false); + expect(isArray('')).toBe(false); + expect(isArray('abc')).toBe(false); + expect(isArray({})).toBe(false); + expect(isArray(null)).toBe(false); + expect(isArray(undefined)).toBe(false); + }); +}); diff --git a/__tests__/unit/lodash/is-date.spec.ts b/__tests__/unit/lodash/is-date.spec.ts new file mode 100644 index 0000000..deab59b --- /dev/null +++ b/__tests__/unit/lodash/is-date.spec.ts @@ -0,0 +1,19 @@ +import { isDate } from '../../../src/lodash'; + +describe('isDate', () => { + it('date', () => { + expect(isDate(new Date())).toBe(true); + expect(isDate(new Date('2024'))).toBe(true); + }); + + it('not date', () => { + expect(isDate(0)).toBe(false); + expect(isDate(123)).toBe(false); + expect(isDate('')).toBe(false); + expect(isDate('abc')).toBe(false); + expect(isDate([])).toBe(false); + expect(isDate({})).toBe(false); + expect(isDate(null)).toBe(false); + expect(isDate(undefined)).toBe(false); + }); +}); diff --git a/__tests__/unit/lodash/is-decimal.spec.ts b/__tests__/unit/lodash/is-decimal.spec.ts new file mode 100644 index 0000000..e754d3e --- /dev/null +++ b/__tests__/unit/lodash/is-decimal.spec.ts @@ -0,0 +1,23 @@ +import { isDecimal } from '../../../src/lodash'; + +describe('isDecimal', () => { + it('decimal', () => { + expect(isDecimal(0.1)).toBe(true); + expect(isDecimal(0.123)).toBe(true); + expect(isDecimal(0.123456789)).toBe(true); + expect(isDecimal(0.1234567890123456789)).toBe(true); + expect(isDecimal(0.12345678901234567890123456789)).toBe(true); + expect(isDecimal(0.123456789012345678901234567890123456789)).toBe(true); + }); + + it('not decimal', () => { + expect(isDecimal(0)).toBe(false); + expect(isDecimal(123)).toBe(false); + expect(isDecimal('')).toBe(false); + expect(isDecimal('abc')).toBe(false); + expect(isDecimal([])).toBe(false); + expect(isDecimal({})).toBe(false); + expect(isDecimal(null)).toBe(false); + expect(isDecimal(undefined)).toBe(false); + }); +}); diff --git a/__tests__/unit/lodash/is-element.spec.ts b/__tests__/unit/lodash/is-element.spec.ts new file mode 100644 index 0000000..ab479bd --- /dev/null +++ b/__tests__/unit/lodash/is-element.spec.ts @@ -0,0 +1,22 @@ +import { isElement } from '../../../src/lodash'; + +describe('isElement', () => { + it('element', () => { + expect(isElement(document.createElement('div'))).toBe(true); + expect(isElement(document.createElement('span'))).toBe(true); + expect(isElement(document.createElement('a'))).toBe(true); + expect(isElement(document)).toBe(true); + }); + + it('not element', () => { + expect(isElement(window)).toBe(false); + expect(isElement(0)).toBe(false); + expect(isElement(123)).toBe(false); + expect(isElement('')).toBe(false); + expect(isElement('abc')).toBe(false); + expect(isElement([])).toBe(false); + expect(isElement({})).toBe(false); + expect(isElement(null)).toBe(false); + expect(isElement(undefined)).toBe(false); + }); +}); diff --git a/__tests__/unit/lodash/is-even.spec.ts b/__tests__/unit/lodash/is-even.spec.ts new file mode 100644 index 0000000..512b2b3 --- /dev/null +++ b/__tests__/unit/lodash/is-even.spec.ts @@ -0,0 +1,31 @@ +import { isEven } from '../../../src/lodash'; + +describe('isEven', () => { + it('even', () => { + expect(isEven(0)).toBe(true); + expect(isEven(2)).toBe(true); + expect(isEven(4)).toBe(true); + expect(isEven(6)).toBe(true); + expect(isEven(8)).toBe(true); + expect(isEven(10)).toBe(true); + expect(isEven(12)).toBe(true); + expect(isEven(14)).toBe(true); + expect(isEven(16)).toBe(true); + expect(isEven(18)).toBe(true); + expect(isEven(20)).toBe(true); + }); + + it('not even', () => { + expect(isEven(1)).toBe(false); + expect(isEven(3)).toBe(false); + expect(isEven(5)).toBe(false); + expect(isEven(7)).toBe(false); + expect(isEven(9)).toBe(false); + expect(isEven(11)).toBe(false); + expect(isEven(13)).toBe(false); + expect(isEven(15)).toBe(false); + expect(isEven(17)).toBe(false); + expect(isEven(19)).toBe(false); + expect(isEven(21)).toBe(false); + }); +}); diff --git a/__tests__/unit/lodash/is-finite.spec.ts b/__tests__/unit/lodash/is-finite.spec.ts new file mode 100644 index 0000000..54252d8 --- /dev/null +++ b/__tests__/unit/lodash/is-finite.spec.ts @@ -0,0 +1,20 @@ +import { isFinite } from '../../../src/lodash'; + +describe('isFinite', () => { + it('finite', () => { + expect(isFinite(0)).toBe(true); + expect(isFinite(123)).toBe(true); + expect(isFinite(0.1)).toBe(true); + expect(isFinite(0.123)).toBe(true); + expect(isFinite(0.123456789)).toBe(true); + expect(isFinite(0.1234567890123456789)).toBe(true); + expect(isFinite(0.12345678901234567890123456789)).toBe(true); + expect(isFinite(0.123456789012345678901234567890123456789)).toBe(true); + }); + + it('not finite', () => { + expect(isFinite(Infinity)).toBe(false); + expect(isFinite(-Infinity)).toBe(false); + expect(isFinite(NaN)).toBe(false); + }); +}); diff --git a/__tests__/unit/lodash/is-function.spec.ts b/__tests__/unit/lodash/is-function.spec.ts new file mode 100644 index 0000000..7113511 --- /dev/null +++ b/__tests__/unit/lodash/is-function.spec.ts @@ -0,0 +1,20 @@ +import { isFunction } from '../../../src/lodash'; + +describe('isFunction', () => { + it('function', () => { + expect(isFunction(() => {})).toBe(true); + expect(isFunction(function () {})).toBe(true); + expect(isFunction(new Function())).toBe(true); + }); + + it('not function', () => { + expect(isFunction(0)).toBe(false); + expect(isFunction(123)).toBe(false); + expect(isFunction('')).toBe(false); + expect(isFunction('abc')).toBe(false); + expect(isFunction([])).toBe(false); + expect(isFunction({})).toBe(false); + expect(isFunction(null)).toBe(false); + expect(isFunction(undefined)).toBe(false); + }); +}); diff --git a/__tests__/unit/lodash/is-integer.spec.ts b/__tests__/unit/lodash/is-integer.spec.ts new file mode 100644 index 0000000..5175244 --- /dev/null +++ b/__tests__/unit/lodash/is-integer.spec.ts @@ -0,0 +1,21 @@ +import { isInteger } from '../../../src/lodash'; + +describe('isInteger', () => { + it('integer', () => { + expect(isInteger(0)).toBe(true); + expect(isInteger(123)).toBe(true); + expect(isInteger(-123)).toBe(true); + }); + + it('not integer', () => { + expect(isInteger(0.1)).toBe(false); + expect(isInteger(0.123)).toBe(false); + expect(isInteger(0.123456789)).toBe(false); + expect(isInteger(0.1234567890123456789)).toBe(false); + expect(isInteger(0.12345678901234567890123456789)).toBe(false); + expect(isInteger(0.123456789012345678901234567890123456789)).toBe(false); + expect(isInteger(Infinity)).toBe(false); + expect(isInteger(-Infinity)).toBe(false); + expect(isInteger(NaN)).toBe(false); + }); +}); diff --git a/__tests__/unit/lodash/is-negative.spec.ts b/__tests__/unit/lodash/is-negative.spec.ts new file mode 100644 index 0000000..ada9b24 --- /dev/null +++ b/__tests__/unit/lodash/is-negative.spec.ts @@ -0,0 +1,24 @@ +import { isNegative } from '../../../src/lodash'; + +describe('isNegative', () => { + it('negative', () => { + expect(isNegative(-1)).toBe(true); + expect(isNegative(-0.1)).toBe(true); + expect(isNegative(-0.123)).toBe(true); + expect(isNegative(-0.123456789)).toBe(true); + expect(isNegative(-0.1234567890123456789)).toBe(true); + expect(isNegative(-0.12345678901234567890123456789)).toBe(true); + expect(isNegative(-0.123456789012345678901234567890123456789)).toBe(true); + }); + + it('not negative', () => { + expect(isNegative(0)).toBe(false); + expect(isNegative(1)).toBe(false); + expect(isNegative(0.1)).toBe(false); + expect(isNegative(0.123)).toBe(false); + expect(isNegative(0.123456789)).toBe(false); + expect(isNegative(0.1234567890123456789)).toBe(false); + expect(isNegative(0.12345678901234567890123456789)).toBe(false); + expect(isNegative(0.123456789012345678901234567890123456789)).toBe(false); + }); +}); diff --git a/__tests__/unit/lodash/is-nil.spec.ts b/__tests__/unit/lodash/is-nil.spec.ts new file mode 100644 index 0000000..40114f0 --- /dev/null +++ b/__tests__/unit/lodash/is-nil.spec.ts @@ -0,0 +1,17 @@ +import { isNil } from '../../../src/lodash'; + +describe('isNil', () => { + it('nil', () => { + expect(isNil(null)).toBe(true); + expect(isNil(undefined)).toBe(true); + }); + + it('not nil', () => { + expect(isNil(0)).toBe(false); + expect(isNil(123)).toBe(false); + expect(isNil('')).toBe(false); + expect(isNil('abc')).toBe(false); + expect(isNil([])).toBe(false); + expect(isNil({})).toBe(false); + }); +}); diff --git a/__tests__/unit/lodash/is-null.spec.ts b/__tests__/unit/lodash/is-null.spec.ts new file mode 100644 index 0000000..a98cf56 --- /dev/null +++ b/__tests__/unit/lodash/is-null.spec.ts @@ -0,0 +1,17 @@ +import { isNull } from '../../../src/lodash'; + +describe('isNull', () => { + it('null', () => { + expect(isNull(null)).toBe(true); + }); + + it('not null', () => { + expect(isNull(0)).toBe(false); + expect(isNull(123)).toBe(false); + expect(isNull('')).toBe(false); + expect(isNull('abc')).toBe(false); + expect(isNull([])).toBe(false); + expect(isNull({})).toBe(false); + expect(isNull(undefined)).toBe(false); + }); +}); diff --git a/__tests__/unit/lodash/is-number-equal.spec.ts b/__tests__/unit/lodash/is-number-equal.spec.ts new file mode 100644 index 0000000..b00acf1 --- /dev/null +++ b/__tests__/unit/lodash/is-number-equal.spec.ts @@ -0,0 +1,24 @@ +import { isNumberEqual } from '../../../src/lodash'; + +describe('isNumberEqual', () => { + it('equal', () => { + expect(isNumberEqual(0, 0)).toBe(true); + expect(isNumberEqual(1, 1)).toBe(true); + expect(isNumberEqual(0.1, 0.1)).toBe(true); + expect(isNumberEqual(0.123, 0.123)).toBe(true); + expect(isNumberEqual(0.123456789, 0.123456789)).toBe(true); + expect(isNumberEqual(0.1234567890123456789, 0.1234567890123456789)).toBe(true); + expect(isNumberEqual(0.12345678901234567890123456789, 0.12345678901234567890123456789)).toBe(true); + expect(isNumberEqual(0.123456789012345678901234567890123456789, 0.123456789012345678901234567890123456789)).toBe( + true, + ); + }); + + it('not equal', () => { + expect(isNumberEqual(0, 1)).toBe(false); + expect(isNumberEqual(1, 0)).toBe(false); + expect(isNumberEqual(0.1, 0.2)).toBe(false); + expect(isNumberEqual(0.123, 0.124)).toBe(false); + expect(isNumberEqual(0.123456789, 0.123456788, 0.0000000001)).toBe(false); + }); +}); diff --git a/__tests__/unit/lodash/is-number.spec.ts b/__tests__/unit/lodash/is-number.spec.ts new file mode 100644 index 0000000..8eef5be --- /dev/null +++ b/__tests__/unit/lodash/is-number.spec.ts @@ -0,0 +1,23 @@ +import { isNumber } from '../../../src/lodash'; + +describe('isNumber', () => { + it('number', () => { + expect(isNumber(0)).toBe(true); + expect(isNumber(123)).toBe(true); + expect(isNumber(0.1)).toBe(true); + expect(isNumber(123.4)).toBe(true); + expect(isNumber(-0)).toBe(true); + expect(isNumber(-123)).toBe(true); + expect(isNumber(-0.1)).toBe(true); + expect(isNumber(-123.4)).toBe(true); + }); + + it('not number', () => { + expect(isNumber('')).toBe(false); + expect(isNumber('abc')).toBe(false); + expect(isNumber([])).toBe(false); + expect(isNumber({})).toBe(false); + expect(isNumber(null)).toBe(false); + expect(isNumber(undefined)).toBe(false); + }); +}); diff --git a/__tests__/unit/lodash/is-odd.spec.ts b/__tests__/unit/lodash/is-odd.spec.ts new file mode 100644 index 0000000..44f9b81 --- /dev/null +++ b/__tests__/unit/lodash/is-odd.spec.ts @@ -0,0 +1,30 @@ +import { isOdd } from '../../../src/lodash'; + +describe('isOdd', () => { + it('odd', () => { + expect(isOdd(1)).toBe(true); + expect(isOdd(3)).toBe(true); + expect(isOdd(5)).toBe(true); + expect(isOdd(7)).toBe(true); + expect(isOdd(9)).toBe(true); + expect(isOdd(11)).toBe(true); + expect(isOdd(13)).toBe(true); + expect(isOdd(15)).toBe(true); + expect(isOdd(17)).toBe(true); + expect(isOdd(19)).toBe(true); + }); + + it('not odd', () => { + expect(isOdd(0)).toBe(false); + expect(isOdd(2)).toBe(false); + expect(isOdd(4)).toBe(false); + expect(isOdd(6)).toBe(false); + expect(isOdd(8)).toBe(false); + expect(isOdd(10)).toBe(false); + expect(isOdd(12)).toBe(false); + expect(isOdd(14)).toBe(false); + expect(isOdd(16)).toBe(false); + expect(isOdd(18)).toBe(false); + expect(isOdd(20)).toBe(false); + }); +}); diff --git a/__tests__/unit/lodash/is-string.spec.ts b/__tests__/unit/lodash/is-string.spec.ts new file mode 100644 index 0000000..ee967aa --- /dev/null +++ b/__tests__/unit/lodash/is-string.spec.ts @@ -0,0 +1,24 @@ +import { isString } from '../../../src/lodash'; + +describe('isString', () => { + it('string literal', () => { + expect(isString('')).toBe(true); + expect(isString('abc')).toBe(true); + expect(isString('123')).toBe(true); + }); + + it('string object', () => { + expect(isString(new String(''))).toBe(true); + expect(isString(new String('abc'))).toBe(true); + expect(isString(new String('123'))).toBe(true); + }); + + it('not string', () => { + expect(isString(0)).toBe(false); + expect(isString(123)).toBe(false); + expect(isString([])).toBe(false); + expect(isString({})).toBe(false); + expect(isString(null)).toBe(false); + expect(isString(undefined)).toBe(false); + }); +}); diff --git a/__tests__/unit/lodash/max.spec.ts b/__tests__/unit/lodash/max.spec.ts new file mode 100644 index 0000000..1f1ef5e --- /dev/null +++ b/__tests__/unit/lodash/max.spec.ts @@ -0,0 +1,18 @@ +import { max } from '../../../src/lodash'; + +describe('max', () => { + it('max', () => { + expect(max([1, 2, 3])).toBe(3); + expect(max([3, 2, 1])).toBe(3); + expect(max([1, 3, 2])).toBe(3); + expect(max([-Infinity, 0])).toBe(0); + expect(max([-Infinity, -Infinity])).toBe(-Infinity); + expect(max([-Infinity, 0, Infinity])).toBe(Infinity); + }); + + it('empty', () => { + expect(max([])).toBe(undefined); + expect(max([NaN])).toBe(undefined); + expect(max([1, 2, NaN])).toBe(undefined); + }); +}); diff --git a/__tests__/unit/lodash/memoize.spec.ts b/__tests__/unit/lodash/memoize.spec.ts new file mode 100644 index 0000000..7af4cbf --- /dev/null +++ b/__tests__/unit/lodash/memoize.spec.ts @@ -0,0 +1,40 @@ +import { memoize } from '../../../src/lodash'; + +describe('memoize', () => { + it('memoize', () => { + const fn = jest.fn((a: number, b: number) => a + b); + const memoizedFn = memoize(fn); + + expect(memoizedFn(1, 2)).toBe(3); + expect(memoizedFn(1, 2)).toBe(3); + expect(fn).toBeCalledTimes(1); + }); + + it('memoize with resolver', () => { + const fn = jest.fn((a: number, b: number) => a + b); + const memoizedFn = memoize(fn, (...args) => args.join(',')); + + expect(memoizedFn(1, 2)).toBe(3); + expect(memoizedFn(1, 2)).toBe(3); + expect(fn).toBeCalledTimes(1); + }); + + it('memoize with maxSize', () => { + const fn = jest.fn((a: number, b: number) => a + b); + const memoizedFn = memoize(fn, undefined, 1); + + expect(memoizedFn(1, 2)).toBe(3); + expect(memoizedFn(1, 2)).toBe(3); + expect(fn).toBeCalledTimes(1); + expect(memoizedFn.cache.size()).toEqual(1); + expect(memoizedFn.cache.has(1)).toBe(true); + + expect(memoizedFn(2, 3)).toBe(5); + expect(memoizedFn(2, 3)).toBe(5); + expect(fn).toBeCalledTimes(2); + + expect(memoizedFn.cache.size()).toEqual(1); + expect(memoizedFn.cache.has(1)).toBe(false); + expect(memoizedFn.cache.has(2)).toBe(true); + }); +}); From 03941ebca817d950d1c0464dd29bd93f200c9714 Mon Sep 17 00:00:00 2001 From: Aaron Date: Thu, 19 Sep 2024 17:52:37 +0800 Subject: [PATCH 3/7] chore: config lint-staged and commitlint --- .husky/.gitignore | 1 + .husky/commit-msg | 1 + .husky/pre-commit | 1 + package.json | 23 ++++++++++++----------- 4 files changed, 15 insertions(+), 11 deletions(-) create mode 100644 .husky/.gitignore create mode 100755 .husky/commit-msg create mode 100755 .husky/pre-commit diff --git a/.husky/.gitignore b/.husky/.gitignore new file mode 100644 index 0000000..31354ec --- /dev/null +++ b/.husky/.gitignore @@ -0,0 +1 @@ +_ diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100755 index 0000000..dab272d --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1 @@ +npx --no-install commitlint --edit "" diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..2312dc5 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx lint-staged diff --git a/package.json b/package.json index 6aa735b..1ffa925 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,9 @@ "build": "run-p build:*", "ci": "run-s lint test build", "prepublishOnly": "npm run ci", - "benchmarks": "node benchmarks/path-2-string.test && node benchmarks/path-2-absolute.test" + "benchmarks": "node benchmarks/path-2-string.test && node benchmarks/path-2-absolute.test", + "postinstall": "husky install", + "prepare": "husky install" }, "keywords": [ "util", @@ -34,7 +36,8 @@ ], "devDependencies": { "@antv/path-util": "^2.0.15", - "@commitlint/cli": "^11.0.0", + "@commitlint/cli": "^18.6.1", + "@commitlint/config-conventional": "^18.6.3", "@rollup/plugin-commonjs": "^22.0.0", "@rollup/plugin-node-resolve": "^13.3.0", "@rollup/plugin-typescript": "^8.3.2", @@ -44,11 +47,11 @@ "benchmark": "^2.1.4", "eslint": "^7.22.0", "eslint-plugin-import": "^2.22.1", - "husky": "^5.0.9", + "husky": "^5.2.0", "jest": "^26.6.3", "jest-electron": "^0.1.12", "limit-size": "^0.1.4", - "lint-staged": "^10.5.4", + "lint-staged": "^15.2.10", "npm-run-all": "^4.1.5", "prettier": "^2.2.1", "rimraf": "^3.0.2", @@ -70,8 +73,7 @@ "lint-staged": { "*.{ts,tsx}": [ "eslint --fix", - "prettier --write", - "git add" + "prettier --write" ] }, "limit-size": [ @@ -85,11 +87,10 @@ "limit": "40 Kb" } ], - "husky": { - "hooks": { - "pre-commit": "npm run lint-staged", - "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" - } + "commitlint": { + "extends": [ + "@commitlint/config-conventional" + ] }, "dependencies": { "fast-deep-equal": "^3.1.3", From ccb2b7c3b8834e4518e3bf93cd124cfd8e2e8541 Mon Sep 17 00:00:00 2001 From: Aaron Date: Thu, 19 Sep 2024 18:51:40 +0800 Subject: [PATCH 4/7] refactor: adjust memoize --- __tests__/unit/lodash/memoize.spec.ts | 6 ------ src/lodash/memoize.ts | 15 +++++++-------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/__tests__/unit/lodash/memoize.spec.ts b/__tests__/unit/lodash/memoize.spec.ts index 7af4cbf..1465020 100644 --- a/__tests__/unit/lodash/memoize.spec.ts +++ b/__tests__/unit/lodash/memoize.spec.ts @@ -26,15 +26,9 @@ describe('memoize', () => { expect(memoizedFn(1, 2)).toBe(3); expect(memoizedFn(1, 2)).toBe(3); expect(fn).toBeCalledTimes(1); - expect(memoizedFn.cache.size()).toEqual(1); - expect(memoizedFn.cache.has(1)).toBe(true); expect(memoizedFn(2, 3)).toBe(5); expect(memoizedFn(2, 3)).toBe(5); expect(fn).toBeCalledTimes(2); - - expect(memoizedFn.cache.size()).toEqual(1); - expect(memoizedFn.cache.has(1)).toBe(false); - expect(memoizedFn.cache.has(2)).toBe(true); }); }); diff --git a/src/lodash/memoize.ts b/src/lodash/memoize.ts index 5c052b5..b23dead 100644 --- a/src/lodash/memoize.ts +++ b/src/lodash/memoize.ts @@ -49,6 +49,8 @@ class FLRUCache { } } +const CacheMap = new Map>(); + /** * 缓存函数的计算结果,避免重复计算 * @example @@ -58,21 +60,18 @@ class FLRUCache { * @param resolver 生成缓存 key 的函数 * @param maxSize lru 缓存的大小 */ -export default function memoize(fn: Function, resolver?: (...args: any[]) => string, maxSize = 128) { +export default function memoize(fn: T, resolver?: (...args: any[]) => string, maxSize = 128) { const memoized = function (...args) { // 使用方法构造 key,如果不存在 resolver,则直接取第一个参数作为 key const key = resolver ? resolver.apply(this, args) : args[0]; - const cache = memoized.cache; + if (!CacheMap.has(fn)) CacheMap.set(fn, new FLRUCache(maxSize)); + const cache = CacheMap.get(fn); - if (cache.has(key)) { - return cache.get(key); - } + if (cache.has(key)) return cache.get(key); const result = fn.apply(this, args); cache.set(key, result); return result; }; - memoized.cache = new FLRUCache(maxSize); - - return memoized; + return memoized as unknown as T; } From 022f14ec2f249f33a05a89b1f87d946b4d3f0cbc Mon Sep 17 00:00:00 2001 From: Aaron Date: Thu, 19 Sep 2024 19:38:40 +0800 Subject: [PATCH 5/7] chore: update rollup config --- package.json | 24 ++++++++++++------------ rollup.config.js => rollup.config.mjs | 11 ++++++++--- 2 files changed, 20 insertions(+), 15 deletions(-) rename rollup.config.js => rollup.config.mjs (66%) diff --git a/package.json b/package.json index 1ffa925..0e5c391 100644 --- a/package.json +++ b/package.json @@ -38,9 +38,10 @@ "@antv/path-util": "^2.0.15", "@commitlint/cli": "^18.6.1", "@commitlint/config-conventional": "^18.6.3", - "@rollup/plugin-commonjs": "^22.0.0", - "@rollup/plugin-node-resolve": "^13.3.0", - "@rollup/plugin-typescript": "^8.3.2", + "@rollup/plugin-commonjs": "^26.0.1", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-terser": "^0.4.4", + "@rollup/plugin-typescript": "^11.1.6", "@types/jest": "^26.0.20", "@typescript-eslint/eslint-plugin": "^4.18.0", "@typescript-eslint/parser": "^4.18.0", @@ -48,22 +49,21 @@ "eslint": "^7.22.0", "eslint-plugin-import": "^2.22.1", "husky": "^5.2.0", - "jest": "^26.6.3", - "jest-electron": "^0.1.12", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "jsdom": "^25.0.0", "limit-size": "^0.1.4", "lint-staged": "^15.2.10", "npm-run-all": "^4.1.5", - "prettier": "^2.2.1", + "prettier": "^3.3.3", "rimraf": "^3.0.2", - "rollup": "^2.39.0", - "rollup-plugin-uglify": "^6.0.4", - "ts-jest": "^26.5.1", - "typescript": "~4.4.0" + "rollup": "^4.22.0", + "ts-jest": "^29.2.5", + "typescript": "^5" }, "jest": { - "runner": "jest-electron/runner", - "testEnvironment": "jest-electron/environment", "preset": "ts-jest", + "testEnvironment": "jsdom", "collectCoverage": true, "testRegex": "(/__tests__/.*\\.(test|spec))\\.ts$", "collectCoverageFrom": [ diff --git a/rollup.config.js b/rollup.config.mjs similarity index 66% rename from rollup.config.js rename to rollup.config.mjs index 442a062..6c67d31 100644 --- a/rollup.config.js +++ b/rollup.config.mjs @@ -1,9 +1,9 @@ -import { uglify } from 'rollup-plugin-uglify'; import resolve from '@rollup/plugin-node-resolve'; import typescript from '@rollup/plugin-typescript'; import commonjs from '@rollup/plugin-commonjs'; +import terser from '@rollup/plugin-terser'; -module.exports = [ +export default [ { input: 'src/index.ts', output: { @@ -12,6 +12,11 @@ module.exports = [ format: 'umd', sourcemap: false, }, - plugins: [resolve(), typescript(), commonjs(), uglify()], + plugins: [ + resolve(), + typescript(), + commonjs(), + terser(), + ], }, ]; From 5efc22e35dcd6c677f3740751b89c5158075eb50 Mon Sep 17 00:00:00 2001 From: Aaron Date: Thu, 19 Sep 2024 19:39:01 +0800 Subject: [PATCH 6/7] test: fix test case --- __tests__/unit/color/index.spec.ts | 27 +++++++++++++++++++++++++++ src/color/torgb.ts | 3 +-- src/path/process/reverse-curve.ts | 2 +- src/path/util/equalize-segments.ts | 9 ++++----- 4 files changed, 33 insertions(+), 8 deletions(-) diff --git a/__tests__/unit/color/index.spec.ts b/__tests__/unit/color/index.spec.ts index d90e291..1df209f 100644 --- a/__tests__/unit/color/index.spec.ts +++ b/__tests__/unit/color/index.spec.ts @@ -1,6 +1,33 @@ import { gradient, toCSSGradient, toRGB } from '../../../src'; describe('color', function () { + let mockGetComputedStyle; + beforeAll(() => { + const colorMap = { + red: 'rgb(255, 0, 0)', + white: 'rgb(255, 255, 255)', + black: 'rgb(0, 0, 0)', + blue: 'rgb(0, 0, 255)', + '#ddd': 'rgb(221, 221, 221)', + '#eeeeee': 'rgb(238, 238, 238)', + }; + + // implement document defaultView getComputedStyle getPropertyValue in jsdom + mockGetComputedStyle = jest.spyOn(window, 'getComputedStyle').mockImplementation( + (el) => + ({ + getPropertyValue: () => { + const color = (el as HTMLElement).style.color; + return colorMap[color] || color; + }, + }) as any, + ); + }); + + afterAll(() => { + mockGetComputedStyle?.mockRestore(); + }); + it('toRGB', () => { expect(toRGB('red')).toBe('#ff0000'); expect(toRGB('white')).toBe('#ffffff'); diff --git a/src/color/torgb.ts b/src/color/torgb.ts index 93a63b9..60d5a03 100644 --- a/src/color/torgb.ts +++ b/src/color/torgb.ts @@ -35,8 +35,7 @@ function toRGBString(color: string): string { iEl.style.color = color; - let rst = document.defaultView.getComputedStyle(iEl, '').getPropertyValue('color'); - + let rst = window.getComputedStyle(iEl, '').getPropertyValue('color'); const matches = RGB_REG.exec(rst) as string[]; const cArray: number[] = matches[1].split(/\s*,\s*/).map((s) => Number(s)); diff --git a/src/path/process/reverse-curve.ts b/src/path/process/reverse-curve.ts index a2b366f..21cdda9 100644 --- a/src/path/process/reverse-curve.ts +++ b/src/path/process/reverse-curve.ts @@ -2,7 +2,7 @@ import type { CurveArray } from '../types'; // reverse CURVE based pathArray segments only export function reverseCurve(pathArray: CurveArray): CurveArray { - const rotatedCurve = pathArray + const rotatedCurve: any = pathArray .slice(1) .map((x, i, curveOnly) => // @ts-ignore diff --git a/src/path/util/equalize-segments.ts b/src/path/util/equalize-segments.ts index f68f351..181686a 100644 --- a/src/path/util/equalize-segments.ts +++ b/src/path/util/equalize-segments.ts @@ -2,10 +2,9 @@ import type { CurveArray, PathArray } from '../types'; import { midPoint } from './mid-point'; import { segmentCubicFactory } from './segment-cubic-factory'; -function splitCubic( - pts: [number, number, number, number, number, number, number, number], - t = 0.5, -): [CurveArray, CurveArray] { +type SplitArray = [number, number, number, number, number, number, number, number, number]; + +function splitCubic(pts: SplitArray, t = 0.5): [CurveArray, CurveArray] { const p0 = pts.slice(0, 2) as [number, number]; const p1 = pts.slice(2, 4) as [number, number]; const p2 = pts.slice(4, 6) as [number, number]; @@ -28,7 +27,7 @@ function splitCubic( function getCurveArray(segments: PathArray) { return segments.map((segment, i, pathArray) => { // @ts-ignore - const segmentData = i && pathArray[i - 1].slice(-2).concat(segment.slice(1)); + const segmentData = i && (pathArray[i - 1].slice(-2).concat(segment.slice(1)) as SplitArray); // @ts-ignore const curveLength = i From 3f1a306c2c6b3bb5cd800323f522debcfaad075b Mon Sep 17 00:00:00 2001 From: Aaron Date: Thu, 19 Sep 2024 20:20:28 +0800 Subject: [PATCH 7/7] refactor: fix cr issue --- __tests__/unit/color/index.spec.ts | 2 +- __tests__/unit/lodash/is-number.spec.ts | 9 ++- __tests__/unit/lodash/is-string.spec.ts | 6 +- __tests__/unit/lodash/max.spec.ts | 6 +- src/color/torgb.ts | 2 +- src/lodash/is-number.ts | 2 +- src/lodash/is-string.ts | 2 +- src/lodash/max.ts | 33 +++------- src/lodash/memoize.ts | 81 +++++++++++-------------- 9 files changed, 62 insertions(+), 81 deletions(-) diff --git a/__tests__/unit/color/index.spec.ts b/__tests__/unit/color/index.spec.ts index 1df209f..5e56926 100644 --- a/__tests__/unit/color/index.spec.ts +++ b/__tests__/unit/color/index.spec.ts @@ -13,7 +13,7 @@ describe('color', function () { }; // implement document defaultView getComputedStyle getPropertyValue in jsdom - mockGetComputedStyle = jest.spyOn(window, 'getComputedStyle').mockImplementation( + mockGetComputedStyle = jest.spyOn(document.defaultView!, 'getComputedStyle').mockImplementation( (el) => ({ getPropertyValue: () => { diff --git a/__tests__/unit/lodash/is-number.spec.ts b/__tests__/unit/lodash/is-number.spec.ts index 8eef5be..fa31db8 100644 --- a/__tests__/unit/lodash/is-number.spec.ts +++ b/__tests__/unit/lodash/is-number.spec.ts @@ -1,7 +1,7 @@ import { isNumber } from '../../../src/lodash'; describe('isNumber', () => { - it('number', () => { + it('number literal', () => { expect(isNumber(0)).toBe(true); expect(isNumber(123)).toBe(true); expect(isNumber(0.1)).toBe(true); @@ -12,6 +12,13 @@ describe('isNumber', () => { expect(isNumber(-123.4)).toBe(true); }); + it('number object', () => { + expect(isNumber(new Number(0))).toBe(false); + expect(isNumber(new Number(123))).toBe(false); + expect(isNumber(new Number(0.1))).toBe(false); + expect(isNumber(new Number(123.4))).toBe(false); + }); + it('not number', () => { expect(isNumber('')).toBe(false); expect(isNumber('abc')).toBe(false); diff --git a/__tests__/unit/lodash/is-string.spec.ts b/__tests__/unit/lodash/is-string.spec.ts index ee967aa..f91e6be 100644 --- a/__tests__/unit/lodash/is-string.spec.ts +++ b/__tests__/unit/lodash/is-string.spec.ts @@ -8,9 +8,9 @@ describe('isString', () => { }); it('string object', () => { - expect(isString(new String(''))).toBe(true); - expect(isString(new String('abc'))).toBe(true); - expect(isString(new String('123'))).toBe(true); + expect(isString(new String(''))).toBe(false); + expect(isString(new String('abc'))).toBe(false); + expect(isString(new String('123'))).toBe(false); }); it('not string', () => { diff --git a/__tests__/unit/lodash/max.spec.ts b/__tests__/unit/lodash/max.spec.ts index 1f1ef5e..450f97b 100644 --- a/__tests__/unit/lodash/max.spec.ts +++ b/__tests__/unit/lodash/max.spec.ts @@ -11,8 +11,8 @@ describe('max', () => { }); it('empty', () => { - expect(max([])).toBe(undefined); - expect(max([NaN])).toBe(undefined); - expect(max([1, 2, NaN])).toBe(undefined); + expect(max([])).toBe(-Infinity); + expect(max([NaN])).toBe(NaN); + expect(max([1, 2, NaN])).toBe(NaN); }); }); diff --git a/src/color/torgb.ts b/src/color/torgb.ts index 60d5a03..37a84ca 100644 --- a/src/color/torgb.ts +++ b/src/color/torgb.ts @@ -35,7 +35,7 @@ function toRGBString(color: string): string { iEl.style.color = color; - let rst = window.getComputedStyle(iEl, '').getPropertyValue('color'); + let rst = document.defaultView.getComputedStyle(iEl, '').getPropertyValue('color'); const matches = RGB_REG.exec(rst) as string[]; const cArray: number[] = matches[1].split(/\s*,\s*/).map((s) => Number(s)); diff --git a/src/lodash/is-number.ts b/src/lodash/is-number.ts index ba11eb9..c633191 100644 --- a/src/lodash/is-number.ts +++ b/src/lodash/is-number.ts @@ -3,5 +3,5 @@ * @return 是否为数字 */ export default function isNumber(value: unknown): value is number { - return typeof value === 'number' || value instanceof Number; + return typeof value === 'number'; } diff --git a/src/lodash/is-string.ts b/src/lodash/is-string.ts index a786a0d..39f1af0 100644 --- a/src/lodash/is-string.ts +++ b/src/lodash/is-string.ts @@ -3,5 +3,5 @@ * @return 是否为字符串 */ export default function isString(value: unknown): value is string { - return typeof value === 'string' || value instanceof String; + return typeof value === 'string'; } diff --git a/src/lodash/max.ts b/src/lodash/max.ts index 817c81b..498e8ef 100644 --- a/src/lodash/max.ts +++ b/src/lodash/max.ts @@ -1,34 +1,15 @@ -import isNil from './is-nil'; - /** - * @param {Array} arr The array to iterate over. - * @return {*} Returns the maximum value. - * @example - * - * max([1, 2]); - * // => 2 - * - * max([]); - * // => undefined - * - * const data = new Array(1250010).fill(1).map((d,idx) => idx); - * - * max(data); - * // => 1250010 - * // Math.max(...data) will encounter "Maximum call stack size exceeded" error + * 计算数组的最大值 + * @param arr 数组 + * @return 最大值 */ -export default function max(arr: number[]): number | undefined { +export default function max(arr: number[]): number { + if (!Array.isArray(arr)) return -Infinity; const length = arr.length; - if (length === 0) return undefined; + if (!length) return -Infinity; let max = arr[0]; - - const isNonNumber = (value: number) => isNil(value) || isNaN(value); - if (isNonNumber(max)) return undefined; - for (let i = 1; i < length; i++) { - if (isNonNumber(arr[i])) return undefined; - if (arr[i] > max) max = arr[i]; + max = Math.max(max, arr[i]); } - return max; } diff --git a/src/lodash/memoize.ts b/src/lodash/memoize.ts index b23dead..0f68c32 100644 --- a/src/lodash/memoize.ts +++ b/src/lodash/memoize.ts @@ -1,55 +1,48 @@ -class FLRUCache { - private cache: Map; - private capacity: number; +function flru(max: number) { + let num, curr, prev; + const limit = max || 1; - constructor(capacity: number) { - this.capacity = Math.max(capacity, 1); - this.cache = new Map(); - } - - // 获取缓存中的值,并更新使用顺序 - get(key: K): V | undefined { - if (!this.cache.has(key)) { - return undefined; + function keep(key, value) { + if (++num > limit) { + prev = curr; + reset(1); + ++num; } - - // 先删除再插入,以便更新顺序 - const value = this.cache.get(key)!; - this.cache.delete(key); - this.cache.set(key, value); - return value; + curr[key] = value; } - // 设置缓存值,更新使用顺序,并维护缓存大小 - set(key: K, value: V): void { - if (this.cache.has(key)) { - // 如果 key 已存在,更新值并重新设置顺序 - this.cache.delete(key); - } else if (this.cache.size >= this.capacity) { - // 容量已满,移除最久未使用的键值对 - const firstKey = this.cache.keys().next().value; - this.cache.delete(firstKey); - } - this.cache.set(key, value); + function reset(isPartial?: number) { + num = 0; + curr = Object.create(null); + isPartial || (prev = Object.create(null)); } - // 检查是否存在指定的 key - has(key: K): boolean { - return this.cache.has(key); - } + reset(); - // 获取当前缓存大小 - size(): number { - return this.cache.size; - } - - // 清空缓存 - clear(): void { - this.cache.clear(); - } + return { + clear: reset, + has: function (key) { + return curr[key] !== void 0 || prev[key] !== void 0; + }, + get: function (key) { + var val = curr[key]; + if (val !== void 0) return val; + if ((val = prev[key]) !== void 0) { + keep(key, val); + return val; + } + }, + set: function (key, value) { + if (curr[key] !== void 0) { + curr[key] = value; + } else { + keep(key, value); + } + }, + }; } -const CacheMap = new Map>(); +const CacheMap = new Map>(); /** * 缓存函数的计算结果,避免重复计算 @@ -64,7 +57,7 @@ export default function memoize(fn: T, resolver?: (...args: const memoized = function (...args) { // 使用方法构造 key,如果不存在 resolver,则直接取第一个参数作为 key const key = resolver ? resolver.apply(this, args) : args[0]; - if (!CacheMap.has(fn)) CacheMap.set(fn, new FLRUCache(maxSize)); + if (!CacheMap.has(fn)) CacheMap.set(fn, flru(maxSize)); const cache = CacheMap.get(fn); if (cache.has(key)) return cache.get(key);