Files
react/src/utils/LegacyImmutableObject.js
T
Sebastian Markbage 8210beeef4 Hide Object.assign polyfill behind a module
Because the JS community's polyfilling infrastructure sucks and we'll
have to fix it for them before we require this.

JSX spread uses React.__spread
(which might get special behavior for key/ref, not sure yet)

This never uses the native implementation and throws for prototype chains.
Once the native implementations are faster, we'll start using them.
2014-10-16 09:21:10 -07:00

188 lines
5.8 KiB
JavaScript

/**
* Copyright 2013-2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule LegacyImmutableObject
* @typechecks
*/
"use strict";
var assign = require('Object.assign');
var invariant = require('invariant');
var isNode = require('isNode');
var mergeHelpers = require('mergeHelpers');
var checkMergeObjectArgs = mergeHelpers.checkMergeObjectArgs;
var isTerminal = mergeHelpers.isTerminal;
/**
* Wrapper around JavaScript objects that provide a guarantee of immutability at
* developer time when strict mode is used. The extra computations required to
* enforce immutability is stripped out in production for performance reasons.
*/
var LegacyImmutableObject;
function assertLegacyImmutableObject(immutableObject) {
invariant(
immutableObject instanceof LegacyImmutableObject,
'LegacyImmutableObject: Attempted to set fields on an object that is not ' +
'an instance of LegacyImmutableObject.'
);
}
if (__DEV__) {
/**
* Constructs an instance of `LegacyImmutableObject`.
*
* @param {?object} initialProperties The initial set of properties.
* @constructor
*/
LegacyImmutableObject = function LegacyImmutableObject(initialProperties) {
assign(this, initialProperties);
deepFreeze(this);
};
/**
* Checks if an object should be deep frozen. Instances of
* `LegacyImmutableObject` are assumed to have already been deep frozen.
*
* @param {*} object The object to check.
* @return {boolean} Whether or not deep freeze is needed.
*/
var shouldRecurseFreeze = function(object) {
return (
typeof object === 'object' &&
!(object instanceof LegacyImmutableObject) &&
object !== null
);
};
/**
* Freezes the supplied object deeply.
*
* @param {*} object The object to freeze.
*/
var deepFreeze = function(object) {
if (isNode(object)) {
return; // Don't try to freeze DOM nodes.
}
Object.freeze(object); // First freeze the object.
for (var prop in object) {
if (object.hasOwnProperty(prop)) {
var field = object[prop];
if (shouldRecurseFreeze(field)) {
deepFreeze(field);
}
}
}
};
/**
* Returns a new LegacyImmutableObject that is identical to the supplied
* object but with the supplied changes, `put`.
*
* @param {LegacyImmutableObject} immutableObject Starting object.
* @param {?object} put Fields to merge into the object.
* @return {LegacyImmutableObject} The result of merging in `put` fields.
*/
LegacyImmutableObject.set = function(immutableObject, put) {
assertLegacyImmutableObject(immutableObject);
var totalNewFields = assign({}, immutableObject, put);
return new LegacyImmutableObject(totalNewFields);
};
} else {
/**
* Constructs an instance of `LegacyImmutableObject`.
*
* @param {?object} initialProperties The initial set of properties.
* @constructor
*/
LegacyImmutableObject = function LegacyImmutableObject(initialProperties) {
assign(this, initialProperties);
};
/**
* Returns a new LegacyImmutableObject that is identical to the supplied
* object but with the supplied changes, `put`.
*
* @param {LegacyImmutableObject} immutableObject Starting object.
* @param {?object} put Fields to merge into the object.
* @return {LegacyImmutableObject} The result of merging in `put` fields.
*/
LegacyImmutableObject.set = function(immutableObject, put) {
assertLegacyImmutableObject(immutableObject);
var newMap = new LegacyImmutableObject(immutableObject);
assign(newMap, put);
return newMap;
};
}
/**
* Sugar for:
* `LegacyImmutableObject.set(LegacyImmutableObject, {fieldName: putField})`.
*
* @param {LegacyImmutableObject} immutableObject Object on which to set field.
* @param {string} fieldName Name of the field to set.
* @param {*} putField Value of the field to set.
* @return {LegacyImmutableObject} [description]
*/
LegacyImmutableObject.setField = function(immutableObject, fieldName, putField) {
var put = {};
put[fieldName] = putField;
return LegacyImmutableObject.set(immutableObject, put);
};
/**
* Returns a new LegacyImmutableObject that is identical to the supplied object
* but with the supplied changes recursively applied.
*
* @param {LegacyImmutableObject} immutableObject Object on which to set fields.
* @param {object} put Fields to merge into the object.
* @return {LegacyImmutableObject} The result of merging in `put` fields.
*/
LegacyImmutableObject.setDeep = function(immutableObject, put) {
assertLegacyImmutableObject(immutableObject);
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 LegacyImmutableObject ||
put instanceof LegacyImmutableObject) ?
new LegacyImmutableObject(totalNewFields) :
totalNewFields;
}
module.exports = LegacyImmutableObject;