Reputation: 13519
Currently we have built our own Javascript framework for building widgets, divs, panels and forms for our complex web application. All our widgets (aka. Components) inherit from a super object called Viewable which primarily defines a view which is a HTMLElememnt.
Viewable = {
getView: function() {
if (!this.view)
this.view = this.createView();
return this.view;
}
createView: function() {
alert(‘You have not implemented createView. You are a very naughty boy!);
}
}
Then we use Object.create(Viewable) to create our own components, which all need to implement createView defined in Viewable.
Header = Object.create(Viewable);
Header.createView = function() {
var div = document.createElement('div');
div.id = 'Header';
}
Header.foobar = function() {
}
I would like to move away from this type of inheritance, this create-object-on-the-fly and just add methods on depending on my mood.
I have looked at this other approach using $.extends from jQuery. Then I can create my object (or better to say ‘define my function’? ‘defined my class’?)
function Header() {
$.extend(true, this, Viewable);
this.createView = function() {
var div = document.createElement('div');
div.id = 'Header';
}
this.foobar = function() {
}
}
I would like to refactor my code to this second approach because for me the benefits are :
But I am not sure. Are there any drawbacks? I would have to refactor 50+ files, so I am a little nervous about doing this. I am still relatively new to Javascript.
Also while we are at it, a quick sub-question. If I refactored my Viewable to look like this :
function Viewable() {
this.getView = function() {
if (!this.view)
this.view = this.createView();
return this.view;
},
createView:function() {
alert(‘You have not implemented createView. You are a very naughty boy!);
}
}
Would this be more beneficial? I like it because to me it makes the code look consistent.
Upvotes: 1
Views: 179
Reputation: 11
I had similar need in one of our java script based project. It was a JS port of Java implementation and one requirement was code structure and the logic should be as much as similar to the Java one. Here is what i have come up with.
Disclaimer: I am bit new to Java Script. The below solution is based on my understanding i have so far on the language and i hope it is within the accepted guideline and best practice.
Below code demonstrates how 'extends' key word in Java can be mimicked in JS.
Function.prototype.extends = function (parent) {
var child = this;
child.prototype.inherit = function () {
var parentProto = parent;
var childProto = child;
var parentObj = new parentProto();
childProto.prototype = parentObj;
childProto.prototype.$super = parentObj;
var newObj;
if(child.arguments.length >0) newObj = new childProto(child.arguments[0]);
else newObj = new childProto();
/*Restore the inherit function back in type prototype so that subsequent
creation of unique objects are possible*/
if(typeof this == "function") childProto.prototype.inherit = this;
return newObj;
};
} ;
Here goes rest of the code.
//Class A
var A = function(){
//some field
//some methods
}
//Class B
var B = function(){
//Check if inheritance needed
if(this.inherit){
var newInst = this.inherit.call(this.inherit);
return newInst;
}
//rest of class B specific code
}
//Here goes the inheritance statement like "Class B extends A"
B.extends(A);
So far this works for me and have used only for 1 level inheritance. Hope this helps.
Upvotes: 1
Reputation: 3902
There are at least two distinct and useful strategies for implementing a class-type code structure. One is prototypical and the other is factory-style inheritance.
Prototypal Inheritance
JavaScript's prototypal inheritance is classic and effective.
// A viewable device.
function Device() {
var _element = null,
_type = 'div';
// Get or set the element type.
this.type = function type(type_) {
if (!arguments.length)
return _type;
_type = type_;
return this;
};
// Lazy creation of the element, or set it explicitly.
this.element = function element(element_) {
if (!arguments.length)
return _element || (_element = $('<' + _type + '>').get(0));
_element = element_;
return this;
};
// Allow constructor chaining on subclasses.
return this;
}
Device.prototype = Object.create(null);
Device.prototype.constructor = Device;
// Get/set. Hide or show this device.
Device.prototype.visible = function visible(show) {
if (!arguments.length)
return $(this.element()).css('display') !== 'none';
$(this.element()).css('display', show ? '' : 'none');
return this;
};
// Add or remove a css class, or check for its presence.
Device.prototype.classed = function classed(css_class, classed_) {
if(arguments.length === 1)
return $(this.element()).hasClass(css_class);
if (classed_)
$(this.element()).addClass(css_class);
else
$(this.element()).removeClass(css_class);
return this;
};
Although Device
is a base class, it can be instantiated and configured like this:
// Create a list item device.
var ul = new Device()
.type('ul')
.classed('list-items', true)
.visible(false);
// Check for the class.
ul.classed('list-items'); // => true
// Is the device visible?
ul.visible() // => false
// Show the device.
ul.visible(true);
ul.visible(); // => true
To make the list-items
device a subclass:
function ListItems() {
Device.call(this)
.classed('list-items', true)
.visible(false);
return this;
}
ListItems.prototype = Object.create(Device.prototype);
ListItems.prototype.constructor = ListItems;
ListItems.prototype.addItem = function addItem(content, css_class) {
$(this.element()).append($('<li>')
.addClass(css_class || 'list-item')
.html(content));
return this;
};
To instantiate the subclass:
var ul = new ListItems()
.addItem('Item 1')
.addItem('Item 2')
.addItem('Item 3')
.visible(true);
ul.element();
/*
<ul class="list-items">
<li class="list-item">Item 1</li>
<li class="list-item">Item 2</li>
<li class="list-item">Item 3</li>
</ul>
*/
Factory Inheritance
Factory inheritance is elegant, and obviates the need for the new
keyword if it's bothersome.
function device() {
var self = {},
_type = 'div',
_element = null;
self.type = function type(type_) {
if (!arguments.length)
return _type;
_type = type_;
return this;
};
self.element = function element(element_) {
if (!arguments.length)
return _element || (_element = $('<' + _type + '>').get(0));
_element = element_;
return this;
};
self.visible = function visible(show) {
if (!arguments.length)
return $(this.element()).css('display') !== 'none';
$(this.element()).css('display', show ? '' : 'none');
return this;
};
self.classed = function classed(css_class, classed_) {
if(arguments.length === 1)
return $(this.element()).hasClass(css_class);
if (classed_)
$(this.element()).addClass(css_class);
else
$(this.element()).removeClass(css_class);
return this;
};
return self;
}
To instantiate a device:
var ul = device()
.type('ul')
.classed('list-items', true)
.visible(false);
To inherit from device:
function listItems() {
var _super = device()
.type('ul')
.classed('list-items', true)
.visible(false),
self = Object.create(_super);
self.addItem = function addItem(content, css_class) {
$(this.element()).append($('<li>')
.addClass(css_class || 'list-item')
.html(content);
return this;
};
return self;
}
To instantiate listItems:
var ul = listItems()
.addItem('Item 1')
.addItem('Item 2')
.addItem('Item 3')
.visible(true);
ul.element();
/*
<ul class="list-items">
<li class="list-item">Item 1</li>
<li class="list-item">Item 2</li>
<li class="list-item">Item 3</li>
</ul>
*/
Which pattern to use is largely a matter of preference, although prototypal inheritance classes have a distinct identity in the debugger, so might be preferable for debugging and profiling situations.
Prototypal inheritance works with instanceof
too, so that's another consideration. An instanceOf
-type mechanism could be added to factory inheritance with some effort.
Upvotes: 1
Reputation: 57693
JavaScript has no class definitions (yet), no interfaces. add methods on depending on my mood is how prototypical inheritance works.
Using Object.create
or $.extend
does not change this.
Keep in mind that $.extend
does not give you an inheritance tree. Header instanceof Viewable
is false
, you're just copying the properties defined on Viewable
. $.extend
is like an object merge that modifies the input argument.
If instanceof
is not important to you then you can use either method.
ES6 has some good new ideas on how to get class definitions in JavaScript. ES6 is not implemented yet but if you want a workable preview you can have a look at Microsoft's TypeScript. TypeScript is not 100% compatible with ES6 but it's compatible where it matters.
TypeScript adds classes and interfaces and all that jazz. This gives you the type hints you want, but is removed when compiled back into JavaScript.
Upvotes: 1