I've read through most of the Javascript inheritance references, but I'm afraid I'm still scratching my head as to how to do this.
I'm trying to put together several classes, in the general sense, that will have similar behavior and thought using prototypes might be a way to achieve that. So I create a base class as follows:
function my_base_class()
{
var a_common_object = undefined; // The true value of this can't be set until runtime.
// some other stuff ...
};
Ideally, I'd like a_common_object to be private or at least protected, but just getting it working would be a good first step. I then need to create several derived classes of which this might be one:
function my_derived_class()
{
this.do_something_to_common_object = function()
{
// Here I need to reference my_base_class.a_common_object but
// at this point there's no relationship between the two classes
};
};
I now set the prototype of my_derived_class while creating an instance:
my_derived_class.prototype = new my_base_class();
var my_derived_class_inst = new my_derived_class();
So at this point I'm hoping that I have an object - my_derived_class_inst which has traits of my_base_class including the static object a_common_object which I can access.
I have two questions:
How do I refer to a_common_object within my_derived_class when
there's no relationship established between the two classes?
How can I then change a_common_object to its true value, so that
all derived classes seamlessly pick up the new value.
Please don't simply refer me to the standard reference web sites on inheritance as I've read most of them through and I'm still no wiser. It seems to me that the answer should be really simple but so far it escapes me. Many thanks.
do_something_to_common_object() really doesn't have a way of reaching a_common_object directly.
a_common_object isn't a member of the instance created for the prototype. It's a local variable scoped inside the constructor. So, only a function that's also defined within the constructor can reach it (ref: closures):
function my_base_class()
{
var a_common_object = undefined;
Object.defineProperty(this, 'a_common_object', {
get: function () {
return a_common_object;
}
});
// ...
}
function my_derived_class()
{
this.do_something_to_common_object = function()
{
console.log(this.a_common_object); // uses getter to retrieve the value
};
};
It would still be publicly accessible, but your options are limited as JavaScript doesn't yet support or have an equivalent to access modifiers.
Though, with Object.defineProperty(), it would at least be read-only so far and non-enumerable by default (won't appear in a for..in loop).
At least until #2, where you'd need to also have a setter. Though, it would be a chance to validate the value being storing it.
Object.defineProperty(this, 'a_common_object', {
// ....
set: function (value) {
if (/* validator */) {
a_common_object = value;
}
}
});
Related
I have an assignment to implement a diagram using javascript prototypes and constructors.
For now I have to implement multiple inheritance using prototypes. I know how to implement single inheritance and I am stuck on inheriting multiple prototypes.
This question is focusing on WeatherData inheriting Event and DataType objects.
import { DataType, Event } from "./../common/EventData.mjs"
export function WeatherData(value, { time, place }, { type, unit, isInternational }) {
Event.call(time, place)
DataType.call(type, unit, isInternational)
this.value = value
}
WeatherData.setPrototypeOf(WeatherData.prototype, Event.prototype)
WeatherData.setPrototypeOf(WeatherData.prototype, DataType.prototype)
WeatherData.prototype.getValue = function () { return this.value }
I havent tested the code but I am sure it's wrong because the second .setPrototypeOf() overwrites the first function, which means that the WeatherData's prototype will be DataType.
I have searched the internet and could not find answer for this, maybe because this methodology is obsolete.
One could give the OP's code a refactoring try of muti-inheritance glue-code like this ...
import { DataType, Event } from "./../common/EventData.mjs"
function WeatherData(
value,
{ time, place },
{ type, unit, isInternational }
) {
// - close to an instance level super call but done twice.
//
// - technically applying two function based mixins.
Event.call(this, time, place);
DataType.call(this, type, unit, isInternational)
this.value = value
}
// - prototype level kind of multiple superclass extension.
//
// - technically mixed-in prototype objects via
// `Object.assign`
WeatherData.prototype = Object.assign(
// ... aggregate a compound prototype.
{},
Event.prototype,
DataType.prototype,
);
// prevent latest mixed-in super-constructor, here
// `DataType`, from being the sub-classed constructor.
WeatherData.prototype.constructor = WeatherData;
WeatherData.prototype.getValue = function () {
return this.value;
}
export/* default*/ WeatherData;
The above constructor implementation covers the mixin part at instance/object level. The code which aggregates and assigns a prototype compound from two other prototype objects is the closest one can come to multiple inheritance with what is available in JS.
But the above code's design also is flawed in a way that such a compound prototype does loose any further linkage into any of the possibly available prototype chains of either Event or DataType.
Thus from a modeling perspective it was better if the available code base was provided in a way that one could let WeatherData inherit from DataType whereas a prototype agnostic implementation of Event could be applied additionally as function based mixin.
How can I get name of object's class? I mean "Process" in this example
I see two ways to get it. First one is to write a getter in this class like
getClassName(){return "Process"}
But I suppose it will be an error if I try to call this method in object which doesn't belong to this class and hasn't got method like this.
And second one is using object instanceof Process. But maybe there is some way to make it better and more correctly?
You can get it from name on constructor:
console.log(object.constructor.name);
When you do ex = new Example, for instance, in the normal course of things that makes Example.prototype the prototype of the object that was created (ex), and the object inherits a constructor property from that object that refers back to the constructor (Example).
I say "in the normal course of things" because there are various ways those normal relationships can be changed. For instance, code could have overridden the constructor property with an own property on the object (ex.constructor = somethingElse;). To rule out that specific scenario, you could use:
console.log(Object.getPrototypeOf(object).constructor.name);
Live example:
class Example1 {
}
const e1 = new Example1();
console.log(e1.constructor.name); // "Example1"
class Example2 {
constructor() {
this.constructor = "I'm special";
}
}
const e2 = new Example2();
console.log(Object.getPrototypeOf(e2).constructor.name); // "Example2"
The TC39 committee members that specify JavaScript were happy enough to use the instance's constructor property in Promises when building the new promise that then and catch return (see StepĀ 3 here which goes here and reads constructor from the instance) (and in some other places), so you wouldn't be out on your own if you also used it. They don't even go to the prototype of the instance.
But yes, just for completeness, even if you go to the prototype for it, it's still possible for that to lead you astray, since the prototype's constructor property can also be mucked with:
class Example {
}
Example.prototype.constructor = Object; // Why would anyone do this? People are weird.
const e = new Example();
console.log(Object.getPrototypeOf(e).constructor.name); // "Object"
It's also possible to redefine the name on a function:
class Example {
}
// Why would someone do this? People are weird.
Object.defineProperty(Example, "name", {
value: "flibberdeegibbit"
});
const e = new Example();
console.log(Object.getPrototypeOf(e).constructor.name); // "flibberdeegibbit"
So...caveat user.
Note that the function name property is new as of ES2015 (as is class syntax). If you're using class syntax via a transpiler, it may or may not set name correctly.
Generally object instanceof Process is desirable if it's known for sure that object originates from this class/function. There may be situations where this won't be so. The appearance of several Process can be caused by iframes, multiple package versions, etc.
There is name property that already exists in regular functions class constructors. A known pitfall is that it will be mangled in minified code, so it is generally useless in browser JS, and its use can be considered an antipattern. name cannot be reassigned (in some browsers), so a separate property is needed to identify the class.
The proper way is to avoid this problem
But I suppose it will be an error if I try to call this method in object which doesn't belong to this class and hasn't got method like this.
is to use a getter:
class Process {
get className() { return 'Process'; }
...
}
Or a property:
class Process {
...
}
Process.prototype.className = 'Process';
As a result, there may be several Process classes that have Process className identifier. This may be desirable or not. While instanceof associates class instance with one particular class.
Use .constructor.name on the object. Each object's constructor by default refers to his creation function, which has a name property. It returns the name of the function.
class SomeClass {
}
const obj = new SomeClass();
console.log(obj.constructor.name);
Use the name property as follows:
class Process {}
console.log(Process.name);
const process = new Process;
console.log(process.constructor.name);
This is the same way it works for normal prototypal inheritance using functions.
I'm building custom libraries to handle GUI and creating divs and stuff programatically. I also want to extend these objects with children and methods to do something like this:
Function CustomElement() {
this = document.createElement('div');
///--------
Some custom properties
///--------
}
CustomElement.prototype.customMethod = function(args) {
///--------
Some code here
///--------
};
var elem = new CustomElement();
document.body.appendChild(elem);
elem.customMethod(args);
I've thoroughly searched for an answer but found none. How can I accomplish this?
Note: I'm posting from my cell phone. Please excuse me if the code looks awful. I'll correct it as soon as I have access to a PC.
I appears you are confused between classical languages such that you are probably use to, and prototypical like languages such as Javascript.
Also, in your example, assigning the value of this is an invalid statement.
In Javascript, instead of creating children of a super class, we create objects that inherit the properties of other objects through the prototype chain. Still with me? This means that your customMethod is not technically a method, rather it is a property called customMethod which has the value of a function object.
Every constructor object (which is just a fancy name for your CustomElement function) has a magical property named prototype as you have discovered. Objects don't have this property, but they do have an implicit reference to their constructor's prototype object. This means you can call your customMethod as if it were a property of elem, but it is really a property of the constructors prototype object. So I guess you could say the prototype object is kind of like a parent and the object is kind of like a child (although this is incorrect terminology). This prototype object may also again have an implicit reference to it's constructor prototype, which may reference it's constructor prototype... and so on. That's why its called the prototype chain.
So to answer your question:
I also want to extend these objects with children and methods... How can I accomplish this?
For a suggestion to emulate child like inheritance, see below. However, your library requires a different approach...
A common angle of attack is to create a constructor which creates a new object, with a new Element object as a property of that object. For example:
function CustomElement(doesLikeTrains) {
// keep element in this property
this.nativeElement = document.createElement('div');
// other properties are separate
this.likesTrains = doesLikeTrains;
}
// these are also separate
CustomElement.prototype.doesLikeTrains = function() {
return this.likesTrains;
};
// Lets make objects!
var elem1 = new CustomElement(true);
var elem2 = new CustomElement(false);
// use object property and inherited properties
// we can still use the element ok
document.body.appendChild(elem2.nativeElement);
elem1.doesLikeTrains(); // prints true
elem2.doesLikeTrains(); // prints false :(
The DOM element assigned to the nativeElement property. This means you may add other properties without changing the native element object, but still have access to them. Both elem1 and elem2 inherit the same doesLikeTrains property with the same value, but each have their own likesTrains property, which is initialised in the constructor, and can keep a value specific to the object instance.
The advantage of this is that you could change the doesLikeTrains function to always return true, and because all objects created using your CustomELement constructor inherit the same prototype, all objects would then like trains regardless!
How would one create children like objects?
To emulate a child structure, consider...
function CustomOtherElement(likesTrains, runsOnCoal) {
// create new object and inherit from CustomElement
function EmptyConstructor() {}
EmptyConstructor.prototype = new CustomElement(likesTrains);
// add extra stuff to CustomOtherElements only
EmptyConstructor.runsOnCoal = runsOnCoal;
EmptyConstructor.isTrainSuperFan = function () {
return "Hoot hoot, chugga chugga!";
}
// return the new object
return new EmptyConstructor();
}
// now you can do
var elem3 = CustomOtherElement(true, true);
document.body.appendChild(elem3.nativeElement);
elem3.doesLikeTrains(); // true
elem3.isTrainSuperFan(); // "Hoot hoot, chugga chug!"
The above uses this new CustomOtherElement constructor to make an object that inherits CustomeElement and then add some new properties to this new object. Now you can use both the inherited properties from CustomElement and the new ones created on elem3! Happy Javascripting!
Resource: ECMAScript Language Specifications 5.1 section 4.2.1 (Objects)
Consider the approach sometimes called "parasitical inheritance". In this pattern, you write a constructor function, but return something else after adding methods/properties to it, such as
function CustomElement() {
var elt = document.createElement('div');
///--------
Some custom properties
///--------
elt.customMethod = function(args) {
///--------
Some code here
///--------
};
return elt;
}
var myCustomElement = new CustomElement();
This can be simpler, and more reliable, than trying to subclass HTMLElement, which can be a delicate operation, or wrapping the underlying HTML element, as other answers suggest.
Some might complain that the above approach is fat or slow because the "prototype" methods are being placed on each instance. However, that's something that's not really an issue on modern machines and browsers.
In any case, once we've come this far, we need to ask why we are trying to use constructors and new at all, when we can simply say:
function makeCustomElement() {
var elt = ...;
// set custom properties
// set custom methods
return elt;
}
var myCustomElement = makeCustomElement();
Defining a "subclass" is as simple as:
function makeCustomElementSubclass() {
var elt = makeCustomElement();
// set custom properties and methods
return elt;
}
In none of the cases above are prototypes being used (except methods on the built-in prototype such as HTMLElement). They're not really necessary. As I understand it, this is the direction in which mega-guru Douglas Crockford has gravitated in his style. Many cases where we see people using prototypes, it is a matter of "let me figure out a way to do this using prototypes, because they exist and I sort of think I'm supposed to be using them", or "let me figure out a way to do this using prototypes because they sort of behave like the classes I'm used to from C++/Java/C#", or "let me use prototypes to do this because putting methods once on prototypes is so much more efficient than putting them on each object"--but none of these are compelling reasons.
Some time ago I tried to extend Object.prototype... I was surprised when later I saw errors in the console which comes from jQuery file. I tried to figured out what is wrong and of course I found information that extending Object.prototype is a "evil", "you shouldn't do that because JS is dynamic language and your code will not work soon" and information that jQuery will now add hasOwnProperty method to their for in loops.
Because I didn't want to leave jQuery, I drop the idea about extending Object.prototype.
Till now. My project getting bigger and I am really annoyed because I have to repeat many times some parts of the code. Below is a bit of the structure which I am using in my projects:
charts.js:
CHARTS = {
_init: function () {
this.monthlyChart();
/*
*
* more propertys goes here
*
*/
return this;
},
monthlyChart: function () {
//create my chart
return {
update: function () {
// update chart
}
};
}()
/*
*
* more propertys goes here
*
*/
}._init;
dashboard.js
NAVBAR = {
_init: function () {
/*
*
* more propertys goes here
*
*/
return this;
},
doSomething: function(){
$(document).ready(function(){
$('.myButton').on('click', function(){
var data = [];
// calling property from charts.js
CHARTS.monthlyChart.update(data);
});
});
}
}._init
As I mentioned project is really big now - it's over 40 js files and some of them has a few thousands line of code. It is really annoying that I have to repeat _init section every time, as well as I many functions I have to repeat $(document).ready && $(window).load.
I tried to find another solution for my problem. I tried to create class with init property (more you can find here) but I this solution forced me to add another "unnecessary" piece of the code to every file and accessing other file object property makes it to complicated too (return proper objects everywhere etc). As advised in the comment I started reading about getters and setters in JS.
After all I created something like that:
//Auto initialization
if (typeof $document === 'undefined') {
var $document = $(document),
$window = $(window),
$body = $('body');
}
Object.defineProperty(Object.prototype, '_init', {
get: function () {
// if object has no property named `_init`
if (!this.hasOwnProperty('_init')) {
for (var key in this) {
// checking if name of property does starts from '_' and if it is function
if (this.hasOwnProperty(key) && key[0] === '_' && typeof this[key] === 'function') {
if (key.indexOf('_ready_') > -1) {
//add function to document ready if property name starts from '_ready_'
$document.ready(this[key].bind(this));
} else if (key.indexOf('_load_') > -1) {
//add function to window load if property name starts from '_load_'
$window.load(this[key].bind(this));
} else {
// else execute function now
this[key].bind(this)();
}
}
}
return this;
}
}
});
and my object:
var DASHBOARD = {
_runMe: function(){
},
_ready_runMeOnReady: function(){
},
_load_runMeOnLoad: function(){
},
iAmAString: ''
}._init
It seems that this solution works with jQuery. But is it safe to use? I don't see any problem the code can cause and I don't see any further problems that it may cause. I will be really happy if somebody will tell me why I shouldn't use this solution.
Also I'm trying to understand how it works in details. Theoretically I defined property for the Object.prototype by defineProperty, without assigning value to it. Somehow it doesn't cause any errors in jQuery fore in loop, why? Does that mean that property _init is not defined at some point or at all because I am defined only getter of it?
Any help will be appreciated :)
By not including the descriptor in Object.defineProperty(obj, prop, descriptor) JavaScript defaults all the Boolean descriptor attributes to false. Namely
writable, enumerable, and configurable. Your new property is hidden from the for in iterators because your _init property is enumerable:false.
I am not a fan of JQuery so will not comment on why in regard to JQuery
There is no absolute rule to adding properties to JavaScript's basic type and will depend on the environment that your code is running. Adding to the basic type will add it to the global namespace. If your application is sharing the namespace with 3rd party scripts you can potentially get conflicts, causing your code or the third party code or both to fail.
If you are the only code then conflicts will not be an issues, but adding to object.prototype will incur an addition overhead on all code that uses object.
I would strongly suggest that you re examine the need for a global _init. Surely you don't use it every time you need a new object. I am a fan of the add hock approach to JavaScript data structures and try to keep away from the formal OOP paradigms
Your question in fact contains two questions.
It seams that this solution works with jQuery. But is it safe to use? I don't see any problem the code can cause and I don't see any further problems that it may cause. I will be really happy if somebody will tell me why I shouldn't use this solution.
First of all, there are three main reasons to avoid modification of built-in prototypes.
For-in loops
There is too much code using for-in loop without hasOwnProperty check. In your case that is jQuery code that does not perform check.
Solutions
Don't use for-in loop without .hasOwnProperty check.
Doesn't apply in this case because it's third-party code and you can't modify it.
for-in loop traverses only enumerable keys.
You have used that solution. Object.defineProperty creates non-enumerable properties by default (ECMAScript 5.1 specification)
Not supported by IE8.
Conflicts
There is risk of property name. Imagine that you use jQuery plugin that checks for existence of ._init property on objects - and it can lead to subtle and hard to debug bugs. Names prefixed with underscore are widely used in modern JavaScript libraries for indicating private properties.
Encapsulation violation (bad design)
But you have worser problem. Definining global ._init property suggests that every object have universal initialization logic. It breaks encapsulation, because your objects don't have full control over their state.
You can't rely on presence of _init method due to this. Your coworkers can't implement their own class with
Alternative designs
Global initializer
You can create global function initialize and wrap all your objects that require initialization in it.
Decouple view and logic
Your objects should not merge logic and view in one object (it violates single responsibility principle) and you are victim of spaghetti code.
Moreover - object initialization should not bind it to DOM, some controller objects should be a proxy between your logic and display.
It can be good idea to inspect how popular client-side MVC frameworks have solved this problem (Angular, Ember, Backbone) have solved this problem.
Is it safe to use getters and setters?
Yes. But if you only support IE9+.
Is it safe to modify Object.prototype?
No. Create another object to inherit all of your application objects from.
Why extending basic JavaScript objects is eval evil?
Because EVERY SINGLE object created on the webpage where your script is loaded will inherit that property or method.
There is a lot cons like collisions and performance overhead if you do it that way.
There is a lot of ways to make it better, let me show you the one I use.
// Here we create the base object:
var someBaseObject = {};
someBaseObject.someMethod = function () {
// some code here
}
someBaseObject.someProperty = "something";
// And inherit another object from the someBaseObject
someObject = Object.create(someBaseObject);
someObject.someAnotherMethod = function () {
// some code here
}
This approach allow us to leave the Object prototype alone, and build a prototype chain where someObject inherits from someBaseObject, and someBaseObject inherits from Object.
The only thing I want to say by this post: leave base objects alone and build your own, so you will have much less headache.
Note: Object.create is supported in IE9+. Here is shim for IE8 and lower by Douglas Crockford:
if (typeof Object.create !== 'function') {
Object.create = function (o) {
function F() {}
F.prototype = o;
return new F();
};
}
So, first question here - please be gentle.
I'm working on a fairly JavaScript heavy project with a few other developers from various non-web programming backgrounds and we've decided to try and use public and private methods and properties in our JavaScript psuedo-classes purely as a good coding practice (ie. we know theres no actual advantage or security in it)
We've toyed with a few different ways of doing public & private (ie. using locally scoped variables and functions with privileged methods for public consumption) and we've currently settled on having our JavaScript class constructors actually return an object that represents only their public interface, effectively hiding everything else.
Here's an example:
function MyObject()
{
var _this = this;
this._privateProperty = 'somevalue';
this._privateMethod = function()
{
// Do Something
}
this.public =
{
publicProperty : _this._privateProperty,
publicMethod : function(){ return _this.privateMethod() }
}
return this.public;
}
Which when instantiated and logged in Chrome:
var obj = new MyObject();
console.log(obj);
Outputs:
> Object
> publicMethod: function (){ return _this.privateMethod() }
> publicProperty: "somevalue"
>__proto__: Object
Now to my question:
Because were returning the public interface from the constructor as new object, when you console.log you'll notice that it identifies itself as > Object - whereas if we don't return that public interface it is identified as > MyObject.
Ideally, we'd like to have the latter displayed for debugging purposes and I know how to access the "MyObject" name of the contstructor with _this.constructor.name, but have no idea how to set it so its recognized that way.
Does anyone know how to manually set this?
Note:
I know this is in some ways a bastardization of JavaScript convention and trying to fit a square peg in a round hole, but we found it to be a very obvious and readable way to accomplish what we were trying to do. I'm open to suggestions on how to accomplish this with a different design, but I'm ultimately looking for an answer that fits our current design.
You should be able to set the 'toString' function of the obj. E.g.
obj.constructor.prototype.toString = function() { return "MyObject"; }
This will make the console.log display 'MyObject' instead of Object.
Despite the valiant efforts of my team members and the very helpful users of this site who offered solutions we've decided that this design is ultimately unmaintainable and have instead opted for a design that is a little less obvious, but achieves the same result.
function MyObject()
{
var _privateProperty = 'somevalue';
function _privateMethod()
{
// Do Something
}
// Public Interface
this.publicProperty = _privateProperty;
this.publicMethod = _privateMethod;
}
With this approach we are basically making EVERY property and method private by default, and at the bottom of our "classes" we expose the ones we want available publicly. I think most would agree that this follows the normal JavaScript convention better than our initial design, which hopefully means it will be easier to read and maintain for others.
Thanks again for those who took the time to try a come up with a solution.
This is kind-of weird (but then, you're probably used to that by now :-) but you could override the actual "MyObject" constructor with a local one:
function MyObject() {
function MyObject(p) {
for (var k in p) if (p.hasOwnProperty(k)) this[k] = p[k];
}
this.public = new MyObject({
publicMethod: function() { /* whatever */ },
publicProperty: "something"
});
return this.public;
}
Clearly you could factor out the code for most of that, so that you could have a function that, given a function to perform the initialization for a particular class, would set all that up for you. (Having to maintain a system like you're proposing without some library support to handle the details seems like a real chore, and long-term a very hard thing to keep working, and even harder to change.)