-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathclass.js
339 lines (320 loc) · 12 KB
/
class.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
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
/*!
* Class-based inheritance model for JavaScript v1.0
* Copyright (c) 2011, Pawel Preneta <[email protected]>
* MIT Licensed (http://www.opensource.org/licenses/mit-license.php)
*/
/**
* @fileOverview Class-based inheritance model for JavaScript
* @version 1.1
*/
/**
* Class for managing class-based inheritance.<br />
* It's based on Prototype's Class with some changes (the way of super method calls)
* and the ability to call the constructor with a arguments list.
*
* @namespace Class for managing class-based inheritance
*/
var Class = (function() {
var fnTest = /xyz/.test(function() {xyz;}) ? /\b_super\b/ : /.*/;
/**
* Creates a class and returns a constructor function for instances of the class.<br />
* Calling returned constructor with "new" statement will create new class
* instance and automatically invoke class's "init" method.<br />
* Accepts two kind of params: <br />
* - the first one - "superclass" - is optional and it can be other Class to extend;
* if given - all its methods and properties are inherited by created class;
* one can access parent's methods calling "super" method (see examples)<br />
* - other params are objects, which methods are copied to new class; if there's
* more then one object and method names are overlapping, later one take precedence.
*
* @example
* // create base class
* var Animal = Class.create({
* init: function(name, sound) {
* this.name = name;
* this.sound = sound;
* },
* speak: function() {
* alert(this.name + ' says: ' + this.sound + '!');
* }
* });
* // extend base class
* var Snake = Class.create(Animal, {
* init: function(name) {
* this._super('init', name, 'hissssssssss');
* }
* });
* // create instance
* var ringneck = new Snake('Ringneck');
* ringneck.speak(); // alerts "Ringneck says: hissssssss!"
*
* @name Class.create
* @static
* @function
*
* @param {Class} [superclass] Optional superclass to extend
* @param {Object} methods One or more objects with methods for new class
*
* @return {Class} Class constructor
*/
function create() {
'klass:nomunge'; // do not obfuscate constructor name
var parent = null, args = Array.prototype.slice.call(arguments);
if (typeof args[0] === 'function') {
parent = args.shift();
}
function klass() {
this.init.apply(this, arguments);
}
extend(klass, Methods);
klass.superclass = parent;
klass.subclasses = [];
if (parent) {
var subclass = function() {};
subclass.prototype = parent.prototype;
klass.prototype = new subclass();
parent.subclasses.push(klass);
}
for (var i = 0; i < args.length; i++) {
klass.addMethods(args[i]);
}
if (!klass.prototype.init) {
klass.prototype.init = function() {};
}
klass.prototype.constructor = klass;
return klass;
}
/**
* Extends object by copying all properties from the source object to the destination object.<br />
* By default source properties override destination properties, if such exists.
* This can be avoided with safe param set to true.
*
* @example
* // simple usage
* var dest = {
* a: 1
* },
* source = {
* a: 2,
* b: true
* };
* Class.extend(dest, source, true); // dest is now {a: 1, b: true}
*
* // extend Animal class with some static method
* Class.extend(Animal, {
* staticProp: true,
* staticMethod: function() {
* alert('Animal.staticMethod called!');
* }
* });
* Animal.staticMethod(); // alerts "Animal.staticMethod called!"
*
* @name Class.extend
* @static
* @function
*
* @param {Object} dest Destination object, where new properties will be copied
* @param {Object} source Source object
* @param {Boolean} [safe=false] If set to true, the destination object properties won't be overwritten
*
* @return {Object} Extended object
*/
function extend(dest, source, safe) {
safe = safe || false;
for (var prop in source) {
if (!dest[prop] || !safe) {
dest[prop] = source[prop];
}
}
return dest;
}
/**
* Calls class constructor with an arbitrary number of arguments.<br />
* Allows to simulate use of .apply() on class constructor.
*
* @example
* var args = ['some name', 'some sound'];
* var instance = Class.construct(Animal, args); // works the same as new Animal('some name', 'some sound');
*
* @name Class.construct
* @static
* @function
*
* @param {Class} klass Class object
* @param {*} args Arguments to pass to klass constructor
*
* @return {Class} New instance of given klass
*/
function construct(klass, args) {
function F() {
return klass.apply(this, args);
}
F.prototype = klass.prototype;
return new F();
}
var privates = 'superclass subclasses addMethods getMethods hasMethod getStaticProperties hasStaticProperty'.split(' '),
Methods = {
/**
* Allows to add new (or redefine existing) instance methods.<br />
* This method is available on classes created by {@link Class.create}.<br />
* New methods are added to all subclasses as well as the already instantiated instances.
*
* @example
* var Animal = Class.create({
* init: function(name, sound) {
* this.name = name;
* this.sound = sound;
* },
* speak: function() {
* alert(this.name + ' says: ' + this.sound + '!');
* }
* });
* var Bird = Class.create(Animal, {
* init: function(sound) {
* this._super('init', 'Bird', sound);
* }
* });
* var littleBird = new Bird('Bird', 'tweet, tweet');
* Animal.addMethods({
* speakLoud: function() {
* alert(this.name + ' says: ' + this.sound.toUpperCase() + '!');
* }
* });
* littleBird.speakLoud(); // alerts "Bird says: TWEET, TWEET!"
*
* @name Class#addMethods
* @function
*
* @param {Object} source Source object containing methods to add
*
* @return {Class}
*/
addMethods: function(source) {
var ancestor = this.superclass && this.superclass.prototype;
for (var name in source) {
this.prototype[name] = typeof source[name] === 'function' &&
ancestor && typeof ancestor[name] === 'function' && fnTest.test(source[name]) ? (function(name, fn) {
return function() {
this._super = function(method) {
return ancestor[method].apply(this, Array.prototype.slice.call(arguments, 1));
};
return fn.apply(this, arguments);
};
})(name, source[name]) : source[name];
}
return this;
},
/**
* Gets the class methods' names.
*
* @example
* var Animal = Class.create({
* init: function(name, sound) {
* this.name = name;
* this.sound = sound;
* },
* speak: function() {
* alert(this.name + ' says: ' + this.sound + '!');
* }
* });
* Animal.getMethods(); // returns ['init', 'speak']
*
* @name Class#getMethods
* @function
*
* @return {Array} Array of class methods names
*/
getMethods: function() {
var methods = [];
for (var name in this.prototype) {
if (name !== 'constructor' && typeof this.prototype[name] === 'function') {
methods.push(name);
}
}
return methods;
},
/**
* Checks if the class method exists.
*
* @example
* var Animal = Class.create({
* init: function(name, sound) {
* this.name = name;
* this.sound = sound;
* },
* speak: function() {
* alert(this.name + ' says: ' + this.sound + '!');
* }
* });
* Animal.hasMethod('speak'); // returns true
* Animal.hasMethod('speakQuietly'); // returns false
*
* @name Class#hasMethod
* @function
*
* @param {String} name Method name to check
*
* @return {Boolean}
*/
hasMethod: function(name) {
return typeof this.prototype[name] === 'function';
},
/**
* Gets the class static properties names.<br />
* Internal properties and methods (such as superclass, subclasses etc) are ignored.
*
* @example
* Class.extend(Animal, {
* staticProp: true,
* staticMethod: function() {
* alert('Animal.staticMethod called!');
* }
* });
* Animal.getStaticProperties(); // returns ['staticProp', 'staticMethod']
* Animal.hasMethod('speakQuietly'); // returns false
*
* @name Class#getStaticProperties
* @function
*
* @return {Array} Array of class static properties
*/
getStaticProperties: function() {
var properties = [];
for (var name in this) {
if (privates.indexOf(name) === -1) {
properties.push(name);
}
}
return properties;
},
/**
* Checks if the class static property exists.
* Internal properties and methods (such as superclass, subclasses etc) are ignored.
*
* @example
* Class.extend(Animal, {
* staticProp: true,
* staticMethod: function() {
* alert('Animal.staticMethod called!');
* }
* });
* Animal.hasStaticProperty('staticMethod'); // returns true
* Animal.hasStaticProperty('speakQuietly'); // returns false
*
* @name Class#hasStaticProperty
* @function
*
* @param {String} name Property name to check
*
* @return {Boolean}
*/
hasStaticProperty: function(name) {
return typeof this[name] !== 'undefined' && privates.indexOf(name) === -1;
}
};
return {
create: create,
extend: extend,
construct: construct
};
})();