Created
August 15, 2011 05:14
-
-
Save huandu/1145744 to your computer and use it in GitHub Desktop.
A way to define javascript class. support multiple base class and static method.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function(window, undefined) { | |
var Abstract = function() { | |
return arguments.callee; | |
}, | |
Static = function(cb) { | |
this.cb = cb; | |
}, | |
/** | |
* define your class. | |
* | |
* @note how to use | |
* | |
* - define a class: var A = Class({...}) | |
* - define a class with constructor: | |
* var A = Class({ | |
* constructor: function() { | |
* console.log('A::constructor'); | |
* } | |
* }); | |
* // create instance of A and print A::constructor | |
* a = A.create(); | |
* - define abstract or static method: | |
* var A = Class({ | |
* a1: Class.Abstract, // cannot be called unless implement it | |
* a2: Class.Static(function() { | |
* console.log('A::a2'); | |
* }) | |
* }); | |
* // print A::a2 | |
* A.a2(); | |
* - derive from one class | |
* B = A.extend({ | |
* constructor: function() { | |
* // call A's constructor | |
* this.Super(A, some, params); | |
* } | |
* }); | |
* - what's cool, you can define multiple super class. | |
* C = Class(A, B).extend({ | |
* constructor: function() { | |
* this.Super(A, some, params); | |
* this.Super(B, other, params); | |
* } | |
* }); | |
* - last but not least, polute existing class prototype | |
* Class(Array, { | |
* isArray: function() {return this instanceof Array;} | |
* }); | |
* | |
* @author Huan Du (http://huandu.me/+) | |
*/ | |
Class = function(methods) { | |
var klass = function() {}, | |
index = 0, | |
i, arg, dontPolute, proto, chain; | |
// if methods is function and no other function in param list, | |
// means to polute this function prototype with my class style. | |
if (typeof(methods) === 'function') { | |
dontPolute = false; | |
for (i = 1; i < arguments.length; i++) { | |
if (typeof(arguments[i]) === 'function') { | |
dontPolute = true; | |
break; | |
} | |
} | |
if (!dontPolute) { | |
klass = methods; | |
index = 1; | |
} | |
} | |
// extend this class with methods. | |
// by default, don't polute current class' prototype. | |
klass.extend = function(methods, polute) { | |
var target = this, | |
proto = target.prototype, | |
method, i; | |
// also support f(polute, methods) | |
if (methods === true) { | |
methods = polute; | |
polute = true; | |
} | |
if (!polute) { | |
target = Class(this.prototype); | |
proto = target.prototype; | |
proto.Super.superClassChain = [this]; | |
} | |
delete methods.Super; | |
for (i in methods) { | |
method = methods[i]; | |
if (method === Abstract) { | |
method = (function(name) { | |
return function() { | |
throw new Error('must implement function "' + name + '"'); | |
} | |
})(i); | |
} else if (method instanceof Static) { | |
method = method.cb; | |
method._classIsStatic = true; | |
target[i] = method; | |
} else if (method._classIsStatic) { // static method can be derived | |
target[i] = method; | |
} | |
proto[i] = method; | |
} | |
// on IE 8 or lower, for..in loop cannot get 'constructor' attr in methods | |
if ('constructor' in methods) { | |
proto['constructor'] = methods.constructor; | |
} | |
return target; | |
}; | |
klass.create = function() { | |
var self = new this(); | |
this.prototype.constructor.apply(self, arguments); | |
self.constructor = this; | |
return self; | |
}; | |
proto = klass.prototype; | |
proto.Super = function(superClass) { | |
var chain = this.Super.superClassChain, | |
args, i; | |
// check whether the superClass is really the super class of this | |
for (i in chain) { | |
if (chain[i] === superClass) { | |
args = Array.prototype.slice.call(arguments); | |
args.shift(); | |
return superClass.prototype.constructor.apply(this, args); | |
} | |
} | |
throw new Error('this class does not derived from such super class'); | |
}; | |
chain = proto.Super.superClassChain = []; | |
// iterately add all methods to klass | |
for (; index < arguments.length; index++) { | |
arg = arguments[index]; | |
if (typeof(arg) === 'function') { | |
klass.extend(arg.prototype, true); | |
chain.push(arg); | |
} else { | |
klass.extend(arg, true); | |
} | |
} | |
return klass; | |
}; | |
Class.Abstract = Abstract; | |
Class.Static = function(cb) { | |
return new Static(cb); | |
} | |
window.Class = Class; | |
})(window); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// here is a piece of sample code | |
// create a new class | |
A = Class({ | |
constructor: function(str) { | |
console.log('your input: ' + str); | |
}, | |
a: function() { | |
console.log('A::a'); | |
} | |
}); | |
// expected output: | |
// case 1: | |
// your input: hello | |
// A::a | |
console.log('case 1:'); | |
A.create('hello').a(); | |
// polute a normal class | |
AOld = function() {}; | |
Class(AOld, { | |
constructor: function() { | |
console.log('AOld::constructor'); | |
}, | |
aOld: function() { | |
console.log('AOld::aOld'); | |
} | |
}); | |
// expected output: | |
// case 2: | |
// AOld::constructor | |
// AOld::aOld | |
console.log('case 2:'); | |
AOld.create().aOld(); | |
// polute a built-in class | |
Class(Array, { | |
forEach: function(cb) { | |
var i; | |
for (i = 0; i < this.length; i++) { | |
cb.call(this, this[i]); | |
} | |
} | |
}); | |
// expected output: | |
// case 3: | |
// got 6 | |
// got 1 | |
// got 4 | |
console.log('case 3:'); | |
[6, 1, 4].forEach(function(item) { | |
console.log('got ' + item); | |
}); | |
// ASub derive from A | |
ASub = A.extend({ | |
constructor: function() { | |
this.Super(A, 'wow'); | |
}, | |
a: function() { | |
console.log('ASub::a'); | |
} | |
}); | |
// expected output: | |
// case 4: | |
// your input: wow | |
// ASub::a | |
console.log('case 4:'); | |
ASub.create().a(); | |
// B is abstract class | |
B = Class({ | |
b: Class.Abstract, | |
bStatic: Class.Static(function() { | |
console.log('B::bStatic'); | |
}) | |
}); | |
// if you use B, it's ok. | |
// but if you call b::b(), an error will be thrown. | |
try { | |
B.create().b(); | |
} catch (e) { | |
// expected output: | |
// case 5: | |
// must implement function "b" | |
// B::bStatic | |
console.log('case 5:'); | |
console.log(e.message); | |
B.bStatic(); | |
} | |
// extend B and implement b() | |
BSub = B.extend({ | |
b: function() { | |
console.log('BSub::b'); | |
} | |
}); | |
// expected output: | |
// case 6: | |
// BSub::b | |
console.log('case 6:'); | |
BSub.create().b(); | |
// derived from multiple Super class | |
// you can also use Class(A, B).extend({...}) | |
// they are the same | |
// NOTE: static method B::bStatic() can be derived | |
C = Class(A, B, { | |
constructor: function() { | |
this.Super(A, 'hey'); | |
}, | |
b: function() { | |
console.log('C::b'); | |
}, | |
c: function() { | |
console.log('C::c'); | |
} | |
}); | |
// expected output: | |
// case 7: | |
// your input: hey | |
// C::b | |
// C::c | |
// B::bStatic | |
console.log('case 7:'); | |
c = C.create(); | |
c.b(); | |
c.c(); | |
C.bStatic(); | |
// no need to demonstrate more, right? | |
// send your feedback to me in github/twitter or contact me at http://huandu.me/+ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
去掉了对象prototype中的_classSuperChain这个变量,把它挪到Super方法中去了
代码整体做了修正,换成了我们公司线上用的版本~