HTML Preparation code:
x
 
1
2
<script src='https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js'></script>
3
<script>
4
function stringifiable(obj) {
5
    // Safely stringify Object.create(null)
6
    /* istanbul ignore next */
7
    return typeof obj === 'object' && !('toString' in obj) ?
8
        Object.prototype.toString.call(obj).slice(8, -1) :
9
        obj;
10
}
11
var isProduction = typeof process === 'object' && process.env.NODE_ENV === 'production';
12
function invariant(condition, message) {
13
    if (!condition) {
14
        /* istanbul ignore next */
15
        if (isProduction) {
16
            throw new Error('Invariant failed');
17
        }
18
        throw new Error(message());
19
    }
20
}
21
var hasOwnProperty = Object.prototype.hasOwnProperty;
22
var splice = Array.prototype.splice;
23
var toString = Object.prototype.toString;
24
function type(obj) {
25
    return toString.call(obj).slice(8, -1);
26
}
27
var assign = Object.assign || /* istanbul ignore next */ (function (target, source) {
28
    getAllKeys(source).forEach(function (key) {
29
        if (hasOwnProperty.call(source, key)) {
30
            target[key] = source[key];
31
        }
32
    });
33
    return target;
34
});
35
var getAllKeys = typeof Object.getOwnPropertySymbols === 'function'
36
    ? function (obj) { return Object.keys(obj).concat(Object.getOwnPropertySymbols(obj)); }
37
    /* istanbul ignore next */
38
    : function (obj) { return Object.keys(obj); };
39
function copy(object) {
40
    return Array.isArray(object)
41
        ? assign(object.constructor(object.length), object)
42
        : (type(object) === 'Map')
43
            ? new Map(object)
44
            : (type(object) === 'Set')
45
                ? new Set(object)
46
                : (object && typeof object === 'object')
47
                    ? assign(Object.create(Object.getPrototypeOf(object)), object)
48
                    /* istanbul ignore next */
49
                    : object;
50
}
51
var Context = /** @class */ (function () {
52
    function Context() {
53
        this.commands = assign({}, defaultCommands);
54
        this.update = this.update.bind(this);
55
        // Deprecated: update.extend, update.isEquals and update.newContext
56
        this.update.extend = this.extend = this.extend.bind(this);
57
        this.update.isEquals = function (x, y) { return x === y; };
58
        this.update.newContext = function () { return new Context().update; };
59
    }
60
    Object.defineProperty(Context.prototype, "isEquals", {
61
        get: function () {
62
            return this.update.isEquals;
63
        },
64
        set: function (value) {
65
            this.update.isEquals = value;
66
        },
67
        enumerable: true,
68
        configurable: true
69
    });
70
    Context.prototype.extend = function (directive, fn) {
71
        this.commands[directive] = fn;
72
    };
73
    Context.prototype.update = function (object, $spec) {
74
        var _this = this;
75
        var spec = (typeof $spec === 'function') ? { $apply: $spec } : $spec;
76
        if (!(Array.isArray(object) && Array.isArray(spec))) {
77
            invariant(!Array.isArray(spec), function () { return "update(): You provided an invalid spec to update(). The spec may " +
78
                "not contain an array except as the value of $set, $push, $unshift, " +
79
                "$splice or any custom command allowing an array value."; });
80
        }
81
        invariant(typeof spec === 'object' && spec !== null, function () { return "update(): You provided an invalid spec to update(). The spec and " +
82
            "every included key path must be plain objects containing one of the " +
83
            ("following commands: " + Object.keys(_this.commands).join(', ') + "."); });
84
        var nextObject = object;
85
        getAllKeys(spec).forEach(function (key) {
86
            if (hasOwnProperty.call(_this.commands, key)) {
87
                var objectWasNextObject = object === nextObject;
88
                nextObject = _this.commands[key](spec[key], nextObject, spec, object);
89
                if (objectWasNextObject && _this.isEquals(nextObject, object)) {
90
                    nextObject = object;
91
                }
92
            }
93
            else {
94
                var nextValueForKey = type(object) === 'Map'
95
                    ? _this.update(object.get(key), spec[key])
96
                    : _this.update(object[key], spec[key]);
97
                var nextObjectValue = type(nextObject) === 'Map'
98
                    ? nextObject.get(key)
99
                    : nextObject[key];
100
                if (!_this.isEquals(nextValueForKey, nextObjectValue)
101
                    || typeof nextValueForKey === 'undefined'
102
                        && !hasOwnProperty.call(object, key)) {
103
                    if (nextObject === object) {
104
                        nextObject = copy(object);
105
                    }
106
                    if (type(nextObject) === 'Map') {
107
                        nextObject.set(key, nextValueForKey);
108
                    }
109
                    else {
110
                        nextObject[key] = nextValueForKey;
111
                    }
112
                }
113
            }
114
        });
115
        return nextObject;
116
    };
117
    return Context;
118
}());
119
var defaultCommands = {
120
    $push: function (value, nextObject, spec) {
121
        invariantPushAndUnshift(nextObject, spec, '$push');
122
        return value.length ? nextObject.concat(value) : nextObject;
123
    },
124
    $unshift: function (value, nextObject, spec) {
125
        invariantPushAndUnshift(nextObject, spec, '$unshift');
126
        return value.length ? value.concat(nextObject) : nextObject;
127
    },
128
    $splice: function (value, nextObject, spec, originalObject) {
129
        invariantSplices(nextObject, spec);
130
        value.forEach(function (args) {
131
            invariantSplice(args);
132
            if (nextObject === originalObject && args.length) {
133
                nextObject = copy(originalObject);
134
            }
135
            splice.apply(nextObject, args);
136
        });
137
        return nextObject;
138
    },
139
    $set: function (value, _nextObject, spec) {
140
        invariantSet(spec);
141
        return value;
142
    },
143
    $toggle: function (targets, nextObject) {
144
        invariantSpecArray(targets, '$toggle');
145
        var nextObjectCopy = targets.length ? copy(nextObject) : nextObject;
146
        targets.forEach(function (target) {
147
            nextObjectCopy[target] = !nextObject[target];
148
        });
149
        return nextObjectCopy;
150
    },
151
    $unset: function (value, nextObject, _spec, originalObject) {
152
        invariantSpecArray(value, '$unset');
153
        value.forEach(function (key) {
154
            if (Object.hasOwnProperty.call(nextObject, key)) {
155
                if (nextObject === originalObject) {
156
                    nextObject = copy(originalObject);
157
                }
158
                delete nextObject[key];
159
            }
160
        });
161
        return nextObject;
162
    },
163
    $add: function (values, nextObject, _spec, originalObject) {
164
        invariantMapOrSet(nextObject, '$add');
165
        invariantSpecArray(values, '$add');
166
        if (type(nextObject) === 'Map') {
167
            values.forEach(function (_a) {
168
                var key = _a[0], value = _a[1];
169
                if (nextObject === originalObject && nextObject.get(key) !== value) {
170
                    nextObject = copy(originalObject);
171
                }
172
                nextObject.set(key, value);
173
            });
174
        }
175
        else {
176
            values.forEach(function (value) {
177
                if (nextObject === originalObject && !nextObject.has(value)) {
178
                    nextObject = copy(originalObject);
179
                }
180
                nextObject.add(value);
181
            });
182
        }
183
        return nextObject;
184
    },
185
    $remove: function (value, nextObject, _spec, originalObject) {
186
        invariantMapOrSet(nextObject, '$remove');
187
        invariantSpecArray(value, '$remove');
188
        value.forEach(function (key) {
189
            if (nextObject === originalObject && nextObject.has(key)) {
190
                nextObject = copy(originalObject);
191
            }
192
            nextObject.delete(key);
193
        });
194
        return nextObject;
195
    },
196
    $merge: function (value, nextObject, _spec, originalObject) {
197
        invariantMerge(nextObject, value);
198
        getAllKeys(value).forEach(function (key) {
199
            if (value[key] !== nextObject[key]) {
200
                if (nextObject === originalObject) {
201
                    nextObject = copy(originalObject);
202
                }
203
                nextObject[key] = value[key];
204
            }
205
        });
206
        return nextObject;
207
    },
208
    $apply: function (value, original) {
209
        invariantApply(value);
210
        return value(original);
211
    },
212
};
213
var defaultContext = new Context();
214
var update = defaultContext.update;
215
// invariants
216
function invariantPushAndUnshift(value, spec, command) {
217
    invariant(Array.isArray(value), function () { return "update(): expected target of " + stringifiable(command) + " to be an array; got " + stringifiable(value) + "."; });
218
    invariantSpecArray(spec[command], command);
219
}
220
function invariantSpecArray(spec, command) {
221
    invariant(Array.isArray(spec), function () { return "update(): expected spec of " + stringifiable(command) + " to be an array; got " + stringifiable(spec) + ". " +
222
        "Did you forget to wrap your parameter in an array?"; });
223
}
224
function invariantSplices(value, spec) {
225
    invariant(Array.isArray(value), function () { return "Expected $splice target to be an array; got " + stringifiable(value); });
226
    invariantSplice(spec.$splice);
227
}
228
function invariantSplice(value) {
229
    invariant(Array.isArray(value), function () { return "update(): expected spec of $splice to be an array of arrays; got " + stringifiable(value) + ". " +
230
        "Did you forget to wrap your parameters in an array?"; });
231
}
232
function invariantApply(fn) {
233
    invariant(typeof fn === 'function', function () { return "update(): expected spec of $apply to be a function; got " + stringifiable(fn) + "."; });
234
}
235
function invariantSet(spec) {
236
    invariant(Object.keys(spec).length === 1, function () { return "Cannot have more than one key in an object with $set"; });
237
}
238
function invariantMerge(target, specValue) {
239
    invariant(specValue && typeof specValue === 'object', function () { return "update(): $merge expects a spec of type 'object'; got " + stringifiable(specValue); });
240
    invariant(target && typeof target === 'object', function () { return "update(): $merge expects a target of type 'object'; got " + stringifiable(target); });
241
}
242
function invariantMapOrSet(target, command) {
243
    var typeOfTarget = type(target);
244
    invariant(typeOfTarget === 'Map' || typeOfTarget === 'Set', function () { return "update(): " + stringifiable(command) + " expects a target of type Set or Map; got " + stringifiable(typeOfTarget); });
245
}
246
</script>
247
Script Preparation code:
 
var myArr = Array.from({
    length: 1_000_000
}, () => ({ id: Math.floor(Math.random() * 1000) }));
var myArr2 = Array.from({
    length: 1_000_000
}, () => ({ id: Math.floor(Math.random() * 1000) }));
var myCopy = null;
Tests:
  • uniqBy

     
    myCopy = _.uniqBy([...myArr, ...myArr2], 'id');
  • Set with filter

     
    myCopy = (() => {
        const existingIds = new Set(myArr.map((machine) => machine.id));
        const newMachines = myArr2.filter((machine) => !existingIds.has(machine.id));
        return update(myArr, {
            $push: newMachines,
        });
    })();
Rendered benchmark preparation results:

Suite status: <idle, ready to run>

Previous results

Experimental features:

  • Test case name Result
    uniqBy
    Set with filter

    Fastest: N/A

    Slowest: N/A

Latest run results:
Run details: (Test run date: one month ago)
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36
Chrome 133 on Mac OS X 10.15.7
View result in a separate tab
Test name Executions per second
uniqBy 25.7 Ops/sec
Set with filter 16.0 Ops/sec