@codady/utils 0.0.9 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/dist/utils.cjs.js +488 -28
- package/dist/utils.cjs.min.js +3 -3
- package/dist/utils.esm.js +488 -28
- package/dist/utils.esm.min.js +3 -3
- package/dist/utils.umd - /345/211/257/346/234/254.js" +749 -0
- package/dist/utils.umd.js +490 -32
- package/dist/utils.umd.min.js +3 -3
- package/dist.zip +0 -0
- package/modules.js +24 -4
- package/modules.ts +24 -4
- package/package.json +1 -1
- package/src/arrayMutableMethods - /345/211/257/346/234/254.js" +5 -0
- package/src/arrayMutableMethods.js +5 -0
- package/src/{mutableMethods.ts → arrayMutableMethods.ts} +3 -3
- package/src/deepClone.js +151 -26
- package/src/deepClone.ts +194 -35
- package/src/deepCloneToJSON - /345/211/257/346/234/254.js" +47 -0
- package/src/deepEqual.js +48 -0
- package/src/deepEqual.ts +46 -0
- package/src/deepMerge.js +34 -0
- package/src/deepMerge.ts +40 -0
- package/src/deepMergeArrays.js +45 -0
- package/src/deepMergeArrays.ts +62 -0
- package/src/deepMergeHelper.js +40 -0
- package/src/deepMergeHelper.ts +45 -0
- package/src/deepMergeMaps - /345/211/257/346/234/254.js" +78 -0
- package/src/deepMergeMaps.js +57 -0
- package/src/deepMergeMaps.ts +67 -0
- package/src/deepMergeObjects.js +82 -0
- package/src/deepMergeObjects.ts +85 -0
- package/src/deepMergeSets.js +48 -0
- package/src/deepMergeSets.ts +55 -0
- package/src/getUniqueId.js +11 -7
- package/src/getUniqueId.ts +16 -9
- package/src/mapMutableMethods.js +5 -0
- package/src/mapMutableMethods.ts +15 -0
- package/src/mutableMethods.js +2 -2
- package/src/setMutableMethods - /345/211/257/346/234/254.js" +5 -0
- package/src/setMutableMethods.js +5 -0
- package/src/setMutableMethods.ts +14 -0
- package/src/wrapArrayMethods.js +5 -5
- package/src/wrapArrayMethods.ts +7 -7
- package/src/wrapMap - /345/211/257/346/234/254.js" +119 -0
- package/src/wrapMapMethods.js +118 -0
- package/src/wrapMapMethods.ts +226 -0
- package/src/wrapSetMethods.js +112 -0
- package/src/wrapSetMethods.ts +215 -0
|
@@ -0,0 +1,749 @@
|
|
|
1
|
+
|
|
2
|
+
/*!
|
|
3
|
+
* @since Last modified: 2025-12-24 17:11:26
|
|
4
|
+
* @name Utils for web front-end.
|
|
5
|
+
* @version 0.0.10
|
|
6
|
+
* @author AXUI development team <3217728223@qq.com>
|
|
7
|
+
* @description This is a set of general-purpose JavaScript utility functions developed by the AXUI team. All functions are pure and do not involve CSS or other third-party libraries. They are suitable for any web front-end environment.
|
|
8
|
+
* @see {@link https://www.axui.cn|Official website}
|
|
9
|
+
* @see {@link https://github.com/codady/utils/issues|github issues}
|
|
10
|
+
* @see {@link https://gitee.com/codady/utils/issues|Gitee issues}
|
|
11
|
+
* @see {@link https://www.npmjs.com/package/@codady/utils|NPM}
|
|
12
|
+
* @issue QQ Group No.1:952502085
|
|
13
|
+
* @copyright This software supports the MIT License, allowing free learning and commercial use, but please retain the terms 'ax,' 'axui,' 'AX,' and 'AXUI' within the software.
|
|
14
|
+
* @license MIT license
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
(function (global, factory) {
|
|
18
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('assert')) :
|
|
19
|
+
typeof define === 'function' && define.amd ? define(['assert'], factory) :
|
|
20
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.utils = factory(global.assert));
|
|
21
|
+
})(this, (function (assert) { 'use strict';
|
|
22
|
+
|
|
23
|
+
const getDataType = (obj) => {
|
|
24
|
+
let tmp = Object.prototype.toString.call(obj).slice(8, -1), result;
|
|
25
|
+
if (tmp === 'Function' && /^\s*class\s+/.test(obj.toString())) {
|
|
26
|
+
result = 'Class';
|
|
27
|
+
}
|
|
28
|
+
else if (tmp === 'Object' && Object.getPrototypeOf(obj) !== Object.prototype) {
|
|
29
|
+
result = 'Instance';
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
result = tmp;
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
//document.createElement -> HTMLxxxElement
|
|
36
|
+
//document.createDocumentFragment() -> DocumentFragment
|
|
37
|
+
//document.createComment() -> Comment
|
|
38
|
+
//document.createTextNode -> Text
|
|
39
|
+
//document.createCDATASection() -> XMLDocument
|
|
40
|
+
//document.createProcessingInstruction() -> ProcessingInstruction
|
|
41
|
+
//document.createRange() -> Range
|
|
42
|
+
//document.createTreeWalker() -> TreeWalker
|
|
43
|
+
//document.createNodeIterator() -> NodeIterator
|
|
44
|
+
//document.createElementNS('http://www.w3.org/2000/svg', 'svg'); -> SVGSVGElement
|
|
45
|
+
//document.createElementNS('http://www.w3.org/1998/Math/MathML', 'math'); -> MathMLElement
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
//支持原始值的复制,包括Number、String、Boolean、Null
|
|
49
|
+
//支持Date和Regex对象值复制(值转换)
|
|
50
|
+
//支持{},[],Set和Map的遍历
|
|
51
|
+
const deepClone = (data, options = {}) => {
|
|
52
|
+
const dataType = getDataType(data),
|
|
53
|
+
// Default options
|
|
54
|
+
opts = Object.assign({
|
|
55
|
+
cloneSet: true,
|
|
56
|
+
cloneMap: true,
|
|
57
|
+
cloneObject: true,
|
|
58
|
+
cloneArray: true,
|
|
59
|
+
//可重新构建
|
|
60
|
+
cloneDate: true,
|
|
61
|
+
cloneRegex: true,
|
|
62
|
+
//节点默认不复制
|
|
63
|
+
//cloneElement: false,
|
|
64
|
+
//cloneFragment: false,
|
|
65
|
+
}, options);
|
|
66
|
+
// Check interceptor - if it returns a value (not null/undefined), use it directly
|
|
67
|
+
if (opts.interceptor && typeof opts.interceptor === 'function') {
|
|
68
|
+
let result = opts.interceptor(data, dataType);
|
|
69
|
+
if ((result ?? false)) {
|
|
70
|
+
// Call onAfterClone if set
|
|
71
|
+
opts.onAfterClone?.({
|
|
72
|
+
output: result,
|
|
73
|
+
input: data,
|
|
74
|
+
type: dataType,
|
|
75
|
+
cloned: result !== data,
|
|
76
|
+
});
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
// If interceptor returns null/undefined, continue with normal cloning process
|
|
80
|
+
}
|
|
81
|
+
// Callback before cloning
|
|
82
|
+
opts.onBeforeClone?.(data, dataType);
|
|
83
|
+
let newData, cloned = true;
|
|
84
|
+
if (dataType === 'Object' && opts.cloneObject) {
|
|
85
|
+
const newObj = {}, symbols = Object.getOwnPropertySymbols(data);
|
|
86
|
+
// Clone regular properties
|
|
87
|
+
for (const key in data) {
|
|
88
|
+
newObj[key] = deepClone(data[key], opts);
|
|
89
|
+
}
|
|
90
|
+
// Clone Symbol properties
|
|
91
|
+
if (symbols.length > 0) {
|
|
92
|
+
for (const symbol of symbols) {
|
|
93
|
+
newObj[symbol] = deepClone(data[symbol], opts);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
newData = newObj;
|
|
97
|
+
}
|
|
98
|
+
else if (dataType === 'Array' && opts.cloneArray) {
|
|
99
|
+
newData = data.map(item => deepClone(item, opts));
|
|
100
|
+
}
|
|
101
|
+
else if (dataType === 'Map' && opts.cloneMap) {
|
|
102
|
+
const newMap = new Map();
|
|
103
|
+
for (const [key, value] of data) {
|
|
104
|
+
// Both Map keys and values need deep cloning
|
|
105
|
+
newMap.set(deepClone(key, opts), deepClone(value, opts));
|
|
106
|
+
}
|
|
107
|
+
newData = newMap;
|
|
108
|
+
}
|
|
109
|
+
else if (dataType === 'Set' && opts.cloneSet) {
|
|
110
|
+
const newSet = new Set();
|
|
111
|
+
for (const value of data) {
|
|
112
|
+
// Set values need deep cloning
|
|
113
|
+
newSet.add(deepClone(value, opts));
|
|
114
|
+
}
|
|
115
|
+
newData = newSet;
|
|
116
|
+
}
|
|
117
|
+
else if (dataType === 'Date' && opts.cloneDate) {
|
|
118
|
+
newData = new Date(data.getTime());
|
|
119
|
+
}
|
|
120
|
+
else if (dataType === 'RegExp' && opts.cloneRegex) {
|
|
121
|
+
const regex = data;
|
|
122
|
+
newData = new RegExp(regex.source, regex.flags);
|
|
123
|
+
// } else if ((dataType.includes('HTML') && opts.cloneElement) ||
|
|
124
|
+
// (dataType === 'DocumentFragment' && opts.cloneFragment)
|
|
125
|
+
//) {
|
|
126
|
+
//Text,Comment,HTML*Element,DocumentFragment,Attr
|
|
127
|
+
// newData = (data as any).cloneNode(true) as T;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
// Number, String, Boolean, Symbol, Function,Error,Promise,ArrayBuffer,Blob,File, return directly
|
|
131
|
+
newData = data;
|
|
132
|
+
cloned = false;
|
|
133
|
+
}
|
|
134
|
+
// Callback after cloning
|
|
135
|
+
opts.onAfterClone?.({
|
|
136
|
+
output: newData,
|
|
137
|
+
input: data,
|
|
138
|
+
type: dataType,
|
|
139
|
+
cloned,
|
|
140
|
+
});
|
|
141
|
+
return newData;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const deepCloneToJSON = (data) => {
|
|
145
|
+
const dataType = getDataType(data);
|
|
146
|
+
// Handle objects
|
|
147
|
+
if (dataType === 'Object') {
|
|
148
|
+
const newObj = {};
|
|
149
|
+
// Clone regular properties
|
|
150
|
+
for (const key in data) {
|
|
151
|
+
newObj[key] = deepCloneToJSON(data[key]);
|
|
152
|
+
}
|
|
153
|
+
for (const key in newObj) {
|
|
154
|
+
newObj[key] === undefined && Reflect.deleteProperty(newObj, key);
|
|
155
|
+
}
|
|
156
|
+
return newObj;
|
|
157
|
+
// Handle arrays
|
|
158
|
+
}
|
|
159
|
+
else if (dataType === 'Array') {
|
|
160
|
+
let tmp = data.map((item, index) => deepCloneToJSON(item)).filter(item => item !== undefined);
|
|
161
|
+
return tmp;
|
|
162
|
+
// Handle Date objects
|
|
163
|
+
}
|
|
164
|
+
else if (!['Number', 'String', 'Boolean', 'Null'].includes(dataType)) {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
return data;
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const arrayMutableMethods = [
|
|
173
|
+
'push', 'pop', 'shift', 'unshift', 'splice',
|
|
174
|
+
'sort', 'reverse', 'copyWithin', 'fill'
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
const wrapArrayMethods = ({ target, onBeforeMutate = () => { }, onAfterMutate = () => { }, allowList, props = {}, }) => {
|
|
178
|
+
// Validation: Ensure target is an array and method is allowed
|
|
179
|
+
if (!Array.isArray(target)) {
|
|
180
|
+
throw new TypeError("The 'target' parameter must be an array.");
|
|
181
|
+
}
|
|
182
|
+
//使用默认
|
|
183
|
+
if (!allowList || allowList?.length) {
|
|
184
|
+
allowList = arrayMutableMethods;
|
|
185
|
+
}
|
|
186
|
+
const methods = {};
|
|
187
|
+
for (let method of allowList) {
|
|
188
|
+
methods[method] = function (...args) {
|
|
189
|
+
const context = {}, len = target.length;
|
|
190
|
+
// Capture "Pre-mutation" context for Undo/Redo/Tracking purposes
|
|
191
|
+
switch (method) {
|
|
192
|
+
// 'push' and 'unshift' are add operations, in undo/redo,
|
|
193
|
+
// we can determine the original data structure from result.length and args.length
|
|
194
|
+
// but methods that involve deletion need to record the deleted items to ensure the patch can restore the data structure during undo
|
|
195
|
+
case 'push':
|
|
196
|
+
case 'unshift':
|
|
197
|
+
context.addedItems = [...args];
|
|
198
|
+
break;
|
|
199
|
+
case 'pop':
|
|
200
|
+
context.poppedItem = target[len - 1];
|
|
201
|
+
break;
|
|
202
|
+
case 'shift':
|
|
203
|
+
context.shiftedItem = target[0];
|
|
204
|
+
break;
|
|
205
|
+
case 'splice':
|
|
206
|
+
const [s, d] = args,
|
|
207
|
+
// Calculate actual start index (handling negative values)
|
|
208
|
+
start = s < 0 ? Math.max(len + s, 0) : Math.min(s, len),
|
|
209
|
+
// Handle deleteCount defaults
|
|
210
|
+
deleteCount = d === undefined ? len - start : d;
|
|
211
|
+
context.deletedItems = target.slice(start, start + deleteCount);
|
|
212
|
+
break;
|
|
213
|
+
case 'sort':
|
|
214
|
+
case 'reverse':
|
|
215
|
+
// These methods reorder the whole array; requires a full shallow copy
|
|
216
|
+
context.oldSnapshot = [...target];
|
|
217
|
+
break;
|
|
218
|
+
case 'fill':
|
|
219
|
+
case 'copyWithin':
|
|
220
|
+
const startIdx = args[1] || 0, endIdx = args[2] === undefined ? len : args[2];
|
|
221
|
+
// Overwritten values
|
|
222
|
+
context.oldItems = target.slice(startIdx, endIdx);
|
|
223
|
+
context.start = startIdx;
|
|
224
|
+
context.end = endIdx;
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
// Execute the "onBeforeMutate" callback before mutation
|
|
228
|
+
onBeforeMutate?.(context);
|
|
229
|
+
// Execute the native array mutation
|
|
230
|
+
const value = Array.prototype[method].apply(target, args), patch = {
|
|
231
|
+
value,
|
|
232
|
+
key: method,
|
|
233
|
+
args,
|
|
234
|
+
context,
|
|
235
|
+
target,
|
|
236
|
+
...props
|
|
237
|
+
};
|
|
238
|
+
// Construct and trigger the patch callback
|
|
239
|
+
onAfterMutate?.(patch);
|
|
240
|
+
return value;
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
return methods;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const requireTypes = (data, require, cb) => {
|
|
247
|
+
// Normalize the input types (convert to array if it's a single type)
|
|
248
|
+
let requiredTypes = Array.isArray(require) ? require : [require], dataType = getDataType(data), typeLower = dataType.toLowerCase(),
|
|
249
|
+
// Normalize the type names (to lower case)
|
|
250
|
+
normalizedTypes = requiredTypes.map((type) => type.toLowerCase()),
|
|
251
|
+
// Check if the type is an HTML element (more specific than just 'html')
|
|
252
|
+
normalizedDataType = typeLower.includes('html') ? 'element' : typeLower;
|
|
253
|
+
// If callback exists, handle error through callback
|
|
254
|
+
if (cb) {
|
|
255
|
+
try {
|
|
256
|
+
if (!normalizedTypes.includes(normalizedDataType)) {
|
|
257
|
+
throw new TypeError(`Expected data type(s): [${normalizedTypes.join(', ')}], but got: ${normalizedDataType}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
cb(error, dataType);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
// If no callback is provided, throw an error directly
|
|
266
|
+
if (!normalizedTypes.includes(normalizedDataType)) {
|
|
267
|
+
throw new TypeError(`Expected data type(s): [${normalizedTypes.join(', ')}], but got: ${normalizedDataType}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return dataType;
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const getUniqueId = (options = {}) => {
|
|
274
|
+
const prefix = options.prefix, suffix = options.suffix, base10 = options.base10, base36 = options.base36;
|
|
275
|
+
// Current timestamp in milliseconds (since Unix epoch)
|
|
276
|
+
// This provides the primary uniqueness guarantee
|
|
277
|
+
const timestamp = Date.now(),
|
|
278
|
+
// Generate a base-36 random string (0-9, a-z)
|
|
279
|
+
// Math.random() returns a number in [0, 1), converting to base-36 gives a compact representation
|
|
280
|
+
// substring(2, 11) extracts 9 characters starting from index 2
|
|
281
|
+
//0.259854635->0.9crs03e8v2
|
|
282
|
+
base36Random = base36 ? '-' + Math.random().toString(36).substring(2, 11) : '',
|
|
283
|
+
// Additional 4-digit random number for extra randomness
|
|
284
|
+
// This helps avoid collisions in high-frequency generation scenarios
|
|
285
|
+
base10Random = base10 ? '-' + Math.floor(Math.random() * 10000).toString().padStart(4, '0') : '', prefixString = prefix ? prefix + '-' : '', suffixString = suffix ? '-' + suffix : '';
|
|
286
|
+
// Construct the final ID string
|
|
287
|
+
// Format: [prefix_]timestamp_randomBase36_extraRandom
|
|
288
|
+
return `${prefixString}${timestamp}${base36Random}${base10Random}${suffixString}`;
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const setMutableMethods = ['add', 'delete', 'clear'];
|
|
292
|
+
|
|
293
|
+
const mapMutableMethods = ['set', 'delete', 'clear'];
|
|
294
|
+
|
|
295
|
+
const wrapSetMethods = ({ target, onBeforeMutate = () => { }, onAfterMutate = () => { }, allowList = setMutableMethods, props = {}, }) => {
|
|
296
|
+
// Validation: Ensure target is a Set
|
|
297
|
+
if (!(target instanceof Set)) {
|
|
298
|
+
throw new TypeError("The 'target' parameter must be a Set.");
|
|
299
|
+
}
|
|
300
|
+
// Create method wrappers
|
|
301
|
+
const methods = {};
|
|
302
|
+
// Helper to create wrapped method
|
|
303
|
+
const createWrappedMethod = (method) => {
|
|
304
|
+
return function (...args) {
|
|
305
|
+
const context = {};
|
|
306
|
+
// Capture pre-mutation context based on method
|
|
307
|
+
switch (method) {
|
|
308
|
+
case 'add': {
|
|
309
|
+
const [value] = args;
|
|
310
|
+
context.addedItem = value;
|
|
311
|
+
//context.existed=true,说明值重复
|
|
312
|
+
context.existed = target.has(value);
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
case 'delete': {
|
|
316
|
+
const [value] = args;
|
|
317
|
+
context.existed = target.has(value);
|
|
318
|
+
context.deletedItem = context.existed ? value : undefined;
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
case 'clear': {
|
|
322
|
+
context.clearedItems = Array.from(target);
|
|
323
|
+
//用来做验证
|
|
324
|
+
context.previousSize = target.size;
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
// Execute before mutation callback
|
|
329
|
+
onBeforeMutate(context);
|
|
330
|
+
// Execute the native Set method
|
|
331
|
+
const result = target[method].apply(target, args);
|
|
332
|
+
// Construct patch object
|
|
333
|
+
const patch = {
|
|
334
|
+
method,
|
|
335
|
+
result,
|
|
336
|
+
args,
|
|
337
|
+
context,
|
|
338
|
+
target,
|
|
339
|
+
...props
|
|
340
|
+
};
|
|
341
|
+
// Execute after mutation callback
|
|
342
|
+
onAfterMutate(patch);
|
|
343
|
+
return result;
|
|
344
|
+
};
|
|
345
|
+
};
|
|
346
|
+
// Wrap allowed methods
|
|
347
|
+
for (const method of allowList) {
|
|
348
|
+
if (setMutableMethods.includes(method)) {
|
|
349
|
+
methods[method] = createWrappedMethod(method);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
// Add target reference
|
|
353
|
+
Object.defineProperty(methods, 'target', {
|
|
354
|
+
get: () => target,
|
|
355
|
+
enumerable: false,
|
|
356
|
+
configurable: false
|
|
357
|
+
});
|
|
358
|
+
return methods;
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const wrapMapMethods = ({ target, onBeforeMutate = () => { }, onAfterMutate = () => { }, allowList = mapMutableMethods, props = {}, }) => {
|
|
362
|
+
// Validation: Ensure target is a Map
|
|
363
|
+
if (!(target instanceof Map)) {
|
|
364
|
+
throw new TypeError("The 'target' parameter must be a Map.");
|
|
365
|
+
}
|
|
366
|
+
// Create method wrappers
|
|
367
|
+
const methods = {};
|
|
368
|
+
// Helper to create wrapped method
|
|
369
|
+
const createWrappedMethod = (method) => {
|
|
370
|
+
return function (...args) {
|
|
371
|
+
const context = {};
|
|
372
|
+
// Capture pre-mutation context based on method
|
|
373
|
+
switch (method) {
|
|
374
|
+
case 'set': {
|
|
375
|
+
const [key, newValue] = args;
|
|
376
|
+
context.key = key;
|
|
377
|
+
context.newValue = newValue;
|
|
378
|
+
context.existed = target.has(key);
|
|
379
|
+
context.oldValue = context.existed ? target.get(key) : undefined;
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
382
|
+
case 'delete': {
|
|
383
|
+
const [key] = args;
|
|
384
|
+
context.key = key;
|
|
385
|
+
context.existed = target.has(key);
|
|
386
|
+
context.value = context.existed ? target.get(key) : undefined;
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
case 'clear': {
|
|
390
|
+
context.clearedItems = Array.from(target.entries());
|
|
391
|
+
//用来做验证
|
|
392
|
+
context.previousSize = target.size;
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
// Execute before mutation callback
|
|
397
|
+
onBeforeMutate(context);
|
|
398
|
+
// Execute the native Map method
|
|
399
|
+
const result = target[method].apply(target, args);
|
|
400
|
+
// Construct patch object
|
|
401
|
+
const patch = {
|
|
402
|
+
method,
|
|
403
|
+
result,
|
|
404
|
+
args,
|
|
405
|
+
context,
|
|
406
|
+
target,
|
|
407
|
+
...props
|
|
408
|
+
};
|
|
409
|
+
// Execute after mutation callback
|
|
410
|
+
onAfterMutate(patch);
|
|
411
|
+
return result;
|
|
412
|
+
};
|
|
413
|
+
};
|
|
414
|
+
// Wrap allowed methods
|
|
415
|
+
for (const method of allowList) {
|
|
416
|
+
if (mapMutableMethods.includes(method)) {
|
|
417
|
+
methods[method] = createWrappedMethod(method);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
// Add target reference
|
|
421
|
+
Object.defineProperty(methods, 'target', {
|
|
422
|
+
get: () => target,
|
|
423
|
+
enumerable: false,
|
|
424
|
+
configurable: false
|
|
425
|
+
});
|
|
426
|
+
return methods;
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
const deepEqual = (a, b) => {
|
|
430
|
+
// If both are equal by reference
|
|
431
|
+
if (a === b)
|
|
432
|
+
return true;
|
|
433
|
+
// If both are arrays, check equality recursively
|
|
434
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
435
|
+
if (a.length !== b.length)
|
|
436
|
+
return false;
|
|
437
|
+
for (let i = 0; i < a.length; i++) {
|
|
438
|
+
if (!deepEqual(a[i], b[i]))
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
// If both are objects, check equality recursively
|
|
444
|
+
if (typeof a === 'object' && typeof b === 'object') {
|
|
445
|
+
const keysA = Object.keys(a), keysB = Object.keys(b);
|
|
446
|
+
if (keysA.length !== keysB.length)
|
|
447
|
+
return false;
|
|
448
|
+
for (let key of keysA) {
|
|
449
|
+
if (!keysB.includes(key) || !deepEqual(a[key], b[key]))
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
return true;
|
|
453
|
+
}
|
|
454
|
+
// For other types, direct comparison
|
|
455
|
+
return a === b;
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
const deepMergeMaps = (target, source, options = { itemMode: 'merge', propAppend: true, targetClone: false, useEnable: true }) => {
|
|
459
|
+
// Ensure both target and source are Maps
|
|
460
|
+
if (!(target instanceof Map) || !(source instanceof Map))
|
|
461
|
+
return target;
|
|
462
|
+
// Merge options, with default values
|
|
463
|
+
const opts = Object.assign({ itemMode: 'merge', propAppend: true, targetClone: false, useEnable: true }, options),
|
|
464
|
+
// If cloning is enabled, create a deep copy of the target Map
|
|
465
|
+
result = opts.targetClone ? new Map(target) : target;
|
|
466
|
+
// Handle different merge strategies based on itemMode
|
|
467
|
+
if (opts.itemMode === 'replace') {
|
|
468
|
+
// Replace mode: clear the target Map and add all entries from the source Map
|
|
469
|
+
result.clear();
|
|
470
|
+
source.forEach((value, key) => result.set(key, value));
|
|
471
|
+
return result;
|
|
472
|
+
}
|
|
473
|
+
else if (opts.itemMode === 'concat') {
|
|
474
|
+
// Concatenate mode: add all entries from the source Map to the target Map
|
|
475
|
+
source.forEach((value, key) => result.set(key, value));
|
|
476
|
+
return result;
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
// Default "merge" mode: recursively merge entries in the Maps
|
|
480
|
+
source.forEach((value, key) => {
|
|
481
|
+
// Check if the key already exists in the target Map
|
|
482
|
+
if (result.has(key)) {
|
|
483
|
+
const targetValue = result.get(key), sourceValue = value;
|
|
484
|
+
const targetType = getDataType(targetValue), sourceType = getDataType(sourceValue);
|
|
485
|
+
// If both target and source values are of the same type, merge them
|
|
486
|
+
if (targetType === sourceType) {
|
|
487
|
+
if (targetType === 'Object') {
|
|
488
|
+
// If both target and source are objects, merge them recursively
|
|
489
|
+
result.set(key, deepMergeObjects(targetValue, sourceValue, opts));
|
|
490
|
+
}
|
|
491
|
+
else if (targetType === 'Array') {
|
|
492
|
+
// If both target and source are arrays, merge them recursively
|
|
493
|
+
deepMergeArrays(targetValue, sourceValue, opts);
|
|
494
|
+
}
|
|
495
|
+
else if (targetType === 'Map') {
|
|
496
|
+
// If both target and source are Maps, merge them recursively
|
|
497
|
+
deepMergeMaps(targetValue, sourceValue, opts);
|
|
498
|
+
}
|
|
499
|
+
else if (targetType === 'Set') {
|
|
500
|
+
// If both target and source are Sets, merge them recursively
|
|
501
|
+
deepMergeSets(targetValue, sourceValue, opts);
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
// For simple values, overwrite the target value with the source value
|
|
505
|
+
result.set(key, sourceValue);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
// If types don't match, overwrite the target value with the source value
|
|
510
|
+
result.set(key, sourceValue);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
else {
|
|
514
|
+
// If the key doesn't exist in the target, add the entry from the source Map
|
|
515
|
+
result.set(key, value);
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
return result;
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
const deepMergeSets = (target, source, options = { itemMode: 'merge', propAppend: true, targetClone: false, useEnable: true }) => {
|
|
523
|
+
// Ensure both target and source are Sets
|
|
524
|
+
if (!(target instanceof Set) || !(source instanceof Set))
|
|
525
|
+
return target;
|
|
526
|
+
// Merge options, with default values
|
|
527
|
+
const opts = Object.assign({ itemMode: 'merge', propAppend: true, targetClone: false, useEnable: true }, options),
|
|
528
|
+
// If cloning is enabled, create a deep copy of the target Set
|
|
529
|
+
result = opts.targetClone ? new Set(target) : target;
|
|
530
|
+
// Handle different merge strategies based on itemMode
|
|
531
|
+
if (opts.itemMode === 'replace') {
|
|
532
|
+
// Replace mode: clear the target Set and add all items from the source Set
|
|
533
|
+
result.clear();
|
|
534
|
+
for (let item of source)
|
|
535
|
+
result.add(item);
|
|
536
|
+
return result;
|
|
537
|
+
}
|
|
538
|
+
else if (opts.itemMode === 'concat') {
|
|
539
|
+
// Concatenate mode: add all items from the source Set to the target Set
|
|
540
|
+
for (let item of source)
|
|
541
|
+
result.add(item);
|
|
542
|
+
return result;
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
// Default "merge" mode: recursively merge items in the Sets
|
|
546
|
+
for (let item of source) {
|
|
547
|
+
// Check the type of the target and source items
|
|
548
|
+
const _target = [...result].find(val => deepEqual(val, item)), _targetType = getDataType(_target), // Find existing value in the target Set
|
|
549
|
+
_sourceType = getDataType(item);
|
|
550
|
+
// If both target and source are arrays, merge them recursively
|
|
551
|
+
if (_targetType === 'Array' && _sourceType === 'Array') {
|
|
552
|
+
deepMergeArrays(_target, item, opts);
|
|
553
|
+
// If both target and source are objects, merge them recursively
|
|
554
|
+
}
|
|
555
|
+
else if (_targetType === 'Object' && _sourceType === 'Object') {
|
|
556
|
+
deepMergeObjects(_target, item, opts);
|
|
557
|
+
// If both target and source are Maps, merge them recursively
|
|
558
|
+
}
|
|
559
|
+
else if (_targetType === 'Map' && _sourceType === 'Map') {
|
|
560
|
+
deepMergeMaps(_target, item, opts);
|
|
561
|
+
// If both target and source are Sets, merge them recursively
|
|
562
|
+
}
|
|
563
|
+
else if (_targetType === 'Set' && _sourceType === 'Set') {
|
|
564
|
+
deepMergeSets(_target, item, opts);
|
|
565
|
+
// If the items are simple values, add them directly
|
|
566
|
+
}
|
|
567
|
+
else {
|
|
568
|
+
result.add(item);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
return result;
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
const deepMergeArrays = (target, source, options = { itemMode: 'merge', propAppend: true, targetClone: false, useEnable: true }) => {
|
|
576
|
+
// Ensure both target and source are arrays
|
|
577
|
+
if (!Array.isArray(target) || !Array.isArray(source))
|
|
578
|
+
return target;
|
|
579
|
+
// Merge options, with default values
|
|
580
|
+
const opts = Object.assign({ itemMode: 'merge', propAppend: true, targetClone: false }, options),
|
|
581
|
+
// If cloning is enabled, create a deep copy of the target array
|
|
582
|
+
result = opts.targetClone ? [...target] : target;
|
|
583
|
+
// Handle different merge strategies based on itemMode
|
|
584
|
+
if (opts.itemMode === 'replace') {
|
|
585
|
+
// Replace mode: clear the target array and push all items from the source array
|
|
586
|
+
result.length = 0;
|
|
587
|
+
result.push(...source);
|
|
588
|
+
return result;
|
|
589
|
+
}
|
|
590
|
+
else if (opts.itemMode === 'concat') {
|
|
591
|
+
// Concatenate mode: append all items from the source array to the target array
|
|
592
|
+
result.push(...source);
|
|
593
|
+
return result;
|
|
594
|
+
}
|
|
595
|
+
else {
|
|
596
|
+
// Default "merge" mode: recursively merge items in the arrays
|
|
597
|
+
for (let i = 0; i < source.length; i++) {
|
|
598
|
+
const _targetType = getDataType(result[i]), _sourceType = getDataType(source[i]);
|
|
599
|
+
// If both target and source are arrays, merge them recursively
|
|
600
|
+
if (_targetType === 'Array' && _sourceType === 'Array') {
|
|
601
|
+
deepMergeArrays(result[i], source[i], opts);
|
|
602
|
+
// If both target and source are objects, merge them recursively
|
|
603
|
+
}
|
|
604
|
+
else if (_targetType === 'Object' && _sourceType === 'Object') {
|
|
605
|
+
deepMergeObjects(result[i], source[i], opts);
|
|
606
|
+
// If both target and source are Maps, merge them recursively
|
|
607
|
+
}
|
|
608
|
+
else if (_targetType === 'Map' && _sourceType === 'Map') {
|
|
609
|
+
deepMergeMaps(result[i], source[i], opts);
|
|
610
|
+
// If both target and source are Sets, merge them recursively
|
|
611
|
+
}
|
|
612
|
+
else if (_targetType === 'Set' && _sourceType === 'Set') {
|
|
613
|
+
deepMergeSets(result[i], source[i], opts);
|
|
614
|
+
// If the items are simple values, replace them directly
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
result[i] = source[i];
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
return result;
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
const deepMergeObjects = (target, source, opts = {}) => {
|
|
625
|
+
let targetType = getDataType(target), sourceType = getDataType(source);
|
|
626
|
+
//target不是对象或者source为空则直接返回
|
|
627
|
+
if (targetType !== 'Object' || sourceType !== 'Object') {
|
|
628
|
+
return target;
|
|
629
|
+
}
|
|
630
|
+
const options = Object.assign({ itemMode: 'merge', propAppend: true, targetClone: false, useEnable: true }, opts),
|
|
631
|
+
//如果是复制方法,则先复制target
|
|
632
|
+
result = options.targetClone ? deepClone(target) : target;
|
|
633
|
+
for (let k in source) {
|
|
634
|
+
if (source.hasOwnProperty(k) && result.hasOwnProperty(k)) {
|
|
635
|
+
let _resultType = getDataType(result[k]), _sourceType = getDataType(source[k]);
|
|
636
|
+
if (_resultType !== _sourceType) {
|
|
637
|
+
//类型不同则直接覆盖
|
|
638
|
+
if (options.useEnable && result.hasOwnProperty(k) && result[k]?.hasOwnProperty('enable') && typeof source[k] === 'boolean') {
|
|
639
|
+
//部分替换,仅针对result={enable:true/false,a:''},source=false/true这种情况和相反的情况,因为这种情况再笨框架比较多见
|
|
640
|
+
if (result[k]?.hasOwnProperty('enable') && typeof source[k] === 'boolean') {
|
|
641
|
+
//result={enable:true,a:'',b:''},source[k]=false=>result={enable:false,a:'',b:''}
|
|
642
|
+
result[k].enable = source[k];
|
|
643
|
+
}
|
|
644
|
+
else if (source[k]?.hasOwnProperty('enable') && typeof result[k] === 'boolean') {
|
|
645
|
+
//source={enable:true,a:'',b:''},(result as any)[k]=false=>result={enable:false,a:'',b:''}
|
|
646
|
+
result = Object.assign({ enable: result[k] }, source[k]);
|
|
647
|
+
}
|
|
648
|
+
else {
|
|
649
|
+
//完全替换
|
|
650
|
+
result[k] = source[k];
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
else {
|
|
654
|
+
//完全替换
|
|
655
|
+
result[k] = source[k];
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
//类型相同
|
|
660
|
+
if (_sourceType === 'Object') {
|
|
661
|
+
//如果遇上对象则深度复制
|
|
662
|
+
result[k] = deepMergeObjects(result[k], source[k], options);
|
|
663
|
+
}
|
|
664
|
+
else if (_sourceType === 'Array') {
|
|
665
|
+
//如果遇上数组,且arrAppend为true追加模式,则在尾部追加数组
|
|
666
|
+
deepMergeArrays(result[k], source[k], options);
|
|
667
|
+
}
|
|
668
|
+
else if (_sourceType === 'Map') {
|
|
669
|
+
//如果遇上数组,且arrAppend为true追加模式,则在尾部追加数组
|
|
670
|
+
deepMergeMaps(result[k], source[k], options);
|
|
671
|
+
}
|
|
672
|
+
else if (_sourceType === 'Set') {
|
|
673
|
+
//如果遇上数组,且arrAppend为true追加模式,则在尾部追加数组
|
|
674
|
+
deepMergeSets(result[k], source[k], options);
|
|
675
|
+
}
|
|
676
|
+
else {
|
|
677
|
+
//其他清空则直接覆盖
|
|
678
|
+
result[k] = source[k];
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
else if (source.hasOwnProperty(k) && !result.hasOwnProperty(k) && options.propAppend) {
|
|
683
|
+
//如果source有属性,result没有该属性,但是options允许追加属性则直接赋值
|
|
684
|
+
result[k] = source[k];
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
//Symbol键直接追加,因为Symbol是唯一,结果同Object.assign
|
|
688
|
+
if (options.useSymbol) {
|
|
689
|
+
let symbols = Object.getOwnPropertySymbols(source);
|
|
690
|
+
if (symbols.length > 0) {
|
|
691
|
+
for (let k of symbols) {
|
|
692
|
+
result[k] = source[k];
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
return result;
|
|
697
|
+
};
|
|
698
|
+
|
|
699
|
+
const deepMerge = (target, source, opts = {}) => {
|
|
700
|
+
// Get the data types of the target and source
|
|
701
|
+
let targetType = getDataType(target), sourceType = getDataType(source), options = Object.assign({
|
|
702
|
+
itemMode: 'merge', // Default merge mode
|
|
703
|
+
propAppend: true, // Default to appending properties from source to target
|
|
704
|
+
targetClone: false, // Do not clone target by default
|
|
705
|
+
useEnable: true // Enable special handling for objects with an `enable` property
|
|
706
|
+
}, opts);
|
|
707
|
+
if (targetType === 'Object' && sourceType === 'Object') {
|
|
708
|
+
return deepMergeObjects(target, source, options);
|
|
709
|
+
}
|
|
710
|
+
else if (targetType === 'Array' && sourceType === 'Array') {
|
|
711
|
+
return deepMergeArrays(target, source, options);
|
|
712
|
+
}
|
|
713
|
+
else if (targetType === 'Set' && sourceType === 'Set') {
|
|
714
|
+
return deepMergeSets(target, source, options);
|
|
715
|
+
}
|
|
716
|
+
else if (targetType === 'Map' && sourceType === 'Map') {
|
|
717
|
+
return deepMergeMaps(target, source, options);
|
|
718
|
+
}
|
|
719
|
+
else {
|
|
720
|
+
return target;
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
const utils = {
|
|
725
|
+
//executeStr,
|
|
726
|
+
getDataType,
|
|
727
|
+
requireTypes,
|
|
728
|
+
//renderTpl,
|
|
729
|
+
//parseStr,
|
|
730
|
+
deepClone,
|
|
731
|
+
deepCloneToJSON,
|
|
732
|
+
wrapArrayMethods,
|
|
733
|
+
arrayMutableMethods,
|
|
734
|
+
setMutableMethods,
|
|
735
|
+
mapMutableMethods,
|
|
736
|
+
wrapSetMethods,
|
|
737
|
+
wrapMapMethods,
|
|
738
|
+
getUniqueId,
|
|
739
|
+
deepEqual: assert.deepEqual,
|
|
740
|
+
deepMerge,
|
|
741
|
+
deepMergeArrays,
|
|
742
|
+
deepMergeMaps,
|
|
743
|
+
deepMergeObjects,
|
|
744
|
+
deepMergeSets,
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
return utils;
|
|
748
|
+
|
|
749
|
+
}));
|