lazy.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. "use strict";
  2. var isPlainFunction = require("type/plain-function/is")
  3. , ensureValue = require("type/value/ensure")
  4. , isValue = require("type/value/is")
  5. , map = require("es5-ext/object/map")
  6. , contains = require("es5-ext/string/#/contains");
  7. var call = Function.prototype.call
  8. , defineProperty = Object.defineProperty
  9. , getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor
  10. , getPrototypeOf = Object.getPrototypeOf
  11. , hasOwnProperty = Object.prototype.hasOwnProperty
  12. , cacheDesc = { configurable: false, enumerable: false, writable: false, value: null }
  13. , define;
  14. define = function (name, options) {
  15. var value, dgs, cacheName, desc, writable = false, resolvable, flat;
  16. options = Object(ensureValue(options));
  17. cacheName = options.cacheName;
  18. flat = options.flat;
  19. if (!isValue(cacheName)) cacheName = name;
  20. delete options.cacheName;
  21. value = options.value;
  22. resolvable = isPlainFunction(value);
  23. delete options.value;
  24. dgs = { configurable: Boolean(options.configurable), enumerable: Boolean(options.enumerable) };
  25. if (name !== cacheName) {
  26. dgs.get = function () {
  27. if (hasOwnProperty.call(this, cacheName)) return this[cacheName];
  28. cacheDesc.value = resolvable ? call.call(value, this, options) : value;
  29. cacheDesc.writable = writable;
  30. defineProperty(this, cacheName, cacheDesc);
  31. cacheDesc.value = null;
  32. if (desc) defineProperty(this, name, desc);
  33. return this[cacheName];
  34. };
  35. } else if (!flat) {
  36. dgs.get = function self() {
  37. var ownDesc;
  38. if (hasOwnProperty.call(this, name)) {
  39. ownDesc = getOwnPropertyDescriptor(this, name);
  40. // It happens in Safari, that getter is still called after property
  41. // was defined with a value, following workarounds that
  42. // While in IE11 it may happen that here ownDesc is undefined (go figure)
  43. if (ownDesc) {
  44. if (ownDesc.hasOwnProperty("value")) return ownDesc.value;
  45. if (typeof ownDesc.get === "function" && ownDesc.get !== self) {
  46. return ownDesc.get.call(this);
  47. }
  48. return value;
  49. }
  50. }
  51. desc.value = resolvable ? call.call(value, this, options) : value;
  52. defineProperty(this, name, desc);
  53. desc.value = null;
  54. return this[name];
  55. };
  56. } else {
  57. dgs.get = function self() {
  58. var base = this, ownDesc;
  59. if (hasOwnProperty.call(this, name)) {
  60. // It happens in Safari, that getter is still called after property
  61. // was defined with a value, following workarounds that
  62. ownDesc = getOwnPropertyDescriptor(this, name);
  63. if (ownDesc.hasOwnProperty("value")) return ownDesc.value;
  64. if (typeof ownDesc.get === "function" && ownDesc.get !== self) {
  65. return ownDesc.get.call(this);
  66. }
  67. }
  68. while (!hasOwnProperty.call(base, name)) base = getPrototypeOf(base);
  69. desc.value = resolvable ? call.call(value, base, options) : value;
  70. defineProperty(base, name, desc);
  71. desc.value = null;
  72. return base[name];
  73. };
  74. }
  75. dgs.set = function (value) {
  76. if (hasOwnProperty.call(this, name)) {
  77. throw new TypeError("Cannot assign to lazy defined '" + name + "' property of " + this);
  78. }
  79. dgs.get.call(this);
  80. this[cacheName] = value;
  81. };
  82. if (options.desc) {
  83. desc = {
  84. configurable: contains.call(options.desc, "c"),
  85. enumerable: contains.call(options.desc, "e")
  86. };
  87. if (cacheName === name) {
  88. desc.writable = contains.call(options.desc, "w");
  89. desc.value = null;
  90. } else {
  91. writable = contains.call(options.desc, "w");
  92. desc.get = dgs.get;
  93. desc.set = dgs.set;
  94. }
  95. delete options.desc;
  96. } else if (cacheName === name) {
  97. desc = {
  98. configurable: Boolean(options.configurable),
  99. enumerable: Boolean(options.enumerable),
  100. writable: Boolean(options.writable),
  101. value: null
  102. };
  103. }
  104. delete options.configurable;
  105. delete options.enumerable;
  106. delete options.writable;
  107. return dgs;
  108. };
  109. module.exports = function (props) {
  110. return map(props, function (desc, name) { return define(name, desc); });
  111. };