├── .babelrc
├── .eslintrc
├── .gitignore
├── .npmignore
├── .npmrc
├── .prettierrc
├── .release-it.beta.json
├── .release-it.json
├── CHANGELOG.md
├── DEV_ONLY
└── index.tsx
├── LICENSE
├── README.md
├── __tests__
├── __helpers__
│ ├── values.ts
│ └── words.json
├── hash.ts
├── index.ts
└── stringify.ts
├── benchmarks
├── compare.js
├── index.js
└── tests.js
├── build
├── rollup
│ ├── config.base.js
│ ├── config.cjs.js
│ ├── config.esm.js
│ ├── config.min.js
│ └── config.umd.js
├── tsconfig
│ ├── base.json
│ ├── cjs.json
│ ├── declarations.json
│ ├── esm.json
│ ├── min.json
│ └── umd.json
└── webpack.config.js
├── index.d.ts
├── jest.config.js
├── package.json
├── results_2.1.2.txt
├── results_3.0.0.txt
├── results_4.1.0.txt
├── results_5.0.2.txt
├── results_latest.txt
├── src
├── cache.ts
├── constants.ts
├── hash.ts
├── index.ts
├── sort.ts
├── stringify.ts
└── utils.ts
├── tsconfig.json
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "lib": {
4 | "presets": [
5 | [
6 | "@babel/preset-env",
7 | {
8 | "loose": true
9 | }
10 | ]
11 | ]
12 | },
13 | "test": {
14 | "presets": [
15 | [
16 | "@babel/preset-env",
17 | {
18 | "loose": true
19 | }
20 | ]
21 | ]
22 | }
23 | },
24 | "presets": [
25 | "@babel/preset-typescript",
26 | [
27 | "@babel/preset-env",
28 | {
29 | "loose": true,
30 | "modules": false
31 | }
32 | ]
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "node": true,
5 | "es6": true
6 | },
7 | "extends": ["plugin:@typescript-eslint/recommended"],
8 | "parser": "@typescript-eslint/parser",
9 | "parserOptions": {
10 | "ecmaVersion": 2021,
11 | "sourceType": "module"
12 | },
13 | "plugins": ["@typescript-eslint"],
14 | "rules": {
15 | "prefer-rest-params": 0,
16 |
17 | "@typescript-eslint/explicit-function-return-type": 0,
18 | "@typescript-eslint/explicit-module-boundary-types": 0,
19 | "@typescript-eslint/no-explicit-any": 0,
20 | "@typescript-eslint/no-non-null-assertion": 0
21 | },
22 | "settings": {
23 | "react": {
24 | "version": "detect"
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .nyc_output
3 | coverage
4 | dist
5 | node_modules
6 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .babelrc
2 | .eslintrc
3 | .gitignore
4 | .npmignore
5 | .prettierrc
6 | .release-it*.json
7 | __tests__
8 | benchmarks
9 | build
10 | coverage
11 | DEV_ONLY
12 | node_modules
13 | src
14 | jest.config.js
15 | *.csv
16 | results_*.txt
17 | tsconfig.json
18 | yarn*
19 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | scripts-prepend-node-path=true
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "always",
3 | "bracketSpacing": true,
4 | "semi": true,
5 | "singleQuote": true,
6 | "tabWidth": 2,
7 | "trailingComma": "all"
8 | }
9 |
--------------------------------------------------------------------------------
/.release-it.beta.json:
--------------------------------------------------------------------------------
1 | {
2 | "github": {
3 | "release": true,
4 | "tagName": "v${version}"
5 | },
6 | "npm": {
7 | "tag": "next"
8 | },
9 | "preReleaseId": "beta",
10 | "hooks": {
11 | "before:init": [
12 | "npm run lint",
13 | "npm run typecheck",
14 | "npm run test",
15 | "npm run build"
16 | ]
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/.release-it.json:
--------------------------------------------------------------------------------
1 | {
2 | "github": {
3 | "release": true,
4 | "tagName": "v${version}"
5 | },
6 | "hooks": {
7 | "before:init": [
8 | "npm run lint",
9 | "npm run typecheck",
10 | "npm run test",
11 | "npm run build"
12 | ]
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # hash-it CHANGELOG
2 |
3 | ## 6.0.0
4 |
5 | **Breaking changes**
6 |
7 | - Equality utilities (`is` / `is.any` / `is.all` / `is.not`) are no longer provided
8 | - `Error` type hashes now include the message (previously only included stack)
9 | - Non-enumerable type hashes (`Generator`, `Promise`, `WeakMap`, `WeakSet`) now hash uniquely based on reference
10 | - `WeakMap` is now required at runtime (used as cache for circular references)
11 |
12 | **Enhancements**
13 |
14 | - Better support for system-specific loading (ESM vs CJS vs UMD)
15 | - Added support for primitive wrappers (e.g. `new Number('123')`)
16 | - Added support for more object classes
17 | - `AsyncFunction`
18 | - `AsyncGeneratorFunction`
19 | - `BigInt64Array`
20 | - `BigUint64Array`
21 | - `GeneratorFunction`
22 | - `SharedArrayBuffer`
23 | - `WeakRef` (same limitations as those for `WeakMap` / `WeakSet`)
24 |
25 | ## 5.0.2
26 |
27 | - Reduce code size by 29.29% (19.42% gzipped size)
28 | - Activate strict mode for typing
29 |
30 | ## 5.0.1
31 |
32 | - Update `.npmignore` to reduce package tarball size ([#39](https://github.com/planttheidea/hash-it/pull/39))
33 |
34 | ## 5.0.0
35 |
36 | ### Breaking changes
37 |
38 | - Remove autocurrying of `hash.is` methods
39 | - Remove transpiled builds in favor of rollup distributed files (deep-linking will no longer work)
40 |
41 | ### Enhancements
42 |
43 | - Codebase rewritten in TypeScript
44 | - Added `BigInt` support
45 |
46 | ## 4.1.0
47 |
48 | - Add TypeScript definitions
49 | - Significant speed improvements
50 |
51 | ## 4.0.5
52 |
53 | - Fix issues related to string encoding and collisions
54 | [#23](https://github.com/planttheidea/hash-it/issues/23)
55 |
56 | ## 4.0.4
57 |
58 | - Improve speed of complex objects (Objects, Arrays, Maps, Sets)
59 | - Fix security issue with old version of `webpack-dev-server`
60 |
61 | ## 4.0.3
62 |
63 | - Upgrade to use babel 7 for builds
64 |
65 | ## 4.0.2
66 |
67 | - Fix [#18](https://github.com/planttheidea/hash-it/pull/18) - IE11 not allowing global `toString`
68 | to be used, instead using `Object.prototype.toString` (thanks
69 | [@JorgenEvens](https://github.com/JorgenEvens))
70 |
71 | ## 4.0.1
72 |
73 | - Remove unused values from publish
74 |
75 | ## 4.0.0
76 |
77 | Rewrite! Lots of changes under-the-hood for a much more consistent hash, and circular object
78 | handling out of the box.
79 |
80 | #### BREAKING CHANGES
81 |
82 | - `isEmpty`, `isEqual`,`isNull`, and `isUndefined` have been removed (all can be reproduced with new
83 | `is` and `is.all` functions)
84 | - `hash.isNull` => `hash.is(null)`
85 | - `hash.isUndefined` => `hash.is(undefined)`
86 | - `hash.isEqual` => `hash.is.all`
87 | - `hash.isEmpty` =>
88 | `(object) => hash.is.any(object, undefined, null, '', 0, [], {}, new Map(), new Set())`
89 | - `Error` hashes now based on `error.stack` instead of `error.message`
90 |
91 | #### ENHANCEMENTS
92 |
93 | - Circular objects are now handled out of the box, thanks to
94 | [`fast-stringify`](https://github.com/planttheidea/fast-stringify)
95 | - Collision rates are near-zero (previously used traditional DJB2, which has small collision rates)
96 | - Better `ArrayBuffer` support with the use of `Buffer.from` when supported
97 | - SVG elements, DocumentFragments, and Events are now supported
98 | - `is` partial-application function allows for easy creation of any type of `isEqual` comparison
99 | method
100 | - `is.any` performs the same multiple-object check that `is.all` does, but only checks if one of the
101 | other objects is equal
102 | - `is.not` performs the same comparison that `is` does, but checks for non-equality
103 |
104 | #### FIXES
105 |
106 | - `Object` / `Map` / `Set` no longer returns different hashes based on order of key addition
107 | - `hash.isEqual` will no longer fail if nothing is passed
108 |
109 | ## 3.1.2
110 |
111 | - Remove extraneous `toString` call (performance)
112 |
113 | ## 3.1.1
114 |
115 | - Improve hash uniqueness for HTML elements
116 |
117 | ## 3.1.0
118 |
119 | - Add support for `Generator` (not just `GeneratorFunction`)
120 | - Streamline `typeof`- vs `toString`-driven handling for improved speed for most types
121 |
122 | ## 3.0.0
123 |
124 | - Improve speed (2-4x faster depending on type)
125 | - Smaller footprint (~25% reduction)
126 | - Improve hash accuracy for functions (hash now includes function body)
127 | - Fix issue where stack remained in memory after hash was built
128 | - Add ES transpilation for module-ready build tools
129 |
130 | #### BREAKING CHANGES
131 |
132 | - If using CommonJS, you need to specify `require('hash-it').default` instead of just
133 | `require('hash-it')`
134 | - Hashes themselves may have changed (especially for circular objects)
135 | - Removed `isRecursive` method on `hashIt` object (which was wrongly named to begin with)
136 | - To specifically handle _circular_ objects (which is what it really did), pass `true` as the
137 | second parameter to `hashIt`
138 |
139 | ## 2.1.2
140 |
141 | - Move up isNull check in replacer (improve performance of more likely use-case)
142 |
143 | ## 2.1.1
144 |
145 | - Create isNull utility instead of checking strict equality in multiple places
146 |
147 | ## 2.1.0
148 |
149 | - Overall speed improvement by an average of 18.74% (35.27% improvement on complex objects)
150 |
151 | ## 2.0.1
152 |
153 | - More speed improvements
154 |
155 | ## 2.0.0
156 |
157 | - Use JSON.stringify with replacer as default, without try/catch
158 | - Move use of try/catch with fallback to prune to new `hashIt.withRecursion` method (only necessary
159 | for deeply-recursive objects like `window`)
160 | - Reorder switch statement for commonality of use cases
161 | - Leverage typeof in switch statements when possible for performance
162 |
163 | ## 1.3.1
164 |
165 | - Add optimize-js plugin for performance in script version
166 |
167 | ## 1.3.0
168 |
169 | - Add hashIt.isUndefined, hashIt.isNull, and hashIt.isEmpty methods
170 | - Reorder switch statements in replacer and getValueForStringification to reflect most likely to
171 | least likely (improves performance a touch)
172 | - Remove "Number" from number stringification
173 | - Leverage prependTypeToString whereever possible
174 | - Include Arguments object class
175 |
176 | ## 1.2.1
177 |
178 | - Calculation of Math hashCode now uses properties
179 | - Fix README
180 |
181 | ## 1.2.0
182 |
183 | - Add hashIt.isEqual method to test for equality
184 | - Prepend all values not string or number with object class name to help avoid collision with
185 | equivalent string values
186 |
187 | ## 1.1.0
188 |
189 | - Add support for a variety of more object types
190 | - Fix replacer not using same stringifier for int arrays
191 |
192 | ## 1.0.0
193 |
194 | - Initial release
195 |
--------------------------------------------------------------------------------
/DEV_ONLY/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createRoot } from 'react-dom/client';
3 | import hash from '../src';
4 |
5 | document.body.style.backgroundColor = '#1d1d1d';
6 | document.body.style.color = '#d5d5d5';
7 | document.body.style.margin = '0px';
8 | document.body.style.padding = '0px';
9 |
10 | const fragment = document.createDocumentFragment();
11 |
12 | fragment.appendChild(document.createElement('div'));
13 | fragment.appendChild(document.createElement('ul'));
14 | fragment.appendChild(document.createElement('span'));
15 |
16 | class StatefulComponent extends React.Component {
17 | render() {
18 | return
test
;
19 | }
20 | }
21 |
22 | const StatelessComponent = () => test
;
23 |
24 | const a: Record = {
25 | foo: 'bar',
26 | };
27 |
28 | const b = {
29 | a,
30 | };
31 |
32 | a.b = b;
33 |
34 | function getArguments() {
35 | return arguments;
36 | }
37 |
38 | const object = {
39 | ReactStatefulClass: StatefulComponent,
40 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
41 | // @ts-ignore
42 | ReactStatefulElement: ,
43 | ReactStatelessClass: StatelessComponent,
44 | ReactStatelessElement: ,
45 | // @ts-expect-error - arguments are not defined
46 | arguments: getArguments('foo', 'bar'),
47 | arr: ['foo', 'bar'],
48 | arrayBuffer: new Uint16Array([1, 2, 3]).buffer,
49 | bigint: BigInt(9007199254740991),
50 | bool: true,
51 | dataView: new DataView(new ArrayBuffer(2)),
52 | doc: document,
53 | el: document.createElement('div'),
54 | err: new Error('Stuff'),
55 | event: (() => {
56 | try {
57 | return new Event('custom');
58 | } catch (error) {
59 | const event = document.createEvent('Event');
60 |
61 | event.initEvent('custom', true, true);
62 |
63 | return event;
64 | }
65 | })(),
66 | float32Array: new Float32Array([1, 2, 3]),
67 | float64Array: new Float64Array([1, 2, 3]),
68 | fragment,
69 | func() {
70 | alert('y');
71 | },
72 | *generator(): Generator {
73 | const value = yield 1;
74 |
75 | yield value + 2;
76 | },
77 | int8Array: new Int8Array([1, 2, 3]),
78 | int16Array: new Int16Array([1, 2, 3]),
79 | int32Array: new Int32Array([1, 2, 3]),
80 | map: new Map().set(true, 7).set({ foo: 3 }, ['abc']),
81 | math: Math,
82 | nil: null,
83 | nodeList: document.querySelectorAll('div'),
84 | num: 12,
85 | obj: {
86 | foo: 'bar',
87 | },
88 | promise: Promise.resolve(1),
89 | regexp: /test/,
90 | set: new Set().add('foo').add(2),
91 | string: 'foo',
92 | svg: document.createElementNS('http://www.w3.org/2000/svg', 'svg'),
93 | symbol: Symbol('test'),
94 | uint8Array: new Uint8Array([1, 2, 3]),
95 | uint8ClampedArray: new Uint8ClampedArray([1, 2, 3]),
96 | uint16Array: new Uint16Array([1, 2, 3]),
97 | uint32Array: new Uint32Array([1, 2, 3]),
98 | undef: undefined,
99 | weakMap: new WeakMap().set({}, 7).set({ foo: 3 }, ['abc']),
100 | weakSet: new WeakSet().add({}).add({ foo: 'bar' }),
101 | win: window,
102 | };
103 |
104 | // const profile = (iterations = 100) => {
105 | // let index = -1;
106 |
107 | // console.profile('hash timing');
108 |
109 | // while (++index < iterations) {
110 | // hash(object);
111 | // hash(a);
112 |
113 | // for (const key in object) {
114 | // // @ts-expect-error - not worth it
115 | // hash(object[key]);
116 | // }
117 |
118 | // hash(document.body);
119 | // // this is the killer, so only profiled if you uncomment
120 | // // hash(window);
121 | // }
122 |
123 | // console.profileEnd('hash timing');
124 |
125 | // console.log('Check the Profiles tab in DevTools to see the output.');
126 | // };
127 |
128 | // const benchmark = () => require('../benchmarks/index');
129 |
130 | const visualValidation = () => {
131 | console.log(object, hash(object));
132 | console.log(a, hash(a));
133 | console.log(object.string, hash(object.string));
134 | console.log(object.num, hash(object.num));
135 | console.log(object.bigint, hash(object.bigint));
136 | console.log(object.bool, hash(object.bool));
137 | console.log(object.func, hash(object.func));
138 | console.log(object.undef, hash(object.undef));
139 | console.log(object.nil, hash(object.nil));
140 | console.log(object.obj, hash(object.obj));
141 | console.log(object.arr, hash(object.arr));
142 | console.log(object.el, hash(object.el));
143 | console.log(object.fragment, hash(object.fragment));
144 | console.log(object.svg, hash(object.svg));
145 | console.log(object.regexp, hash(object.regexp));
146 | console.log(object.event, hash(object.event));
147 | console.log(object.nodeList, hash(object.nodeList));
148 |
149 | // comment out for older browser testing
150 | console.log(object.symbol, hash(object.symbol));
151 | console.log(object.err, hash(object.err));
152 | console.log(object.map, hash(object.map));
153 | console.log(object.set, hash(object.set));
154 | console.log(object.weakMap, hash(object.weakMap));
155 | console.log(object.weakSet, hash(object.weakSet));
156 | console.log(object.dataView, hash(object.dataView));
157 | console.log(object.arrayBuffer, hash(object.arrayBuffer));
158 | console.log(object.float32Array, hash(object.float32Array));
159 | console.log(object.float64Array, hash(object.float64Array));
160 | console.log(object.generator, hash(object.generator));
161 | console.log(object.int8Array, hash(object.int8Array));
162 | console.log(object.int16Array, hash(object.int16Array));
163 | console.log(object.int32Array, hash(object.int32Array));
164 | console.log(object.promise, hash(object.promise));
165 | console.log(object.uint8Array, hash(object.uint8Array));
166 | console.log(object.uint8ClampedArray, hash(object.uint8ClampedArray));
167 | console.log(object.uint16Array, hash(object.uint16Array));
168 | console.log(object.uint32Array, hash(object.uint32Array));
169 |
170 | console.log(object.ReactStatefulClass, hash(object.ReactStatefulClass));
171 | console.log(object.ReactStatefulElement, hash(object.ReactStatefulElement));
172 | console.log(object.ReactStatelessClass, hash(object.ReactStatelessClass));
173 | console.log(object.ReactStatelessElement, hash(object.ReactStatelessElement));
174 | console.log(object.doc.body, hash(object.doc.body));
175 | console.log(object.win, hash(object.win));
176 | };
177 |
178 | // const hashOnlyValidation = () => {
179 | // console.log(hash(object));
180 | // console.log(hash(a));
181 | // console.log(hash(object.string));
182 | // console.log(hash(object.num));
183 | // console.log(hash(object.bool));
184 | // console.log(hash(object.func));
185 | // console.log(hash(object.undef));
186 | // console.log(hash(object.nil));
187 | // console.log(hash(object.obj));
188 | // console.log(hash(object.arr));
189 | // console.log(hash(object.el));
190 | // console.log(hash(object.fragment));
191 | // console.log(hash(object.svg));
192 | // console.log(hash(object.math));
193 | // console.log(hash(object.regexp));
194 | // console.log(hash(object.event));
195 | // console.log(hash(object.nodeList));
196 |
197 | // // comment out for older browser testing
198 | // console.log(hash(object.symbol));
199 | // console.log(hash(object.err));
200 | // console.log(hash(object.map));
201 | // console.log(hash(object.set));
202 | // console.log(hash(object.weakMap));
203 | // console.log(hash(object.weakSet));
204 | // console.log(hash(object.arrayBuffer));
205 | // console.log(hash(object.dataView));
206 | // console.log(hash(object.float32Array));
207 | // console.log(hash(object.float64Array));
208 | // console.log(hash(object.generator));
209 | // console.log(hash(object.int8Array));
210 | // console.log(hash(object.int16Array));
211 | // console.log(hash(object.int32Array));
212 | // console.log(hash(object.promise));
213 | // console.log(hash(object.uint8Array));
214 | // console.log(hash(object.uint8ClampedArray));
215 | // console.log(hash(object.uint16Array));
216 | // console.log(hash(object.uint32Array));
217 |
218 | // console.log(hash(object.ReactStatefulClass));
219 | // console.log(hash(object.ReactStatefulElement));
220 | // console.log(hash(object.ReactStatelessClass));
221 | // console.log(hash(object.ReactStatelessElement));
222 | // console.log(hash(document.body));
223 | // console.log(hash(window));
224 | // };
225 |
226 | // benchmark();
227 | // profile(1000);
228 | visualValidation();
229 | // hashOnlyValidation();
230 |
231 | const promise = Promise.resolve(123);
232 |
233 | console.log(hash(promise));
234 | console.log(hash(promise));
235 | console.log(hash(Promise.resolve(123)));
236 |
237 | const div = document.createElement('div');
238 |
239 | const root = createRoot(div);
240 |
241 | document.body.appendChild(div);
242 |
243 | root.render(Check the console for more details!
);
244 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Plant The Idea
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # hash-it
2 |
3 | Fast and consistent hashCode for any object type
4 |
5 | ## Table of contents
6 |
7 | - [hash-it](#hash-it)
8 | - [Table of contents](#table-of-contents)
9 | - [Usage](#usage)
10 | - [Overview](#overview)
11 | - [Hash consistency](#hash-consistency)
12 | - [Support](#support)
13 | - [Browsers](#browsers)
14 | - [Node](#node)
15 | - [Development](#development)
16 |
17 | ## Usage
18 |
19 | ```javascript
20 | // ES2015
21 | import hash from 'hash-it';
22 |
23 | // CommonJS
24 | const hash = require('hash-it');
25 |
26 | // hash any standard object
27 | console.log(hash({ foo: 'bar' })); // 13729774857125
28 |
29 | // or a circular object
30 | console.log(hash(window)); // 3270731309314
31 | ```
32 |
33 | ## Overview
34 |
35 | `hash-it` has a simple goal: provide a fast, consistent, unique hashCode for any object type that is uniquely based on its values. This has a number of uses such as duplication prevention, equality comparisons, blockchain construction, etc.
36 |
37 | _Any object type?_
38 |
39 | Yes, any object type. Primitives, ES2015 classes like `Symbol`, DOM elements (yes, you can even hash the `window` object if you want). Any object type. Here is the list of object classes that produce consistent, unique hashes based on their value:
40 |
41 | - `Arguments`
42 | - `Array`
43 | - `ArrayBuffer`
44 | - `AsyncFunction` (based on `toString`)
45 | - `AsyncGeneratorFunction` (based on `toString`)
46 | - `BigInt`
47 | - `BigInt64Array`
48 | - `BigUint64Array`
49 | - `Boolean`
50 | - `DataView` (based on its `buffer`)
51 | - `Date` (based on `getTime`)
52 | - `DocumentFragment` (based on `outerHTML` of all `children`)
53 | - `Error` (based on `stack`)
54 | - Includes all sub-types (e.g., `TypeError`, `ReferenceError`, etc.)
55 | - `Event` (based on all properties other than `Event.timeStamp`)
56 | - Includes all sub-types (e.g., `MouseEvent`, `KeyboardEvent`, etc.)
57 | - `Float32Array`
58 | - `Float64Array`
59 | - `Function` (based on `toString`)
60 | - `GeneratorFunction` (based on `toString`)
61 | - `Int8Array`
62 | - `Int16Array`
63 | - `Int32Array`
64 | - `HTMLElement` (based on `outerHTML`)
65 | - Includes all sub-types (e.g., `HTMLAnchorElement`, `HTMLDivElement`, etc.)
66 | - `Map` (order-agnostic)
67 | - `Null`
68 | - `Number`
69 | - `Object` (handles circular objects, order-agnostic)
70 | - `Proxy`
71 | - `RegExp`
72 | - `Set` (order-agnostic)
73 | - `SharedArrayBuffer`
74 | - `String`
75 | - `SVGElement` (based on `outerHTML`)
76 | - Includes all sub-types (e.g., `SVGRectElement`, `SVGPolygonElement`, etc.)
77 | - `Symbol` (based on `toString`)
78 | - `Uint8Array`
79 | - `Uint8ClampedArray`
80 | - `Uint16Array`
81 | - `Uint32Array`
82 | - `Undefined`
83 | - `Window`
84 |
85 | _Are there any exceptions?_
86 |
87 | Sadly, yes, there are a few scenarios where internal values cannot be introspected for the object. In this case, the object is hashed based on its class type and reference.
88 |
89 | - `Promise`
90 | - There is no way to obtain the values contained within due to its asynchronous nature
91 | - `Generator` (the result of calling a `GeneratorFunction`)
92 | - Like `Promise`, there is no way to obtain the values contained within due to its dynamic iterable nature
93 | - `WeakMap` / `WeakRef` / `WeakSet`
94 | - The spec explicitly forbids iteration over them, so the unique values cannot be discovered
95 |
96 | ```ts
97 | const promise = Promise.resolve(123);
98 |
99 | console.log(hash(promise)); // 16843037491939
100 | console.log(hash(promise)); // 16843037491939
101 | console.log(hash(Promise.resolve(123))); // 4622327363876
102 | ```
103 |
104 | If there is an object class or data type that is missing, please submit an issue.
105 |
106 | ## Hash consistency
107 |
108 | While the hashes will be consistent when calculated within the same environment, there is no guarantee that the resulting hash will be the same across different environments due to environment-specific or browser-specific implementations of features. This is limited to extreme edge cases, such as hashing the `window` object, but should be considered if being used with persistence over different environments.
109 |
110 | ## Support
111 |
112 | ### Browsers
113 |
114 | - Chrome (all versions)
115 | - Firefox (all versions)
116 | - Edge (all versions)
117 | - Opera 15+
118 | - IE 9+
119 | - Safari 6+
120 | - iOS 8+
121 | - Android 4+
122 |
123 | ### Node
124 |
125 | - 4+
126 |
127 | ## Development
128 |
129 | Clone the repo and dependencies via `yarn`. The npm scripts available:
130 |
131 | - `benchmark` => run benchmark of various data types
132 | - `benchmark:compare` => run benchmark of some data types comparing against other hashing modules
133 | - `build` => run rollup to build ESM, CJS, and UMD files
134 | - `clean` => remove files produced from `build` script
135 | - `dev` => run webpack dev server to run example app / playground
136 | - `lint` => run ESLint against all files in the `src` folder
137 | - `lint:fix` => run `lint` script, automatically applying fixable changes
138 | - `prepublishOnly` => run `typecheck`, `lint`, `test`, and `build`
139 | - `start` => alias for `dev` script
140 | - `test` => run jest test functions with `NODE_ENV=test`
141 | - `test:coverage` => run `test` with coverage checker
142 | - `test:watch` => run `test` with persistent watcher
143 | - `typecheck` => run `tsc` to validate internal typings
144 |
--------------------------------------------------------------------------------
/__tests__/__helpers__/values.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-unused-vars */
2 |
3 | export const INTEGER_ARRAY = [1, 2, 3];
4 | // @ts-expect-error - BigInt values not supported with lower targets.
5 | export const BIG_INTEGER_ARRAY = [21n, 31n];
6 |
7 | export const ARRAY_BUFFER = new Uint16Array(INTEGER_ARRAY).buffer;
8 | export const DATE = new Date();
9 | export const ERROR = new Error('boom');
10 |
11 | const DOCUMENT_FRAGMENT = document.createDocumentFragment();
12 | DOCUMENT_FRAGMENT.appendChild(document.createElement('div'));
13 |
14 | const EVENT = new Event('custom');
15 |
16 | export const TEST_VALUES = [
17 | {
18 | comparator: 'deepEqual',
19 | key: '',
20 | value: {},
21 | },
22 | {
23 | comparator: 'deepEqual',
24 | key: 'array',
25 | value: ['foo', 'bar'],
26 | },
27 | {
28 | comparator: 'deepEqual',
29 | key: 'arguments',
30 | value: (function (_a: string, _b: string) {
31 | return arguments;
32 | })('foo', 'bar'),
33 | },
34 | {
35 | comparator: 'deepEqual',
36 | key: 'arrayBuffer',
37 | value: ARRAY_BUFFER,
38 | },
39 | {
40 | comparator: 'is',
41 | key: 'bigInt',
42 | value: BigInt(9007199254740991),
43 | },
44 | {
45 | comparator: 'is',
46 | key: 'bigInt64Array',
47 | value: new BigInt64Array(BIG_INTEGER_ARRAY),
48 | },
49 | {
50 | comparator: 'is',
51 | key: 'bigUint64Array',
52 | value: new BigUint64Array(BIG_INTEGER_ARRAY),
53 | },
54 | {
55 | comparator: 'is',
56 | key: 'boolean',
57 | value: true,
58 | },
59 | {
60 | comparator: 'deepEqual',
61 | key: 'dataView',
62 | value: new DataView(new ArrayBuffer(2)),
63 | },
64 | {
65 | comparator: 'is',
66 | key: 'date',
67 | value: DATE,
68 | },
69 | {
70 | comparator: 'is',
71 | key: 'error',
72 | value: ERROR,
73 | },
74 | {
75 | comparator: 'deepEqual',
76 | key: 'event',
77 | value: EVENT,
78 | },
79 | {
80 | comparator: 'is',
81 | key: 'float32Array',
82 | value: new Float32Array(INTEGER_ARRAY),
83 | },
84 | {
85 | comparator: 'is',
86 | key: 'float64Array',
87 | value: new Float64Array(INTEGER_ARRAY),
88 | },
89 | {
90 | comparator: 'is',
91 | key: 'function',
92 | // eslint-disable-next-line @typescript-eslint/no-empty-function
93 | value() {},
94 | },
95 | {
96 | comparator: 'is',
97 | key: 'generator',
98 | value: (() => {
99 | function* gen() {
100 | yield 1;
101 | yield 2;
102 | yield 3;
103 | }
104 |
105 | return gen();
106 | })(),
107 | },
108 | {
109 | comparator: 'is',
110 | key: 'generatorFunction',
111 | // eslint-disable-next-line @typescript-eslint/no-empty-function
112 | *value() {},
113 | },
114 | {
115 | comparator: 'deepEqual',
116 | key: 'htmlElement',
117 | value: (() => {
118 | const div = document.createElement('div');
119 |
120 | div.textContent = 'foo';
121 | div.className = 'class';
122 |
123 | return div;
124 | })(),
125 | },
126 | {
127 | comparator: 'deepEqual',
128 | key: 'svgElement',
129 | value: (() =>
130 | document.createElementNS('http://www.w3.org/2000/svg', 'svg'))(),
131 | },
132 | {
133 | comparator: 'deepEqual',
134 | key: 'documentFragment',
135 | value: DOCUMENT_FRAGMENT,
136 | },
137 | {
138 | comparator: 'deepEqual',
139 | key: 'int8Array',
140 | value: new Int8Array(INTEGER_ARRAY),
141 | },
142 | {
143 | comparator: 'deepEqual',
144 | key: 'int16Array',
145 | value: new Int16Array(INTEGER_ARRAY),
146 | },
147 | {
148 | comparator: 'deepEqual',
149 | key: 'int32Array',
150 | value: new Int32Array(INTEGER_ARRAY),
151 | },
152 | {
153 | comparator: 'deepEqual',
154 | key: 'map',
155 | value: new Map().set('foo', 'bar'),
156 | },
157 | {
158 | comparator: 'is',
159 | expectedResult: 'null|null',
160 | expectedString: 'null|null',
161 | key: 'null',
162 | value: null,
163 | },
164 | {
165 | comparator: 'is',
166 | key: 'number',
167 | value: 12,
168 | },
169 | {
170 | comparator: 'deepEqual',
171 | key: 'object',
172 | value: { foo: 'bar' },
173 | },
174 | {
175 | comparator: 'is',
176 | key: 'promise',
177 | value: Promise.resolve(1),
178 | },
179 | {
180 | comparator: 'is',
181 | key: 'regexp',
182 | value: /foo/,
183 | },
184 | {
185 | comparator: 'deepEqual',
186 | key: 'set',
187 | value: new Set().add('foo'),
188 | },
189 | {
190 | comparator: 'is',
191 | key: 'string',
192 | value: 'foo',
193 | },
194 | {
195 | comparator: 'is',
196 | key: 'symbol',
197 | value: Symbol('foo'),
198 | },
199 | {
200 | comparator: 'deepEqual',
201 | key: 'uint8Array',
202 | value: new Uint8Array(INTEGER_ARRAY),
203 | },
204 | {
205 | comparator: 'deepEqual',
206 | key: 'uint8ClampedArray',
207 | value: new Uint8ClampedArray(INTEGER_ARRAY),
208 | },
209 | {
210 | comparator: 'deepEqual',
211 | key: 'uint16Array',
212 | value: new Uint16Array(INTEGER_ARRAY),
213 | },
214 | {
215 | comparator: 'deepEqual',
216 | key: 'uint32Array',
217 | value: new Uint32Array(INTEGER_ARRAY),
218 | },
219 | {
220 | comparator: 'is',
221 | key: 'undefined',
222 | value: undefined,
223 | },
224 | {
225 | comparator: 'is',
226 | key: 'weakMap',
227 | value: new WeakMap().set({}, 'foo'),
228 | },
229 | {
230 | comparator: 'is',
231 | key: 'weakSet',
232 | value: new WeakSet().add({}),
233 | },
234 | {
235 | comparator: 'is',
236 | key: 'weakRef',
237 | value: new WeakRef({}),
238 | },
239 | ];
240 |
--------------------------------------------------------------------------------
/__tests__/hash.ts:
--------------------------------------------------------------------------------
1 | import { hash } from '../src/hash';
2 |
3 | describe('hash', () => {
4 | it('should return the correct value', () => {
5 | const string = 'foo';
6 |
7 | expect(hash(string)).toBe(794144147649);
8 | });
9 | });
10 |
--------------------------------------------------------------------------------
/__tests__/index.ts:
--------------------------------------------------------------------------------
1 | import hash from '../src/index';
2 |
3 | import { TEST_VALUES } from './__helpers__/values';
4 | import WORDS from './__helpers__/words.json';
5 |
6 | const VALUES = TEST_VALUES.map(({ key, value }) => ({ key, value }));
7 | const VALUE_HASHES = VALUES.reduce((map, { key, value }) => {
8 | map[key] = hash(value);
9 |
10 | return map;
11 | }, {} as Record);
12 |
13 | const CONSISTENCY_ITERATIONS = 10000;
14 |
15 | describe('hash', () => {
16 | it('should have hashed values that are non-zero', () => {
17 | VALUES.forEach(({ value }) => {
18 | expect(hash(value)).not.toBe(0);
19 | });
20 | });
21 |
22 | it('should have a unique hash', () => {
23 | VALUES.forEach(({ key, value }) => {
24 | VALUES.forEach(({ key: otherKey, value: otherValue }) => {
25 | if (value !== otherValue) {
26 | expect(
27 | hash(value),
28 | `{ key: ${key}, otherKey: ${otherKey} }`,
29 | ).not.toBe(hash(otherValue));
30 | }
31 | });
32 | });
33 | });
34 |
35 | it('should have a consistent hash', () => {
36 | TEST_VALUES.forEach(({ key, value }) => {
37 | for (let index = 0; index < CONSISTENCY_ITERATIONS; ++index) {
38 | expect(hash(value)).toBe(VALUE_HASHES[key]);
39 | }
40 | });
41 | });
42 |
43 | it(`should not have collisions with ${WORDS.length} integers`, () => {
44 | const collision: Record = {};
45 |
46 | let count = 0;
47 |
48 | WORDS.forEach((word: string, index: number) => {
49 | const result = hash(index);
50 |
51 | if (collision[result]) {
52 | count++;
53 | }
54 |
55 | collision[result] = word;
56 | });
57 |
58 | expect(count).toBe(0);
59 | });
60 |
61 | it(`should not have collisions with ${WORDS.length} strings`, () => {
62 | const collision: Record = {};
63 |
64 | let count = 0;
65 |
66 | WORDS.forEach((word: string) => {
67 | const result = hash(word);
68 |
69 | if (collision[result]) {
70 | count++;
71 | }
72 |
73 | collision[result] = word;
74 | });
75 |
76 | expect(count).toBe(0);
77 | });
78 |
79 | it('should hash based on a sorted Map', () => {
80 | const map1 = new Map([
81 | ['foo', 'bar'],
82 | ['bar', 'foo'],
83 | ]);
84 | const map2 = new Map([
85 | ['bar', 'foo'],
86 | ['foo', 'bar'],
87 | ]);
88 |
89 | expect(hash(map1)).toBe(hash(map2));
90 | });
91 |
92 | it('should hash based on a sorted Set', () => {
93 | const set1 = new Set(['foo', 'bar']);
94 | const set2 = new Set(['bar', 'foo']);
95 |
96 | expect(hash(set1)).toBe(hash(set2));
97 | });
98 |
99 | it('should hash the same non-enumerable references the same, but unique references differently', () => {
100 | [
101 | () =>
102 | (function* foo() {
103 | yield true;
104 | })(),
105 | () => Promise.resolve(),
106 | () => new WeakMap(),
107 | () => new WeakSet(),
108 | ].forEach((getItem) => {
109 | const item1 = getItem();
110 | const item2 = getItem();
111 |
112 | expect(hash(item1)).toBe(hash(item1));
113 | expect(hash(item1)).not.toBe(hash(item2));
114 | });
115 | });
116 |
117 | it('should support primitive wrappers', () => {
118 | [
119 | [
120 | // eslint-disable-next-line @typescript-eslint/no-empty-function
121 | async function () {},
122 | // eslint-disable-next-line @typescript-eslint/no-empty-function
123 | async function () {},
124 | async function () {
125 | await console.log('foo');
126 | },
127 | ],
128 | [
129 | // eslint-disable-next-line @typescript-eslint/no-empty-function
130 | async function* () {},
131 | // eslint-disable-next-line @typescript-eslint/no-empty-function
132 | async function* () {},
133 | async function* () {
134 | await console.log('foo');
135 | },
136 | ],
137 | [
138 | // eslint-disable-next-line @typescript-eslint/no-empty-function
139 | function () {},
140 | // eslint-disable-next-line @typescript-eslint/no-empty-function
141 | function () {},
142 | function () {
143 | console.log('foo');
144 | },
145 | ],
146 | [
147 | // eslint-disable-next-line @typescript-eslint/no-empty-function
148 | function* () {},
149 | // eslint-disable-next-line @typescript-eslint/no-empty-function
150 | function* () {},
151 | function* () {
152 | console.log('foo');
153 | },
154 | ],
155 | [new Boolean(true), new Boolean(true), new Boolean(false)],
156 | [new Number('123'), new Number('123'), new Number('234')],
157 | [new String('foo'), new String('foo'), new String('bar')],
158 | ].forEach(([item, equalItem, unequalItem]) => {
159 | expect(hash(item)).toBe(hash(equalItem));
160 | expect(hash(item)).not.toBe(hash(unequalItem));
161 | });
162 | });
163 | });
164 |
--------------------------------------------------------------------------------
/__tests__/stringify.ts:
--------------------------------------------------------------------------------
1 | import {
2 | stringifyArrayBufferFallback,
3 | stringifyArrayBufferModern,
4 | stringifyArrayBufferNone,
5 | } from '../src/stringify';
6 | import { ARRAY_BUFFER, INTEGER_ARRAY } from './__helpers__/values';
7 |
8 | describe('ArrayBuffer support', () => {
9 | it('should support modern usage', () => {
10 | const stringified = 'stringified';
11 | const toString = jest.fn(() => stringified);
12 |
13 | const fromBuffer = Buffer.from;
14 |
15 | const spy = jest.spyOn(Buffer, 'from').mockImplementation((buffer) => {
16 | const result = fromBuffer(buffer);
17 |
18 | result.toString = toString;
19 |
20 | return result;
21 | });
22 |
23 | const result = stringifyArrayBufferModern(ARRAY_BUFFER);
24 |
25 | expect(spy).toHaveBeenCalledWith(ARRAY_BUFFER);
26 |
27 | spy.mockRestore();
28 |
29 | expect(result).toBe(stringified);
30 | });
31 |
32 | it('should support fallback usage', () => {
33 | const stringified = 'stringified';
34 |
35 | const spy = jest.spyOn(String, 'fromCharCode').mockReturnValue(stringified);
36 |
37 | const result = stringifyArrayBufferFallback(ARRAY_BUFFER);
38 |
39 | expect(spy).toHaveBeenCalledWith(...INTEGER_ARRAY);
40 |
41 | spy.mockRestore();
42 |
43 | expect(result).toBe(stringified);
44 | });
45 |
46 | it('should handle no support', () => {
47 | const result = stringifyArrayBufferNone();
48 |
49 | expect(result).toBe('UNSUPPORTED');
50 | });
51 | });
52 |
--------------------------------------------------------------------------------
/benchmarks/compare.js:
--------------------------------------------------------------------------------
1 | import Benchmark from 'benchmark';
2 | import { faker } from '@faker-js/faker';
3 | import objectHash from 'object-hash';
4 | import createNodeObjectHash from 'node-object-hash';
5 | import hashObject from 'hash-object';
6 | import hash from '../dist/esm/index.mjs';
7 |
8 | const nodeObjectHash = createNodeObjectHash();
9 | const suite = new Benchmark.Suite();
10 | const dataArray = [];
11 |
12 | let dataStairs = { end: 'is near' };
13 |
14 | console.log('Creating fake data...');
15 |
16 | for (let i = 0; i < 50; i++) {
17 | dataArray.push({
18 | name: faker.name.firstName(),
19 | date: new Date(),
20 | address: {
21 | city: faker.address.city(),
22 | streetAddress: faker.address.streetAddress(),
23 | country: faker.address.country(),
24 | },
25 | email: [
26 | faker.internet.email(),
27 | faker.internet.email(),
28 | faker.internet.email(),
29 | faker.internet.email(),
30 | ],
31 | randoms: [
32 | faker.datatype.number(),
33 | faker.random.alphaNumeric(),
34 | faker.datatype.number(),
35 | faker.random.alphaNumeric(),
36 | faker.random.words(),
37 | faker.random.word(),
38 | ],
39 | avatars: [
40 | {
41 | number: faker.datatype.number(),
42 | avatar: faker.internet.avatar(),
43 | },
44 | {
45 | number: faker.datatype.number(),
46 | avatar: faker.internet.avatar(),
47 | },
48 | {
49 | number: faker.datatype.number(),
50 | avatar: faker.internet.avatar(),
51 | },
52 | {
53 | number: faker.datatype.number(),
54 | avatar: faker.internet.avatar(),
55 | },
56 | ],
57 | });
58 | }
59 |
60 | let tmp;
61 |
62 | for (let i = 0; i < 100; i++) {
63 | tmp = {
64 | data: dataStairs,
65 | };
66 | dataStairs = tmp;
67 | }
68 |
69 | // test preparations
70 | const hashObjectOpts = { algorithm: 'sha256' };
71 | const objectHashOpts = {
72 | algorithm: 'sha256',
73 | encoding: 'hex',
74 | unorderedArrays: true,
75 | };
76 |
77 | console.log('Running benchmarks...');
78 |
79 | // add tests
80 | suite
81 | .add('hash-object', () => {
82 | hashObject(dataStairs, hashObjectOpts);
83 | hashObject(dataArray, hashObjectOpts);
84 | })
85 | .add('hash-it', () => {
86 | hash(dataStairs);
87 | hash(dataArray);
88 | })
89 | .add('node-object-hash', () => {
90 | nodeObjectHash.hash(dataStairs);
91 | nodeObjectHash.hash(dataArray);
92 | })
93 | .add('object-hash', () => {
94 | objectHash(dataStairs, objectHashOpts);
95 | objectHash(dataArray, objectHashOpts);
96 | })
97 | // add listeners
98 | .on('cycle', (event) => {
99 | console.log(String(event.target));
100 | })
101 | .on('complete', function () {
102 | const fastest = this.filter('fastest').map('name');
103 |
104 | console.log(`Fastest is ${fastest}`);
105 | })
106 | // run async
107 | .run({ async: true });
108 |
--------------------------------------------------------------------------------
/benchmarks/index.js:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs';
2 | import {
3 | hashArray,
4 | hashBoolean,
5 | hashCircularObject,
6 | hashDate,
7 | hashError,
8 | hashEvent,
9 | hashFunction,
10 | hashInfinity,
11 | hashMap,
12 | hashNaN,
13 | hashNull,
14 | hashNumber,
15 | hashObject,
16 | hashRegExp,
17 | hashSet,
18 | hashString,
19 | hashUndefined,
20 | } from './tests.js';
21 |
22 | const REPEATS = [1000, 5000, 10000, 50000, 100000, 500000, 1000000, 5000000];
23 | const TOTAL = REPEATS.reduce((sum, cycles) => sum + cycles, 0);
24 |
25 | function runTest(name, benchmark) {
26 | let totalTime = 0;
27 | let startTime;
28 | let testTime;
29 |
30 | let displayText = `\n${name}:\n${REPEATS.map((cycles) => {
31 | startTime = Date.now();
32 |
33 | benchmark(cycles);
34 |
35 | testTime = Date.now() - startTime;
36 |
37 | if (global && global.gc) {
38 | global.gc();
39 | }
40 |
41 | totalTime += testTime;
42 |
43 | return `${cycles.toLocaleString()}: ${testTime / 1000} sec`;
44 | }).join('\n')}`;
45 |
46 | displayText += `\nAverage: ${Math.round(
47 | (TOTAL / totalTime) * 1000,
48 | ).toLocaleString()} ops/sec`;
49 |
50 | return displayText;
51 | }
52 |
53 | const header = () =>
54 | `Benchmark cycles: ${REPEATS.map((cycles) => cycles.toLocaleString()).join(
55 | ' ',
56 | )}`;
57 |
58 | let results = [];
59 |
60 | const logAndSave = (it) => {
61 | results.push(it);
62 | console.log(it);
63 | };
64 |
65 | console.log('');
66 |
67 | // header
68 | logAndSave(header());
69 |
70 | console.log('');
71 | console.log('Starting benchmarks...');
72 | console.log('');
73 |
74 | console.log('Standard value objects:');
75 |
76 | // primitive tests
77 | logAndSave(runTest('Boolean', hashBoolean));
78 | logAndSave(runTest('Infinity', hashInfinity));
79 | logAndSave(runTest('NaN', hashNaN));
80 | logAndSave(runTest('null', hashNull));
81 | logAndSave(runTest('Number', hashNumber));
82 | logAndSave(runTest('String', hashString));
83 | logAndSave(runTest('undefined', hashUndefined));
84 | logAndSave(runTest('Function', hashFunction));
85 | logAndSave(runTest('RegExp', hashRegExp));
86 |
87 | console.log('');
88 | console.log('Nested value objects:');
89 | console.log('');
90 |
91 | // complex tests
92 | logAndSave(runTest('Array', hashArray));
93 | logAndSave(runTest('Date', hashDate));
94 | logAndSave(runTest('Error', hashError));
95 | logAndSave(runTest('Event', hashEvent));
96 | logAndSave(runTest('Map', hashMap));
97 | logAndSave(runTest('Object', hashObject));
98 | logAndSave(runTest('Object (circular)', hashCircularObject));
99 | logAndSave(runTest('Set', hashSet));
100 |
101 | console.log('');
102 |
103 | // write to file
104 | if (fs && fs.writeFileSync) {
105 | fs.writeFileSync('results_latest.txt', results.join('\n'), 'utf8');
106 | console.log('Benchmarks done! Results saved to results_latest.txt');
107 | } else {
108 | console.log('Benchmarks done!');
109 | }
110 |
--------------------------------------------------------------------------------
/benchmarks/tests.js:
--------------------------------------------------------------------------------
1 | import hash from '../dist/esm/index.mjs';
2 |
3 | function createHashTest(data) {
4 | return function hashTest(cycles) {
5 | let index = -1,
6 | val;
7 |
8 | while (++index < cycles) {
9 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
10 | val = hash(data);
11 | }
12 | };
13 | }
14 |
15 | const boolean = true;
16 | const infinite = Infinity;
17 | const notANumber = NaN;
18 | const nul = null;
19 | const number = 123;
20 | const string = 'foo';
21 | const undef = undefined;
22 |
23 | export const hashBoolean = createHashTest(boolean);
24 | export const hashInfinity = createHashTest(infinite);
25 | export const hashNaN = createHashTest(notANumber);
26 | export const hashNull = createHashTest(nul);
27 | export const hashNumber = createHashTest(number);
28 | export const hashString = createHashTest(string);
29 | export const hashUndefined = createHashTest(undef);
30 |
31 | const array = [boolean, infinite, notANumber, nul, number, string, undef];
32 | const circularObject = (() => {
33 | function Circular(value) {
34 | this.deeply = {
35 | nested: {
36 | reference: this,
37 | value,
38 | },
39 | };
40 | }
41 |
42 | return new Circular();
43 | })();
44 | const date = new Date(2000, 0, 1);
45 | const error = new Error('boom');
46 | const event = new Event('click');
47 | // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
48 | const fn = function (_foo, _bar) {};
49 | const map = new Map([
50 | ['foo', 'bar'],
51 | ['bar', 'baz'],
52 | ]);
53 | const object = {
54 | boolean,
55 | infinite,
56 | notANumber,
57 | nul,
58 | number,
59 | string,
60 | undef,
61 | };
62 | const regExp = /foo/g;
63 | const set = new Set(['foo', 'bar', 'baz']);
64 |
65 | export const hashArray = createHashTest(array);
66 | export const hashDate = createHashTest(date);
67 | export const hashError = createHashTest(error);
68 | export const hashEvent = createHashTest(event);
69 | export const hashFunction = createHashTest(fn);
70 | export const hashMap = createHashTest(map);
71 | export const hashObject = createHashTest(object);
72 | export const hashCircularObject = createHashTest(circularObject);
73 | export const hashRegExp = createHashTest(regExp);
74 | export const hashSet = createHashTest(set);
75 |
--------------------------------------------------------------------------------
/build/rollup/config.base.js:
--------------------------------------------------------------------------------
1 | import commonjs from '@rollup/plugin-commonjs';
2 | import { nodeResolve } from '@rollup/plugin-node-resolve';
3 | import replace from '@rollup/plugin-replace';
4 | import typescript from '@rollup/plugin-typescript';
5 | import fs from 'fs';
6 | import path from 'path';
7 | import tsc from 'typescript';
8 | import { fileURLToPath } from 'url';
9 |
10 | const ROOT = fileURLToPath(new URL('../..', import.meta.url));
11 |
12 | export const PACKAGE_JSON = JSON.parse(
13 | fs.readFileSync(path.resolve(ROOT, 'package.json')),
14 | );
15 |
16 | const external = [
17 | ...Object.keys(PACKAGE_JSON.dependencies || {}),
18 | ...Object.keys(PACKAGE_JSON.peerDependencies || {}),
19 | ];
20 | const globals = external.reduce((globals, name) => {
21 | globals[name] = name;
22 |
23 | return globals;
24 | }, {});
25 |
26 | export const BASE_CONFIG = {
27 | external,
28 | input: path.resolve(ROOT, 'src', 'index.ts'),
29 | output: {
30 | exports: 'named',
31 | globals,
32 | name: 'hash-it',
33 | sourcemap: true,
34 | },
35 | plugins: [
36 | replace({
37 | 'process.env.NODE_ENV': JSON.stringify('production'),
38 | preventAssignment: true,
39 | }),
40 | nodeResolve({
41 | mainFields: ['module', 'browser', 'main'],
42 | }),
43 | commonjs({ include: /use-sync-external-store/ }),
44 | typescript({
45 | tsconfig: path.resolve(ROOT, 'build', 'tsconfig', 'base.json'),
46 | typescript: tsc,
47 | }),
48 | ],
49 | };
50 |
--------------------------------------------------------------------------------
/build/rollup/config.cjs.js:
--------------------------------------------------------------------------------
1 | import { BASE_CONFIG, PACKAGE_JSON } from './config.base.js';
2 |
3 | export default {
4 | ...BASE_CONFIG,
5 | output: {
6 | ...BASE_CONFIG.output,
7 | file: PACKAGE_JSON.main,
8 | format: 'cjs',
9 | },
10 | };
11 |
--------------------------------------------------------------------------------
/build/rollup/config.esm.js:
--------------------------------------------------------------------------------
1 | import { BASE_CONFIG, PACKAGE_JSON } from './config.base.js';
2 |
3 | export default {
4 | ...BASE_CONFIG,
5 | output: {
6 | ...BASE_CONFIG.output,
7 | file: PACKAGE_JSON.module,
8 | format: 'es',
9 | },
10 | };
11 |
--------------------------------------------------------------------------------
/build/rollup/config.min.js:
--------------------------------------------------------------------------------
1 | import { BASE_CONFIG, PACKAGE_JSON } from './config.base.js';
2 | import terser from '@rollup/plugin-terser';
3 |
4 | export default {
5 | ...BASE_CONFIG,
6 | output: {
7 | ...BASE_CONFIG.output,
8 | file: PACKAGE_JSON.browser.replace('umd', 'min'),
9 | format: 'umd',
10 | sourcemap: false,
11 | },
12 | plugins: [...BASE_CONFIG.plugins, terser()],
13 | };
14 |
--------------------------------------------------------------------------------
/build/rollup/config.umd.js:
--------------------------------------------------------------------------------
1 | import { BASE_CONFIG, PACKAGE_JSON } from './config.base.js';
2 |
3 | export default {
4 | ...BASE_CONFIG,
5 | output: {
6 | ...BASE_CONFIG.output,
7 | file: PACKAGE_JSON.browser,
8 | format: 'umd',
9 | },
10 | };
11 |
--------------------------------------------------------------------------------
/build/tsconfig/base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowUnreachableCode": false,
4 | "baseUrl": "../../src",
5 | "esModuleInterop": true,
6 | "jsx": "react-jsx",
7 | "lib": ["DOM", "ESNext"],
8 | "module": "ESNext",
9 | "moduleResolution": "Node",
10 | "noFallthroughCasesInSwitch": true,
11 | "noImplicitAny": true,
12 | "noImplicitReturns": true,
13 | "noImplicitThis": true,
14 | "noUncheckedIndexedAccess": true,
15 | "noUnusedLocals": true,
16 | "noUnusedParameters": true,
17 | "outDir": "../../../dist",
18 | "resolveJsonModule": true,
19 | "skipLibCheck": true,
20 | "sourceMap": true,
21 | "strict": true,
22 | "strictBindCallApply": true,
23 | "strictNullChecks": true,
24 | "inlineSources": true,
25 | "target": "es5",
26 | "types": ["jest", "node", "react"]
27 | },
28 | "exclude": ["../../node_modules"],
29 | "include": ["../../src/**/*", "../../__tests__/**/*", "../../DEV_ONLY/**/*"],
30 | "files": ["../../node_modules/jest-expect-message/types/index.d.ts"]
31 | }
32 |
--------------------------------------------------------------------------------
/build/tsconfig/cjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./declarations.json",
3 | "compilerOptions": {
4 | "declarationDir": "../../dist/cjs/types",
5 | "module": "CommonJS",
6 | "outDir": "../../dist/cjs"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/build/tsconfig/declarations.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./base.json",
3 | "compilerOptions": {
4 | "declaration": true,
5 | "emitDeclarationOnly": true,
6 | "resolveJsonModule": false
7 | },
8 | "include": ["../../src/*"]
9 | }
10 |
--------------------------------------------------------------------------------
/build/tsconfig/esm.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./declarations.json",
3 | "compilerOptions": {
4 | "declarationDir": "../../dist/esm/types",
5 | "module": "ESNext",
6 | "outDir": "../../dist/esm"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/build/tsconfig/min.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./declarations.json",
3 | "compilerOptions": {
4 | "declarationDir": "../../dist/min/types",
5 | "module": "UMD",
6 | "outDir": "../../dist/min"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/build/tsconfig/umd.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./declarations.json",
3 | "compilerOptions": {
4 | "declarationDir": "../../dist/umd/types",
5 | "module": "UMD",
6 | "outDir": "../../dist/umd"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/build/webpack.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-var-requires */
2 |
3 | import ESLintWebpackPlugin from 'eslint-webpack-plugin';
4 | import HtmlWebpackPlugin from 'html-webpack-plugin';
5 | import path from 'path';
6 | import { fileURLToPath } from 'url';
7 | import webpack from 'webpack';
8 |
9 | const ROOT = fileURLToPath(new URL('..', import.meta.url));
10 | const PORT = 3000;
11 |
12 | export default {
13 | cache: true,
14 |
15 | devServer: {
16 | host: 'localhost',
17 | port: PORT,
18 | },
19 |
20 | devtool: 'source-map',
21 |
22 | entry: [path.resolve(ROOT, 'DEV_ONLY', 'index.tsx')],
23 |
24 | mode: 'development',
25 |
26 | module: {
27 | rules: [
28 | {
29 | include: [path.resolve(ROOT, 'src'), path.resolve(ROOT, 'DEV_ONLY')],
30 | loader: 'ts-loader',
31 | options: {
32 | reportFiles: ['src/*.{ts|tsx}'],
33 | },
34 | test: /\.(ts|tsx)$/,
35 | },
36 | ],
37 | },
38 |
39 | output: {
40 | filename: 'hash-it.js',
41 | library: 'hashIt',
42 | libraryTarget: 'umd',
43 | path: path.resolve(ROOT, 'dist'),
44 | publicPath: `http://localhost:${PORT}/`,
45 | umdNamedDefine: true,
46 | },
47 |
48 | plugins: [
49 | new ESLintWebpackPlugin(),
50 | new webpack.EnvironmentPlugin(['NODE_ENV']),
51 | new HtmlWebpackPlugin(),
52 | ],
53 |
54 | resolve: {
55 | extensions: ['.tsx', '.ts', '.js'],
56 | },
57 | };
58 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | export default function hashIt(value: Value): number;
2 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('ts-jest').JestConfigWithTsJest} */
2 | export default {
3 | coveragePathIgnorePatterns: ['node_modules', 'src/types.ts', '__helpers__'],
4 | preset: 'ts-jest',
5 | setupFilesAfterEnv: ['jest-expect-message'],
6 | testEnvironment: 'jsdom',
7 | testPathIgnorePatterns: ['/__tests__/__helpers__', '/benchmark'],
8 | transform: {
9 | // '^.+\\.[tj]sx?$' to process js/ts with `ts-jest`
10 | // '^.+\\.m?[tj]sx?$' to process js/ts/mjs/mts with `ts-jest`
11 | '^.+\\.tsx?$': [
12 | 'ts-jest',
13 | {
14 | tsconfig: '/build/tsconfig/base.json'
15 | },
16 | ],
17 | },
18 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "planttheidea",
3 | "browser": "dist/umd/index.js",
4 | "browserslist": [
5 | "defaults",
6 | "Explorer >= 9",
7 | "Safari >= 6",
8 | "Opera >= 15",
9 | "iOS >= 8",
10 | "Android >= 4"
11 | ],
12 | "bugs": {
13 | "url": "https://github.com/planttheidea/hash-it/issues"
14 | },
15 | "description": "Hash any object based on its value",
16 | "devDependencies": {
17 | "@faker-js/faker": "^7.6.0",
18 | "@rollup/plugin-commonjs": "^24.0.0",
19 | "@rollup/plugin-node-resolve": "^15.0.1",
20 | "@rollup/plugin-replace": "^5.0.2",
21 | "@rollup/plugin-terser": "^0.3.0",
22 | "@rollup/plugin-typescript": "^11.0.0",
23 | "@types/jest": "^29.2.5",
24 | "@types/react": "^18.0.26",
25 | "@types/react-dom": "^18.0.10",
26 | "@typescript-eslint/eslint-plugin": "^5.48.0",
27 | "@typescript-eslint/parser": "^5.48.0",
28 | "benchmark": "^2.1.4",
29 | "eslint": "^8.31.0",
30 | "eslint-friendly-formatter": "^4.0.1",
31 | "eslint-webpack-plugin": "^3.2.0",
32 | "hash-object": "^0.1.7",
33 | "html-webpack-plugin": "^5.5.0",
34 | "husky": "^8.0.3",
35 | "jest": "^29.3.1",
36 | "jest-environment-jsdom": "^29.3.1",
37 | "jest-expect-message": "^1.1.3",
38 | "node-object-hash": "^2.3.10",
39 | "object-hash": "^3.0.0",
40 | "ora": "^6.1.2",
41 | "prop-types": "^15.8.1",
42 | "react": "^18.2.0",
43 | "react-dom": "^18.2.0",
44 | "release-it": "^15.6.0",
45 | "rimraf": "^3.0.2",
46 | "rollup": "^3.9.1",
47 | "ts-jest": "^29.0.3",
48 | "ts-loader": "^9.4.2",
49 | "tslib": "^2.4.1",
50 | "typescript": "^4.9.4",
51 | "webpack": "^5.75.0",
52 | "webpack-cli": "^5.0.1",
53 | "webpack-dev-server": "^4.11.1"
54 | },
55 | "exports": {
56 | ".": {
57 | "import": {
58 | "types": "./dist/esm/types/index.d.ts",
59 | "default": "./dist/esm/index.mjs"
60 | },
61 | "require": {
62 | "types": "./dist/cjs/types/index.d.ts",
63 | "default": "./dist/cjs/index.cjs"
64 | },
65 | "default": {
66 | "types": "./dist/umd/types/index.d.ts",
67 | "default": "./dist/umd/index.js"
68 | }
69 | }
70 | },
71 | "homepage": "https://github.com/planttheidea/hash-it#readme",
72 | "keywords": [
73 | "hash",
74 | "hashcode",
75 | "object-hash",
76 | "unique"
77 | ],
78 | "license": "MIT",
79 | "main": "dist/cjs/index.cjs",
80 | "module": "dist/esm/index.mjs",
81 | "name": "hash-it",
82 | "repository": {
83 | "type": "git",
84 | "url": "git+https://github.com/planttheidea/hash-it.git"
85 | },
86 | "resolutions": {
87 | "decompress-response": "^8.0.0"
88 | },
89 | "scripts": {
90 | "benchmark": "npm run build:esm && node benchmarks/index.js",
91 | "benchmark:compare": "npm run build:esm && node benchmarks/compare.js",
92 | "build": "npm run build:esm && npm run build:cjs && npm run build:umd && npm run build:min",
93 | "build:cjs": "rimraf dist/cjs && NODE_ENV=production rollup -c build/rollup/config.cjs.js && tsc -p ./build/tsconfig/cjs.json",
94 | "build:esm": "rimraf dist/esm && NODE_ENV=production rollup -c build/rollup/config.esm.js && tsc -p ./build/tsconfig/esm.json",
95 | "build:min": "rimraf dist/min && NODE_ENV=production rollup -c build/rollup/config.min.js && tsc -p ./build/tsconfig/min.json",
96 | "build:umd": "rimraf dist/umd && NODE_ENV=production rollup -c build/rollup/config.umd.js && tsc -p ./build/tsconfig/umd.json",
97 | "clean": "rimraf dist",
98 | "dev": "NODE_ENV=development webpack serve --progress --config=build/webpack.config.js",
99 | "lint": "NODE_ENV=test eslint src",
100 | "lint:fix": "npm run lint -- --fix",
101 | "release": "release-it",
102 | "release:beta": "release-it --config=.release-it.beta.json",
103 | "release:scripts": "npm run typecheck && npm run lint && npm run test && npm run build",
104 | "start": "npm run dev",
105 | "test": "NODE_ENV=test jest",
106 | "test:coverage": "npm test -- --coverage",
107 | "test:watch": "npm test -- --watch",
108 | "typecheck": "tsc --noEmit"
109 | },
110 | "type": "module",
111 | "types": "./index.d.ts",
112 | "version": "6.0.0"
113 | }
114 |
--------------------------------------------------------------------------------
/results_2.1.2.txt:
--------------------------------------------------------------------------------
1 | Benchmark cycles: 1,000 5,000 10,000 50,000 100,000 500,000 1,000,000 5,000,000
2 |
3 | Boolean:
4 | 1,000: 0.002 sec
5 | 5,000: 0.006 sec
6 | 10,000: 0.009 sec
7 | 50,000: 0.036 sec
8 | 100,000: 0.07 sec
9 | 500,000: 0.353 sec
10 | 1,000,000: 0.706 sec
11 | 5,000,000: 3.532 sec
12 | Average: 1,414,086 ops/sec
13 |
14 | Infinity:
15 | 1,000: 0 sec
16 | 5,000: 0.002 sec
17 | 10,000: 0.005 sec
18 | 50,000: 0.016 sec
19 | 100,000: 0.03 sec
20 | 500,000: 0.153 sec
21 | 1,000,000: 0.304 sec
22 | 5,000,000: 1.521 sec
23 | Average: 3,282,127 ops/sec
24 |
25 | NaN:
26 | 1,000: 0 sec
27 | 5,000: 0.002 sec
28 | 10,000: 0.004 sec
29 | 50,000: 0.016 sec
30 | 100,000: 0.03 sec
31 | 500,000: 0.152 sec
32 | 1,000,000: 0.305 sec
33 | 5,000,000: 1.52 sec
34 | Average: 3,285,362 ops/sec
35 |
36 | null:
37 | 1,000: 0.001 sec
38 | 5,000: 0.001 sec
39 | 10,000: 0.005 sec
40 | 50,000: 0.015 sec
41 | 100,000: 0.03 sec
42 | 500,000: 0.153 sec
43 | 1,000,000: 0.305 sec
44 | 5,000,000: 1.529 sec
45 | Average: 3,269,250 ops/sec
46 |
47 | Number:
48 | 1,000: 0 sec
49 | 5,000: 0.002 sec
50 | 10,000: 0.004 sec
51 | 50,000: 0.013 sec
52 | 100,000: 0.028 sec
53 | 500,000: 0.137 sec
54 | 1,000,000: 0.271 sec
55 | 5,000,000: 1.359 sec
56 | Average: 3,674,752 ops/sec
57 |
58 | String:
59 | 1,000: 0 sec
60 | 5,000: 0 sec
61 | 10,000: 0.002 sec
62 | 50,000: 0.003 sec
63 | 100,000: 0.006 sec
64 | 500,000: 0.029 sec
65 | 1,000,000: 0.059 sec
66 | 5,000,000: 0.293 sec
67 | Average: 17,005,102 ops/sec
68 |
69 | undefined:
70 | 1,000: 0 sec
71 | 5,000: 0.003 sec
72 | 10,000: 0.005 sec
73 | 50,000: 0.022 sec
74 | 100,000: 0.044 sec
75 | 500,000: 0.22 sec
76 | 1,000,000: 0.441 sec
77 | 5,000,000: 2.201 sec
78 | Average: 2,270,436 ops/sec
79 |
80 | Function:
81 | 1,000: 0.002 sec
82 | 5,000: 0.007 sec
83 | 10,000: 0.012 sec
84 | 50,000: 0.056 sec
85 | 100,000: 0.111 sec
86 | 500,000: 0.556 sec
87 | 1,000,000: 1.117 sec
88 | 5,000,000: 5.535 sec
89 | Average: 901,298 ops/sec
90 |
91 | RegExp:
92 | 1,000: 0.003 sec
93 | 5,000: 0.007 sec
94 | 10,000: 0.016 sec
95 | 50,000: 0.071 sec
96 | 100,000: 0.142 sec
97 | 500,000: 0.706 sec
98 | 1,000,000: 1.42 sec
99 | 5,000,000: 7.074 sec
100 | Average: 706,219 ops/sec
101 |
102 | Array:
103 | 1,000: 0.007 sec
104 | 5,000: 0.031 sec
105 | 10,000: 0.058 sec
106 | 50,000: 0.284 sec
107 | 100,000: 0.57 sec
108 | 500,000: 2.838 sec
109 | 1,000,000: 5.668 sec
110 | 5,000,000: 28.313 sec
111 | Average: 176,494 ops/sec
112 |
113 | Map:
114 | 1,000: 0.011 sec
115 | 5,000: 0.046 sec
116 | 10,000: 0.092 sec
117 | 50,000: 0.449 sec
118 | 100,000: 0.901 sec
119 | 500,000: 4.498 sec
120 | 1,000,000: 8.996 sec
121 | 5,000,000: 44.969 sec
122 | Average: 111,170 ops/sec
123 |
124 | Object:
125 | 1,000: 0.011 sec
126 | 5,000: 0.049 sec
127 | 10,000: 0.098 sec
128 | 50,000: 0.48 sec
129 | 100,000: 0.957 sec
130 | 500,000: 4.797 sec
131 | 1,000,000: 9.58 sec
132 | 5,000,000: 47.898 sec
133 | Average: 104,368 ops/sec
134 |
135 | Object (recursive):
136 | 1,000: 0.006 sec
137 | 5,000: 0.022 sec
138 | 10,000: 0.045 sec
139 | 50,000: 0.214 sec
140 | 100,000: 0.43 sec
141 | 500,000: 2.155 sec
142 | 1,000,000: 4.321 sec
143 | 5,000,000: 21.637 sec
144 | Average: 231,217 ops/sec
145 |
146 | Set:
147 | 1,000: 0.013 sec
148 | 5,000: 0.06 sec
149 | 10,000: 0.119 sec
150 | 50,000: 0.59 sec
151 | 100,000: 1.179 sec
152 | 500,000: 5.937 sec
153 | 1,000,000: 11.895 sec
154 | 5,000,000: 58.968 sec
155 | Average: 84,636 ops/sec
--------------------------------------------------------------------------------
/results_3.0.0.txt:
--------------------------------------------------------------------------------
1 | Benchmark cycles: 1,000 5,000 10,000 50,000 100,000 500,000 1,000,000 5,000,000
2 |
3 | Boolean:
4 | 1,000: 0.002 sec
5 | 5,000: 0.005 sec
6 | 10,000: 0.006 sec
7 | 50,000: 0.014 sec
8 | 100,000: 0.015 sec
9 | 500,000: 0.078 sec
10 | 1,000,000: 0.156 sec
11 | 5,000,000: 0.774 sec
12 | Average: 6,348,571 ops/sec
13 |
14 | Infinity:
15 | 1,000: 0 sec
16 | 5,000: 0.003 sec
17 | 10,000: 0.009 sec
18 | 50,000: 0.028 sec
19 | 100,000: 0.057 sec
20 | 500,000: 0.244 sec
21 | 1,000,000: 0.482 sec
22 | 5,000,000: 2.531 sec
23 | Average: 1,987,478 ops/sec
24 |
25 | NaN:
26 | 1,000: 0.001 sec
27 | 5,000: 0.003 sec
28 | 10,000: 0.009 sec
29 | 50,000: 0.032 sec
30 | 100,000: 0.049 sec
31 | 500,000: 0.246 sec
32 | 1,000,000: 0.499 sec
33 | 5,000,000: 2.442 sec
34 | Average: 2,031,698 ops/sec
35 |
36 | null:
37 | 1,000: 0.001 sec
38 | 5,000: 0.002 sec
39 | 10,000: 0.003 sec
40 | 50,000: 0.013 sec
41 | 100,000: 0.016 sec
42 | 500,000: 0.077 sec
43 | 1,000,000: 0.154 sec
44 | 5,000,000: 0.808 sec
45 | Average: 6,206,704 ops/sec
46 |
47 | Number:
48 | 1,000: 0.001 sec
49 | 5,000: 0.002 sec
50 | 10,000: 0.007 sec
51 | 50,000: 0.026 sec
52 | 100,000: 0.048 sec
53 | 500,000: 0.239 sec
54 | 1,000,000: 0.48 sec
55 | 5,000,000: 2.46 sec
56 | Average: 2,042,905 ops/sec
57 |
58 | String:
59 | 1,000: 0.001 sec
60 | 5,000: 0 sec
61 | 10,000: 0.001 sec
62 | 50,000: 0.007 sec
63 | 100,000: 0.003 sec
64 | 500,000: 0.015 sec
65 | 1,000,000: 0.031 sec
66 | 5,000,000: 0.148 sec
67 | Average: 32,359,223 ops/sec
68 |
69 | undefined:
70 | 1,000: 0.001 sec
71 | 5,000: 0.001 sec
72 | 10,000: 0.004 sec
73 | 50,000: 0.015 sec
74 | 100,000: 0.03 sec
75 | 500,000: 0.159 sec
76 | 1,000,000: 0.282 sec
77 | 5,000,000: 1.508 sec
78 | Average: 3,333,000 ops/sec
79 |
80 | Function:
81 | 1,000: 0.001 sec
82 | 5,000: 0.004 sec
83 | 10,000: 0.012 sec
84 | 50,000: 0.039 sec
85 | 100,000: 0.074 sec
86 | 500,000: 0.357 sec
87 | 1,000,000: 0.737 sec
88 | 5,000,000: 3.327 sec
89 | Average: 1,464,733 ops/sec
90 |
91 | RegExp:
92 | 1,000: 0.001 sec
93 | 5,000: 0.004 sec
94 | 10,000: 0.013 sec
95 | 50,000: 0.038 sec
96 | 100,000: 0.072 sec
97 | 500,000: 0.363 sec
98 |
99 | 1,000,000: 0.731 sec
100 | 5,000,000: 3.728 sec
101 | Average: 1,346,667 ops/sec
102 |
103 | Array:
104 | 1,000: 0.006 sec
105 | 5,000: 0.018 sec
106 | 10,000: 0.041 sec
107 | 50,000: 0.175 sec
108 | 100,000: 0.296 sec
109 | 500,000: 1.497 sec
110 | 1,000,000: 2.956 sec
111 | 5,000,000: 15.282 sec
112 | Average: 328,844 ops/sec
113 |
114 | Map:
115 | 1,000: 0.006 sec
116 | 5,000: 0.021 sec
117 | 10,000: 0.043 sec
118 | 50,000: 0.16 sec
119 | 100,000: 0.328 sec
120 | 500,000: 1.468 sec
121 | 1,000,000: 2.929 sec
122 | 5,000,000: 15.159 sec
123 | Average: 331,411 ops/sec
124 |
125 | Object:
126 | 1,000: 0.005 sec
127 | 5,000: 0.019 sec
128 | 10,000: 0.044 sec
129 | 50,000: 0.181 sec
130 | 100,000: 0.366 sec
131 | 500,000: 1.854 sec
132 | 1,000,000: 3.641 sec
133 | 5,000,000: 18.252 sec
134 | Average: 273,623 ops/sec
135 |
136 | Object (circular):
137 | 1,000: 0.002 sec
138 | 5,000: 0.011 sec
139 | 10,000: 0.019 sec
140 | 50,000: 0.07 sec
141 | 100,000: 0.135 sec
142 | 500,000: 0.698 sec
143 | 1,000,000: 1.369 sec
144 | 5,000,000: 7.074 sec
145 | Average: 710,813 ops/sec
146 |
147 | Set:
148 | 1,000: 0.004 sec
149 | 5,000: 0.019 sec
150 | 10,000: 0.044 sec
151 | 50,000: 0.187 sec
152 | 100,000: 0.373 sec
153 | 500,000: 1.877 sec
154 | 1,000,000: 3.858 sec
155 | 5,000,000: 18.738 sec
156 | Average: 265,578 ops/sec
157 |
--------------------------------------------------------------------------------
/results_4.1.0.txt:
--------------------------------------------------------------------------------
1 | Benchmark cycles: 1,000 5,000 10,000 50,000 100,000 500,000 1,000,000 5,000,000
2 |
3 | Boolean:
4 | 1,000: 0.002 sec
5 | 5,000: 0.003 sec
6 | 10,000: 0.004 sec
7 | 50,000: 0.005 sec
8 | 100,000: 0.007 sec
9 | 500,000: 0.034 sec
10 | 1,000,000: 0.062 sec
11 | 5,000,000: 0.312 sec
12 | Average: 15,538,462 ops/sec
13 |
14 | Infinity:
15 | 1,000: 0.001 sec
16 | 5,000: 0.002 sec
17 | 10,000: 0.004 sec
18 | 50,000: 0.007 sec
19 | 100,000: 0.013 sec
20 | 500,000: 0.064 sec
21 | 1,000,000: 0.122 sec
22 | 5,000,000: 0.612 sec
23 | Average: 8,080,000 ops/sec
24 |
25 | NaN:
26 | 1,000: 0.001 sec
27 | 5,000: 0 sec
28 | 10,000: 0.003 sec
29 | 50,000: 0.005 sec
30 | 100,000: 0.007 sec
31 | 500,000: 0.037 sec
32 | 1,000,000: 0.069 sec
33 | 5,000,000: 0.347 sec
34 | Average: 14,213,220 ops/sec
35 |
36 | null:
37 | 1,000: 0 sec
38 | 5,000: 0.001 sec
39 | 10,000: 0.001 sec
40 | 50,000: 0.003 sec
41 | 100,000: 0.006 sec
42 | 500,000: 0.031 sec
43 | 1,000,000: 0.058 sec
44 | 5,000,000: 0.287 sec
45 | Average: 17,224,806 ops/sec
46 |
47 | Number:
48 | 1,000: 0 sec
49 | 5,000: 0.001 sec
50 | 10,000: 0.001 sec
51 | 50,000: 0.005 sec
52 | 100,000: 0.007 sec
53 | 500,000: 0.035 sec
54 | 1,000,000: 0.065 sec
55 | 5,000,000: 0.326 sec
56 | Average: 15,150,000 ops/sec
57 |
58 | String:
59 | 1,000: 0 sec
60 | 5,000: 0 sec
61 | 10,000: 0.003 sec
62 | 50,000: 0.004 sec
63 | 100,000: 0.006 sec
64 | 500,000: 0.03 sec
65 | 1,000,000: 0.056 sec
66 | 5,000,000: 0.278 sec
67 | Average: 17,681,698 ops/sec
68 |
69 | undefined:
70 | 1,000: 0 sec
71 | 5,000: 0.001 sec
72 | 10,000: 0.003 sec
73 | 50,000: 0.01 sec
74 | 100,000: 0.015 sec
75 | 500,000: 0.078 sec
76 | 1,000,000: 0.152 sec
77 | 5,000,000: 0.756 sec
78 | Average: 6,567,488 ops/sec
79 |
80 | Function:
81 | 1,000: 0.001 sec
82 | 5,000: 0.002 sec
83 | 10,000: 0.005 sec
84 | 50,000: 0.016 sec
85 | 100,000: 0.034 sec
86 | 500,000: 0.157 sec
87 | 1,000,000: 0.314 sec
88 | 5,000,000: 1.573 sec
89 | Average: 3,171,265 ops/sec
90 |
91 | RegExp:
92 | 1,000: 0.001 sec
93 | 5,000: 0.003 sec
94 | 10,000: 0.008 sec
95 | 50,000: 0.023 sec
96 | 100,000: 0.044 sec
97 | 500,000: 0.22 sec
98 | 1,000,000: 0.439 sec
99 | 5,000,000: 2.196 sec
100 | Average: 2,271,984 ops/sec
101 |
102 | Array:
103 | 1,000: 0.005 sec
104 | 5,000: 0.017 sec
105 | 10,000: 0.034 sec
106 | 50,000: 0.141 sec
107 | 100,000: 0.282 sec
108 | 500,000: 1.416 sec
109 | 1,000,000: 2.849 sec
110 | 5,000,000: 14.519 sec
111 | Average: 346,052 ops/sec
112 |
113 | Map:
114 | 1,000: 0.004 sec
115 | 5,000: 0.013 sec
116 | 10,000: 0.024 sec
117 | 50,000: 0.084 sec
118 | 100,000: 0.169 sec
119 | 500,000: 0.837 sec
120 | 1,000,000: 1.664 sec
121 | 5,000,000: 8.27 sec
122 | Average: 602,440 ops/sec
123 |
124 | Object:
125 | 1,000: 0.007 sec
126 | 5,000: 0.02 sec
127 | 10,000: 0.041 sec
128 | 50,000: 0.172 sec
129 | 100,000: 0.362 sec
130 | 500,000: 1.717 sec
131 | 1,000,000: 3.431 sec
132 | 5,000,000: 17.187 sec
133 | Average: 290,622 ops/sec
134 |
135 | Object (circular):
136 | 1,000: 0.004 sec
137 | 5,000: 0.014 sec
138 | 10,000: 0.031 sec
139 | 50,000: 0.126 sec
140 | 100,000: 0.249 sec
141 | 500,000: 1.253 sec
142 | 1,000,000: 2.512 sec
143 | 5,000,000: 12.591 sec
144 | Average: 397,259 ops/sec
145 |
146 | Set:
147 | 1,000: 0.003 sec
148 | 5,000: 0.011 sec
149 | 10,000: 0.022 sec
150 | 50,000: 0.081 sec
151 | 100,000: 0.157 sec
152 | 500,000: 0.799 sec
153 | 1,000,000: 1.558 sec
154 | 5,000,000: 7.934 sec
155 | Average: 630,951 ops/sec
--------------------------------------------------------------------------------
/results_5.0.2.txt:
--------------------------------------------------------------------------------
1 | Benchmark cycles: 1,000 5,000 10,000 50,000 100,000 500,000 1,000,000 5,000,000
2 |
3 | Boolean:
4 | 1,000: 0.002 sec
5 | 5,000: 0.003 sec
6 | 10,000: 0.003 sec
7 | 50,000: 0.005 sec
8 | 100,000: 0.007 sec
9 | 500,000: 0.027 sec
10 | 1,000,000: 0.048 sec
11 | 5,000,000: 0.235 sec
12 | Average: 20,200,000 ops/sec
13 |
14 | Infinity:
15 | 1,000: 0.001 sec
16 | 5,000: 0.001 sec
17 | 10,000: 0.002 sec
18 | 50,000: 0.006 sec
19 | 100,000: 0.013 sec
20 | 500,000: 0.056 sec
21 | 1,000,000: 0.101 sec
22 | 5,000,000: 0.508 sec
23 | Average: 9,688,953 ops/sec
24 |
25 | NaN:
26 | 1,000: 0 sec
27 | 5,000: 0 sec
28 | 10,000: 0.001 sec
29 | 50,000: 0.005 sec
30 | 100,000: 0.005 sec
31 | 500,000: 0.029 sec
32 | 1,000,000: 0.05 sec
33 | 5,000,000: 0.238 sec
34 | Average: 20,323,171 ops/sec
35 |
36 | null:
37 | 1,000: 0 sec
38 | 5,000: 0.001 sec
39 | 10,000: 0.001 sec
40 | 50,000: 0.005 sec
41 | 100,000: 0.008 sec
42 | 500,000: 0.028 sec
43 | 1,000,000: 0.047 sec
44 | 5,000,000: 0.227 sec
45 | Average: 21,028,391 ops/sec
46 |
47 | Number:
48 | 1,000: 0 sec
49 | 5,000: 0.001 sec
50 | 10,000: 0.002 sec
51 | 50,000: 0.004 sec
52 | 100,000: 0.005 sec
53 | 500,000: 0.029 sec
54 | 1,000,000: 0.05 sec
55 | 5,000,000: 0.231 sec
56 | Average: 20,701,863 ops/sec
57 |
58 | String:
59 | 1,000: 0 sec
60 | 5,000: 0 sec
61 | 10,000: 0.001 sec
62 | 50,000: 0.006 sec
63 | 100,000: 0.005 sec
64 | 500,000: 0.03 sec
65 | 1,000,000: 0.067 sec
66 | 5,000,000: 0.254 sec
67 | Average: 18,363,636 ops/sec
68 |
69 | undefined:
70 | 1,000: 0 sec
71 | 5,000: 0.001 sec
72 | 10,000: 0.004 sec
73 | 50,000: 0.009 sec
74 | 100,000: 0.013 sec
75 | 500,000: 0.061 sec
76 | 1,000,000: 0.116 sec
77 | 5,000,000: 0.586 sec
78 | Average: 8,437,975 ops/sec
79 |
80 | Function:
81 | 1,000: 0.001 sec
82 | 5,000: 0.001 sec
83 | 10,000: 0.005 sec
84 | 50,000: 0.016 sec
85 | 100,000: 0.028 sec
86 | 500,000: 0.131 sec
87 | 1,000,000: 0.258 sec
88 | 5,000,000: 1.284 sec
89 | Average: 3,866,589 ops/sec
90 |
91 | RegExp:
92 | 1,000: 0.001 sec
93 | 5,000: 0.003 sec
94 | 10,000: 0.007 sec
95 | 50,000: 0.018 sec
96 | 100,000: 0.033 sec
97 | 500,000: 0.152 sec
98 | 1,000,000: 0.307 sec
99 | 5,000,000: 1.529 sec
100 | Average: 3,251,707 ops/sec
101 |
102 | Array:
103 | 1,000: 0.005 sec
104 | 5,000: 0.017 sec
105 | 10,000: 0.034 sec
106 | 50,000: 0.134 sec
107 | 100,000: 0.27 sec
108 | 500,000: 1.33 sec
109 | 1,000,000: 2.635 sec
110 | 5,000,000: 12.868 sec
111 | Average: 385,474 ops/sec
112 |
113 | Map:
114 | 1,000: 0.003 sec
115 | 5,000: 0.014 sec
116 | 10,000: 0.023 sec
117 | 50,000: 0.082 sec
118 | 100,000: 0.159 sec
119 | 500,000: 0.8 sec
120 | 1,000,000: 1.599 sec
121 | 5,000,000: 8.169 sec
122 | Average: 614,435 ops/sec
123 |
124 | Object:
125 | 1,000: 0.007 sec
126 | 5,000: 0.021 sec
127 | 10,000: 0.038 sec
128 | 50,000: 0.149 sec
129 | 100,000: 0.304 sec
130 | 500,000: 1.49 sec
131 | 1,000,000: 3.043 sec
132 | 5,000,000: 14.881 sec
133 | Average: 334,420 ops/sec
134 |
135 | Object (circular):
136 | 1,000: 0.004 sec
137 | 5,000: 0.014 sec
138 | 10,000: 0.03 sec
139 | 50,000: 0.109 sec
140 | 100,000: 0.21 sec
141 | 500,000: 1.052 sec
142 | 1,000,000: 2.089 sec
143 | 5,000,000: 10.427 sec
144 | Average: 478,364 ops/sec
145 |
146 | Set:
147 | 1,000: 0.002 sec
148 | 5,000: 0.011 sec
149 | 10,000: 0.022 sec
150 | 50,000: 0.079 sec
151 | 100,000: 0.149 sec
152 | 500,000: 0.738 sec
153 | 1,000,000: 1.481 sec
154 | 5,000,000: 7.357 sec
155 | Average: 677,508 ops/sec
--------------------------------------------------------------------------------
/results_latest.txt:
--------------------------------------------------------------------------------
1 | Benchmark cycles: 1,000 5,000 10,000 50,000 100,000 500,000 1,000,000 5,000,000
2 |
3 | Boolean:
4 | 1,000: 0 sec
5 | 5,000: 0.002 sec
6 | 10,000: 0.001 sec
7 | 50,000: 0.002 sec
8 | 100,000: 0.002 sec
9 | 500,000: 0.008 sec
10 | 1,000,000: 0.016 sec
11 | 5,000,000: 0.082 sec
12 | Average: 58,991,150 ops/sec
13 |
14 | Infinity:
15 | 1,000: 0 sec
16 | 5,000: 0.001 sec
17 | 10,000: 0.001 sec
18 | 50,000: 0.003 sec
19 | 100,000: 0.003 sec
20 | 500,000: 0.016 sec
21 | 1,000,000: 0.03 sec
22 | 5,000,000: 0.151 sec
23 | Average: 32,517,073 ops/sec
24 |
25 | NaN:
26 | 1,000: 0 sec
27 | 5,000: 0 sec
28 | 10,000: 0.001 sec
29 | 50,000: 0.001 sec
30 | 100,000: 0.002 sec
31 | 500,000: 0.01 sec
32 | 1,000,000: 0.021 sec
33 | 5,000,000: 0.104 sec
34 | Average: 47,956,835 ops/sec
35 |
36 | null:
37 | 1,000: 0.001 sec
38 | 5,000: 0 sec
39 | 10,000: 0.001 sec
40 | 50,000: 0.003 sec
41 | 100,000: 0.002 sec
42 | 500,000: 0.01 sec
43 | 1,000,000: 0.021 sec
44 | 5,000,000: 0.103 sec
45 | Average: 47,276,596 ops/sec
46 |
47 | Number:
48 | 1,000: 0 sec
49 | 5,000: 0 sec
50 | 10,000: 0 sec
51 | 50,000: 0.001 sec
52 | 100,000: 0.002 sec
53 | 500,000: 0.01 sec
54 | 1,000,000: 0.02 sec
55 | 5,000,000: 0.1 sec
56 | Average: 50,120,301 ops/sec
57 |
58 | String:
59 | 1,000: 0 sec
60 | 5,000: 0.001 sec
61 | 10,000: 0.001 sec
62 | 50,000: 0.002 sec
63 | 100,000: 0.003 sec
64 | 500,000: 0.01 sec
65 | 1,000,000: 0.02 sec
66 | 5,000,000: 0.103 sec
67 | Average: 47,614,286 ops/sec
68 |
69 | undefined:
70 | 1,000: 0 sec
71 | 5,000: 0 sec
72 | 10,000: 0.001 sec
73 | 50,000: 0.001 sec
74 | 100,000: 0.004 sec
75 | 500,000: 0.016 sec
76 | 1,000,000: 0.033 sec
77 | 5,000,000: 0.163 sec
78 | Average: 30,577,982 ops/sec
79 |
80 | Function:
81 | 1,000: 0 sec
82 | 5,000: 0.001 sec
83 | 10,000: 0.002 sec
84 | 50,000: 0.007 sec
85 | 100,000: 0.015 sec
86 | 500,000: 0.073 sec
87 | 1,000,000: 0.146 sec
88 | 5,000,000: 0.728 sec
89 | Average: 6,858,025 ops/sec
90 |
91 | RegExp:
92 | 1,000: 0.001 sec
93 | 5,000: 0.002 sec
94 | 10,000: 0.006 sec
95 | 50,000: 0.015 sec
96 | 100,000: 0.03 sec
97 | 500,000: 0.146 sec
98 | 1,000,000: 0.294 sec
99 | 5,000,000: 1.468 sec
100 | Average: 3,397,554 ops/sec
101 |
102 | Array:
103 | 1,000: 0.003 sec
104 | 5,000: 0.005 sec
105 | 10,000: 0.012 sec
106 | 50,000: 0.036 sec
107 | 100,000: 0.074 sec
108 | 500,000: 0.367 sec
109 | 1,000,000: 0.699 sec
110 | 5,000,000: 3.331 sec
111 | Average: 1,472,498 ops/sec
112 |
113 | Date:
114 | 1,000: 0 sec
115 | 5,000: 0.001 sec
116 | 10,000: 0.003 sec
117 | 50,000: 0.01 sec
118 | 100,000: 0.014 sec
119 | 500,000: 0.072 sec
120 | 1,000,000: 0.144 sec
121 | 5,000,000: 0.718 sec
122 | Average: 6,929,314 ops/sec
123 |
124 | Error:
125 | 1,000: 0.001 sec
126 | 5,000: 0.005 sec
127 | 10,000: 0.013 sec
128 | 50,000: 0.049 sec
129 | 100,000: 0.097 sec
130 | 500,000: 0.483 sec
131 | 1,000,000: 0.966 sec
132 | 5,000,000: 4.899 sec
133 | Average: 1,023,491 ops/sec
134 |
135 | Event:
136 | 1,000: 0.002 sec
137 | 5,000: 0.006 sec
138 | 10,000: 0.008 sec
139 | 50,000: 0.03 sec
140 | 100,000: 0.059 sec
141 | 500,000: 0.291 sec
142 | 1,000,000: 0.582 sec
143 | 5,000,000: 2.913 sec
144 | Average: 1,713,184 ops/sec
145 |
146 | Map:
147 | 1,000: 0.001 sec
148 | 5,000: 0.006 sec
149 | 10,000: 0.006 sec
150 | 50,000: 0.03 sec
151 | 100,000: 0.06 sec
152 | 500,000: 0.298 sec
153 | 1,000,000: 0.596 sec
154 | 5,000,000: 2.983 sec
155 | Average: 1,674,874 ops/sec
156 |
157 | Object:
158 | 1,000: 0.003 sec
159 | 5,000: 0.007 sec
160 | 10,000: 0.011 sec
161 | 50,000: 0.055 sec
162 | 100,000: 0.105 sec
163 | 500,000: 0.525 sec
164 | 1,000,000: 1.051 sec
165 | 5,000,000: 5.246 sec
166 | Average: 951,878 ops/sec
167 |
168 | Object (circular):
169 | 1,000: 0.002 sec
170 | 5,000: 0.007 sec
171 | 10,000: 0.01 sec
172 | 50,000: 0.052 sec
173 | 100,000: 0.1 sec
174 | 500,000: 0.497 sec
175 | 1,000,000: 0.994 sec
176 | 5,000,000: 4.984 sec
177 | Average: 1,003,009 ops/sec
178 |
179 | Set:
180 | 1,000: 0.001 sec
181 | 5,000: 0.006 sec
182 | 10,000: 0.007 sec
183 | 50,000: 0.027 sec
184 | 100,000: 0.056 sec
185 | 500,000: 0.275 sec
186 | 1,000,000: 0.549 sec
187 | 5,000,000: 2.749 sec
188 | Average: 1,816,349 ops/sec
--------------------------------------------------------------------------------
/src/cache.ts:
--------------------------------------------------------------------------------
1 | import { SEPARATOR } from './constants';
2 | import { namespaceComplexValue } from './utils';
3 |
4 | import type { Class } from './constants';
5 |
6 | export const NON_ENUMERABLE_CLASS_CACHE = new WeakMap<
7 | NonEnumerableObject,
8 | string
9 | >();
10 |
11 | type NonEnumerableObject =
12 | | Generator
13 | | Promise
14 | | WeakMap
15 | | WeakSet;
16 |
17 | let refId = 0;
18 | export function getUnsupportedHash(
19 | value: NonEnumerableObject,
20 | classType: Class,
21 | ): string {
22 | const cached = NON_ENUMERABLE_CLASS_CACHE.get(value);
23 |
24 | if (cached) {
25 | return cached;
26 | }
27 |
28 | const toCache = namespaceComplexValue(
29 | classType,
30 | 'NOT_ENUMERABLE' + SEPARATOR + refId++,
31 | );
32 |
33 | NON_ENUMERABLE_CLASS_CACHE.set(value, toCache);
34 |
35 | return toCache;
36 | }
37 |
--------------------------------------------------------------------------------
/src/constants.ts:
--------------------------------------------------------------------------------
1 | export const SEPARATOR = '|';
2 | export const XML_ELEMENT_REGEXP = /\[object ([HTML|SVG](.*)Element)\]/;
3 |
4 | export const CLASSES = {
5 | '[object Arguments]': 0,
6 | '[object Array]': 1,
7 | '[object ArrayBuffer]': 2,
8 | '[object AsyncFunction]': 3,
9 | '[object AsyncGeneratorFunction]': 4,
10 | '[object BigInt]': 5,
11 | '[object BigInt64Array]': 6,
12 | '[object BigUint64Array]': 7,
13 | '[object Boolean]': 8,
14 | '[object DataView]': 9,
15 | '[object Date]': 10,
16 | '[object DocumentFragment]': 11,
17 | '[object Error]': 12,
18 | '[object Event]': 13,
19 | '[object Float32Array]': 14,
20 | '[object Float64Array]': 15,
21 | '[object Function]': 16,
22 | '[object Generator]': 17,
23 | '[object GeneratorFunction]': 18,
24 | '[object Int8Array]': 19,
25 | '[object Int16Array]': 20,
26 | '[object Map]': 21,
27 | '[object Number]': 22,
28 | '[object Object]': 23,
29 | '[object Promise]': 24,
30 | '[object RegExp]': 25,
31 | '[object Set]': 26,
32 | '[object SharedArrayBuffer]': 27,
33 | '[object String]': 28,
34 | '[object Uint8Array]': 29,
35 | '[object Uint8ClampedArray]': 30,
36 | '[object Uint16Array]': 31,
37 | '[object Uint32Array]': 32,
38 | '[object WeakMap]': 33,
39 | '[object WeakRef]': 34,
40 | '[object WeakSet]': 35,
41 | CUSTOM: 36,
42 | ELEMENT: 37,
43 | } as const;
44 |
45 | export type Class = keyof typeof CLASSES;
46 |
47 | export const ARRAY_LIKE_CLASSES = {
48 | '[object Arguments]': 1,
49 | '[object Array]': 2,
50 | } as const;
51 |
52 | export type ArrayLikeClass = keyof typeof ARRAY_LIKE_CLASSES;
53 |
54 | export const NON_ENUMERABLE_CLASSES = {
55 | '[object Generator]': 1,
56 | '[object Promise]': 2,
57 | '[object WeakMap]': 3,
58 | '[object WeakRef]': 4,
59 | '[object WeakSet]': 5,
60 | } as const;
61 |
62 | export type NonEnumerableClass = keyof typeof NON_ENUMERABLE_CLASSES;
63 |
64 | export const PRIMITIVE_WRAPPER_CLASSES = {
65 | '[object AsyncFunction]': 1,
66 | '[object AsyncGeneratorFunction]': 2,
67 | '[object Boolean]': 3,
68 | '[object Function]': 4,
69 | '[object GeneratorFunction]': 5,
70 | '[object Number]': 6,
71 | '[object String]': 7,
72 | } as const;
73 |
74 | export type PrimitiveWrapperClass = keyof typeof PRIMITIVE_WRAPPER_CLASSES;
75 |
76 | export const TYPED_ARRAY_CLASSES = {
77 | '[object BigInt64Array]': 1,
78 | '[object BigUint64Array]': 2,
79 | '[object Float32Array]': 3,
80 | '[object Float64Array]': 4,
81 | '[object Int8Array]': 5,
82 | '[object Int16Array]': 6,
83 | '[object Uint8Array]': 7,
84 | '[object Uint8ClampedArray]': 8,
85 | '[object Uint16Array]': 9,
86 | '[object Uint32Array]': 10,
87 | } as const;
88 |
89 | export type TypedArrayClass = keyof typeof TYPED_ARRAY_CLASSES;
90 |
91 | export const RECURSIVE_CLASSES = {
92 | '[object Arguments]': 1,
93 | '[object Array]': 2,
94 | '[object ArrayBuffer]': 3,
95 | '[object BigInt64Array]': 4,
96 | '[object BigUint64Array]': 5,
97 | '[object DataView]': 6,
98 | '[object Float32Array]': 7,
99 | '[object Float64Array]': 8,
100 | '[object Int8Array]': 9,
101 | '[object Int16Array]': 10,
102 | '[object Map]': 11,
103 | '[object Object]': 12,
104 | '[object Set]': 13,
105 | '[object SharedArrayBuffer]': 14,
106 | '[object Uint8Array]': 15,
107 | '[object Uint8ClampedArray]': 16,
108 | '[object Uint16Array]': 17,
109 | '[object Uint32Array]': 18,
110 | CUSTOM: 19,
111 | } as const;
112 |
113 | export type RecursiveClass = keyof typeof RECURSIVE_CLASSES;
114 |
115 | export const HASHABLE_TYPES = {
116 | bigint: 'i',
117 | boolean: 'b',
118 | empty: 'e',
119 | function: 'g',
120 | number: 'n',
121 | object: 'o',
122 | string: 's',
123 | symbol: 's',
124 | } as const;
125 |
126 | export type HashableType = keyof typeof HASHABLE_TYPES;
127 |
--------------------------------------------------------------------------------
/src/hash.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * based on string passed, get the integer hash value
3 | * through bitwise operation (based on spinoff of dbj2
4 | * with enhancements for reduced collisions)
5 | */
6 | export function hash(string: string): number {
7 | let index = string.length;
8 | let hashA = 5381;
9 | let hashB = 52711;
10 | let charCode;
11 |
12 | while (index--) {
13 | charCode = string.charCodeAt(index);
14 |
15 | hashA = (hashA * 33) ^ charCode;
16 | hashB = (hashB * 33) ^ charCode;
17 | }
18 |
19 | return (hashA >>> 0) * 4096 + (hashB >>> 0);
20 | }
21 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { hash } from './hash';
2 | import { stringify } from './stringify';
3 |
4 | export default function hashIt(value: Value): number {
5 | return hash(stringify(value, undefined));
6 | }
7 |
--------------------------------------------------------------------------------
/src/sort.ts:
--------------------------------------------------------------------------------
1 | export function sortByKey(
2 | first: [string, string],
3 | second: [string, string],
4 | ): boolean {
5 | return first[0] > second[0];
6 | }
7 |
8 | export function sortBySelf(first: string, second: string) {
9 | return first > second;
10 | }
11 |
12 | export function sort(
13 | array: any[],
14 | fn: (item: any, comparisonItem: any) => boolean,
15 | ) {
16 | let subIndex;
17 | let value;
18 |
19 | for (let index = 0; index < array.length; ++index) {
20 | value = array[index];
21 |
22 | for (
23 | subIndex = index - 1;
24 | ~subIndex && fn(array[subIndex], value);
25 | --subIndex
26 | ) {
27 | array[subIndex + 1] = array[subIndex];
28 | }
29 |
30 | array[subIndex + 1] = value;
31 | }
32 |
33 | return array;
34 | }
35 |
--------------------------------------------------------------------------------
/src/stringify.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ARRAY_LIKE_CLASSES,
3 | HASHABLE_TYPES,
4 | NON_ENUMERABLE_CLASSES,
5 | PrimitiveWrapperClass,
6 | PRIMITIVE_WRAPPER_CLASSES,
7 | RECURSIVE_CLASSES,
8 | SEPARATOR,
9 | TYPED_ARRAY_CLASSES,
10 | XML_ELEMENT_REGEXP,
11 | } from './constants';
12 | import { sort, sortByKey, sortBySelf } from './sort';
13 |
14 | import type {
15 | ArrayLikeClass,
16 | Class,
17 | NonEnumerableClass,
18 | RecursiveClass,
19 | TypedArrayClass,
20 | } from './constants';
21 | import { getUnsupportedHash } from './cache';
22 | import { namespaceComplexValue } from './utils';
23 |
24 | interface RecursiveState {
25 | cache: WeakMap;
26 | id: number;
27 | }
28 |
29 | const toString = Object.prototype.toString;
30 |
31 | function stringifyComplexType(
32 | value: any,
33 | classType: Class,
34 | state: RecursiveState,
35 | ) {
36 | if (RECURSIVE_CLASSES[classType as RecursiveClass]) {
37 | return stringifyRecursiveAsJson(classType as RecursiveClass, value, state);
38 | }
39 |
40 | if (classType === '[object Date]') {
41 | return namespaceComplexValue(classType, value.getTime());
42 | }
43 |
44 | if (classType === '[object RegExp]') {
45 | return namespaceComplexValue(classType, value.toString());
46 | }
47 |
48 | if (classType === '[object Event]') {
49 | return namespaceComplexValue(
50 | classType,
51 | [
52 | value.bubbles,
53 | value.cancelBubble,
54 | value.cancelable,
55 | value.composed,
56 | value.currentTarget,
57 | value.defaultPrevented,
58 | value.eventPhase,
59 | value.isTrusted,
60 | value.returnValue,
61 | value.target,
62 | value.type,
63 | ].join(),
64 | );
65 | }
66 |
67 | if (classType === '[object Error]') {
68 | return namespaceComplexValue(
69 | classType,
70 | value.message + SEPARATOR + value.stack,
71 | );
72 | }
73 |
74 | if (classType === '[object DocumentFragment]') {
75 | return namespaceComplexValue(classType, stringifyDocumentFragment(value));
76 | }
77 |
78 | const element = classType.match(XML_ELEMENT_REGEXP);
79 |
80 | if (element) {
81 | return namespaceComplexValue(
82 | 'ELEMENT',
83 | element[1] + SEPARATOR + value.outerHTML,
84 | );
85 | }
86 |
87 | if (NON_ENUMERABLE_CLASSES[classType as NonEnumerableClass]) {
88 | return getUnsupportedHash(value, classType);
89 | }
90 |
91 | if (PRIMITIVE_WRAPPER_CLASSES[classType as PrimitiveWrapperClass]) {
92 | return namespaceComplexValue(classType, value.toString());
93 | }
94 |
95 | // This would only be hit with custom `toStringTag` values
96 | return stringifyRecursiveAsJson('CUSTOM', value, state);
97 | }
98 |
99 | function stringifyRecursiveAsJson(
100 | classType: RecursiveClass,
101 | value: any,
102 | state: RecursiveState,
103 | ) {
104 | const cached = state.cache.get(value);
105 |
106 | if (cached) {
107 | return namespaceComplexValue(classType, 'RECURSIVE~' + cached);
108 | }
109 |
110 | state.cache.set(value, ++state.id);
111 |
112 | if (classType === '[object Object]') {
113 | return value[Symbol.iterator]
114 | ? getUnsupportedHash(value, classType)
115 | : namespaceComplexValue(classType, stringifyObject(value, state));
116 | }
117 |
118 | if (ARRAY_LIKE_CLASSES[classType as ArrayLikeClass]) {
119 | return namespaceComplexValue(classType, stringifyArray(value, state));
120 | }
121 |
122 | if (classType === '[object Map]') {
123 | return namespaceComplexValue(classType, stringifyMap(value, state));
124 | }
125 |
126 | if (classType === '[object Set]') {
127 | return namespaceComplexValue(classType, stringifySet(value, state));
128 | }
129 |
130 | if (TYPED_ARRAY_CLASSES[classType as TypedArrayClass]) {
131 | return namespaceComplexValue(classType, value.join());
132 | }
133 |
134 | if (classType === '[object ArrayBuffer]') {
135 | return namespaceComplexValue(classType, stringifyArrayBuffer(value));
136 | }
137 |
138 | if (classType === '[object DataView]') {
139 | return namespaceComplexValue(classType, stringifyArrayBuffer(value.buffer));
140 | }
141 |
142 | if (NON_ENUMERABLE_CLASSES[classType as NonEnumerableClass]) {
143 | return getUnsupportedHash(value, classType);
144 | }
145 |
146 | return namespaceComplexValue('CUSTOM', stringifyObject(value, state));
147 | }
148 |
149 | export function stringifyArray(value: any[], state: RecursiveState) {
150 | let index = value.length;
151 |
152 | const result: string[] = new Array(index);
153 |
154 | while (--index >= 0) {
155 | result[index] = stringify(value[index], state);
156 | }
157 |
158 | return result.join();
159 | }
160 |
161 | export function stringifyArrayBufferModern(buffer: ArrayBufferLike): string {
162 | return Buffer.from(buffer).toString('utf8');
163 | }
164 |
165 | export function stringifyArrayBufferFallback(buffer: ArrayBufferLike): string {
166 | return String.fromCharCode.apply(
167 | null,
168 | new Uint16Array(buffer) as unknown as number[],
169 | );
170 | }
171 |
172 | export function stringifyArrayBufferNone(): string {
173 | return 'UNSUPPORTED';
174 | }
175 |
176 | export function stringifyDocumentFragment(fragment: DocumentFragment): string {
177 | const children = fragment.children;
178 |
179 | let index = children.length;
180 |
181 | const innerHTML: string[] = new Array(index);
182 |
183 | while (--index >= 0) {
184 | innerHTML[index] = children[index]!.outerHTML;
185 | }
186 |
187 | return innerHTML.join();
188 | }
189 |
190 | const stringifyArrayBuffer =
191 | typeof Buffer !== 'undefined' && typeof Buffer.from === 'function'
192 | ? stringifyArrayBufferModern
193 | : typeof Uint16Array === 'function'
194 | ? stringifyArrayBufferFallback
195 | : stringifyArrayBufferNone;
196 |
197 | export function stringifyMap(map: Map, state: RecursiveState) {
198 | const result: string[] | Array<[string, string]> = new Array(map.size);
199 |
200 | let index = 0;
201 | map.forEach((value, key) => {
202 | result[index++] = [stringify(key, state), stringify(value, state)];
203 | });
204 |
205 | sort(result, sortByKey);
206 |
207 | while (--index >= 0) {
208 | result[index] = '[' + result[index]![0] + ',' + result[index]![1] + ']';
209 | }
210 |
211 | return '[' + result.join() + ']';
212 | }
213 |
214 | export function stringifyObject(
215 | value: Record,
216 | state: RecursiveState,
217 | ) {
218 | const properties = sort(Object.getOwnPropertyNames(value), sortBySelf);
219 | const length = properties.length;
220 | const result: string[] = new Array(length);
221 |
222 | let index = length;
223 |
224 | while (--index >= 0) {
225 | result[index] =
226 | properties[index]! + ':' + stringify(value[properties[index]!], state);
227 | }
228 |
229 | return '{' + result.join() + '}';
230 | }
231 |
232 | export function stringifySet(set: Set, state: RecursiveState) {
233 | const result: string[] = new Array(set.size);
234 |
235 | let index = 0;
236 | set.forEach((value) => {
237 | result[index++] = stringify(value, state);
238 | });
239 |
240 | return '[' + sort(result, sortBySelf).join() + ']';
241 | }
242 |
243 | export function stringify(
244 | value: any,
245 | state: RecursiveState | undefined,
246 | ): string {
247 | const type = typeof value;
248 |
249 | if (value == null || type === 'undefined') {
250 | return HASHABLE_TYPES.empty + value;
251 | }
252 |
253 | if (type === 'object') {
254 | return stringifyComplexType(
255 | value,
256 | toString.call(value) as unknown as Class,
257 | state || { cache: new WeakMap(), id: 1 },
258 | );
259 | }
260 |
261 | if (type === 'function' || type === 'symbol') {
262 | return HASHABLE_TYPES[type] + value.toString();
263 | }
264 |
265 | if (type === 'boolean') {
266 | return HASHABLE_TYPES.boolean + +value;
267 | }
268 |
269 | return HASHABLE_TYPES[type] + value;
270 | }
271 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { CLASSES, HASHABLE_TYPES, SEPARATOR } from './constants';
2 |
3 | import type { Class } from './constants';
4 |
5 | export function namespaceComplexValue(
6 | classType: Class,
7 | value: string | number | boolean,
8 | ) {
9 | return (
10 | HASHABLE_TYPES.object + SEPARATOR + CLASSES[classType] + SEPARATOR + value
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./build/tsconfig/base.json"
3 | }
4 |
--------------------------------------------------------------------------------