Skip to content

Instantly share code, notes, and snippets.

@huandu
Created August 15, 2011 05:14
Show Gist options
  • Save huandu/1145744 to your computer and use it in GitHub Desktop.
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.
(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
Copy link

@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的),因为调用父类的初始化方法或者说初始化父类里的数据是一个比较常见的操作

@blankyao
Copy link

上个例子中,如果B和B1里同时有init方法的话,在B1.create()的时候是调用不到B1的init方法的,只调用了B的init(),这样感觉不太合理
我个人比较倾向于John Resig写的Simple JavaScript Inheritance,但是他写的没有你实现的这个多继承
:)

@huandu
Copy link
Author

huandu commented Aug 23, 2011

@blankyao 确实有些问题,我会再改改这个代码。

我的这个实现一个主要目的就是想解决多继承(或者js的interface)的问题,在真正的产品代码里面,Class实现比这个其实复杂很多,不过也有一些其他问题。这个我再整理一下吧~ :)

@huandu
Copy link
Author

huandu commented Aug 29, 2011

@blankyao 更新了代码,现在应该更加稳定了。不再用hack方法处理static/abstract方法,同时可以使用Class()来polute已有的类。 Enjoy~

@blankyao
Copy link

这个版本相当强大呀~

@huandu
Copy link
Author

huandu commented Nov 21, 2011

去掉了对象prototype中的_classSuperChain这个变量,把它挪到Super方法中去了

代码整体做了修正,换成了我们公司线上用的版本~

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment