@samitouri / QOS-React-2 / commits / 40963e503b

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,