forked from facebook/react
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathImmutableObject.js
More file actions
184 lines (164 loc) · 5.53 KB
/
ImmutableObject.js
File metadata and controls
184 lines (164 loc) · 5.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule ImmutableObject
*/
"use strict";
var keyMirror = require('keyMirror');
var merge = require('merge');
var mergeInto = require('mergeInto');
var mergeHelpers = require('mergeHelpers');
var throwIf = require('throwIf');
var checkMergeObjectArgs = mergeHelpers.checkMergeObjectArgs;
var isTerminal = mergeHelpers.isTerminal;
/**
* Simple wrapper around javascript key/value objects that provide a dev time
* guarantee of immutability (assuming all modules that may potentially mutate
* include a "use strict" declaration). Retaining immutability requires CPU
* cycles (in order to perform the freeze), but this computation can be avoided
* in production. The fact that mutation attempts in __DEV__ will be caught,
* allows us to reasonably assume that mutation on those objects won't even be
* attempted in production. This means that an object being an instanceof
* `ImmutableObject` implies that the object may never change.
*
* TODO: Require strict mode in source files that use ImmutableObject (lint
* rule).
*
* @class ImmutableObject
*/
var ERRORS;
var ImmutableObject;
if (__DEV__) {
ERRORS = {
INVALID_MAP_SET_ARG: 'You have attempted to set fields on an object that ' +
'is not an instance of ImmutableObject'
};
/**
* Constructs an instance of `ImmutableObject`.
*
* @param {!Object} initMap The initial set of properties.
* @constructor
*/
ImmutableObject = function(initMap) {
mergeInto(this, initMap);
deepFreeze(this, initMap);
};
/**
* Objects that are instances of `ImmutableObject` are assumed to be deep
* frozen.
* @param {!Object} o The object to deep freeze.
* @return {!boolean} Whether or not deep freeze is needed.
*/
var shouldRecurseFreeze = function(o) {
return (typeof o) === 'object' &&
!(o instanceof ImmutableObject) && o !== null;
};
/**
* Freezes an object `o` deeply. Invokes `shouldRecurseFreeze` to determine if
* further freezing is needed.
*
* @param {!Object} o The object to freeze.
*/
var deepFreeze = function(o) {
var prop;
Object.freeze(o); // First freeze the object.
for (prop in o) {
var field = o[prop];
if (o.hasOwnProperty(prop) && shouldRecurseFreeze(field)) {
deepFreeze(field);
}
}
};
/**
* Returns a new ImmutableObject that is that is the same as the parameter
* `immutableObject` but with the differences specified in `put`.
* @param {!ImmutableObject} ImmutableObject The ImmutableObject object to set
* fields on.
* @param {!Object} put Subset of fields to merge into the returned result.
* @return {!ImmutableObject} The result of merging in `put` fields.
*/
ImmutableObject.set = function(immutableObject, put) {
throwIf(
!(immutableObject instanceof ImmutableObject),
ERRORS.INVALID_MAP_SET_ARG
);
var totalNewFields = merge(immutableObject, put);
return new ImmutableObject(totalNewFields);
};
} else {
ERRORS = keyMirror({INVALID_MAP_SET_ARG: null});
ImmutableObject = function(initMap) {
mergeInto(this, initMap);
};
ImmutableObject.set = function(immutableObject, put) {
throwIf(
!(immutableObject instanceof ImmutableObject),
ERRORS.INVALID_MAP_SET_ARG
);
var newMap = new ImmutableObject(immutableObject);
mergeInto(newMap, put);
return newMap;
};
}
/**
* Sugar for `ImmutableObject.set(ImmutableObject, {fieldName: putField})`
* @see ImmutableObject.set
*/
ImmutableObject.setField = function(immutableObject, fieldName, putField) {
var put = {};
put[fieldName] = putField;
return ImmutableObject.set(immutableObject, put);
};
/**
* Returns a new ImmutableObject that is that is the same as the parameter
* `immutableObject` but with the differences specified in `put` recursively
* applied.
*/
ImmutableObject.setDeep = function(immutableObject, put) {
throwIf(
!(immutableObject instanceof ImmutableObject),
ERRORS.INVALID_MAP_SET_ARG
);
return _setDeep(immutableObject, put);
};
function _setDeep(object, put) {
checkMergeObjectArgs(object, put);
var totalNewFields = {};
// To maintain the order of the keys, copy the base object's entries first.
var keys = Object.keys(object);
for (var ii = 0; ii < keys.length; ii++) {
var key = keys[ii];
if (!put.hasOwnProperty(key)) {
totalNewFields[key] = object[key];
} else if (isTerminal(object[key]) || isTerminal(put[key])) {
totalNewFields[key] = put[key];
} else {
totalNewFields[key] = _setDeep(object[key], put[key]);
}
}
// Apply any new keys that the base object didn't have.
var newKeys = Object.keys(put);
for (ii = 0; ii < newKeys.length; ii++) {
var newKey = newKeys[ii];
if (object.hasOwnProperty(newKey)) {
continue;
}
totalNewFields[newKey] = put[newKey];
}
return (object instanceof ImmutableObject || put instanceof ImmutableObject) ?
new ImmutableObject(totalNewFields) :
totalNewFields;
}
module.exports = ImmutableObject;