123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393 |
- (function(){
- "use strict";
- function addPropertyTo(target, methodName, value) {
- Object.defineProperty(target, methodName, {
- enumerable: false,
- configurable: false,
- writable: false,
- value: value
- });
- }
- function banProperty(target, methodName) {
- addPropertyTo(target, methodName, function() {
- throw new ImmutableError("The " + methodName +
- " method cannot be invoked on an Immutable data structure.");
- });
- }
- var immutabilityTag = "__immutable_invariants_hold";
- function addImmutabilityTag(target) {
- addPropertyTo(target, immutabilityTag, true);
- }
- function isImmutable(target) {
- if (typeof target === "object") {
- return target === null || target.hasOwnProperty(immutabilityTag);
- } else {
- // In JavaScript, only objects are even potentially mutable.
- // strings, numbers, null, and undefined are all naturally immutable.
- return true;
- }
- }
- function isMergableObject(target) {
- return target !== null && typeof target === "object" && !(target instanceof Array) && !(target instanceof Date);
- }
- var mutatingObjectMethods = [
- "setPrototypeOf"
- ];
- var nonMutatingObjectMethods = [
- "keys"
- ];
- var mutatingArrayMethods = mutatingObjectMethods.concat([
- "push", "pop", "sort", "splice", "shift", "unshift", "reverse"
- ]);
- var nonMutatingArrayMethods = nonMutatingObjectMethods.concat([
- "map", "filter", "slice", "concat", "reduce", "reduceRight"
- ]);
- function ImmutableError(message) {
- var err = new Error(message);
- err.__proto__ = ImmutableError;
- return err;
- }
- ImmutableError.prototype = Error.prototype;
- function makeImmutable(obj, bannedMethods) {
- // Tag it so we can quickly tell it's immutable later.
- addImmutabilityTag(obj);
- if ("development" === "development") {
- // Make all mutating methods throw exceptions.
- for (var index in bannedMethods) {
- if (bannedMethods.hasOwnProperty(index)) {
- banProperty(obj, bannedMethods[index]);
- }
- }
- // Freeze it and return it.
- Object.freeze(obj);
- }
- return obj;
- }
- function makeMethodReturnImmutable(obj, methodName) {
- var currentMethod = obj[methodName];
- addPropertyTo(obj, methodName, function() {
- return Immutable(currentMethod.apply(obj, arguments));
- });
- }
- function makeImmutableArray(array) {
- // Don't change their implementations, but wrap these functions to make sure
- // they always return an immutable value.
- for (var index in nonMutatingArrayMethods) {
- if (nonMutatingArrayMethods.hasOwnProperty(index)) {
- var methodName = nonMutatingArrayMethods[index];
- makeMethodReturnImmutable(array, methodName);
- }
- }
- addPropertyTo(array, "flatMap", flatMap);
- addPropertyTo(array, "asObject", asObject);
- addPropertyTo(array, "asMutable", asMutableArray);
- for(var i = 0, length = array.length; i < length; i++) {
- array[i] = Immutable(array[i]);
- }
- return makeImmutable(array, mutatingArrayMethods);
- }
- /**
- * Effectively performs a map() over the elements in the array, using the
- * provided iterator, except that whenever the iterator returns an array, that
- * array's elements are added to the final result instead of the array itself.
- *
- * @param {function} iterator - The iterator function that will be invoked on each element in the array. It will receive three arguments: the current value, the current index, and the current object.
- */
- function flatMap(iterator) {
- // Calling .flatMap() with no arguments is a no-op. Don't bother cloning.
- if (arguments.length === 0) {
- return this;
- }
- var result = [],
- length = this.length,
- index;
- for (index = 0; index < length; index++) {
- var iteratorResult = iterator(this[index], index, this);
- if (iteratorResult instanceof Array) {
- // Concatenate Array results into the return value we're building up.
- result.push.apply(result, iteratorResult);
- } else {
- // Handle non-Array results the same way map() does.
- result.push(iteratorResult);
- }
- }
- return makeImmutableArray(result);
- }
- /**
- * Returns an Immutable copy of the object without the given keys included.
- *
- * @param {array} keysToRemove - A list of strings representing the keys to exclude in the return value. Instead of providing a single array, this method can also be called by passing multiple strings as separate arguments.
- */
- function without(keysToRemove) {
- // Calling .without() with no arguments is a no-op. Don't bother cloning.
- if (arguments.length === 0) {
- return this;
- }
- // If we weren't given an array, use the arguments list.
- if (!(keysToRemove instanceof Array)) {
- keysToRemove = Array.prototype.slice.call(arguments);
- }
- var result = this.instantiateEmptyObject();
- for (var key in this) {
- if (this.hasOwnProperty(key) && (keysToRemove.indexOf(key) === -1)) {
- result[key] = this[key];
- }
- }
- return makeImmutableObject(result,
- {instantiateEmptyObject: this.instantiateEmptyObject});
- }
- function asMutableArray(opts) {
- var result = [], i, length;
- if(opts && opts.deep) {
- for(i = 0, length = this.length; i < length; i++) {
- result.push( asDeepMutable(this[i]) );
- }
- } else {
- for(i = 0, length = this.length; i < length; i++) {
- result.push(this[i]);
- }
- }
- return result;
- }
- /**
- * Effectively performs a [map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) over the elements in the array, expecting that the iterator function
- * will return an array of two elements - the first representing a key, the other
- * a value. Then returns an Immutable Object constructed of those keys and values.
- *
- * @param {function} iterator - A function which should return an array of two elements - the first representing the desired key, the other the desired value.
- */
- function asObject(iterator) {
- // If no iterator was provided, assume the identity function
- // (suggesting this array is already a list of key/value pairs.)
- if (typeof iterator !== "function") {
- iterator = function(value) { return value; };
- }
- var result = {},
- length = this.length,
- index;
- for (index = 0; index < length; index++) {
- var pair = iterator(this[index], index, this),
- key = pair[0],
- value = pair[1];
- result[key] = value;
- }
- return makeImmutableObject(result);
- }
- function asDeepMutable(obj) {
- if(!obj || !obj.hasOwnProperty(immutabilityTag) || obj instanceof Date) { return obj; }
- return obj.asMutable({deep: true});
- }
- function quickCopy(src, dest) {
- for (var key in src) {
- if (src.hasOwnProperty(key)) {
- dest[key] = src[key];
- }
- }
- return dest;
- }
- /**
- * Returns an Immutable Object containing the properties and values of both
- * this object and the provided object, prioritizing the provided object's
- * values whenever the same key is present in both objects.
- *
- * @param {object} other - The other object to merge. Multiple objects can be passed as an array. In such a case, the later an object appears in that list, the higher its priority.
- * @param {object} config - Optional config object that contains settings. Supported settings are: {deep: true} for deep merge and {merger: mergerFunc} where mergerFunc is a function
- * that takes a property from both objects. If anything is returned it overrides the normal merge behaviour.
- */
- function merge(other, config) {
- // Calling .merge() with no arguments is a no-op. Don't bother cloning.
- if (arguments.length === 0) {
- return this;
- }
- if (other === null || (typeof other !== "object")) {
- throw new TypeError("Immutable#merge can only be invoked with objects or arrays, not " + JSON.stringify(other));
- }
- var anyChanges = false,
- result = quickCopy(this, this.instantiateEmptyObject()), // A shallow clone of this object.
- receivedArray = (other instanceof Array),
- deep = config && config.deep,
- merger = config && config.merger,
- key;
- // Use the given key to extract a value from the given object, then place
- // that value in the result object under the same key. If that resulted
- // in a change from this object's value at that key, set anyChanges = true.
- function addToResult(currentObj, otherObj, key) {
- var immutableValue = Immutable(otherObj[key]);
- var mergerResult = merger && merger(currentObj[key], immutableValue, config);
- if (merger && mergerResult && mergerResult === currentObj[key]) return;
- anyChanges = anyChanges ||
- mergerResult !== undefined ||
- (!currentObj.hasOwnProperty(key) ||
- ((immutableValue !== currentObj[key]) &&
- // Avoid false positives due to (NaN !== NaN) evaluating to true
- (immutableValue === immutableValue)));
- if (mergerResult) {
- result[key] = mergerResult;
- } else if (deep && isMergableObject(currentObj[key]) && isMergableObject(immutableValue)) {
- result[key] = currentObj[key].merge(immutableValue, config);
- } else {
- result[key] = immutableValue;
- }
- }
- // Achieve prioritization by overriding previous values that get in the way.
- if (!receivedArray) {
- // The most common use case: just merge one object into the existing one.
- for (key in other) {
- if (other.hasOwnProperty(key)) {
- addToResult(this, other, key);
- }
- }
- } else {
- // We also accept an Array
- for (var index=0; index < other.length; index++) {
- var otherFromArray = other[index];
- for (key in otherFromArray) {
- if (otherFromArray.hasOwnProperty(key)) {
- addToResult(this, otherFromArray, key);
- }
- }
- }
- }
- if (anyChanges) {
- return makeImmutableObject(result,
- {instantiateEmptyObject: this.instantiateEmptyObject});
- } else {
- return this;
- }
- }
- function asMutableObject(opts) {
- var result = this.instantiateEmptyObject(), key;
- if(opts && opts.deep) {
- for (key in this) {
- if (this.hasOwnProperty(key)) {
- result[key] = asDeepMutable(this[key]);
- }
- }
- } else {
- for (key in this) {
- if (this.hasOwnProperty(key)) {
- result[key] = this[key];
- }
- }
- }
- return result;
- }
- // Creates plain object to be used for cloning
- function instantiatePlainObject() {
- return {};
- }
- // Finalizes an object with immutable methods, freezes it, and returns it.
- function makeImmutableObject(obj, options) {
- var instantiateEmptyObject =
- (options && options.instantiateEmptyObject) ?
- options.instantiateEmptyObject : instantiatePlainObject;
- addPropertyTo(obj, "merge", merge);
- addPropertyTo(obj, "without", without);
- addPropertyTo(obj, "asMutable", asMutableObject);
- addPropertyTo(obj, "instantiateEmptyObject", instantiateEmptyObject);
- return makeImmutable(obj, mutatingObjectMethods);
- }
- function Immutable(obj, options) {
- if (isImmutable(obj)) {
- return obj;
- } else if (obj instanceof Array) {
- return makeImmutableArray(obj.slice());
- } else if (obj instanceof Date) {
- return makeImmutable(new Date(obj.getTime()));
- } else {
- // Don't freeze the object we were given; make a clone and use that.
- var prototype = options && options.prototype;
- var instantiateEmptyObject =
- (!prototype || prototype === Object.prototype) ?
- instantiatePlainObject : (function() { return Object.create(prototype); });
- var clone = instantiateEmptyObject();
- for (var key in obj) {
- if (obj.hasOwnProperty(key)) {
- clone[key] = Immutable(obj[key]);
- }
- }
- return makeImmutableObject(clone,
- {instantiateEmptyObject: instantiateEmptyObject});
- }
- }
- // Export the library
- Immutable.isImmutable = isImmutable;
- Immutable.ImmutableError = ImmutableError;
- Object.freeze(Immutable);
- /* istanbul ignore if */
- if (typeof module === "object") {
- module.exports = Immutable;
- } else if (typeof exports === "object") {
- exports.Immutable = Immutable;
- } else if (typeof window === "object") {
- window.Immutable = Immutable;
- } else if (typeof global === "object") {
- global.Immutable = Immutable;
- }
- })();
|