-
-
Save huandu/1145744 to your computer and use it in GitHub Desktop.
(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); |
// 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/+ |
@blankyao 我检查了一下,至少例子的运行结果是预期的
你提到的init和create的继承问题,这可能不是一个问题,因为我设计的是所有的“静态”方法都不会继承,比如init create和用户自定义的function static。这主要是因为在多继承之下无法处理init create这种多继承冲突的问题。如果有好想法,欢迎继续提出 :)
@huandu 我又试了下,还是不符合预期,看下面这部分:
// write "init" and then "calls B1.b3()" to console
B1.b3();
B1.create().b3(); // the same
这里只输出了“calls B1.b3()”,因为这里并没有执行init方法
关于init和create,我感觉init可以做成不是static的(在这里也没有声明init是static的),因为调用父类的初始化方法或者说初始化父类里的数据是一个比较常见的操作
上个例子中,如果B和B1里同时有init方法的话,在B1.create()的时候是调用不到B1的init方法的,只调用了B的init(),这样感觉不太合理
我个人比较倾向于John Resig写的Simple JavaScript Inheritance,但是他写的没有你实现的这个多继承
:)
@blankyao 确实有些问题,我会再改改这个代码。
我的这个实现一个主要目的就是想解决多继承(或者js的interface)的问题,在真正的产品代码里面,Class实现比这个其实复杂很多,不过也有一些其他问题。这个我再整理一下吧~ :)
@blankyao 更新了代码,现在应该更加稳定了。不再用hack方法处理static/abstract方法,同时可以使用Class()来polute已有的类。 Enjoy~
这个版本相当强大呀~
去掉了对象prototype中的_classSuperChain这个变量,把它挪到Super方法中去了
代码整体做了修正,换成了我们公司线上用的版本~
试了下,运行结果和注释里写的不一样,看了下代码,发现是create和init在继承的时候有问题