Skip to content

Commit ce9df42

Browse files
authored
Factor out common methods to avoid prototype sharing. (immutable-js#1386)
Currently all collection types refer to MapPrototype to access common methods. This not only makes Map a bottleneck in dependency analysis, but it also makes minification more challenging. This proposes creating a `methods/` subdir that contains these shared methods - split up into files to avoid the same dependency bottleneck issue. Saves 325 minified bytes (modest 17 gz-bytes).
1 parent 275486e commit ce9df42

22 files changed

+350
-164
lines changed

src/CollectionImpl.js

+7-18
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,6 @@ import {
2424
} from './Predicates';
2525

2626
import { is } from './is';
27-
import { getIn } from './functional/getIn';
28-
import { hasIn } from './functional/hasIn';
29-
3027
import {
3128
NOT_SET,
3229
ensureSize,
@@ -82,6 +79,9 @@ import {
8279
maxFactory,
8380
zipWithFactory
8481
} from './Operations';
82+
import { getIn } from './methods/getIn';
83+
import { hasIn } from './methods/hasIn';
84+
import { toObject } from './methods/toObject';
8585

8686
export {
8787
Collection,
@@ -133,14 +133,7 @@ mixin(Collection, {
133133
return Map(this.toKeyedSeq());
134134
},
135135

136-
toObject() {
137-
assertNotInfinite(this.size);
138-
const object = {};
139-
this.__iterate((v, k) => {
140-
object[k] = v;
141-
});
142-
return object;
143-
},
136+
toObject: toObject,
144137

145138
toOrderedMap() {
146139
// Use Late Binding here to solve the circular dependency.
@@ -396,9 +389,7 @@ mixin(Collection, {
396389
return this.find((_, key) => is(key, searchKey), undefined, notSetValue);
397390
},
398391

399-
getIn(searchKeyPath, notSetValue) {
400-
return getIn(this, searchKeyPath, notSetValue);
401-
},
392+
getIn: getIn,
402393

403394
groupBy(grouper, context) {
404395
return groupByFactory(this, grouper, context);
@@ -408,9 +399,7 @@ mixin(Collection, {
408399
return this.get(searchKey, NOT_SET) !== NOT_SET;
409400
},
410401

411-
hasIn(searchKeyPath) {
412-
return hasIn(this, searchKeyPath);
413-
},
402+
hasIn: hasIn,
414403

415404
isSubset(iter) {
416405
iter = typeof iter.includes === 'function' ? iter : Collection(iter);
@@ -570,7 +559,7 @@ mixin(KeyedCollection, {
570559
const KeyedCollectionPrototype = KeyedCollection.prototype;
571560
KeyedCollectionPrototype[IS_KEYED_SENTINEL] = true;
572561
KeyedCollectionPrototype[ITERATOR_SYMBOL] = CollectionPrototype.entries;
573-
KeyedCollectionPrototype.toJSON = CollectionPrototype.toObject;
562+
KeyedCollectionPrototype.toJSON = toObject;
574563
KeyedCollectionPrototype.__toStringMapper = (v, k) =>
575564
quoteString(k) + ': ' + quoteString(v);
576565

src/List.js

+23-14
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,17 @@ import {
2020
resolveEnd
2121
} from './TrieUtils';
2222
import { IndexedCollection } from './Collection';
23-
import { MapPrototype } from './Map';
2423
import { hasIterator, Iterator, iteratorValue, iteratorDone } from './Iterator';
25-
24+
import { setIn } from './methods/setIn';
25+
import { deleteIn } from './methods/deleteIn';
26+
import { update } from './methods/update';
27+
import { updateIn } from './methods/updateIn';
28+
import { mergeIn } from './methods/mergeIn';
29+
import { mergeDeepIn } from './methods/mergeDeepIn';
30+
import { withMutations } from './methods/withMutations';
31+
import { asMutable } from './methods/asMutable';
32+
import { asImmutable } from './methods/asImmutable';
33+
import { wasAltered } from './methods/wasAltered';
2634
import assertNotInfinite from './utils/assertNotInfinite';
2735

2836
export class List extends IndexedCollection {
@@ -236,21 +244,22 @@ export const ListPrototype = List.prototype;
236244
ListPrototype[IS_LIST_SENTINEL] = true;
237245
ListPrototype[DELETE] = ListPrototype.remove;
238246
ListPrototype.merge = ListPrototype.concat;
239-
ListPrototype.setIn = MapPrototype.setIn;
240-
ListPrototype.deleteIn = ListPrototype.removeIn = MapPrototype.removeIn;
241-
ListPrototype.update = MapPrototype.update;
242-
ListPrototype.updateIn = MapPrototype.updateIn;
243-
ListPrototype.mergeIn = MapPrototype.mergeIn;
244-
ListPrototype.mergeDeepIn = MapPrototype.mergeDeepIn;
245-
ListPrototype.withMutations = MapPrototype.withMutations;
246-
ListPrototype.asMutable = MapPrototype.asMutable;
247-
ListPrototype.asImmutable = MapPrototype.asImmutable;
248-
ListPrototype.wasAltered = MapPrototype.wasAltered;
249-
ListPrototype['@@transducer/init'] = ListPrototype.asMutable;
247+
ListPrototype.setIn = setIn;
248+
ListPrototype.deleteIn = ListPrototype.removeIn = deleteIn;
249+
ListPrototype.update = update;
250+
ListPrototype.updateIn = updateIn;
251+
ListPrototype.mergeIn = mergeIn;
252+
ListPrototype.mergeDeepIn = mergeDeepIn;
253+
ListPrototype.withMutations = withMutations;
254+
ListPrototype.wasAltered = wasAltered;
255+
ListPrototype.asImmutable = asImmutable;
256+
ListPrototype['@@transducer/init'] = ListPrototype.asMutable = asMutable;
250257
ListPrototype['@@transducer/step'] = function(result, arr) {
251258
return result.push(arr);
252259
};
253-
ListPrototype['@@transducer/result'] = MapPrototype['@@transducer/result'];
260+
ListPrototype['@@transducer/result'] = function(obj) {
261+
return obj.asImmutable();
262+
};
254263

255264
class VNode {
256265
constructor(array, ownerID) {

src/Map.js

+26-100
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,6 @@
66
*/
77

88
import { is } from './is';
9-
import { setIn } from './functional/setIn';
10-
import { update } from './functional/update';
11-
import { updateIn } from './functional/updateIn';
12-
import { removeIn } from './functional/removeIn';
13-
import { merge, mergeDeep, mergeDeepWith } from './functional/merge';
149
import { Collection, KeyedCollection } from './Collection';
1510
import { isOrdered } from './Predicates';
1611
import {
@@ -30,6 +25,18 @@ import { Iterator, iteratorValue, iteratorDone } from './Iterator';
3025
import { sortFactory } from './Operations';
3126
import arrCopy from './utils/arrCopy';
3227
import assertNotInfinite from './utils/assertNotInfinite';
28+
import { setIn } from './methods/setIn';
29+
import { deleteIn } from './methods/deleteIn';
30+
import { update } from './methods/update';
31+
import { updateIn } from './methods/updateIn';
32+
import { merge, mergeWith } from './methods/merge';
33+
import { mergeDeep, mergeDeepWith } from './methods/mergeDeep';
34+
import { mergeIn } from './methods/mergeIn';
35+
import { mergeDeepIn } from './methods/mergeDeepIn';
36+
import { withMutations } from './methods/withMutations';
37+
import { asMutable } from './methods/asMutable';
38+
import { asImmutable } from './methods/asImmutable';
39+
import { wasAltered } from './methods/wasAltered';
3340

3441
import { OrderedMap } from './OrderedMap';
3542

@@ -77,18 +84,10 @@ export class Map extends KeyedCollection {
7784
return updateMap(this, k, v);
7885
}
7986

80-
setIn(keyPath, v) {
81-
return setIn(this, keyPath, v);
82-
}
83-
8487
remove(k) {
8588
return updateMap(this, k, NOT_SET);
8689
}
8790

88-
deleteIn(keyPath) {
89-
return removeIn(this, keyPath);
90-
}
91-
9291
deleteAll(keys) {
9392
const collection = Collection(keys);
9493

@@ -101,16 +100,6 @@ export class Map extends KeyedCollection {
101100
});
102101
}
103102

104-
update(key, notSetValue, updater) {
105-
return arguments.length === 1
106-
? key(this)
107-
: update(this, key, notSetValue, updater);
108-
}
109-
110-
updateIn(keyPath, notSetValue, updater) {
111-
return updateIn(this, keyPath, notSetValue, updater);
112-
}
113-
114103
clear() {
115104
if (this.size === 0) {
116105
return this;
@@ -127,30 +116,6 @@ export class Map extends KeyedCollection {
127116

128117
// @pragma Composition
129118

130-
merge(/*...iters*/) {
131-
return mergeIntoMapWith(this, undefined, arguments);
132-
}
133-
134-
mergeWith(merger, ...iters) {
135-
return mergeIntoMapWith(this, merger, iters);
136-
}
137-
138-
mergeIn(keyPath, ...iters) {
139-
return updateIn(this, keyPath, emptyMap(), m => merge(m, ...iters));
140-
}
141-
142-
mergeDeep(...iters) {
143-
return mergeDeep(this, ...iters);
144-
}
145-
146-
mergeDeepWith(merger, ...iters) {
147-
return mergeDeepWith(merger, this, ...iters);
148-
}
149-
150-
mergeDeepIn(keyPath, ...iters) {
151-
return updateIn(this, keyPath, emptyMap(), m => mergeDeep(m, ...iters));
152-
}
153-
154119
sort(comparator) {
155120
// Late binding
156121
return OrderedMap(sortFactory(this, comparator));
@@ -163,24 +128,6 @@ export class Map extends KeyedCollection {
163128

164129
// @pragma Mutability
165130

166-
withMutations(fn) {
167-
const mutable = this.asMutable();
168-
fn(mutable);
169-
return mutable.wasAltered() ? mutable.__ensureOwner(this.__ownerID) : this;
170-
}
171-
172-
asMutable() {
173-
return this.__ownerID ? this : this.__ensureOwner(new OwnerID());
174-
}
175-
176-
asImmutable() {
177-
return this.__ensureOwner();
178-
}
179-
180-
wasAltered() {
181-
return this.__altered;
182-
}
183-
184131
__iterator(type, reverse) {
185132
return new MapIterator(this, type, reverse);
186133
}
@@ -222,10 +169,22 @@ const IS_MAP_SENTINEL = '@@__IMMUTABLE_MAP__@@';
222169
export const MapPrototype = Map.prototype;
223170
MapPrototype[IS_MAP_SENTINEL] = true;
224171
MapPrototype[DELETE] = MapPrototype.remove;
225-
MapPrototype.removeIn = MapPrototype.deleteIn;
226172
MapPrototype.removeAll = MapPrototype.deleteAll;
227173
MapPrototype.concat = MapPrototype.merge;
228-
MapPrototype['@@transducer/init'] = MapPrototype.asMutable;
174+
MapPrototype.setIn = setIn;
175+
MapPrototype.removeIn = MapPrototype.deleteIn = deleteIn;
176+
MapPrototype.update = update;
177+
MapPrototype.updateIn = updateIn;
178+
MapPrototype.merge = merge;
179+
MapPrototype.mergeWith = mergeWith;
180+
MapPrototype.mergeDeep = mergeDeep;
181+
MapPrototype.mergeDeepWith = mergeDeepWith;
182+
MapPrototype.mergeIn = mergeIn;
183+
MapPrototype.mergeDeepIn = mergeDeepIn;
184+
MapPrototype.withMutations = withMutations;
185+
MapPrototype.wasAltered = wasAltered;
186+
MapPrototype.asImmutable = asImmutable;
187+
MapPrototype['@@transducer/init'] = MapPrototype.asMutable = asMutable;
229188
MapPrototype['@@transducer/step'] = function(result, arr) {
230189
return result.set(arr[0], arr[1]);
231190
};
@@ -806,39 +765,6 @@ function expandNodes(ownerID, nodes, bitmap, including, node) {
806765
return new HashArrayMapNode(ownerID, count + 1, expandedNodes);
807766
}
808767

809-
function mergeIntoMapWith(collection, merger, collections) {
810-
const iters = [];
811-
for (let ii = 0; ii < collections.length; ii++) {
812-
const collection = KeyedCollection(collections[ii]);
813-
if (collection.size !== 0) {
814-
iters.push(collection);
815-
}
816-
}
817-
if (iters.length === 0) {
818-
return collection;
819-
}
820-
if (collection.size === 0 && !collection.__ownerID && iters.length === 1) {
821-
return collection.constructor(iters[0]);
822-
}
823-
return collection.withMutations(collection => {
824-
const mergeIntoCollection = merger
825-
? (value, key) => {
826-
update(
827-
collection,
828-
key,
829-
NOT_SET,
830-
oldVal => (oldVal === NOT_SET ? value : merger(oldVal, value, key))
831-
);
832-
}
833-
: (value, key) => {
834-
collection.set(key, value);
835-
};
836-
for (let ii = 0; ii < iters.length; ii++) {
837-
iters[ii].forEach(mergeIntoCollection);
838-
}
839-
});
840-
}
841-
842768
function popCount(x) {
843769
x -= (x >> 1) & 0x55555555;
844770
x = (x & 0x33333333) + ((x >> 2) & 0x33333333);

src/Record.js

+35-19
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,23 @@
88
import { toJS } from './toJS';
99
import { KeyedCollection } from './Collection';
1010
import { keyedSeqFromValue } from './Seq';
11-
import { MapPrototype } from './Map';
1211
import { List } from './List';
13-
import { ITERATOR_SYMBOL } from './Iterator';
12+
import { ITERATE_ENTRIES, ITERATOR_SYMBOL } from './Iterator';
1413
import { isRecord, IS_RECORD_SENTINEL } from './Predicates';
1514
import { CollectionPrototype } from './CollectionImpl';
1615
import { DELETE } from './TrieUtils';
16+
import { getIn } from './methods/getIn';
17+
import { setIn } from './methods/setIn';
18+
import { deleteIn } from './methods/deleteIn';
19+
import { update } from './methods/update';
20+
import { updateIn } from './methods/updateIn';
21+
import { merge, mergeWith } from './methods/merge';
22+
import { mergeDeep, mergeDeepWith } from './methods/mergeDeep';
23+
import { mergeIn } from './methods/mergeIn';
24+
import { mergeDeepIn } from './methods/mergeDeepIn';
25+
import { withMutations } from './methods/withMutations';
26+
import { asMutable } from './methods/asMutable';
27+
import { asImmutable } from './methods/asImmutable';
1728

1829
import invariant from './utils/invariant';
1930
import quoteString from './utils/quoteString';
@@ -148,6 +159,10 @@ export class Record {
148159
return toJS(this);
149160
}
150161

162+
entries() {
163+
return this.__iterator(ITERATE_ENTRIES);
164+
}
165+
151166
__iterator(type, reverse) {
152167
return recordSeq(this).__iterator(type, reverse);
153168
}
@@ -175,26 +190,27 @@ Record.getDescriptiveName = recordName;
175190
const RecordPrototype = Record.prototype;
176191
RecordPrototype[IS_RECORD_SENTINEL] = true;
177192
RecordPrototype[DELETE] = RecordPrototype.remove;
178-
RecordPrototype.deleteIn = RecordPrototype.removeIn = MapPrototype.removeIn;
179-
RecordPrototype.getIn = CollectionPrototype.getIn;
193+
RecordPrototype.deleteIn = RecordPrototype.removeIn = deleteIn;
194+
RecordPrototype.getIn = getIn;
180195
RecordPrototype.hasIn = CollectionPrototype.hasIn;
181-
RecordPrototype.merge = MapPrototype.merge;
182-
RecordPrototype.mergeWith = MapPrototype.mergeWith;
183-
RecordPrototype.mergeIn = MapPrototype.mergeIn;
184-
RecordPrototype.mergeDeep = MapPrototype.mergeDeep;
185-
RecordPrototype.mergeDeepWith = MapPrototype.mergeDeepWith;
186-
RecordPrototype.mergeDeepIn = MapPrototype.mergeDeepIn;
187-
RecordPrototype.setIn = MapPrototype.setIn;
188-
RecordPrototype.update = MapPrototype.update;
189-
RecordPrototype.updateIn = MapPrototype.updateIn;
190-
RecordPrototype.withMutations = MapPrototype.withMutations;
191-
RecordPrototype.asMutable = MapPrototype.asMutable;
192-
RecordPrototype.asImmutable = MapPrototype.asImmutable;
193-
RecordPrototype[ITERATOR_SYMBOL] = CollectionPrototype.entries;
196+
RecordPrototype.merge = merge;
197+
RecordPrototype.mergeWith = mergeWith;
198+
RecordPrototype.mergeIn = mergeIn;
199+
RecordPrototype.mergeDeep = mergeDeep;
200+
RecordPrototype.mergeDeepWith = mergeDeepWith;
201+
RecordPrototype.mergeDeepIn = mergeDeepIn;
202+
RecordPrototype.setIn = setIn;
203+
RecordPrototype.update = update;
204+
RecordPrototype.updateIn = updateIn;
205+
RecordPrototype.withMutations = withMutations;
206+
RecordPrototype.asMutable = asMutable;
207+
RecordPrototype.asImmutable = asImmutable;
208+
RecordPrototype[ITERATOR_SYMBOL] = RecordPrototype.entries;
194209
RecordPrototype.toJSON = RecordPrototype.toObject =
195210
CollectionPrototype.toObject;
196-
RecordPrototype.inspect = RecordPrototype.toSource =
197-
CollectionPrototype.toSource;
211+
RecordPrototype.inspect = RecordPrototype.toSource = function() {
212+
return this.toString();
213+
};
198214

199215
function makeRecord(likeRecord, values, ownerID) {
200216
const record = Object.create(Object.getPrototypeOf(likeRecord));

0 commit comments

Comments
 (0)