From 06f762da77196ac604777c38276a4e9b47defb25 Mon Sep 17 00:00:00 2001 From: Kunal Mehta Date: Tue, 4 Mar 2014 18:11:54 -0800 Subject: [PATCH] Add Immutable As titled. First of 3 diffs to add Immutable, ImmutableMap, and ImmutableObject. No logic changes. --- src/utils/immutable/Immutable.js | 132 +++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 src/utils/immutable/Immutable.js diff --git a/src/utils/immutable/Immutable.js b/src/utils/immutable/Immutable.js new file mode 100644 index 0000000000..444d426e47 --- /dev/null +++ b/src/utils/immutable/Immutable.js @@ -0,0 +1,132 @@ +/** + * Copyright 2014 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 Immutable + */ + +var invariant = require('invariant'); +var isNode = require('isNode'); +var keyOf = require('keyOf'); +var mergeInto = require('mergeInto'); + +var SECRET_KEY = keyOf({_DONT_EVER_TYPE_THIS_SECRET_KEY: null}); + +/** + * `Immutable` provides a guarantee of immutability at developer time when + * strict mode is used. The extra computations required to enforce immutability + * are stripped out in production for performance reasons. `Immutable` + * guarantees to enforce immutability for enumerable, own properties. This + * allows easy wrapping of `Immutable` with the ability to store non-enumerable + * properties on the instance that only your static methods reason about. In + * order to achieve IE8 compatibility (which doesn't have the ability to define + * non-enumerable properties), modules that want to build their own reasoning + * of `Immutable`s and store computations can define their non-enumerable + * properties under the name `toString`, and in IE8 only define a standard + * property called `toString` which will mistakenly be considered not + * enumerable due to its name (but only in IE8). The only limitation is that no + * one can store their own `toString` property. + * https://developer.mozilla.org/en-US/docs/ECMAScript_DontEnum_attribute#JScript_DontEnum_Bug + */ +class Immutable { + /** + * An instance of `Immutable` appears to be a plain JavaScript object, except + * `instanceof Immutable` evaluates to `true`, and it is deeply frozen in + * development mode. + * + * @param {number} secret Ensures this isn't accidentally constructed outside + * of convenience constructors. If created outside of a convenience + * constructor, may not be frozen. Forbidding that use case for now until we + * have a better API. + */ + constructor(secret) { + invariant( + secret === Immutable[SECRET_KEY], + 'Only certain classes should create instances of `Immutable`.' + + 'You probably want something like ImmutableObject.create.' + ); + } + + /** + * Helper method for classes that make use of `Immutable`. + * @param {Immutable} immutable Object to merge properties into. + * @param {Array} propertyObjects List of objects to merge into + * `destination`. + */ + static mergeAllPropertiesInto(destination, propertyObjects) { + var argLength = propertyObjects.length; + for (var i = 0; i < argLength; i++) { + mergeInto(destination, propertyObjects[i]); + } + } + + + /** + * Freezes the supplied object deeply. Other classes may implement their own + * version based on this. + * + * @param {*} object The object to freeze. + */ + static deepFreezeRootNode(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)) { + Immutable.recurseDeepFreeze(object[prop]); + } + } + Object.seal(object); + } + + /** + * Differs from `deepFreezeRootNode`, in that we first check if this is a + * necessary recursion. If the object is already an `Immutable`, then the + * recursion is unnecessary as it is already frozen. That check obviously + * wouldn't work for the root node version `deepFreezeRootNode`! + */ + static recurseDeepFreeze(object) { + if (isNode(object) || !Immutable.shouldRecurseFreeze(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)) { + Immutable.recurseDeepFreeze(object[prop]); + } + } + Object.seal(object); + } + + /** + * Checks if an object should be deep frozen. Instances of `Immutable` are + * assumed to have already been deep frozen, so we can have large `__DEV__` + * time savings by skipping freezing of them. + * + * @param {*} object The object to check. + * @return {boolean} Whether or not deep freeze is needed. + */ + static shouldRecurseFreeze(object) { + return ( + typeof object === 'object' && + !(object instanceof Immutable) && + object !== null + ); + } +} + +Immutable._DONT_EVER_TYPE_THIS_SECRET_KEY = Math.random(); + +module.exports = Immutable;