proto.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. /* global define */
  2. /**
  3. * A base object for ECMAScript 5 style prototypal inheritance.
  4. *
  5. * @see https://github.com/rauschma/proto-js/
  6. * @see http://ejohn.org/blog/simple-javascript-inheritance/
  7. * @see http://uxebu.com/blog/2011/02/23/object-based-inheritance-for-ecmascript-5/
  8. */
  9. (function (root, factory) {
  10. if (typeof define === 'function' && define.amd) {
  11. define([], factory);
  12. } else if (typeof exports === 'object') {
  13. module.exports = factory();
  14. } else {
  15. root.Proto = factory();
  16. }
  17. }(this, function () {
  18. function makeSuper(_super, old, name, fn) {
  19. return function () {
  20. var tmp = this._super;
  21. // Add a new ._super() method that is the same method
  22. // but either pointing to the prototype method
  23. // or to the overwritten method
  24. this._super = (typeof old === 'function') ? old : _super[name];
  25. // The method only need to be bound temporarily, so we
  26. // remove it when we're done executing
  27. var ret = fn.apply(this, arguments);
  28. this._super = tmp;
  29. return ret;
  30. };
  31. }
  32. function legacyMixin(prop, obj) {
  33. var self = obj || this;
  34. var fnTest = /\b_super\b/;
  35. var _super = Object.getPrototypeOf(self) || self.prototype;
  36. var _old;
  37. // Copy the properties over
  38. for (var name in prop) {
  39. // store the old function which would be overwritten
  40. _old = self[name];
  41. // Check if we're overwriting an existing function
  42. if(
  43. ((
  44. typeof prop[name] === 'function' &&
  45. typeof _super[name] === 'function'
  46. ) || (
  47. typeof _old === 'function' &&
  48. typeof prop[name] === 'function'
  49. )) && fnTest.test(prop[name])
  50. ) {
  51. self[name] = makeSuper(_super, _old, name, prop[name]);
  52. } else {
  53. self[name] = prop[name];
  54. }
  55. }
  56. return self;
  57. }
  58. function es5Mixin(prop, obj) {
  59. var self = obj || this;
  60. var fnTest = /\b_super\b/;
  61. var _super = Object.getPrototypeOf(self) || self.prototype;
  62. var descriptors = {};
  63. var proto = prop;
  64. var processProperty = function(name) {
  65. if(!descriptors[name]) {
  66. descriptors[name] = Object.getOwnPropertyDescriptor(proto, name);
  67. }
  68. };
  69. // Collect all property descriptors
  70. do {
  71. Object.getOwnPropertyNames(proto).forEach(processProperty);
  72. } while((proto = Object.getPrototypeOf(proto)) && Object.getPrototypeOf(proto));
  73. Object.keys(descriptors).forEach(function(name) {
  74. var descriptor = descriptors[name];
  75. if(typeof descriptor.value === 'function' && fnTest.test(descriptor.value)) {
  76. descriptor.value = makeSuper(_super, self[name], name, descriptor.value);
  77. }
  78. Object.defineProperty(self, name, descriptor);
  79. });
  80. return self;
  81. }
  82. return {
  83. /**
  84. * Create a new object using Object.create. The arguments will be
  85. * passed to the new instances init method or to a method name set in
  86. * __init.
  87. */
  88. create: function () {
  89. var instance = Object.create(this);
  90. var init = typeof instance.__init === 'string' ? instance.__init : 'init';
  91. if (typeof instance[init] === 'function') {
  92. instance[init].apply(instance, arguments);
  93. }
  94. return instance;
  95. },
  96. /**
  97. * Mixin a given set of properties
  98. * @param prop The properties to mix in
  99. * @param obj [optional] The object to add the mixin
  100. */
  101. mixin: typeof Object.defineProperty === 'function' ? es5Mixin : legacyMixin,
  102. /**
  103. * Extend the current or a given object with the given property
  104. * and return the extended object.
  105. * @param prop The properties to extend with
  106. * @param obj [optional] The object to extend from
  107. * @returns The extended object
  108. */
  109. extend: function (prop, obj) {
  110. return this.mixin(prop, Object.create(obj || this));
  111. },
  112. /**
  113. * Return a callback function with this set to the current or a given context object.
  114. * @param name Name of the method to proxy
  115. * @param args... [optional] Arguments to use for partial application
  116. */
  117. proxy: function (name) {
  118. var fn = this[name];
  119. var args = Array.prototype.slice.call(arguments, 1);
  120. args.unshift(this);
  121. return fn.bind.apply(fn, args);
  122. }
  123. };
  124. }));