Pros and Cons of Class Creation Script in Javascript?
I recently wrote a class creation script (with the help of Crockford's and Resig's code for certain parts) that automates certain tasks such as: merging defaults with passed in params, requiring params, creating a bridge between DOM and class instance, binding custom events with a common namespace, inheritance with access to super method, and the ability for mixins.
My 1st question is, what are the pros and cons of creating your classes this way? My concern is performance and portability (all my classes depend on this script now).
My 2nd question is, what would be better ways to write a class creation script? Code is below.
(function($){
// Crockford's way of creating new objects
function F(){};
function _createObject(o) {
F.prototype = o;
return new F();
};
$.Class = function(){
var
prototype = {}
,_super = {}
,requiredError = ''
,extendedInits = []
,fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
// copy properties/methods to prototype
for (var i = 0, ln = arguments.length; i < ln; ++i){
var obj = arguments[i];
// if extending another class
if ($.isFunction(obj)) _super = obj = obj.prototype;
if (typeof obj == 'object'){
for (var prop in obj){
var objMethod = obj[prop];
// Resig's Simple Javascript Inheritance
// if method already exists, map old method to this._super()
prototype[prop] = typeof objMethod == "function" && typeof _super[prop] == "function" && fnTest.test(obj[prop]) ?
(function(prop, fn){
return function() {
var tmp = this._super;
开发者_如何学Go // Add a new ._super() method that is the same method
// but on the super-class
this._super = _super[prop];
// The method only need to be bound temporarily, so we
// remove it when we're done executing
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
};
})(prop, objMethod) :
// or overwrite the method/property
objMethod;
// if __init method is defined on any of the objects passed in, they will be automatically run at instantiation
if (prop == '__init') extendedInits.push(obj[prop]);
}
}
}
prototype.__initialize = function(){
var
self = this
,dataNS = this.__dataNS
,requiredParams = this.__requiredParams;
// check required parameters
if (requiredParams){
for (var i = 0, ln = requiredParams.length; i < ln; ++i){
var param = requiredParams[i];
if (arguments.length == 0 || !arguments[0][param]) requiredError = requiredError + param + ', ';
}
}
// if all required params are passed in
if (requiredError.length == 0){
// set defaults
this.cfg = $.extend(true, {}, this.__defaults, arguments[0]);
this.$elem = this.cfg.$elem;
// create bridge between dom and instance
if (dataNS) this.$elem.data(dataNS, this);
// init class instance
if (this.init) this.init.apply(this, arguments);
// init objects instance was extended with
if (extendedInits.length > 0){
$.each(extendedInits, function(k, fn){
fn.apply(self, arguments);
});
}
// init instance level
if (this.cfg.__init) this.cfg.__init.apply(this, arguments);
}
else {
// alert missing properties
requiredError = requiredError.substring(0, requiredError.lastIndexOf(','));
var str = 'Required Parameters: ' + requiredError;
if (dataNS) str = dataNS + ' - ' + str;
alert(str);
}
};
function _Class(){
this.__initialize.apply(this, arguments);
}
_Class.prototype = _createObject(prototype);
_Class.constructor = _Class;
return _Class;
}
})(jQuery);
And this is how it would be used:
$.Accordion = new $.Class({
__requiredParams: ['$elem']
,__defaults: {
speed: 200,
onlyOneOpen: true,
classNames: {
panel: 'panel', panelHeader: 'panelHeader', panelContent: 'panelContent'
}
}
,panels: {}
,init: function(opts){
console.log('1st init');
var self = this;
// loop thru panels
this.$elem.find('.' + this.cfg.classNames.panel).each(function(){
var $this = $(this);
var panelName = $this.attr('id');
// uses panel dom element id as name
self.panels[panelName] = new $.Panel({$elem: $this});
});
// panel header on click
this.$elem.find('.' + this.cfg.classNames.panelHeader).click(function(e){
e.preventDefault();
var panelName = $(this).parents('.' + self.cfg.classNames.panel).attr('id');
var action = (self.panels[panelName].isOpen()) ? 'close' : 'open';
self[action](panelName);
});
}
,closeAll: function(){
$.dispatch('close.panel.accordion');
}
,openAll: function(){
$.dispatch('open.panel.accordion');
}
,open: function(panelName){
if (this.cfg.onlyOneOpen) this.closeAll();
if (typeof this.panels[panelName] != 'undefined') $.dispatch('open.' + panelName + '.accordion');
}
,close: function(panelName){
if (typeof this.panels[panelName] != 'undefined') $.dispatch('close.' + panelName + '.accordion');
}
}, $.ClassUtils);
Note: $.ClassUtils in the example above is a set of public methods mixed into the $.Accordion class
Lots of frameworks do what you have done. The primary concern I have with this is that it tries to make javascript something it is not. Javascript is prototypal inheritance language. Its is not a classical inheritance language.
This is not to say that 'class management' libraries are not useful because they are. Some feedback on yours: other examples of the code you have written make it easier to create classes. If you are going to do this, the end result when creating a class should be something like
var myObj = MyStuff.create({
prop: prop,
doSomething: function(){}
});
that is much easier to read, and less to type, than the system you put in place, as far as I can tell from your example...
精彩评论