deepFreezeAndThrowOnMutationInDev.js 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. /**
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. *
  7. * @format
  8. * @flow
  9. */
  10. 'use strict';
  11. /**
  12. * If your application is accepting different values for the same field over
  13. * time and is doing a diff on them, you can either (1) create a copy or
  14. * (2) ensure that those values are not mutated behind two passes.
  15. * This function helps you with (2) by freezing the object and throwing if
  16. * the user subsequently modifies the value.
  17. *
  18. * There are two caveats with this function:
  19. * - If the call site is not in strict mode, it will only throw when
  20. * mutating existing fields, adding a new one
  21. * will unfortunately fail silently :(
  22. * - If the object is already frozen or sealed, it will not continue the
  23. * deep traversal and will leave leaf nodes unfrozen.
  24. *
  25. * Freezing the object and adding the throw mechanism is expensive and will
  26. * only be used in DEV.
  27. */
  28. function deepFreezeAndThrowOnMutationInDev<T: Object>(object: T): T {
  29. if (__DEV__) {
  30. if (
  31. typeof object !== 'object' ||
  32. object === null ||
  33. Object.isFrozen(object) ||
  34. Object.isSealed(object)
  35. ) {
  36. return object;
  37. }
  38. const keys = Object.keys(object);
  39. const hasOwnProperty = Object.prototype.hasOwnProperty;
  40. for (let i = 0; i < keys.length; i++) {
  41. const key = keys[i];
  42. if (hasOwnProperty.call(object, key)) {
  43. Object.defineProperty(object, key, {
  44. get: identity.bind(null, object[key]),
  45. });
  46. Object.defineProperty(object, key, {
  47. set: throwOnImmutableMutation.bind(null, key),
  48. });
  49. }
  50. }
  51. Object.freeze(object);
  52. Object.seal(object);
  53. for (let i = 0; i < keys.length; i++) {
  54. const key = keys[i];
  55. if (hasOwnProperty.call(object, key)) {
  56. deepFreezeAndThrowOnMutationInDev(object[key]);
  57. }
  58. }
  59. }
  60. return object;
  61. }
  62. function throwOnImmutableMutation(key, value) {
  63. throw Error(
  64. 'You attempted to set the key `' +
  65. key +
  66. '` with the value `' +
  67. JSON.stringify(value) +
  68. '` on an object that is meant to be immutable ' +
  69. 'and has been frozen.',
  70. );
  71. }
  72. function identity(value) {
  73. return value;
  74. }
  75. module.exports = deepFreezeAndThrowOnMutationInDev;