-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
235 lines (207 loc) · 8.94 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
'use strict';
/** @namespace extend-me **/
/** @summary Extends an existing "class" (constructor function) into a new "subclass.
*
* @returns {function} A new class (constructor function), extended from the class provided in the execution context, possibly with some prototype additions.
*
* @desc Extends a class creating a subclass with optional prototype additions.
*
* > CAVEAT: Not to be confused with Underscore-style .extend() which is something else entirely. I've used the name "extend" here because other packages (like Backbone.js) use it this way. You are free to call it whatever you want when you "require" it, such as `var inherits = require('extend')`.
*
* Provide any prototype additions you require in the first argument, including optional constructor code in a method called `initialize`.
*
* For example, if you wish to be able to extend your class `MyBase` to a new "class" (i.e., constructor function) with prototype overrides and/or additions, basic usage is:
*
* ```javascript
* function MyBase() { ... }
* MyBase.prototype = { ... };
* MyBase.extend = extend; // mix in .extend
* var MyChild = MyBase.extend(childPrototypeOverridesAndAdditions);
* var MyGrandchild = MyChild.extend(grandchildPrototypeOverridesAndAdditions);
* ```
*
* This `extend` function is automatically added to the new extended class (constructor function) as a static property so it will itself be "extendable."
*
* @this Function - The base class (constructor function) being extended from.
*
* @param {string} [extendedClassName=prototypeAdditions.$$CLASS_NAME || prototypeAdditions.name] - If defined, added to the constructor as static `name` property; and to the prototype as `$$CLASS_NAME`. Useful for debugging because all derived constructors appear to have the same name (`Constructor`) in the debugger. (If omitted, 2nd parameter is promoted to first position.)
*
* @param {prototypeAdditionsObject} [prototypeAdditions] - Object with members to define in the new constructor's prototype.
*
* @memberOf extend-me
*/
function extend(extendedClassName, prototypeAdditions) {
switch (arguments.length) {
case 0:
prototypeAdditions = {};
break;
case 1:
switch (typeof extendedClassName) {
case 'object':
prototypeAdditions = extendedClassName;
extendedClassName = undefined;
break;
case 'string':
prototypeAdditions = {};
break;
default:
throw 'Single-parameter overload must be either string or object.';
}
break;
case 2:
if (typeof extendedClassName !== 'string' || typeof prototypeAdditions !== 'object') {
throw 'Two-parameter overload must be string, object.';
}
break;
default:
throw 'Too many parameters';
}
/**
* @class
*/
function Constructor() {
if (this.preInitialize) {
this.preInitialize.apply(this, arguments);
}
initializePrototypeChain.apply(this, arguments);
if (this.postInitialize) {
this.postInitialize.apply(this, arguments);
}
}
/**
* @method
* @see {@link extend-me.extend}
* @desc Added to each returned extended class constructor.
*/
Constructor.extend = extend;
Constructor.getClassName = getClassName;
/**
* @method
* @param {string} [ancestorConstructorName] - If given, searches up the prototype chain for constructor with matching name.
* @returns {function|null} Constructor of parent class; or ancestor class with matching name; or null
*/
Constructor.parent = parentConstructor;
var prototype = Constructor.prototype = Object.create(this.prototype);
prototype.constructor = Constructor;
extendedClassName = extendedClassName || prototype.$$CLASS_NAME || prototype.name;
if (extendedClassName) {
Object.defineProperty(Constructor, 'name', { value: extendedClassName, configurable: true });
prototype.$$CLASS_NAME = extendedClassName;
}
// define each prototype addition on the prototype (including getter/setters)
var key, descriptor;
for (key in prototypeAdditions) {
if ((descriptor = Object.getOwnPropertyDescriptor(prototypeAdditions, key))) {
Object.defineProperty(prototype, key, descriptor);
}
}
if (typeof this.postExtend === 'function') {
this.postExtend(prototype);
}
return Constructor;
}
function Base() {}
Base.prototype = {
constructor: Base.prototype.constructor,
getClassName: function() {
return (
this.$$CLASS_NAME ||
this.name ||
this.constructor.name // try Function.prototype.name as last resort
);
},
/**
* Access a member of the super class.
* @returns {Object}
*/
get super() {
return Object.getPrototypeOf(Object.getPrototypeOf(this));
},
/**
* Find member on prototype chain beginning with super class.
* @param {string} memberName
* @returns {undefined|*} `undefined` if not found; value otherwise.
*/
superMember: function(memberName) {
var parent = this.super;
do { parent = Object.getPrototypeOf(parent); } while (!parent.hasOwnProperty(memberName));
return parent && parent[memberName];
},
/**
* Find method on prototype chain beginning with super class.
* @param {string} methodName
* @returns {function}
*/
superMethod: function(methodName) {
var method = this.superMember(methodName);
if (typeof method !== 'function') {
throw new TypeError('this.' + methodName + ' is not a function');
}
return method;
},
/**
* Find method on prototype chain beginning with super class and call it with remaining args.
* @param {string} methodName
* @returns {*}
*/
callSuperMethod: function(methodName) {
return this.superMethod(methodName).apply(this, Array.prototype.slice.call(arguments, 1));
}
};
Base.extend = extend;
extend.Base = Base;
/**
* Optional static method is called with new "class" (constructor) after extending.
* This permits miscellaneous tweaking and cleanup of the new class.
* @method postExtend
* @param {object} prototype
* @memberOf Base
*/
/** @typedef {function} extendedConstructor
* @property prototype.super - A reference to the prototype this constructor was extended from.
* @property [extend] - If `prototypeAdditions.extendable` was truthy, this will be a reference to {@link extend.extend|extend}.
*/
/** @typedef {object} prototypeAdditionsObject
* @desc All members are copied to the new object. The following have special meaning.
* @property {function} [initialize] - Additional constructor code for new object. This method is added to the new constructor's prototype. Gets passed new object as context + same args as constructor itself. Called on instantiation after similar function in all ancestors called with same signature.
* @property {function} [preInitialize] - Called before the `initialize` cascade. Gets passed new object as context + same args as constructor itself. If not defined here, the top-most (and only the top-most) definition found on the prototype chain is called.
* @property {function} [postInitialize] - Called after the `initialize` cascade. Gets passed new object as context + same args as constructor itself. If not defined here, the top-most (and only the top-most) definition found on the prototype chain is called.
*/
/** @summary Call all `initialize` methods found in prototype chain, beginning with the most senior ancestor's first.
* @desc This recursive routine is called by the constructor.
* 1. Walks back the prototype chain to `Object`'s prototype
* 2. Walks forward to new object, calling any `initialize` methods it finds along the way with the same context and arguments with which the constructor was called.
* @private
* @memberOf extend-me
*/
function initializePrototypeChain() {
var term = this,
args = arguments;
recur(term);
function recur(obj) {
var proto = Object.getPrototypeOf(obj);
if (proto.constructor !== Object) {
recur(proto);
if (proto.hasOwnProperty('initialize')) {
proto.initialize.apply(term, args);
}
}
}
}
function getClassName() {
return (
this.prototype.$$CLASS_NAME ||
this.prototype.name ||
this.name // try Function.prototype.name as last resort
);
}
function parentConstructor(ancestorConstructorName) {
var prototype = this.prototype;
if (prototype) {
do {
prototype = Object.getPrototypeOf(prototype);
} while (ancestorConstructorName && prototype && prototype.constructor.name !== ancestorConstructorName);
}
return prototype && prototype.constructor;
}
module.exports = extend;