lazy.js 3.8 KB

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