@zhouyihang/js-kit 1.0.0

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 (50) hide show
  1. package/README.md +64 -0
  2. package/package.json +45 -0
  3. package/src/algorithms/index.js +16 -0
  4. package/src/algorithms/myAtoi.js +19 -0
  5. package/src/algorithms/removeDuplicateLetters.js +29 -0
  6. package/src/algorithms/removeKdigits.js +25 -0
  7. package/src/algorithms/sort.js +117 -0
  8. package/src/algorithms/threeSum.js +34 -0
  9. package/src/algorithms/twoSum.js +23 -0
  10. package/src/algorithms/validParentheses.js +22 -0
  11. package/src/algorithms/wordDictionary.js +27 -0
  12. package/src/data-structures/index.js +1 -0
  13. package/src/data-structures/queue.js +46 -0
  14. package/src/dom/index.js +1 -0
  15. package/src/dom/traverseEl.js +19 -0
  16. package/src/framework/hashRouter.js +25 -0
  17. package/src/framework/historyRouter.js +35 -0
  18. package/src/framework/index.js +4 -0
  19. package/src/framework/reactive.js +56 -0
  20. package/src/framework/vdom.js +37 -0
  21. package/src/index.js +66 -0
  22. package/src/javascript/curry.js +8 -0
  23. package/src/javascript/index.js +2 -0
  24. package/src/javascript/inheritance.js +23 -0
  25. package/src/patterns/index.js +3 -0
  26. package/src/patterns/proxyObserver.js +30 -0
  27. package/src/patterns/pubsub.js +30 -0
  28. package/src/patterns/singleton.js +32 -0
  29. package/src/polyfills/call.js +54 -0
  30. package/src/polyfills/deepClone.js +36 -0
  31. package/src/polyfills/deepFreeze.js +11 -0
  32. package/src/polyfills/index.js +8 -0
  33. package/src/polyfills/iterator.js +13 -0
  34. package/src/polyfills/jsonStringify.js +27 -0
  35. package/src/polyfills/new.js +7 -0
  36. package/src/polyfills/promise.js +99 -0
  37. package/src/polyfills/promiseMethods.js +67 -0
  38. package/src/types/index.d.ts +1 -0
  39. package/src/types/nestedArray.d.ts +3 -0
  40. package/src/utils/asyncPool.js +21 -0
  41. package/src/utils/chunkArr.js +10 -0
  42. package/src/utils/debounce.js +11 -0
  43. package/src/utils/flatten.js +40 -0
  44. package/src/utils/formatSizeUnits.js +13 -0
  45. package/src/utils/index.js +10 -0
  46. package/src/utils/makeTree.js +17 -0
  47. package/src/utils/mergeArr.js +18 -0
  48. package/src/utils/parseUrl.js +13 -0
  49. package/src/utils/throttle.js +14 -0
  50. package/src/utils/toTree.js +23 -0
package/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # @yihang/js-kit
2
+
3
+ A long-lasting JavaScript toolkit — polyfills, utilities, design patterns, framework internals, algorithms, and data structures.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @yihang/js-kit
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```js
14
+ // import everything
15
+ import { debounce, deepClone, quickSort, Queue, curry } from "@yihang/js-kit";
16
+
17
+ // or from sub-paths
18
+ import { debounce, throttle } from "@yihang/js-kit/utils";
19
+ import { reactive, effect } from "@yihang/js-kit/framework";
20
+ import { twoSum, quickSort } from "@yihang/js-kit/algorithms";
21
+ import { MyPromise } from "@yihang/js-kit/polyfills";
22
+ ```
23
+
24
+ ## Modules
25
+
26
+ ### Polyfills — `@yihang/js-kit/polyfills`
27
+
28
+ `myCall` `myApply` `myBind` `applyPolyfills` `myNew` `deepClone` `deepFreeze` `jsonStringify` `MyPromise` `promiseAll` `promiseRace` `promiseAllSettled` `promiseAny` `createIterator`
29
+
30
+ ### Utilities — `@yihang/js-kit/utils`
31
+
32
+ `debounce` `throttle` `flatten` `flattenReduce` `flattenDeep` `mergeArr` `chunkArr` `formatSizeUnits` `parseUrl` `makeTree` `toTree` `asyncPool`
33
+
34
+ ### Design Patterns — `@yihang/js-kit/patterns`
35
+
36
+ `Singleton` `Storage` `PubSub` `createProxyObj`
37
+
38
+ ### Framework — `@yihang/js-kit/framework`
39
+
40
+ `reactive` `effect` `h` `mount` `HashRouter` `HistoryRouter`
41
+
42
+ ### Algorithms — `@yihang/js-kit/algorithms`
43
+
44
+ `twoSum` `threeSum` `myAtoi` `WordDictionary` `removeDuplicateLetters` `removeKdigits` `validParentheses` `quickSort` `insertionSort` `selectionSort` `bubbleSort` `shellSort` `mergeSort` `countingSort`
45
+
46
+ ### Data Structures — `@yihang/js-kit/data-structures`
47
+
48
+ `Queue`
49
+
50
+ ### DOM — `@yihang/js-kit/dom`
51
+
52
+ `traverseElRoot`
53
+
54
+ ### JavaScript Basics — `@yihang/js-kit/javascript`
55
+
56
+ `curry` `inherit`
57
+
58
+ ### Types — `@yihang/js-kit/types`
59
+
60
+ `NestedArray<T>`
61
+
62
+ ## License
63
+
64
+ MIT
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@zhouyihang/js-kit",
3
+ "version": "1.0.0",
4
+ "description": "A long-lasting JavaScript toolkit — polyfills, utilities, design patterns, framework internals, algorithms, and data structures",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "types": "src/types/index.d.ts",
8
+ "exports": {
9
+ ".": "./src/index.js",
10
+ "./polyfills": "./src/polyfills/index.js",
11
+ "./utils": "./src/utils/index.js",
12
+ "./patterns": "./src/patterns/index.js",
13
+ "./framework": "./src/framework/index.js",
14
+ "./algorithms": "./src/algorithms/index.js",
15
+ "./data-structures": "./src/data-structures/index.js",
16
+ "./dom": "./src/dom/index.js",
17
+ "./javascript": "./src/javascript/index.js",
18
+ "./types": "./src/types/index.d.ts"
19
+ },
20
+ "files": [
21
+ "src/"
22
+ ],
23
+ "keywords": [
24
+ "javascript",
25
+ "toolkit",
26
+ "polyfills",
27
+ "algorithms",
28
+ "design-patterns",
29
+ "utilities",
30
+ "data-structures",
31
+ "debounce",
32
+ "throttle",
33
+ "deep-clone",
34
+ "promise",
35
+ "sorting",
36
+ "curry"
37
+ ],
38
+ "author": "YiHang",
39
+ "license": "MIT",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/yihang/js-kit.git"
43
+ },
44
+ "packageManager": "pnpm@10.17.1"
45
+ }
@@ -0,0 +1,16 @@
1
+ export { default as twoSum } from "./twoSum.js";
2
+ export { default as threeSum } from "./threeSum.js";
3
+ export { default as myAtoi } from "./myAtoi.js";
4
+ export { default as WordDictionary } from "./wordDictionary.js";
5
+ export { default as removeDuplicateLetters } from "./removeDuplicateLetters.js";
6
+ export { default as removeKdigits } from "./removeKdigits.js";
7
+ export { default as validParentheses } from "./validParentheses.js";
8
+ export {
9
+ quickSort,
10
+ insertionSort,
11
+ selectionSort,
12
+ bubbleSort,
13
+ shellSort,
14
+ mergeSort,
15
+ countingSort,
16
+ } from "./sort.js";
@@ -0,0 +1,19 @@
1
+ // 字符串转整数 (atoi)
2
+ function myAtoi(str) {
3
+ const reg = /\s*([-+]?[0-9]*).*/;
4
+ const groups = str.match(reg);
5
+ const max = Math.pow(2, 31) - 1;
6
+ const min = -max - 1;
7
+
8
+ let result = 0;
9
+ if (groups) {
10
+ result = +groups[1];
11
+ if (isNaN(result)) return 0;
12
+ }
13
+
14
+ if (result > max) return max;
15
+ if (result < min) return min;
16
+ return result;
17
+ }
18
+
19
+ export default myAtoi;
@@ -0,0 +1,29 @@
1
+ // 去除重复字母 - 返回字典序最小的结果
2
+ function removeDuplicateLetters(str) {
3
+ const stack = [];
4
+ const seen = new Set();
5
+ const lastOccurrence = {};
6
+
7
+ for (let i = 0; i < str.length; i++) {
8
+ lastOccurrence[str[i]] = i;
9
+ }
10
+
11
+ for (let i = 0; i < str.length; i++) {
12
+ const char = str[i];
13
+ if (!seen.has(char)) {
14
+ while (
15
+ stack.length > 0 &&
16
+ char < stack[stack.length - 1] &&
17
+ i < lastOccurrence[stack[stack.length - 1]]
18
+ ) {
19
+ seen.delete(stack.pop());
20
+ }
21
+ seen.add(char);
22
+ stack.push(char);
23
+ }
24
+ }
25
+
26
+ return stack.join("");
27
+ }
28
+
29
+ export default removeDuplicateLetters;
@@ -0,0 +1,25 @@
1
+ // 移除 K 位数字 - 使剩余数字最小
2
+ function removeKdigits(num, k) {
3
+ const stack = [];
4
+
5
+ for (let i = 0; i < num.length; i++) {
6
+ while (k > 0 && stack.length && stack[stack.length - 1] > num[i]) {
7
+ stack.pop();
8
+ k--;
9
+ }
10
+ stack.push(num[i]);
11
+ }
12
+
13
+ while (k > 0) {
14
+ stack.pop();
15
+ k--;
16
+ }
17
+
18
+ while (stack[0] === "0") {
19
+ stack.shift();
20
+ }
21
+
22
+ return stack.length ? stack.join("") : "0";
23
+ }
24
+
25
+ export default removeKdigits;
@@ -0,0 +1,117 @@
1
+ // 快速排序
2
+ function quickSort(arr) {
3
+ if (arr.length <= 1) return arr;
4
+ const pivotIndex = Math.floor(arr.length / 2);
5
+ const pivot = arr.splice(pivotIndex, 1)[0];
6
+ const left = [];
7
+ const right = [];
8
+ for (let i = 0; i < arr.length; i++) {
9
+ if (arr[i] < pivot) left.push(arr[i]);
10
+ else right.push(arr[i]);
11
+ }
12
+ return quickSort(left).concat(pivot, quickSort(right));
13
+ }
14
+
15
+ // 插入排序
16
+ function insertionSort(arr) {
17
+ for (let i = 1; i < arr.length; i++) {
18
+ let preIndex = i - 1;
19
+ const current = arr[i];
20
+ while (preIndex >= 0 && arr[preIndex] > current) {
21
+ arr[preIndex + 1] = arr[preIndex];
22
+ preIndex--;
23
+ }
24
+ arr[preIndex + 1] = current;
25
+ }
26
+ return arr;
27
+ }
28
+
29
+ // 选择排序
30
+ function selectionSort(arr) {
31
+ for (let i = 0; i < arr.length - 1; i++) {
32
+ let minIndex = i;
33
+ for (let j = i + 1; j < arr.length; j++) {
34
+ if (arr[j] < arr[minIndex]) minIndex = j;
35
+ }
36
+ if (minIndex !== i) {
37
+ [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
38
+ }
39
+ }
40
+ return arr;
41
+ }
42
+
43
+ // 冒泡排序
44
+ function bubbleSort(arr) {
45
+ for (let i = 0; i < arr.length; i++) {
46
+ let swapped = false;
47
+ for (let j = 0; j < arr.length - i - 1; j++) {
48
+ if (arr[j] > arr[j + 1]) {
49
+ [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
50
+ swapped = true;
51
+ }
52
+ }
53
+ if (!swapped) break;
54
+ }
55
+ return arr;
56
+ }
57
+
58
+ // 希尔排序
59
+ function shellSort(arr) {
60
+ let gap = Math.floor(arr.length / 2);
61
+ while (gap > 0) {
62
+ for (let i = gap; i < arr.length; i++) {
63
+ let j = i;
64
+ while (j >= gap && arr[j - gap] > arr[j]) {
65
+ [arr[j], arr[j - gap]] = [arr[j - gap], arr[j]];
66
+ j -= gap;
67
+ }
68
+ }
69
+ gap = Math.floor(gap / 2);
70
+ }
71
+ return arr;
72
+ }
73
+
74
+ // 归并排序
75
+ function mergeSort(arr) {
76
+ if (arr.length < 2) return arr;
77
+ const mid = Math.floor(arr.length / 2);
78
+ const left = arr.slice(0, mid);
79
+ const right = arr.slice(mid);
80
+ return merge(mergeSort(left), mergeSort(right));
81
+ }
82
+
83
+ function merge(left, right) {
84
+ const result = [];
85
+ while (left.length && right.length) {
86
+ if (left[0] <= right[0]) result.push(left.shift());
87
+ else result.push(right.shift());
88
+ }
89
+ while (left.length) result.push(left.shift());
90
+ while (right.length) result.push(right.shift());
91
+ return result;
92
+ }
93
+
94
+ // 计数排序
95
+ function countingSort(arr, max = Math.max(...arr)) {
96
+ const bucketArr = new Array(max + 1).fill(0);
97
+ for (let i = 0; i < arr.length; i++) {
98
+ bucketArr[arr[i]]++;
99
+ }
100
+ let pointer = 0;
101
+ for (let i = 0; i < bucketArr.length; i++) {
102
+ while (bucketArr[i]-- > 0) {
103
+ arr[pointer++] = i;
104
+ }
105
+ }
106
+ return arr;
107
+ }
108
+
109
+ export {
110
+ quickSort,
111
+ insertionSort,
112
+ selectionSort,
113
+ bubbleSort,
114
+ shellSort,
115
+ mergeSort,
116
+ countingSort,
117
+ };
@@ -0,0 +1,34 @@
1
+ // 三数之和
2
+ function threeSum(nums) {
3
+ const res = [];
4
+ const len = nums.length;
5
+ nums.sort((a, b) => a - b);
6
+
7
+ for (let i = 0; i < len; i++) {
8
+ let left = i + 1,
9
+ right = len - 1;
10
+ const curNum = nums[i];
11
+
12
+ if (curNum > 0) return res;
13
+ if (curNum === nums[i - 1]) continue;
14
+
15
+ while (left < right) {
16
+ const leftNum = nums[left],
17
+ rightNum = nums[right],
18
+ sum = curNum + leftNum + rightNum;
19
+
20
+ if (sum < 0) left++;
21
+ else if (sum > 0) right--;
22
+ else {
23
+ res.push([curNum, leftNum, rightNum]);
24
+ while (left < right && nums[left] === nums[left + 1]) left++;
25
+ while (left < right && nums[right] === nums[right - 1]) right--;
26
+ left++;
27
+ right--;
28
+ }
29
+ }
30
+ }
31
+ return res;
32
+ }
33
+
34
+ export default threeSum;
@@ -0,0 +1,23 @@
1
+ // 两数之和 - 找出所有和为 target 的不重复数对
2
+ function twoSum(nums, target) {
3
+ const map = new Map();
4
+ const result = [];
5
+ const set = new Set();
6
+
7
+ for (let i = 0; i < nums.length; i++) {
8
+ const complement = target - nums[i];
9
+ if (map.has(complement)) {
10
+ const pair = [complement, nums[i]].sort((a, b) => a - b);
11
+ const key = pair.join(",");
12
+ if (!set.has(key)) {
13
+ set.add(key);
14
+ result.push(pair);
15
+ }
16
+ }
17
+ map.set(nums[i], nums[i]);
18
+ }
19
+
20
+ return result;
21
+ }
22
+
23
+ export default twoSum;
@@ -0,0 +1,22 @@
1
+ // 有效的括号
2
+ function isValid(str) {
3
+ const stack = [];
4
+ const map = {
5
+ "(": ")",
6
+ "[": "]",
7
+ "{": "}",
8
+ };
9
+
10
+ for (const char of str) {
11
+ if (map[char]) {
12
+ stack.push(char);
13
+ } else {
14
+ const top = stack.pop();
15
+ if (map[top] !== char) return false;
16
+ }
17
+ }
18
+
19
+ return stack.length === 0;
20
+ }
21
+
22
+ export default isValid;
@@ -0,0 +1,27 @@
1
+ // 单词字典 - 支持通配符 . 的搜索
2
+ class WordDictionary {
3
+ constructor() {
4
+ this.words = {};
5
+ }
6
+
7
+ addWord(word) {
8
+ const len = word.length;
9
+ if (this.words[len]) {
10
+ this.words[len].push(word);
11
+ } else {
12
+ this.words[len] = [word];
13
+ }
14
+ }
15
+
16
+ search(word) {
17
+ const len = word.length;
18
+ if (!this.words[len]) return false;
19
+ if (!word.includes(".")) {
20
+ return this.words[len].includes(word);
21
+ }
22
+ const reg = new RegExp(word);
23
+ return this.words[len].some((element) => reg.test(element));
24
+ }
25
+ }
26
+
27
+ export default WordDictionary;
@@ -0,0 +1 @@
1
+ export { default as Queue } from "./queue.js";
@@ -0,0 +1,46 @@
1
+ class Queue {
2
+ #queue;
3
+ #endIndex;
4
+ #startIndex;
5
+
6
+ constructor() {
7
+ this.#queue = {};
8
+ this.#endIndex = 0;
9
+ this.#startIndex = 0;
10
+ }
11
+
12
+ enqueue(val) {
13
+ this.#queue[this.#endIndex] = val;
14
+ this.#endIndex++;
15
+ return this.#endIndex;
16
+ }
17
+
18
+ dequeue() {
19
+ if (this.isEmpty()) return;
20
+ const val = this.#queue[this.#startIndex];
21
+ delete this.#queue[this.#startIndex];
22
+ this.#startIndex++;
23
+ return val;
24
+ }
25
+
26
+ isEmpty() {
27
+ return this.size() === 0;
28
+ }
29
+
30
+ size() {
31
+ return this.#endIndex - this.#startIndex;
32
+ }
33
+
34
+ clear() {
35
+ this.#queue = {};
36
+ this.#endIndex = 0;
37
+ this.#startIndex = 0;
38
+ }
39
+
40
+ peek() {
41
+ if (this.isEmpty()) return;
42
+ return this.#queue[this.#startIndex];
43
+ }
44
+ }
45
+
46
+ export default Queue;
@@ -0,0 +1 @@
1
+ export { default as traverseElRoot } from "./traverseEl.js";
@@ -0,0 +1,19 @@
1
+ // 遍历 DOM 树,按层级返回标签名
2
+ function traverseElRoot(elRoot) {
3
+ const result = [];
4
+
5
+ function traverse(element, level = 0) {
6
+ if (!result[level]) {
7
+ result[level] = [];
8
+ }
9
+ result[level].push(element.tagName);
10
+ Array.from(element.children).forEach((child) => {
11
+ traverse(child, level + 1);
12
+ });
13
+ }
14
+
15
+ traverse(elRoot);
16
+ return result;
17
+ }
18
+
19
+ export default traverseElRoot;
@@ -0,0 +1,25 @@
1
+ class HashRouter {
2
+ constructor(routes) {
3
+ this.routes = routes;
4
+ this.routeView = null;
5
+ this._init();
6
+ }
7
+
8
+ _init() {
9
+ window.addEventListener("DOMContentLoaded", () => this._onRouteChange());
10
+ window.addEventListener("hashchange", () => this._onRouteChange());
11
+ }
12
+
13
+ _onRouteChange() {
14
+ if (!this.routeView) {
15
+ this.routeView = document.getElementById("route-view");
16
+ }
17
+ const hash = location.hash || "#/";
18
+ const route = this.routes[hash] || this.routes["#/"];
19
+ if (route && this.routeView) {
20
+ this.routeView.innerHTML = route;
21
+ }
22
+ }
23
+ }
24
+
25
+ export default HashRouter;
@@ -0,0 +1,35 @@
1
+ class HistoryRouter {
2
+ constructor(routes) {
3
+ this.routes = routes;
4
+ this.routeView = null;
5
+ this._init();
6
+ }
7
+
8
+ _init() {
9
+ window.addEventListener("DOMContentLoaded", () => this._onRouteChange());
10
+ window.addEventListener("popstate", () => this._onRouteChange());
11
+
12
+ document.addEventListener("click", (e) => {
13
+ const target = e.target.closest("a[href]");
14
+ if (target) {
15
+ e.preventDefault();
16
+ const href = target.getAttribute("href");
17
+ history.pushState(null, "", href);
18
+ this._onRouteChange();
19
+ }
20
+ });
21
+ }
22
+
23
+ _onRouteChange() {
24
+ if (!this.routeView) {
25
+ this.routeView = document.getElementById("route-view");
26
+ }
27
+ const path = location.pathname;
28
+ const route = this.routes[path] || this.routes["/"];
29
+ if (route && this.routeView) {
30
+ this.routeView.innerHTML = route;
31
+ }
32
+ }
33
+ }
34
+
35
+ export default HistoryRouter;
@@ -0,0 +1,4 @@
1
+ export { reactive, effect } from "./reactive.js";
2
+ export { h, mount } from "./vdom.js";
3
+ export { default as HashRouter } from "./hashRouter.js";
4
+ export { default as HistoryRouter } from "./historyRouter.js";
@@ -0,0 +1,56 @@
1
+ const bucket = new WeakMap();
2
+ let activeEffect = null;
3
+
4
+ function isObject(value) {
5
+ return typeof value === "object" && value !== null;
6
+ }
7
+
8
+ function effect(fn) {
9
+ if (typeof fn !== "function") return;
10
+ activeEffect = fn;
11
+ fn();
12
+ activeEffect = null;
13
+ }
14
+
15
+ function track(target, key) {
16
+ if (!activeEffect) return;
17
+ let depMap = bucket.get(target);
18
+ if (!depMap) {
19
+ depMap = new Map();
20
+ bucket.set(target, depMap);
21
+ }
22
+ let depSet = depMap.get(key);
23
+ if (!depSet) {
24
+ depSet = new Set();
25
+ depMap.set(key, depSet);
26
+ }
27
+ depSet.add(activeEffect);
28
+ }
29
+
30
+ function trigger(target, key) {
31
+ const depMap = bucket.get(target);
32
+ if (!depMap) return;
33
+ const depSet = depMap.get(key);
34
+ if (depSet) {
35
+ depSet.forEach((fn) => fn());
36
+ }
37
+ }
38
+
39
+ function reactive(data) {
40
+ if (!isObject(data)) return data;
41
+
42
+ return new Proxy(data, {
43
+ get(target, key) {
44
+ track(target, key);
45
+ const result = target[key];
46
+ return isObject(result) ? reactive(result) : result;
47
+ },
48
+ set(target, key, value) {
49
+ target[key] = value;
50
+ trigger(target, key);
51
+ return true;
52
+ },
53
+ });
54
+ }
55
+
56
+ export { reactive, effect };
@@ -0,0 +1,37 @@
1
+ function h(tag, props, children) {
2
+ return { tag, props, children };
3
+ }
4
+
5
+ function mount(vnode, container) {
6
+ if (typeof container === "string") {
7
+ container = document.getElementById(container);
8
+ }
9
+
10
+ const el = document.createElement(vnode.tag);
11
+ vnode.el = el;
12
+
13
+ if (vnode.props) {
14
+ for (const key in vnode.props) {
15
+ if (key.startsWith("on") && typeof vnode.props[key] === "function") {
16
+ const eventType = key.slice(2).toLowerCase();
17
+ el.addEventListener(eventType, vnode.props[key]);
18
+ continue;
19
+ }
20
+ el.setAttribute(key, vnode.props[key]);
21
+ }
22
+ }
23
+
24
+ if (vnode.children) {
25
+ if (typeof vnode.children === "string") {
26
+ el.textContent = vnode.children;
27
+ } else if (Array.isArray(vnode.children)) {
28
+ vnode.children.forEach((child) => {
29
+ mount(child, el);
30
+ });
31
+ }
32
+ }
33
+
34
+ container.appendChild(el);
35
+ }
36
+
37
+ export { h, mount };