Reduce hash lookups for DOM properties
Ben Alpert committed
Jun 12, 2015 at 17:10 UTC
40963e503b4724241a3f738641bd8e4290ce4637
3 files changed
+118
-162
src/renderers/dom/shared/DOMProperty.js
+86
-133
@@ -60,6 +60,7 @@ var DOMPropertyInjection = {
60
* @param {object} domPropertyConfig the config as described above.
61
*/
62
injectDOMPropertyConfig: function(domPropertyConfig) {
63
+ var Injection = DOMPropertyInjection;
64
var Properties = domPropertyConfig.Properties || {};
65
var DOMAttributeNamespaces = domPropertyConfig.DOMAttributeNamespaces || {};
66
var DOMAttributeNames = domPropertyConfig.DOMAttributeNames || {};
@@ -74,7 +75,7 @@ var DOMPropertyInjection = {
75
76
for (var propName in Properties) {
77
invariant(
77
- !DOMProperty.isStandardName.hasOwnProperty(propName),
78
+ !DOMProperty.properties.hasOwnProperty(propName),
79
'injectDOMPropertyConfig(...): You\'re trying to inject DOM property ' +
80
'\'%s\' which has already been injected. You may be accidentally ' +
81
'injecting the same DOM property config twice, or you may be ' +
@@ -82,73 +83,69 @@ var DOMPropertyInjection = {
83
propName
84
);
85
85
- DOMProperty.isStandardName[propName] = true;
86
-
86
var lowerCased = propName.toLowerCase();
88
- DOMProperty.getPossibleStandardName[lowerCased] = propName;
89
-
90
- if (DOMAttributeNames.hasOwnProperty(propName)) {
91
- var attributeName = DOMAttributeNames[propName];
92
- DOMProperty.getPossibleStandardName[attributeName] = propName;
93
- DOMProperty.getAttributeName[propName] = attributeName;
94
- } else {
95
- DOMProperty.getAttributeName[propName] = lowerCased;
96
- }
97
-
98
- if (DOMAttributeNamespaces.hasOwnProperty(propName)) {
99
- DOMProperty.getAttributeNamespace[propName] =
100
- DOMAttributeNamespaces[propName];
101
- } else {
102
- DOMProperty.getAttributeNamespace[propName] = null;
103
- }
104
-
105
- DOMProperty.getPropertyName[propName] =
106
- DOMPropertyNames.hasOwnProperty(propName) ?
107
- DOMPropertyNames[propName] :
108
- propName;
109
-
110
- if (DOMMutationMethods.hasOwnProperty(propName)) {
111
- DOMProperty.getMutationMethod[propName] = DOMMutationMethods[propName];
112
- } else {
113
- DOMProperty.getMutationMethod[propName] = null;
114
- }
115
-
87
var propConfig = Properties[propName];
117
- DOMProperty.mustUseAttribute[propName] =
118
- checkMask(propConfig, DOMPropertyInjection.MUST_USE_ATTRIBUTE);
119
- DOMProperty.mustUseProperty[propName] =
120
- checkMask(propConfig, DOMPropertyInjection.MUST_USE_PROPERTY);
121
- DOMProperty.hasSideEffects[propName] =
122
- checkMask(propConfig, DOMPropertyInjection.HAS_SIDE_EFFECTS);
123
- DOMProperty.hasBooleanValue[propName] =
124
- checkMask(propConfig, DOMPropertyInjection.HAS_BOOLEAN_VALUE);
125
- DOMProperty.hasNumericValue[propName] =
126
- checkMask(propConfig, DOMPropertyInjection.HAS_NUMERIC_VALUE);
127
- DOMProperty.hasPositiveNumericValue[propName] =
128
- checkMask(propConfig, DOMPropertyInjection.HAS_POSITIVE_NUMERIC_VALUE);
129
- DOMProperty.hasOverloadedBooleanValue[propName] =
130
- checkMask(propConfig, DOMPropertyInjection.HAS_OVERLOADED_BOOLEAN_VALUE);
88
+
89
+ var propertyInfo = {
90
+ attributeName: lowerCased,
91
+ attributeNamespace: null,
92
+ propertyName: propName,
93
+ mutationMethod: null,
94
+
95
+ mustUseAttribute: checkMask(propConfig, Injection.MUST_USE_ATTRIBUTE),
96
+ mustUseProperty: checkMask(propConfig, Injection.MUST_USE_PROPERTY),
97
+ hasSideEffects: checkMask(propConfig, Injection.HAS_SIDE_EFFECTS),
98
+ hasBooleanValue: checkMask(propConfig, Injection.HAS_BOOLEAN_VALUE),
99
+ hasNumericValue: checkMask(propConfig, Injection.HAS_NUMERIC_VALUE),
100
+ hasPositiveNumericValue:
101
+ checkMask(propConfig, Injection.HAS_POSITIVE_NUMERIC_VALUE),
102
+ hasOverloadedBooleanValue:
103
+ checkMask(propConfig, Injection.HAS_OVERLOADED_BOOLEAN_VALUE),
104
+ };
105
106
invariant(
133
- !DOMProperty.mustUseAttribute[propName] ||
134
- !DOMProperty.mustUseProperty[propName],
107
+ !propertyInfo.mustUseAttribute || !propertyInfo.mustUseProperty,
108
'DOMProperty: Cannot require using both attribute and property: %s',
109
propName
110
);
111
invariant(
139
- DOMProperty.mustUseProperty[propName] ||
140
- !DOMProperty.hasSideEffects[propName],
112
+ propertyInfo.mustUseProperty || !propertyInfo.hasSideEffects,
113
'DOMProperty: Properties that have side effects must use property: %s',
114
propName
115
);
116
invariant(
145
- !!DOMProperty.hasBooleanValue[propName] +
146
- !!DOMProperty.hasNumericValue[propName] +
147
- !!DOMProperty.hasOverloadedBooleanValue[propName] <= 1,
117
+ propertyInfo.hasBooleanValue + propertyInfo.hasNumericValue +
118
+ propertyInfo.hasOverloadedBooleanValue <= 1,
119
'DOMProperty: Value can be one of boolean, overloaded boolean, or ' +
120
'numeric value, but not a combination: %s',
121
propName
122
);
123
+
124
+ if (__DEV__) {
125
+ DOMProperty.getPossibleStandardName[lowerCased] = propName;
126
+ }
127
+
128
+ if (DOMAttributeNames.hasOwnProperty(propName)) {
129
+ var attributeName = DOMAttributeNames[propName];
130
+ propertyInfo.attributeName = attributeName;
131
+ if (__DEV__) {
132
+ DOMProperty.getPossibleStandardName[attributeName] = propName;
133
+ }
134
+ }
135
+
136
+ if (DOMAttributeNamespaces.hasOwnProperty(propName)) {
137
+ propertyInfo.attributeNamespace = DOMAttributeNamespaces[propName];
138
+ }
139
+
140
+ if (DOMPropertyNames.hasOwnProperty(propName)) {
141
+ propertyInfo.propertyName = DOMPropertyNames[propName];
142
+ }
143
+
144
+ if (DOMMutationMethods.hasOwnProperty(propName)) {
145
+ propertyInfo.mutationMethod = DOMMutationMethods[propName];
146
+ }
147
+
148
+ DOMProperty.properties[propName] = propertyInfo;
149
}
150
},
151
};
@@ -172,93 +169,49 @@ var DOMProperty = {
169
ID_ATTRIBUTE_NAME: 'data-reactid',
170
171
/**
175
- * Checks whether a property name is a standard property.
176
- * @type {Object}
172
+ * Map from property "standard name" to an object with info about how to set
173
+ * the property in the DOM. Each object contains:
174
+ *
175
+ * attributeName:
176
+ * Used when rendering markup or with `*Attribute()`.
177
+ * attributeNamespace
178
+ * propertyName:
179
+ * Used on DOM node instances. (This includes properties that mutate due to
180
+ * external factors.)
181
+ * mutationMethod:
182
+ * If non-null, used instead of the property or `setAttribute()` after
183
+ * initial render.
184
+ * mustUseAttribute:
185
+ * Whether the property must be accessed and mutated using `*Attribute()`.
186
+ * (This includes anything that fails `<propName> in <element>`.)
187
+ * mustUseProperty:
188
+ * Whether the property must be accessed and mutated as an object property.
189
+ * hasSideEffects:
190
+ * Whether or not setting a value causes side effects such as triggering
191
+ * resources to be loaded or text selection changes. If true, we read from
192
+ * the DOM before updating to ensure that the value is only set if it has
193
+ * changed.
194
+ * hasBooleanValue:
195
+ * Whether the property should be removed when set to a falsey value.
196
+ * hasNumericValue:
197
+ * Whether the property must be numeric or parse as a numeric and should be
198
+ * removed when set to a falsey value.
199
+ * hasPositiveNumericValue:
200
+ * Whether the property must be positive numeric or parse as a positive
201
+ * numeric and should be removed when set to a falsey value.
202
+ * hasOverloadedBooleanValue:
203
+ * Whether the property can be used as a flag as well as with a value.
204
+ * Removed when strictly equal to false; present without a value when
205
+ * strictly equal to true; present with a value otherwise.
206
*/
178
- isStandardName: {},
207
+ properties: {},
208
209
/**
210
* Mapping from lowercase property names to the properly cased version, used
182
- * to warn in the case of missing properties.
183
- * @type {Object}
184
- */
185
- getPossibleStandardName: {},
186
-
187
- /**
188
- * Mapping from normalized names to attribute names that differ. Attribute
189
- * names are used when rendering markup or with `*Attribute()`.
190
- * @type {Object}
191
- */
192
- getAttributeName: {},
193
-
194
- /**
195
- * Mapping from normalized names to namespaces.
196
- * @type {Object}
197
- */
198
- getAttributeNamespace: {},
199
-
200
- /**
201
- * Mapping from normalized names to properties on DOM node instances.
202
- * (This includes properties that mutate due to external factors.)
203
- * @type {Object}
204
- */
205
- getPropertyName: {},
206
-
207
- /**
208
- * Mapping from normalized names to mutation methods. This will only exist if
209
- * mutation cannot be set simply by the property or `setAttribute()`.
210
- * @type {Object}
211
- */
212
- getMutationMethod: {},
213
-
214
- /**
215
- * Whether the property must be accessed and mutated as an object property.
216
- * @type {Object}
217
- */
218
- mustUseAttribute: {},
219
-
220
- /**
221
- * Whether the property must be accessed and mutated using `*Attribute()`.
222
- * (This includes anything that fails `<propName> in <element>`.)
223
- * @type {Object}
224
- */
225
- mustUseProperty: {},
226
-
227
- /**
228
- * Whether or not setting a value causes side effects such as triggering
229
- * resources to be loaded or text selection changes. We must ensure that
230
- * the value is only set if it has changed.
231
- * @type {Object}
232
- */
233
- hasSideEffects: {},
234
-
235
- /**
236
- * Whether the property should be removed when set to a falsey value.
237
- * @type {Object}
238
- */
239
- hasBooleanValue: {},
240
-
241
- /**
242
- * Whether the property must be numeric or parse as a
243
- * numeric and should be removed when set to a falsey value.
244
- * @type {Object}
245
- */
246
- hasNumericValue: {},
247
-
248
- /**
249
- * Whether the property must be positive numeric or parse as a positive
250
- * numeric and should be removed when set to a falsey value.
251
- * @type {Object}
252
- */
253
- hasPositiveNumericValue: {},
254
-
255
- /**
256
- * Whether the property can be used as a flag as well as with a value. Removed
257
- * when strictly equal to false; present without a value when strictly equal
258
- * to true; present with a value otherwise.
211
+ * to warn in the case of missing properties. Available only in __DEV__.
212
* @type {Object}
213
*/
261
- hasOverloadedBooleanValue: {},
214
+ getPossibleStandardName: __DEV__ ? {} : null,
215
216
/**
217
* All of the isCustomAttribute() functions that have been injected.
src/renderers/dom/shared/DOMPropertyOperations.js
+30
-27
@@ -42,12 +42,12 @@ function isAttributeNameSafe(attributeName) {
42
return false;
43
}
44
45
-function shouldIgnoreValue(name, value) {
45
+function shouldIgnoreValue(propertyInfo, value) {
46
return value == null ||
47
- (DOMProperty.hasBooleanValue[name] && !value) ||
48
- (DOMProperty.hasNumericValue[name] && isNaN(value)) ||
49
- (DOMProperty.hasPositiveNumericValue[name] && (value < 1)) ||
50
- (DOMProperty.hasOverloadedBooleanValue[name] && value === false);
47
+ (propertyInfo.hasBooleanValue && !value) ||
48
+ (propertyInfo.hasNumericValue && isNaN(value)) ||
49
+ (propertyInfo.hasPositiveNumericValue && (value < 1)) ||
50
+ (propertyInfo.hasOverloadedBooleanValue && value === false);
51
}
52
53
if (__DEV__) {
@@ -113,14 +113,15 @@ var DOMPropertyOperations = {
113
* @return {?string} Markup string, or null if the property was invalid.
114
*/
115
createMarkupForProperty: function(name, value) {
116
- if (DOMProperty.isStandardName.hasOwnProperty(name) &&
117
- DOMProperty.isStandardName[name]) {
118
- if (shouldIgnoreValue(name, value)) {
116
+ var propertyInfo = DOMProperty.properties.hasOwnProperty(name) ?
117
+ DOMProperty.properties[name] : null;
118
+ if (propertyInfo) {
119
+ if (shouldIgnoreValue(propertyInfo, value)) {
120
return '';
121
}
121
- var attributeName = DOMProperty.getAttributeName[name];
122
- if (DOMProperty.hasBooleanValue[name] ||
123
- (DOMProperty.hasOverloadedBooleanValue[name] && value === true)) {
122
+ var attributeName = propertyInfo.attributeName;
123
+ if (propertyInfo.hasBooleanValue ||
124
+ (propertyInfo.hasOverloadedBooleanValue && value === true)) {
125
return attributeName + '=""';
126
}
127
return attributeName + '=' + quoteAttributeValueForBrowser(value);
@@ -157,16 +158,17 @@ var DOMPropertyOperations = {
158
* @param {*} value
159
*/
160
setValueForProperty: function(node, name, value) {
160
- if (DOMProperty.isStandardName.hasOwnProperty(name) &&
161
- DOMProperty.isStandardName[name]) {
162
- var mutationMethod = DOMProperty.getMutationMethod[name];
161
+ var propertyInfo = DOMProperty.properties.hasOwnProperty(name) ?
162
+ DOMProperty.properties[name] : null;
163
+ if (propertyInfo) {
164
+ var mutationMethod = propertyInfo.mutationMethod;
165
if (mutationMethod) {
166
mutationMethod(node, value);
165
- } else if (shouldIgnoreValue(name, value)) {
167
+ } else if (shouldIgnoreValue(propertyInfo, value)) {
168
this.deleteValueForProperty(node, name);
167
- } else if (DOMProperty.mustUseAttribute[name]) {
168
- var attributeName = DOMProperty.getAttributeName[name];
169
- var namespace = DOMProperty.getAttributeNamespace[name];
169
+ } else if (propertyInfo.mustUseAttribute) {
170
+ var attributeName = propertyInfo.attributeName;
171
+ var namespace = propertyInfo.attributeNamespace;
172
// `setAttribute` with objects becomes only `[object]` in IE8/9,
173
// ('' + value) makes it output the correct toString()-value.
174
if (namespace) {
@@ -175,10 +177,10 @@ var DOMPropertyOperations = {
177
node.setAttribute(attributeName, '' + value);
178
}
179
} else {
178
- var propName = DOMProperty.getPropertyName[name];
180
+ var propName = propertyInfo.propertyName;
181
// Must explicitly cast values for HAS_SIDE_EFFECTS-properties to the
182
// property type before comparing; only `value` does and is string.
181
- if (!DOMProperty.hasSideEffects[name] ||
183
+ if (!propertyInfo.hasSideEffects ||
184
('' + node[propName]) !== ('' + value)) {
185
// Contrary to `setAttribute`, object properties are properly
186
// `toString`ed by IE8/9.
@@ -210,20 +212,21 @@ var DOMPropertyOperations = {
212
* @param {string} name
213
*/
214
deleteValueForProperty: function(node, name) {
213
- if (DOMProperty.isStandardName.hasOwnProperty(name) &&
214
- DOMProperty.isStandardName[name]) {
215
- var mutationMethod = DOMProperty.getMutationMethod[name];
215
+ var propertyInfo = DOMProperty.properties.hasOwnProperty(name) ?
216
+ DOMProperty.properties[name] : null;
217
+ if (propertyInfo) {
218
+ var mutationMethod = propertyInfo.mutationMethod;
219
if (mutationMethod) {
220
mutationMethod(node, undefined);
218
- } else if (DOMProperty.mustUseAttribute[name]) {
219
- node.removeAttribute(DOMProperty.getAttributeName[name]);
221
+ } else if (propertyInfo.mustUseAttribute) {
222
+ node.removeAttribute(propertyInfo.attributeName);
223
} else {
221
- var propName = DOMProperty.getPropertyName[name];
224
+ var propName = propertyInfo.propertyName;
225
var defaultValue = DOMProperty.getDefaultValueForProperty(
226
node.nodeName,
227
propName
228
);
226
- if (!DOMProperty.hasSideEffects[name] ||
229
+ if (!propertyInfo.hasSideEffects ||
230
('' + node[propName]) !== defaultValue) {
231
node[propName] = defaultValue;
232
}
src/renderers/dom/shared/ReactDOMComponent.js
+2
-2
@@ -633,7 +633,7 @@ ReactDOMComponent.Mixin = {
633
deleteListener(this._rootNodeID, propKey);
634
}
635
} else if (
636
- DOMProperty.isStandardName[propKey] ||
636
+ DOMProperty.properties[propKey] ||
637
DOMProperty.isCustomAttribute(propKey)) {
638
BackendIDOperations.deletePropertyByID(
639
this._rootNodeID,
@@ -697,7 +697,7 @@ ReactDOMComponent.Mixin = {
697
nextProp
698
);
699
} else if (
700
- DOMProperty.isStandardName[propKey] ||
700
+ DOMProperty.properties[propKey] ||
701
DOMProperty.isCustomAttribute(propKey)) {
702
BackendIDOperations.updatePropertyByID(
703
this._rootNodeID,