nv-facutil-has 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.
- package/README.md +417 -0
- package/TEST/tst-nest-flat.js +235 -0
- package/TEST/tst.js +136 -0
- package/index.js +340 -0
- package/package.json +11 -0
package/README.md
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
# has - JavaScript Property Inspector
|
|
2
|
+
|
|
3
|
+
A comprehensive utility for checking and inspecting object properties and prototype chains in JavaScript.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
const has = require('nv-facutil-has');
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## ⚠️ Important Usage Note
|
|
12
|
+
|
|
13
|
+
**The main function `has()` is designed for production use. All other utility functions are intended for CLI tools, testing frameworks, and debugging/inspection purposes only.**
|
|
14
|
+
|
|
15
|
+
These inspection utilities provide deep introspection capabilities that are useful for:
|
|
16
|
+
- Interactive REPL/CLI environments
|
|
17
|
+
- Test assertions and debugging
|
|
18
|
+
- Object structure analysis
|
|
19
|
+
- Educational purposes
|
|
20
|
+
|
|
21
|
+
They are **not recommended** for production application logic due to performance considerations and complexity.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Main Function (Production Use)
|
|
26
|
+
|
|
27
|
+
### `has(object, key, only_own=false)`
|
|
28
|
+
|
|
29
|
+
Checks if an object has a specific property.
|
|
30
|
+
|
|
31
|
+
**Parameters:**
|
|
32
|
+
- `object`: The object to check
|
|
33
|
+
- `key`: The property key (string or symbol)
|
|
34
|
+
- `only_own` (default: `false`): If `true`, checks only own properties; if `false`, includes prototype chain
|
|
35
|
+
|
|
36
|
+
**Returns:** `boolean`
|
|
37
|
+
|
|
38
|
+
**Examples:**
|
|
39
|
+
|
|
40
|
+
```javascript
|
|
41
|
+
const obj = { a: 1, b: 2 };
|
|
42
|
+
|
|
43
|
+
// Check with prototype chain
|
|
44
|
+
has(obj, 'a'); // true
|
|
45
|
+
has(obj, 'toString'); // true (inherited from Object.prototype)
|
|
46
|
+
|
|
47
|
+
// Check only own properties
|
|
48
|
+
has(obj, 'a', true); // true
|
|
49
|
+
has(obj, 'toString', true); // false
|
|
50
|
+
|
|
51
|
+
// Safe with null/undefined
|
|
52
|
+
has(null, 'prop'); // false
|
|
53
|
+
has(undefined, 'prop'); // false
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Inspection Utilities (CLI/Testing Only)
|
|
59
|
+
|
|
60
|
+
### `slow(object, key, dig=Infinity)`
|
|
61
|
+
|
|
62
|
+
Searches for a property within a specified depth range in the prototype chain.
|
|
63
|
+
|
|
64
|
+
**Parameters:**
|
|
65
|
+
- `object`: The object to search
|
|
66
|
+
- `key`: The property key to find
|
|
67
|
+
- `dig` (default: `Infinity`): Maximum depth to search
|
|
68
|
+
- `0`: Own properties only
|
|
69
|
+
- `n`: Search up to n levels in prototype chain
|
|
70
|
+
- `Infinity`: Search entire chain
|
|
71
|
+
|
|
72
|
+
**Returns:** `boolean`
|
|
73
|
+
|
|
74
|
+
**Examples:**
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
const grandparent = { a: 1 };
|
|
78
|
+
const parent = Object.create(grandparent);
|
|
79
|
+
parent.b = 2;
|
|
80
|
+
const child = Object.create(parent);
|
|
81
|
+
child.c = 3;
|
|
82
|
+
|
|
83
|
+
has.slow(child, 'c', 0); // true (own property)
|
|
84
|
+
has.slow(child, 'b', 0); // false (not own)
|
|
85
|
+
has.slow(child, 'b', 1); // true (within 1 level)
|
|
86
|
+
has.slow(child, 'a', 2); // true (within 2 levels)
|
|
87
|
+
has.slow(child, 'a', 1); // false (need 2 levels)
|
|
88
|
+
has.slow(child, 'a', Infinity); // true (search all)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
### `exact(object, key, depth=Infinity)`
|
|
94
|
+
|
|
95
|
+
Checks if a property exists at an exact depth in the prototype chain.
|
|
96
|
+
|
|
97
|
+
**Parameters:**
|
|
98
|
+
- `object`: The object to check
|
|
99
|
+
- `key`: The property key
|
|
100
|
+
- `depth`: Exact prototype chain depth
|
|
101
|
+
- `0`: Object's own property
|
|
102
|
+
- `n`: Property at exactly n levels up
|
|
103
|
+
- `Infinity`: Anywhere in chain
|
|
104
|
+
|
|
105
|
+
**Returns:** `boolean`
|
|
106
|
+
|
|
107
|
+
**Examples:**
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
const gp = { a: 1 };
|
|
111
|
+
const p = Object.create(gp);
|
|
112
|
+
p.b = 2;
|
|
113
|
+
const c = Object.create(p);
|
|
114
|
+
c.c = 3;
|
|
115
|
+
|
|
116
|
+
has.exact(c, 'c', 0); // true (own property)
|
|
117
|
+
has.exact(c, 'b', 0); // false (at level 1)
|
|
118
|
+
has.exact(c, 'b', 1); // true (exactly at level 1)
|
|
119
|
+
has.exact(c, 'a', 1); // false (at level 2, not 1)
|
|
120
|
+
has.exact(c, 'a', 2); // true (exactly at level 2)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
### `get_proto_chain(object)`
|
|
126
|
+
|
|
127
|
+
Returns the complete prototype chain as an array.
|
|
128
|
+
|
|
129
|
+
**Parameters:**
|
|
130
|
+
- `object`: The object to inspect
|
|
131
|
+
|
|
132
|
+
**Returns:** `Array` - Array of objects in the prototype chain
|
|
133
|
+
|
|
134
|
+
**Examples:**
|
|
135
|
+
|
|
136
|
+
```javascript
|
|
137
|
+
const parent = { x: 1 };
|
|
138
|
+
const child = Object.create(parent);
|
|
139
|
+
child.y = 2;
|
|
140
|
+
|
|
141
|
+
const chain = has.get_proto_chain(child);
|
|
142
|
+
// [child, parent, Object.prototype]
|
|
143
|
+
|
|
144
|
+
console.log(chain.length); // 3
|
|
145
|
+
console.log(chain[0] === child); // true
|
|
146
|
+
console.log(chain[1] === parent); // true
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
### `get_prop_descs(object, split_layer=false)`
|
|
152
|
+
|
|
153
|
+
Retrieves property descriptors from the prototype chain.
|
|
154
|
+
|
|
155
|
+
**Parameters:**
|
|
156
|
+
- `object`: The object to inspect
|
|
157
|
+
- `split_layer` (default: `false`): Output format
|
|
158
|
+
- `false`: Single object with all descriptors merged
|
|
159
|
+
- `true`: Array of `{object, descriptors}` per layer
|
|
160
|
+
|
|
161
|
+
**Returns:** `Object` or `Array`
|
|
162
|
+
|
|
163
|
+
**Examples:**
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
const obj = { a: 1 };
|
|
167
|
+
Object.defineProperty(obj, 'b', {
|
|
168
|
+
value: 2,
|
|
169
|
+
writable: false,
|
|
170
|
+
enumerable: true
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Merged format (default)
|
|
174
|
+
const descs = has.get_prop_descs(obj, false);
|
|
175
|
+
console.log(descs.a);
|
|
176
|
+
// { value: 1, writable: true, enumerable: true, configurable: true }
|
|
177
|
+
|
|
178
|
+
console.log(descs.b.writable); // false
|
|
179
|
+
|
|
180
|
+
// Layer-by-layer format
|
|
181
|
+
const layers = has.get_prop_descs(obj, true);
|
|
182
|
+
layers.forEach((layer, i) => {
|
|
183
|
+
console.log(`Layer ${i}:`, Object.keys(layer.descriptors));
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
### `get_all_keys(object, split_layer=false, enable_grouping=false)`
|
|
190
|
+
|
|
191
|
+
Gets all property keys (including symbols and non-enumerable) from the prototype chain.
|
|
192
|
+
|
|
193
|
+
**Parameters:**
|
|
194
|
+
- `object`: The object to inspect
|
|
195
|
+
- `split_layer` (default: `false`): Separate by prototype layers
|
|
196
|
+
- `enable_grouping` (default: `false`): Group by property characteristics
|
|
197
|
+
|
|
198
|
+
**Returns:** `Array` or `Object`
|
|
199
|
+
|
|
200
|
+
**Property Signature Format (when `enable_grouping=true`):**
|
|
201
|
+
- `k-__v-wec`: string key, data property, writable+enumerable+configurable
|
|
202
|
+
- `k-g__-_ec`: string key, getter only, enumerable+configurable
|
|
203
|
+
- `k-gs_-_e_`: string key, getter+setter, enumerable only
|
|
204
|
+
- `y-__v-wec`: symbol key, data property, writable+enumerable+configurable
|
|
205
|
+
|
|
206
|
+
Format: `{keyType}-{propType}-{flags}`
|
|
207
|
+
- Key type: `k` (string) or `y` (symbol)
|
|
208
|
+
- Property type: `__v` (data), `g__` (getter), `_s_` (setter), `gs_` (both)
|
|
209
|
+
- Flags: `w` (writable), `e` (enumerable), `c` (configurable), `_` (absent)
|
|
210
|
+
|
|
211
|
+
**Examples:**
|
|
212
|
+
|
|
213
|
+
```javascript
|
|
214
|
+
const obj = { a: 1 };
|
|
215
|
+
Object.defineProperty(obj, 'getter', {
|
|
216
|
+
get: () => 'value',
|
|
217
|
+
enumerable: true
|
|
218
|
+
});
|
|
219
|
+
const sym = Symbol('test');
|
|
220
|
+
obj[sym] = 'symbol value';
|
|
221
|
+
|
|
222
|
+
// Simple list
|
|
223
|
+
const keys = has.get_all_keys(obj);
|
|
224
|
+
console.log(keys); // ['a', 'getter', Symbol(test)]
|
|
225
|
+
|
|
226
|
+
// Grouped by characteristics
|
|
227
|
+
const grouped = has.get_all_keys(obj, false, true);
|
|
228
|
+
console.log(grouped);
|
|
229
|
+
// {
|
|
230
|
+
// 'k-__v-wec': ['a'],
|
|
231
|
+
// 'k-g__-_ec': ['getter'],
|
|
232
|
+
// 'y-__v-wec': [Symbol(test)]
|
|
233
|
+
// }
|
|
234
|
+
|
|
235
|
+
// Split by layers
|
|
236
|
+
const parent = { x: 10 };
|
|
237
|
+
const child = Object.create(parent);
|
|
238
|
+
child.y = 20;
|
|
239
|
+
|
|
240
|
+
const layers = has.get_all_keys(child, true);
|
|
241
|
+
console.log(layers);
|
|
242
|
+
// [
|
|
243
|
+
// ['y'], // Layer 0: child's own
|
|
244
|
+
// ['x'] // Layer 1: parent's own
|
|
245
|
+
// ]
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
### `nestify(object)`
|
|
251
|
+
|
|
252
|
+
Converts a flat object into a nested prototype chain structure.
|
|
253
|
+
|
|
254
|
+
**Parameters:**
|
|
255
|
+
- `object`: A flat object to convert
|
|
256
|
+
|
|
257
|
+
**Returns:** Object with properties distributed across prototype chain
|
|
258
|
+
|
|
259
|
+
**How it works:**
|
|
260
|
+
- First property → deepest prototype
|
|
261
|
+
- Middle properties → intermediate prototypes
|
|
262
|
+
- Last property → object's own property
|
|
263
|
+
|
|
264
|
+
**Examples:**
|
|
265
|
+
|
|
266
|
+
```javascript
|
|
267
|
+
const flat = { a: 100, b: 200, c: 300 };
|
|
268
|
+
const nested = has.nestify(flat);
|
|
269
|
+
|
|
270
|
+
// Resulting structure:
|
|
271
|
+
// nested (own): { c: 300 }
|
|
272
|
+
// └─ prototype (empty layer)
|
|
273
|
+
// └─ prototype: { b: 200 }
|
|
274
|
+
// └─ prototype: { a: 100 }
|
|
275
|
+
|
|
276
|
+
console.log(nested.c); // 300 (own property)
|
|
277
|
+
console.log(nested.b); // 200 (from prototype)
|
|
278
|
+
console.log(nested.a); // 100 (from deeper prototype)
|
|
279
|
+
|
|
280
|
+
console.log(Object.hasOwn(nested, 'c')); // true
|
|
281
|
+
console.log(Object.hasOwn(nested, 'b')); // false
|
|
282
|
+
console.log(Object.hasOwn(nested, 'a')); // false
|
|
283
|
+
|
|
284
|
+
// Verify depths
|
|
285
|
+
has.exact(nested, 'c', 0); // true
|
|
286
|
+
has.exact(nested, 'b', 2); // true
|
|
287
|
+
has.exact(nested, 'a', 3); // true
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
### `flatify(object)`
|
|
293
|
+
|
|
294
|
+
Converts a nested prototype chain back into a flat object.
|
|
295
|
+
|
|
296
|
+
**Parameters:**
|
|
297
|
+
- `object`: Object with prototype chain to flatten
|
|
298
|
+
|
|
299
|
+
**Returns:** Flat object with all properties merged
|
|
300
|
+
|
|
301
|
+
**Examples:**
|
|
302
|
+
|
|
303
|
+
```javascript
|
|
304
|
+
const nested = has.nestify({ a: 1, b: 2, c: 3 });
|
|
305
|
+
const flat = has.flatify(nested);
|
|
306
|
+
|
|
307
|
+
console.log(flat); // { c: 3, b: 2, a: 1 }
|
|
308
|
+
|
|
309
|
+
// All values preserved
|
|
310
|
+
console.log(flat.a === 1); // true
|
|
311
|
+
console.log(flat.b === 2); // true
|
|
312
|
+
console.log(flat.c === 3); // true
|
|
313
|
+
|
|
314
|
+
// Round-trip preservation
|
|
315
|
+
const original = { x: 10, y: 20, z: 30 };
|
|
316
|
+
const restored = has.flatify(has.nestify(original));
|
|
317
|
+
// restored contains all original properties and values
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## Use Cases
|
|
323
|
+
|
|
324
|
+
### Production Code
|
|
325
|
+
|
|
326
|
+
```javascript
|
|
327
|
+
// Simple property existence check
|
|
328
|
+
if (has(config, 'database')) {
|
|
329
|
+
// Use config.database
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Check own properties only
|
|
333
|
+
if (has(userInput, 'password', true)) {
|
|
334
|
+
// Process password
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Testing & Debugging
|
|
339
|
+
|
|
340
|
+
```javascript
|
|
341
|
+
// In a test framework
|
|
342
|
+
describe('Object structure', () => {
|
|
343
|
+
it('should have property at correct depth', () => {
|
|
344
|
+
const obj = createComplexObject();
|
|
345
|
+
assert(has.exact(obj, 'deepProp', 3));
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('should have all expected keys', () => {
|
|
349
|
+
const keys = has.get_all_keys(obj, false, true);
|
|
350
|
+
assert(keys['k-__v-wec'].includes('importantProp'));
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// In CLI inspection tool
|
|
355
|
+
function inspect(obj) {
|
|
356
|
+
const chain = has.get_proto_chain(obj);
|
|
357
|
+
console.log(`Prototype chain depth: ${chain.length}`);
|
|
358
|
+
|
|
359
|
+
const descs = has.get_prop_descs(obj, true);
|
|
360
|
+
descs.forEach((layer, i) => {
|
|
361
|
+
console.log(`Level ${i}:`, Object.keys(layer.descriptors));
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Object Analysis
|
|
367
|
+
|
|
368
|
+
```javascript
|
|
369
|
+
// Analyze object structure
|
|
370
|
+
function analyzeObject(obj) {
|
|
371
|
+
const allKeys = has.get_all_keys(obj, false, true);
|
|
372
|
+
|
|
373
|
+
console.log('Property distribution:');
|
|
374
|
+
for (const [sig, keys] of Object.entries(allKeys)) {
|
|
375
|
+
console.log(`${sig}: ${keys.length} properties`);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const layers = has.get_proto_chain(obj);
|
|
379
|
+
console.log(`\nPrototype depth: ${layers.length}`);
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## Performance Considerations
|
|
386
|
+
|
|
387
|
+
**Main function `has()`:**
|
|
388
|
+
- Optimized for production use
|
|
389
|
+
- O(1) for own properties
|
|
390
|
+
- O(n) for prototype chain (where n = chain depth)
|
|
391
|
+
|
|
392
|
+
**Inspection utilities:**
|
|
393
|
+
- May perform multiple traversals
|
|
394
|
+
- Use reflection APIs (getOwnPropertyDescriptors, etc.)
|
|
395
|
+
- Not optimized for high-frequency calls
|
|
396
|
+
- Best suited for development/debugging scenarios
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## API Summary
|
|
401
|
+
|
|
402
|
+
| Function | Purpose | Production Use |
|
|
403
|
+
|----------|---------|----------------|
|
|
404
|
+
| `has(o, key, only_own)` | Check property existence | ✅ Yes |
|
|
405
|
+
| `slow(o, key, dig)` | Search within depth range | ❌ No (CLI/Testing) |
|
|
406
|
+
| `exact(o, key, depth)` | Check exact depth | ❌ No (CLI/Testing) |
|
|
407
|
+
| `get_proto_chain(o)` | Get prototype chain | ❌ No (CLI/Testing) |
|
|
408
|
+
| `get_prop_descs(o, split)` | Get property descriptors | ❌ No (CLI/Testing) |
|
|
409
|
+
| `get_all_keys(o, split, group)` | Get all keys with details | ❌ No (CLI/Testing) |
|
|
410
|
+
| `nestify(o)` | Flatten to chain | ❌ No (CLI/Testing) |
|
|
411
|
+
| `flatify(o)` | Chain to flat | ❌ No (CLI/Testing) |
|
|
412
|
+
|
|
413
|
+
---
|
|
414
|
+
|
|
415
|
+
## License
|
|
416
|
+
|
|
417
|
+
MIT
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
const has = require('../index.js');
|
|
2
|
+
|
|
3
|
+
console.log("=".repeat(60));
|
|
4
|
+
console.log("=== nestify() and flatify() Detailed Tests ===");
|
|
5
|
+
console.log("=".repeat(60));
|
|
6
|
+
|
|
7
|
+
// Helper function to display prototype chain
|
|
8
|
+
function showProtoChain(obj, name = "obj") {
|
|
9
|
+
console.log(`\nPrototype chain for ${name}:`);
|
|
10
|
+
let current = obj;
|
|
11
|
+
let depth = 0;
|
|
12
|
+
|
|
13
|
+
while (current != null && depth < 10) {
|
|
14
|
+
const ownKeys = Object.keys(current);
|
|
15
|
+
if (ownKeys.length > 0 || depth === 0) {
|
|
16
|
+
const props = ownKeys.map(k => `${k}: ${current[k]}`).join(', ');
|
|
17
|
+
console.log(` Level ${depth}: {${props || 'empty'}}`);
|
|
18
|
+
}
|
|
19
|
+
current = Object.getPrototypeOf(current);
|
|
20
|
+
depth++;
|
|
21
|
+
if (current === Object.prototype) {
|
|
22
|
+
console.log(` Level ${depth}: Object.prototype`);
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Test 1: Empty object
|
|
29
|
+
console.log("\n" + "=".repeat(60));
|
|
30
|
+
console.log("Test 1: Empty object");
|
|
31
|
+
console.log("=".repeat(60));
|
|
32
|
+
const empty = {};
|
|
33
|
+
const nestedEmpty = has.nestify(empty);
|
|
34
|
+
const flatEmpty = has.flatify(nestedEmpty);
|
|
35
|
+
console.log("Original:", empty);
|
|
36
|
+
console.log("Nestified:", nestedEmpty);
|
|
37
|
+
console.log("Flatified:", flatEmpty);
|
|
38
|
+
console.log("Match:", JSON.stringify(flatEmpty) === JSON.stringify(empty));
|
|
39
|
+
|
|
40
|
+
// Test 2: Single property
|
|
41
|
+
console.log("\n" + "=".repeat(60));
|
|
42
|
+
console.log("Test 2: Single property");
|
|
43
|
+
console.log("=".repeat(60));
|
|
44
|
+
const single = { a: 100 };
|
|
45
|
+
const nestedSingle = has.nestify(single);
|
|
46
|
+
const flatSingle = has.flatify(nestedSingle);
|
|
47
|
+
console.log("Original:", single);
|
|
48
|
+
console.log("Nestified:", nestedSingle);
|
|
49
|
+
console.log("Flatified:", flatSingle);
|
|
50
|
+
console.log("Own keys in nested:", Object.keys(nestedSingle));
|
|
51
|
+
console.log("Match:", flatSingle.a === 100);
|
|
52
|
+
|
|
53
|
+
// Test 3: Two properties
|
|
54
|
+
console.log("\n" + "=".repeat(60));
|
|
55
|
+
console.log("Test 3: Two properties {a:100, b:200}");
|
|
56
|
+
console.log("=".repeat(60));
|
|
57
|
+
const two = { a: 100, b: 200 };
|
|
58
|
+
const nestedTwo = has.nestify(two);
|
|
59
|
+
const flatTwo = has.flatify(nestedTwo);
|
|
60
|
+
console.log("Original:", two);
|
|
61
|
+
showProtoChain(nestedTwo, "nestedTwo");
|
|
62
|
+
console.log("Flatified:", flatTwo);
|
|
63
|
+
console.log("Values accessible:", nestedTwo.a, nestedTwo.b);
|
|
64
|
+
console.log("b is own?", Object.hasOwn(nestedTwo, 'b'));
|
|
65
|
+
console.log("a is own?", Object.hasOwn(nestedTwo, 'a'));
|
|
66
|
+
console.log("Match:", flatTwo.a === 100 && flatTwo.b === 200);
|
|
67
|
+
|
|
68
|
+
// Test 4: Three properties
|
|
69
|
+
console.log("\n" + "=".repeat(60));
|
|
70
|
+
console.log("Test 4: Three properties {a:1, b:2, c:3}");
|
|
71
|
+
console.log("=".repeat(60));
|
|
72
|
+
const three = { a: 1, b: 2, c: 3 };
|
|
73
|
+
const nestedThree = has.nestify(three);
|
|
74
|
+
const flatThree = has.flatify(nestedThree);
|
|
75
|
+
console.log("Original:", three);
|
|
76
|
+
showProtoChain(nestedThree, "nestedThree");
|
|
77
|
+
console.log("Flatified:", flatThree);
|
|
78
|
+
console.log("All values accessible:", nestedThree.a, nestedThree.b, nestedThree.c);
|
|
79
|
+
console.log("Match:", flatThree.a === 1 && flatThree.b === 2 && flatThree.c === 3);
|
|
80
|
+
|
|
81
|
+
// Test 5: Five properties
|
|
82
|
+
console.log("\n" + "=".repeat(60));
|
|
83
|
+
console.log("Test 5: Five properties");
|
|
84
|
+
console.log("=".repeat(60));
|
|
85
|
+
const five = { a: 1, b: 2, c: 3, d: 4, e: 5 };
|
|
86
|
+
const nestedFive = has.nestify(five);
|
|
87
|
+
const flatFive = has.flatify(nestedFive);
|
|
88
|
+
console.log("Original:", five);
|
|
89
|
+
showProtoChain(nestedFive, "nestedFive");
|
|
90
|
+
console.log("Flatified:", flatFive);
|
|
91
|
+
console.log("Match:", JSON.stringify(flatFive) === JSON.stringify({a:1, b:2, c:3, d:4, e:5}));
|
|
92
|
+
|
|
93
|
+
// Test 6: Different value types
|
|
94
|
+
console.log("\n" + "=".repeat(60));
|
|
95
|
+
console.log("Test 6: Different value types");
|
|
96
|
+
console.log("=".repeat(60));
|
|
97
|
+
const mixed = {
|
|
98
|
+
num: 42,
|
|
99
|
+
str: "hello",
|
|
100
|
+
bool: true,
|
|
101
|
+
nil: null,
|
|
102
|
+
arr: [1, 2, 3],
|
|
103
|
+
obj: { nested: "value" }
|
|
104
|
+
};
|
|
105
|
+
const nestedMixed = has.nestify(mixed);
|
|
106
|
+
const flatMixed = has.flatify(nestedMixed);
|
|
107
|
+
console.log("Original:", mixed);
|
|
108
|
+
console.log("Flatified:", flatMixed);
|
|
109
|
+
console.log("num match:", flatMixed.num === 42);
|
|
110
|
+
console.log("str match:", flatMixed.str === "hello");
|
|
111
|
+
console.log("bool match:", flatMixed.bool === true);
|
|
112
|
+
console.log("nil match:", flatMixed.nil === null);
|
|
113
|
+
console.log("arr match:", JSON.stringify(flatMixed.arr) === JSON.stringify([1,2,3]));
|
|
114
|
+
console.log("obj match:", JSON.stringify(flatMixed.obj) === JSON.stringify({nested:"value"}));
|
|
115
|
+
|
|
116
|
+
// Test 7: Property depth testing with exact()
|
|
117
|
+
console.log("\n" + "=".repeat(60));
|
|
118
|
+
console.log("Test 7: Property depth testing");
|
|
119
|
+
console.log("=".repeat(60));
|
|
120
|
+
const depth = { first: 1, second: 2, third: 3, fourth: 4 };
|
|
121
|
+
const nestedDepth = has.nestify(depth);
|
|
122
|
+
console.log("Original:", depth);
|
|
123
|
+
showProtoChain(nestedDepth, "nestedDepth");
|
|
124
|
+
|
|
125
|
+
console.log("\nDepth checks with exact():");
|
|
126
|
+
console.log("fourth at depth 0:", has.exact(nestedDepth, 'fourth', 0));
|
|
127
|
+
console.log("fourth at depth 1:", has.exact(nestedDepth, 'fourth', 1));
|
|
128
|
+
console.log("third at depth 0:", has.exact(nestedDepth, 'third', 0));
|
|
129
|
+
console.log("third at depth 2:", has.exact(nestedDepth, 'third', 2));
|
|
130
|
+
console.log("second at depth 2:", has.exact(nestedDepth, 'second', 2));
|
|
131
|
+
console.log("first at depth 3:", has.exact(nestedDepth, 'first', 3));
|
|
132
|
+
|
|
133
|
+
// Test 8: Round-trip preservation
|
|
134
|
+
console.log("\n" + "=".repeat(60));
|
|
135
|
+
console.log("Test 8: Round-trip preservation");
|
|
136
|
+
console.log("=".repeat(60));
|
|
137
|
+
const original = { x: 10, y: 20, z: 30, w: 40 };
|
|
138
|
+
const step1 = has.nestify(original);
|
|
139
|
+
const step2 = has.flatify(step1);
|
|
140
|
+
const step3 = has.nestify(step2);
|
|
141
|
+
const step4 = has.flatify(step3);
|
|
142
|
+
|
|
143
|
+
console.log("Original:", original);
|
|
144
|
+
console.log("After nest->flat:", step2);
|
|
145
|
+
console.log("After nest->flat->nest->flat:", step4);
|
|
146
|
+
console.log("Values preserved:",
|
|
147
|
+
step4.x === 10 && step4.y === 20 && step4.z === 30 && step4.w === 40);
|
|
148
|
+
|
|
149
|
+
// Test 9: null and undefined
|
|
150
|
+
console.log("\n" + "=".repeat(60));
|
|
151
|
+
console.log("Test 9: null and undefined handling");
|
|
152
|
+
console.log("=".repeat(60));
|
|
153
|
+
console.log("nestify(null):", has.nestify(null));
|
|
154
|
+
console.log("nestify(undefined):", has.nestify(undefined));
|
|
155
|
+
console.log("flatify(null):", has.flatify(null));
|
|
156
|
+
console.log("flatify(undefined):", has.flatify(undefined));
|
|
157
|
+
|
|
158
|
+
// Test 10: flatify on regular object with prototype
|
|
159
|
+
console.log("\n" + "=".repeat(60));
|
|
160
|
+
console.log("Test 10: flatify on object with custom prototype");
|
|
161
|
+
console.log("=".repeat(60));
|
|
162
|
+
const proto = { inherited: "from proto" };
|
|
163
|
+
const child = Object.create(proto);
|
|
164
|
+
child.own = "own property";
|
|
165
|
+
const flatChild = has.flatify(child);
|
|
166
|
+
console.log("Child object with prototype");
|
|
167
|
+
console.log("child.own:", child.own);
|
|
168
|
+
console.log("child.inherited:", child.inherited);
|
|
169
|
+
console.log("Flatified:", flatChild);
|
|
170
|
+
console.log("Has both properties:", flatChild.own === "own property" && flatChild.inherited === "from proto");
|
|
171
|
+
|
|
172
|
+
// Test 11: Property order preservation
|
|
173
|
+
console.log("\n" + "=".repeat(60));
|
|
174
|
+
console.log("Test 11: Property order");
|
|
175
|
+
console.log("=".repeat(60));
|
|
176
|
+
const ordered = { z: 1, y: 2, x: 3, w: 4, v: 5 };
|
|
177
|
+
const nestedOrdered = has.nestify(ordered);
|
|
178
|
+
const flatOrdered = has.flatify(nestedOrdered);
|
|
179
|
+
console.log("Original keys:", Object.keys(ordered));
|
|
180
|
+
console.log("Flatified keys:", Object.keys(flatOrdered));
|
|
181
|
+
console.log("Note: Order might differ but values should match");
|
|
182
|
+
console.log("All values present:",
|
|
183
|
+
flatOrdered.z === 1 && flatOrdered.y === 2 &&
|
|
184
|
+
flatOrdered.x === 3 && flatOrdered.w === 4 && flatOrdered.v === 5);
|
|
185
|
+
|
|
186
|
+
// Test 12: Using slow() on nestified objects
|
|
187
|
+
console.log("\n" + "=".repeat(60));
|
|
188
|
+
console.log("Test 12: Using slow() on nestified objects");
|
|
189
|
+
console.log("=".repeat(60));
|
|
190
|
+
const test = { a: 1, b: 2, c: 3 };
|
|
191
|
+
const nestedTest = has.nestify(test);
|
|
192
|
+
console.log("Original:", test);
|
|
193
|
+
console.log("\nUsing slow() to find properties:");
|
|
194
|
+
console.log("slow(nested, 'c', 0):", has.slow(nestedTest, 'c', 0)); // own
|
|
195
|
+
console.log("slow(nested, 'c', 1):", has.slow(nestedTest, 'c', 1)); // within 1 level
|
|
196
|
+
console.log("slow(nested, 'b', 1):", has.slow(nestedTest, 'b', 1)); // need to check deeper
|
|
197
|
+
console.log("slow(nested, 'b', 2):", has.slow(nestedTest, 'b', 2));
|
|
198
|
+
console.log("slow(nested, 'a', 3):", has.slow(nestedTest, 'a', 3));
|
|
199
|
+
console.log("slow(nested, 'a', Infinity):", has.slow(nestedTest, 'a', Infinity));
|
|
200
|
+
|
|
201
|
+
// Test 13: Performance test
|
|
202
|
+
console.log("\n" + "=".repeat(60));
|
|
203
|
+
console.log("Test 13: Performance comparison");
|
|
204
|
+
console.log("=".repeat(60));
|
|
205
|
+
|
|
206
|
+
const large = {};
|
|
207
|
+
for (let i = 0; i < 100; i++) {
|
|
208
|
+
large[`key${i}`] = i;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
console.time("nestify 100 properties");
|
|
212
|
+
const nestedLarge = has.nestify(large);
|
|
213
|
+
console.timeEnd("nestify 100 properties");
|
|
214
|
+
|
|
215
|
+
console.time("flatify 100 properties");
|
|
216
|
+
const flatLarge = has.flatify(nestedLarge);
|
|
217
|
+
console.timeEnd("flatify 100 properties");
|
|
218
|
+
|
|
219
|
+
console.log("All properties preserved:", Object.keys(flatLarge).length === 100);
|
|
220
|
+
|
|
221
|
+
// Test 14: Edge case - Object with numeric keys
|
|
222
|
+
console.log("\n" + "=".repeat(60));
|
|
223
|
+
console.log("Test 14: Numeric keys");
|
|
224
|
+
console.log("=".repeat(60));
|
|
225
|
+
const numeric = { 0: "zero", 1: "one", 2: "two" };
|
|
226
|
+
const nestedNumeric = has.nestify(numeric);
|
|
227
|
+
const flatNumeric = has.flatify(nestedNumeric);
|
|
228
|
+
console.log("Original:", numeric);
|
|
229
|
+
console.log("Flatified:", flatNumeric);
|
|
230
|
+
console.log("Match:", flatNumeric[0] === "zero" && flatNumeric[1] === "one" && flatNumeric[2] === "two");
|
|
231
|
+
|
|
232
|
+
console.log("\n" + "=".repeat(60));
|
|
233
|
+
console.log("=== All tests completed! ===");
|
|
234
|
+
console.log("=".repeat(60));
|
|
235
|
+
|
package/TEST/tst.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
const has = require('../index.js');
|
|
2
|
+
|
|
3
|
+
console.log("=== Basic has() Tests ===\n");
|
|
4
|
+
|
|
5
|
+
// Test 1: Basic own property
|
|
6
|
+
const obj1 = { a: 1, b: 2 };
|
|
7
|
+
console.log("Test 1: Own property");
|
|
8
|
+
console.log("has(obj1, 'a', true):", has(obj1, 'a', true)); // true
|
|
9
|
+
console.log("has(obj1, 'a', false):", has(obj1, 'a', false)); // true
|
|
10
|
+
console.log("has(obj1, 'c', true):", has(obj1, 'c', true)); // false
|
|
11
|
+
|
|
12
|
+
// Test 2: Prototype chain
|
|
13
|
+
const parent = { x: 10 };
|
|
14
|
+
const child = Object.create(parent);
|
|
15
|
+
child.y = 20;
|
|
16
|
+
|
|
17
|
+
console.log("\nTest 2: Prototype chain");
|
|
18
|
+
console.log("has(child, 'y', true):", has(child, 'y', true)); // true (own)
|
|
19
|
+
console.log("has(child, 'x', true):", has(child, 'x', true)); // false (inherited)
|
|
20
|
+
console.log("has(child, 'x', false):", has(child, 'x', false)); // true (in chain)
|
|
21
|
+
|
|
22
|
+
// Test 3: null/undefined handling
|
|
23
|
+
console.log("\nTest 3: null/undefined");
|
|
24
|
+
console.log("has(null, 'a'):", has(null, 'a')); // false
|
|
25
|
+
console.log("has(undefined, 'a'):", has(undefined, 'a')); // false
|
|
26
|
+
|
|
27
|
+
console.log("\n" + "=".repeat(50));
|
|
28
|
+
console.log("=== Detailed has.slow() Tests ===\n");
|
|
29
|
+
|
|
30
|
+
// Setup: 3-level prototype chain
|
|
31
|
+
const grandparent = { level: 'grandparent', gp: 'GP' };
|
|
32
|
+
const parent2 = Object.create(grandparent);
|
|
33
|
+
parent2.level = 'parent';
|
|
34
|
+
parent2.p = 'P';
|
|
35
|
+
const child2 = Object.create(parent2);
|
|
36
|
+
child2.level = 'child';
|
|
37
|
+
child2.c = 'C';
|
|
38
|
+
|
|
39
|
+
console.log("Test Setup: 3-level chain");
|
|
40
|
+
console.log("child2 -> parent2 -> grandparent -> Object.prototype");
|
|
41
|
+
console.log("child2 has: { level: 'child', c: 'C' }");
|
|
42
|
+
console.log("parent2 has: { level: 'parent', p: 'P' }");
|
|
43
|
+
console.log("grandparent has: { level: 'grandparent', gp: 'GP' }");
|
|
44
|
+
|
|
45
|
+
// Test 4: dig = 0 (own property only)
|
|
46
|
+
console.log("\nTest 4: dig = 0 (own property only)");
|
|
47
|
+
console.log("slow(child2, 'c', 0):", has.slow(child2, 'c', 0)); // true
|
|
48
|
+
console.log("slow(child2, 'level', 0):", has.slow(child2, 'level', 0)); // true
|
|
49
|
+
console.log("slow(child2, 'p', 0):", has.slow(child2, 'p', 0)); // false
|
|
50
|
+
console.log("slow(child2, 'gp', 0):", has.slow(child2, 'gp', 0)); // false
|
|
51
|
+
console.log("slow(child2, 'toString', 0):", has.slow(child2, 'toString', 0)); // false
|
|
52
|
+
|
|
53
|
+
// Test 5: dig = 1 (search 1 level up)
|
|
54
|
+
console.log("\nTest 5: dig = 1 (search 1 level up)");
|
|
55
|
+
console.log("slow(child2, 'c', 1):", has.slow(child2, 'c', 1)); // true (own)
|
|
56
|
+
console.log("slow(child2, 'p', 1):", has.slow(child2, 'p', 1)); // true (parent)
|
|
57
|
+
console.log("slow(child2, 'gp', 1):", has.slow(child2, 'gp', 1)); // false (grandparent)
|
|
58
|
+
console.log("slow(child2, 'level', 1):", has.slow(child2, 'level', 1)); // true (own)
|
|
59
|
+
|
|
60
|
+
// Test 6: dig = 2 (search 2 levels up)
|
|
61
|
+
console.log("\nTest 6: dig = 2 (search 2 levels up)");
|
|
62
|
+
console.log("slow(child2, 'c', 2):", has.slow(child2, 'c', 2)); // true
|
|
63
|
+
console.log("slow(child2, 'p', 2):", has.slow(child2, 'p', 2)); // true
|
|
64
|
+
console.log("slow(child2, 'gp', 2):", has.slow(child2, 'gp', 2)); // true (grandparent)
|
|
65
|
+
console.log("slow(child2, 'toString', 2):", has.slow(child2, 'toString', 2)); // false
|
|
66
|
+
|
|
67
|
+
// Test 7: dig = 3 (search 3 levels up)
|
|
68
|
+
console.log("\nTest 7: dig = 3 (search 3 levels up)");
|
|
69
|
+
console.log("slow(child2, 'gp', 3):", has.slow(child2, 'gp', 3)); // true
|
|
70
|
+
console.log("slow(child2, 'toString', 3):", has.slow(child2, 'toString', 3)); // true (Object.prototype)
|
|
71
|
+
|
|
72
|
+
// Test 8: dig = Infinity (entire chain)
|
|
73
|
+
console.log("\nTest 8: dig = Infinity (entire chain)");
|
|
74
|
+
console.log("slow(child2, 'c', Infinity):", has.slow(child2, 'c', Infinity)); // true
|
|
75
|
+
console.log("slow(child2, 'p', Infinity):", has.slow(child2, 'p', Infinity)); // true
|
|
76
|
+
console.log("slow(child2, 'gp', Infinity):", has.slow(child2, 'gp', Infinity)); // true
|
|
77
|
+
console.log("slow(child2, 'toString', Infinity):", has.slow(child2, 'toString', Infinity)); // true
|
|
78
|
+
console.log("slow(child2, 'nonexistent', Infinity):", has.slow(child2, 'nonexistent', Infinity)); // false
|
|
79
|
+
|
|
80
|
+
// Test 9: Edge cases
|
|
81
|
+
console.log("\nTest 9: Edge cases");
|
|
82
|
+
console.log("slow(null, 'a', 0):", has.slow(null, 'a', 0)); // false
|
|
83
|
+
console.log("slow(undefined, 'a', 1):", has.slow(undefined, 'a', 1)); // false
|
|
84
|
+
console.log("slow({}, 'toString', 0):", has.slow({}, 'toString', 0)); // false (inherited)
|
|
85
|
+
console.log("slow({}, 'toString', 1):", has.slow({}, 'toString', 1)); // true (Object.prototype)
|
|
86
|
+
|
|
87
|
+
// Test 10: Array and built-in objects
|
|
88
|
+
console.log("\nTest 10: Arrays");
|
|
89
|
+
const arr = [1, 2, 3];
|
|
90
|
+
arr.custom = 'value';
|
|
91
|
+
console.log("slow(arr, 'custom', 0):", has.slow(arr, 'custom', 0)); // true
|
|
92
|
+
console.log("slow(arr, '0', 0):", has.slow(arr, '0', 0)); // true (index)
|
|
93
|
+
console.log("slow(arr, 'length', 0):", has.slow(arr, 'length', 0)); // true
|
|
94
|
+
console.log("slow(arr, 'push', 0):", has.slow(arr, 'push', 0)); // false
|
|
95
|
+
console.log("slow(arr, 'push', 1):", has.slow(arr, 'push', 1)); // true (Array.prototype)
|
|
96
|
+
|
|
97
|
+
// Test 11: Deep chain
|
|
98
|
+
console.log("\nTest 11: Deep prototype chain");
|
|
99
|
+
let deep = { level0: true };
|
|
100
|
+
for (let i = 1; i <= 5; i++) {
|
|
101
|
+
const newObj = Object.create(deep);
|
|
102
|
+
newObj[`level${i}`] = true;
|
|
103
|
+
deep = newObj;
|
|
104
|
+
}
|
|
105
|
+
// deep now has 5 levels
|
|
106
|
+
console.log("slow(deep, 'level0', 3):", has.slow(deep, 'level0', 3)); // false (need 5)
|
|
107
|
+
console.log("slow(deep, 'level0', 5):", has.slow(deep, 'level0', 5)); // true
|
|
108
|
+
console.log("slow(deep, 'level2', 3):", has.slow(deep, 'level2', 3)); // true
|
|
109
|
+
console.log("slow(deep, 'level5', 0):", has.slow(deep, 'level5', 0)); // true
|
|
110
|
+
|
|
111
|
+
// Test 12: Performance comparison
|
|
112
|
+
console.log("\nTest 12: Performance comparison");
|
|
113
|
+
const testObj = Object.create(Object.create({ deepProp: 1 }));
|
|
114
|
+
testObj.shallowProp = 2;
|
|
115
|
+
|
|
116
|
+
console.time("has() with only_own=false");
|
|
117
|
+
for (let i = 0; i < 100000; i++) {
|
|
118
|
+
has(testObj, 'deepProp', false);
|
|
119
|
+
}
|
|
120
|
+
console.timeEnd("has() with only_own=false");
|
|
121
|
+
|
|
122
|
+
console.time("slow() with dig=Infinity");
|
|
123
|
+
for (let i = 0; i < 100000; i++) {
|
|
124
|
+
has.slow(testObj, 'deepProp', Infinity);
|
|
125
|
+
}
|
|
126
|
+
console.timeEnd("slow() with dig=Infinity");
|
|
127
|
+
|
|
128
|
+
console.time("slow() with dig=2");
|
|
129
|
+
for (let i = 0; i < 100000; i++) {
|
|
130
|
+
has.slow(testObj, 'deepProp', 2);
|
|
131
|
+
}
|
|
132
|
+
console.timeEnd("slow() with dig=2");
|
|
133
|
+
|
|
134
|
+
console.log("\n" + "=".repeat(50));
|
|
135
|
+
console.log("All tests completed!");
|
|
136
|
+
|
package/index.js
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
const slow = (o, key, dig = Infinity) => {
|
|
2
|
+
if (o == null) {
|
|
3
|
+
return false;
|
|
4
|
+
}
|
|
5
|
+
if (dig === 0) {
|
|
6
|
+
// 只检查自身属性
|
|
7
|
+
return Object.hasOwn(o, key);
|
|
8
|
+
} else if (dig === Infinity) {
|
|
9
|
+
// 检查整个原型链
|
|
10
|
+
return (key in o);
|
|
11
|
+
} else {
|
|
12
|
+
// 沿着原型链向上查找指定层数
|
|
13
|
+
let current = o;
|
|
14
|
+
let depth = 0;
|
|
15
|
+
|
|
16
|
+
while (current != null && depth <= dig) {
|
|
17
|
+
if (Object.hasOwn(current, key)) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
current = Object.getPrototypeOf(current);
|
|
21
|
+
depth++;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const exact = (o, key, depth = Infinity) => {
|
|
29
|
+
if (o == null) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
if (depth === 0) {
|
|
33
|
+
// 只检查自身属性
|
|
34
|
+
return Object.hasOwn(o, key);
|
|
35
|
+
} else if (depth === Infinity) {
|
|
36
|
+
// 检查整个原型链
|
|
37
|
+
return (key in o);
|
|
38
|
+
} else {
|
|
39
|
+
// 沿着原型链向上查找指定层数
|
|
40
|
+
// 必须恰好在第 depth 层有此属性
|
|
41
|
+
let current = o;
|
|
42
|
+
|
|
43
|
+
// 移动到指定深度
|
|
44
|
+
for (let i = 0; i < depth; i++) {
|
|
45
|
+
current = Object.getPrototypeOf(current);
|
|
46
|
+
if (current == null) {
|
|
47
|
+
return false; // 原型链不够深
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 检查该层是否有此属性(自身属性)
|
|
52
|
+
return Object.hasOwn(current, key);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const get_proto_chain = (o) => {
|
|
57
|
+
if (o == null) {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const chain = [];
|
|
62
|
+
let current = o;
|
|
63
|
+
|
|
64
|
+
while (current != null) {
|
|
65
|
+
chain.push(current);
|
|
66
|
+
current = Object.getPrototypeOf(current);
|
|
67
|
+
|
|
68
|
+
// 停在 Object.prototype,不包括 null
|
|
69
|
+
if (current === Object.prototype) {
|
|
70
|
+
chain.push(current);
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return chain;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const get_prop_descs = (o, split_layer = false) => {
|
|
79
|
+
if (o == null) {
|
|
80
|
+
return split_layer ? [] : {};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (split_layer) {
|
|
84
|
+
// 按层分割,每层返回 {object, descriptors}
|
|
85
|
+
const descs = [];
|
|
86
|
+
let current = o;
|
|
87
|
+
|
|
88
|
+
while (current != null) {
|
|
89
|
+
const ownDescs = Object.getOwnPropertyDescriptors(current);
|
|
90
|
+
descs.push({
|
|
91
|
+
object: current,
|
|
92
|
+
descriptors: ownDescs
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
current = Object.getPrototypeOf(current);
|
|
96
|
+
|
|
97
|
+
if (current === Object.prototype) {
|
|
98
|
+
descs.push({
|
|
99
|
+
object: current,
|
|
100
|
+
descriptors: Object.getOwnPropertyDescriptors(current)
|
|
101
|
+
});
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return descs;
|
|
107
|
+
} else {
|
|
108
|
+
// 不分层,直接合并所有描述符到一个对象
|
|
109
|
+
const allDescs = {};
|
|
110
|
+
let current = o;
|
|
111
|
+
|
|
112
|
+
while (current != null) {
|
|
113
|
+
const ownDescs = Object.getOwnPropertyDescriptors(current);
|
|
114
|
+
|
|
115
|
+
// 只添加还未出现的属性(保留最近层的描述符)
|
|
116
|
+
for (const key in ownDescs) {
|
|
117
|
+
if (!(key in allDescs)) {
|
|
118
|
+
allDescs[key] = ownDescs[key];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
current = Object.getPrototypeOf(current);
|
|
123
|
+
|
|
124
|
+
if (current === Object.prototype) {
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return allDescs;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// 辅助函数:生成属性特征字符串
|
|
134
|
+
const get_prop_signature = (key, desc) => {
|
|
135
|
+
const isSymbol = typeof key === 'symbol';
|
|
136
|
+
const keyType = isSymbol ? 'y' : 'k';
|
|
137
|
+
|
|
138
|
+
let propType;
|
|
139
|
+
if (desc.get || desc.set) {
|
|
140
|
+
// accessor property
|
|
141
|
+
const g = desc.get ? 'g' : '_';
|
|
142
|
+
const s = desc.set ? 's' : '_';
|
|
143
|
+
propType = `${g}${s}_`;
|
|
144
|
+
} else {
|
|
145
|
+
// data property
|
|
146
|
+
propType = '__v';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const w = desc.writable ? 'w' : '_';
|
|
150
|
+
const e = desc.enumerable ? 'e' : '_';
|
|
151
|
+
const c = desc.configurable ? 'c' : '_';
|
|
152
|
+
|
|
153
|
+
return `${keyType}-${propType}-${w}${e}${c}`;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
const get_all_keys = (
|
|
159
|
+
o,
|
|
160
|
+
split_layer = false,
|
|
161
|
+
enable_grouping = false
|
|
162
|
+
) => {
|
|
163
|
+
if (o == null) {
|
|
164
|
+
return split_layer ? [] : (enable_grouping ? {} : []);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
if (split_layer) {
|
|
170
|
+
// 按层分割
|
|
171
|
+
const layers = [];
|
|
172
|
+
let current = o;
|
|
173
|
+
|
|
174
|
+
while (current != null) {
|
|
175
|
+
const ownKeys = Object.getOwnPropertyNames(current);
|
|
176
|
+
const ownSymbols = Object.getOwnPropertySymbols(current);
|
|
177
|
+
const allKeys = [...ownKeys, ...ownSymbols];
|
|
178
|
+
|
|
179
|
+
if (enable_grouping) {
|
|
180
|
+
const grouped = {};
|
|
181
|
+
const descs = Object.getOwnPropertyDescriptors(current);
|
|
182
|
+
|
|
183
|
+
for (const key of allKeys) {
|
|
184
|
+
const desc = descs[key];
|
|
185
|
+
const sig = get_prop_signature(key, desc);
|
|
186
|
+
|
|
187
|
+
if (!grouped[sig]) {
|
|
188
|
+
grouped[sig] = [];
|
|
189
|
+
}
|
|
190
|
+
grouped[sig].push(key);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
layers.push(grouped);
|
|
194
|
+
} else {
|
|
195
|
+
layers.push(allKeys);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
current = Object.getPrototypeOf(current);
|
|
199
|
+
|
|
200
|
+
if (current === Object.prototype) {
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return layers;
|
|
206
|
+
} else {
|
|
207
|
+
// 不分层,合并所有键
|
|
208
|
+
const allKeys = new Set();
|
|
209
|
+
let current = o;
|
|
210
|
+
|
|
211
|
+
while (current != null) {
|
|
212
|
+
const ownKeys = Object.getOwnPropertyNames(current);
|
|
213
|
+
const ownSymbols = Object.getOwnPropertySymbols(current);
|
|
214
|
+
|
|
215
|
+
for (const key of [...ownKeys, ...ownSymbols]) {
|
|
216
|
+
allKeys.add(key);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
current = Object.getPrototypeOf(current);
|
|
220
|
+
|
|
221
|
+
if (current === Object.prototype) {
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const keysArray = Array.from(allKeys);
|
|
227
|
+
|
|
228
|
+
if (enable_grouping) {
|
|
229
|
+
const grouped = {};
|
|
230
|
+
|
|
231
|
+
for (const key of keysArray) {
|
|
232
|
+
// Find descriptor in prototype chain
|
|
233
|
+
let desc = null;
|
|
234
|
+
let curr = o;
|
|
235
|
+
|
|
236
|
+
while (curr != null) {
|
|
237
|
+
desc = Object.getOwnPropertyDescriptor(curr, key);
|
|
238
|
+
if (desc) break;
|
|
239
|
+
curr = Object.getPrototypeOf(curr);
|
|
240
|
+
if (curr === Object.prototype) break;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (desc) {
|
|
244
|
+
const sig = get_prop_signature(key, desc);
|
|
245
|
+
|
|
246
|
+
if (!grouped[sig]) {
|
|
247
|
+
grouped[sig] = [];
|
|
248
|
+
}
|
|
249
|
+
grouped[sig].push(key);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return grouped;
|
|
254
|
+
} else {
|
|
255
|
+
return keysArray;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
// nestify AND flatify is for creat test-samples
|
|
262
|
+
|
|
263
|
+
const nestify = (o) => {
|
|
264
|
+
if (o == null) {
|
|
265
|
+
return o;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const keys = Object.keys(o);
|
|
269
|
+
|
|
270
|
+
if (keys.length === 0) {
|
|
271
|
+
return {};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (keys.length === 1) {
|
|
275
|
+
return { [keys[0]]: o[keys[0]] };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// 例如 o = {a:100, b:200}
|
|
279
|
+
// 第一个键放在最深的原型
|
|
280
|
+
let proto = { [keys[0]]: o[keys[0]] };
|
|
281
|
+
|
|
282
|
+
// 中间的键,每个键一层
|
|
283
|
+
for (let i = 1; i < keys.length - 1; i++) {
|
|
284
|
+
const layer = { [keys[i]]: o[keys[i]] };
|
|
285
|
+
proto = Object.create(proto);
|
|
286
|
+
Object.assign(proto, layer);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// 创建一个空的中间层 pr
|
|
290
|
+
const pr = Object.create(proto);
|
|
291
|
+
|
|
292
|
+
// 创建最终对象,最后一个键放在自身
|
|
293
|
+
const newO = Object.create(pr);
|
|
294
|
+
newO[keys[keys.length - 1]] = o[keys[keys.length - 1]];
|
|
295
|
+
|
|
296
|
+
return newO;
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const flatify = (o) => {
|
|
300
|
+
if (o == null) {
|
|
301
|
+
return o;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const result = {};
|
|
305
|
+
let current = o;
|
|
306
|
+
|
|
307
|
+
// 遍历整个原型链
|
|
308
|
+
while (current != null) {
|
|
309
|
+
// 获取当前层的自身属性
|
|
310
|
+
const ownKeys = Object.keys(current);
|
|
311
|
+
for (const key of ownKeys) {
|
|
312
|
+
// 只添加还未出现的属性(保留最近层的值)
|
|
313
|
+
if (!(key in result)) {
|
|
314
|
+
result[key] = current[key];
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
current = Object.getPrototypeOf(current);
|
|
318
|
+
|
|
319
|
+
// 避免遍历到 Object.prototype
|
|
320
|
+
if (current === Object.prototype) {
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return result;
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
module.exports = (o, key, only_own = false) => o != null && (only_own ? Object.hasOwn(o, key) : key in o);
|
|
329
|
+
////除主函数外,其他函数只适合用在cli 和测试框架里,例如inspect 某个对象
|
|
330
|
+
|
|
331
|
+
module.exports.slow = slow;
|
|
332
|
+
module.exports.exact = exact;
|
|
333
|
+
module.exports.nestify = nestify;
|
|
334
|
+
module.exports.flatify = flatify;
|
|
335
|
+
module.exports.get_proto_chain = get_proto_chain;
|
|
336
|
+
module.exports.get_prop_descs = get_prop_descs;
|
|
337
|
+
module.exports.get_prop_signature = get_prop_signature;
|
|
338
|
+
module.exports.get_all_keys = get_all_keys;
|
|
339
|
+
|
|
340
|
+
|