lazy.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  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. 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. desc.value = resolvable ? call.call(value, this, options) : value;
  51. defineProperty(this, name, desc);
  52. desc.value = null;
  53. return this[name];
  54. };
  55. } else {
  56. dgs.get = function self() {
  57. var base = this, ownDesc;
  58. if (hasOwnProperty.call(this, name)) {
  59. // It happens in Safari, that getter is still called after property
  60. // was defined with a value, following workarounds that
  61. ownDesc = getOwnPropertyDescriptor(this, name);
  62. if (ownDesc.hasOwnProperty('value')) return ownDesc.value;
  63. if ((typeof ownDesc.get === 'function') && (ownDesc.get !== self)) {
  64. return ownDesc.get.call(this);
  65. }
  66. }
  67. while (!hasOwnProperty.call(base, name)) base = getPrototypeOf(base);
  68. desc.value = resolvable ? call.call(value, base, options) : value;
  69. defineProperty(base, name, desc);
  70. desc.value = null;
  71. return base[name];
  72. };
  73. }
  74. dgs.set = function (value) {
  75. dgs.get.call(this);
  76. this[cacheName] = value;
  77. };
  78. if (options.desc) {
  79. desc = {
  80. configurable: contains.call(options.desc, 'c'),
  81. enumerable: contains.call(options.desc, 'e')
  82. };
  83. if (cacheName === name) {
  84. desc.writable = contains.call(options.desc, 'w');
  85. desc.value = null;
  86. } else {
  87. writable = contains.call(options.desc, 'w');
  88. desc.get = dgs.get;
  89. desc.set = dgs.set;
  90. }
  91. delete options.desc;
  92. } else if (cacheName === name) {
  93. desc = {
  94. configurable: Boolean(options.configurable),
  95. enumerable: Boolean(options.enumerable),
  96. writable: Boolean(options.writable),
  97. value: null
  98. };
  99. }
  100. delete options.configurable;
  101. delete options.enumerable;
  102. delete options.writable;
  103. return dgs;
  104. };
  105. module.exports = function (props) {
  106. return map(props, function (desc, name) { return define(name, desc); });
  107. };