A minimalist approach to OOP in Javascript
gabordemooij.com
gabordemooij.com
* Every time you call createNew(), all the methods and all the properties are re-allocated. This is bad for performance and memory consumption. This is why properties and methods should be added to the "prototype" of an object, so that they don't get re-allocated every time you want to create an instance.
* The inheritance example is not actually inheritance but composition. They are two separate things: http://www.artima.com/designtechniques/compoinh.html
* Stuffing everything inside a createNew() function is not minimalist, it's just bad code.
* It refers to "this", "new" and "prototype" as "confusing JS stuff". This obviously means he/she doesn't know anything about the JavaScript programming language.
* JavaScript provides a basic keyword called "new" that easily lets you create a new object. Changing the API naming convention to something called "createNew", is bad practice and will confuse developers trying to use your code. Stick with "new".
If you write code this way, not only it will quickly become slow, but it will also become hard to have other developers contribute to your code base because of the weird conventions.
If you want to write real minimalist JS code, try reading Eloquent JavaScript instead: http://eloquentjavascript.net/
If you want to compose objects the correct way using prototypes, then you need to use new at some level. If you don't use it, then you're re-allocating the entire object's properties and methods every time you need to use it.
You can do it as Crockford does with his "beget object" abstraction: http://javascript.crockford.com/prototypal.html
But you still need to use "new".
And personally, I'm not that convinced about promoting these sorts of abstractions as Crockford does. It's just really small syntax sugar around "new" and it's totally subjective.
So most of the time I simply feel more comfortable using "new".
* They share the same object, so it's not composition. Does it really matter that it is not 'pure' inheritance?
* createNew() - bad code? why? just an opinion.
* I have written JS apps from day 1. And yes I know how to handle this,new and prototype. Still, I choose no longer to.
* obj.new() can be confused with new obj(); That's why I have chosen a more distinctive method name; createNew.
And yet you claim:
> I dont use new, prototype and this because I find them confusing
> obj.new() can be confused with new obj(); That's why I have chosen a more distinctive method name; createNew
Also, "dog" can be confused with "do", and "ford" confused with "for" - i can't count how many time's i've got syntax errors because my dog loop of nested ford loops just kept outputting "dogs can't drive, stupid" to the console.
Whilst this is true, isn't this something for the Javscript engine to optimize? And doesn't this (very common construct) already get optimized by modern engines? Guess I got a nice Sunday-evening experiment to set up...
Language-wise, I find the method the author describes to be very elegant. Everyone seems to write OOP-Javascript in their own way. And the new keywords in ECMAScript 5 aren't helping to resolve these inconsitencies...
But maybe my experiment is flawed: https://gist.github.com/2340375
This example is horrendous IMO. One of the biggest strengths of JS is the use of prototypes, but people still persist with shit like returning object literals with methods directly defined on them..
There's several different OOP styles in JS, and I quite like this one. Not using prototypes means you can create uncompromisable objects with inheritance.
str = 'a';
str.toString = null
str.toString(); //still works
str = new String();
str.toString = null;
str.toString(); //fails
str2 = new String();
str.toString(); //still works
but:
String.prototype.toString = null;
str = new String();
str.toString(); //unexpected error
The first part of your example is due to the conversion between primitives and objects for method calls. It has little to do with prototypal inheritance.
Then, you override the method on an instance of String. When you set toString to null, of course, the call to toString fails.
Lastly, you override it on the prototype of String, which will apply to all String objects. Why is this an unexpected error? toString is on the prototype, because it's defined on the class, not the instance. I'm not sure what the confusion is here.
By your logic, cars are bad because if you run a pipe from the exhaust to the inside of the car and sit in it with the engine running, it will kill you.
Any developer can do stupid things in any language, with any code pattern, and trying to use that as a reason not to take advantage of a language's features is ridiculous.
At least this method is clean and maintainable. Why use language constructs that you don't need? I really dont understand why prototypes and new-keywords would improve Javascript code. I try to write software that's free of bugs, software that can be trusted. Simplicity seems a logical strategy.
Unfortunately almost every language today is bloated with features, why can't we have a minimal language, just like C?
I have explained my point of view more extensively here: http://www.gabordemooij.com/articles/fatjs.html
Edit: Just because you don't understand something or refuse to understand it does not mean you should dismiss it.
The minimal approach is simpler. Try explaining prototype OOP and minimal OOP to a newcomer and see for yourself. Software complexity is a result of evolution; as the code base grows it becomes more complex, there is no need for us to add it beforehand.
If your entire team is entry level developers who think jquery is magic, maybe you have a point, but frankly I'd rather work alone that with people that dense.
I think this remark about entry level developers is snobistic.
I have an open mind and I am always willing to learn. Yet you just dismiss my approach without providing solid argumentation. The only real argument against the minimalist approach is performance and I was well aware of this when I wrote the article.
And yes, prototypes are more difficult, not just for entry level developers, let's see why:
* you cannot use previous OOP experience (aka 'transfer')
* there are MANY ways to do OOP using prototypes (functions,direct prototyping,__proto__) vs ONE
* 'this' has different meanings depending on context -> requires you to process contextual information
* prototypes make existing objects unreliable -> requires prior knowledge about libraries and APIs (leaky abstraction)
* building an object with prototypes requires far more mental steps: you have to know about objects, functions but also about the prototype chain, the hidden prototype property (why?), the logic behind the new keyword etc. If prototypes had been implemented in a very consistent way (without the new-keyword and only using __proto__) I would have advocated prototypes instead of literals.
On top of this, many different 'sugar' solutions exist, most of them incompatible.
Advantages of the minimalist approach:
* conforms to established industry and business standards in OOP
* requires less knowledge about language details, this leaves much more room to focus on application logic and design
* very transparent, no sugar code (beget) or libraries required
* neat code: methods are grouped together
* allows to implement well known design patterns easily
* protection against corruption of existing objects
* no this, that or self variables
And all you are saying is: it's too easy, it must be for entry level developers. I mean... what is this about? Are we trying to build great products or is this some kind of puzzle contest? If you like complexity so much I recommend to write your next project in malbolge (http://en.wikipedia.org/wiki/Malbolge).
I lead a somewhat large (~10000 sloc) project which uses the module pattern everywhere. I think the best argument against the module pattern is the difficulty of debugging. I can no longer just pop open a console and inspect objects.
Responses to your individual points:
>> * you cannot use previous OOP experience
While unfortunate I believe this is largely unavoidable. Javascript's object system is not 'classical' inheritance. The module pattern, however, is also not 'classical' inheritance. Regardless of OOP technique, the New Javascript Programmer must learn about the object system.
>> * there are MANY ways to do OOP using prototypes
There are also many forms of the module pattern. You just showed me another one I hadn't seen before. Then there's immediately invoking function expressions, which some people like to do like this
(function () { var exports = {}; return exports; })());
and others like to do like this
!function() { var private = 4; return { getPrivate: function () { return private; }; }();
>> * 'this' has different meanings depending on context
Can't argue against this one.
>> * prototypes make existing objects unreliable -> requires prior knowledge about libraries and APIs (leaky abstraction)
I'm not sure I see the argument here.
>> * building an object with prototypes requires far more mental steps: ..
I'm not sure you really need to know about all of these things.. but sure, prototypal inheritance is confusing to those with a 'classical' background.
As for the advantages:
>> * conforms to established industry and business standards in OOP
I don't see why prototypal inheritance does not. If you look at CoffeeScript, it even appears classical.
>> * requires less knowledge about language details
I have to disagree here. Explaining object literals and immediately invoking functions and closure isn't all that easy.
>> * very transparent, no sugar code or libs required
That's true.
>> * neat code
There's no reason why new/this/prototypal code can't be neat!
>> * design patterns blah blah
There's no reason why new/this/prototypal code can't do design patterns!
>> * protection against corruption
I've never seen this as an issue in practice, but my code isn't running in the browser. Unless you're talking about protection from other programmers, in which case, I would quote Guido van Rossum.
>> * no this, that or self variables
Could be a good thing or a bad thing. This means that you have to know what's public and what isn't, rather than it being explicit. I rather like it when things are explicit.
Sometimes we sacrifice a little simplicity for a little flexibility or a little efficiency. Sometimes it's worth it, sometimes it isn't.
The second biggest problem with the method you are using (after performance/memory consumption) is not having access to either instanceof or isPrototypeOf. If you are doing anything complicated having access to one of those two can by crucial.
[1]https://developer.mozilla.org/en/JavaScript/Reference/Global...
http://www.w3counter.com/globalstats.php?year=2011&month...
http://kangax.github.com/es5-compat-table/
and you really dont expect anyone to write clunky property descriptors like this??
https://developer.mozilla.org/en/JavaScript/Reference/Global...
The article I linked to gives a shim for Object.create if you need to target IE7/IE8. All other browsers already support it, including all mobile browsers.
Yeah, the descriptor object is, in my opinion, a design mistake when they did Object.create. Modifying property descriptors is an exceptional need, and there are other ways to do it when you have that need (Object.defineProperty and Object.defineProperties).
However, that's an optional parameter. You don't need to use it. You can just define properties like you normally would to an object. For example:
var one = Object.create(null);
one.foo = 'bar';
var two = Object.create(one);
two.fee = 'fi fo fum';
If you want extra sugar to make the definition of own properties more eloquent (by having them in a single object literal) there are libraries/snippets that will do that for you. Here is one I wrote (which is just a very small snippet around Object.create): http://code.matthewphillips.info/thingjs/I am not used to participate in technical debates like this. All I know is that the I like the minimalistic way of OOP in JS and that it works for me. All I tried to do is share my experience. I no longer feel the need to prolong this discussion as I am no longer capable of providing arguments. I am left wondering why a perfectly reasonable way of coding JS is dismissed here so aggressively. For me hackernews has been a big dissappointment (first thread/comments here). I had hoped to either learn something new or find support for my method. Instead I have felt attacked for my opinion. As a natural response I try to defend myself; however this rhetoric gets in the way of reason and rational argumentation.
So why am I not allowed to share something that works just because it may not fully comply with the 'preferred javascript style culture'?
What is this about?
Your claim of "no real drawbacks" is very much your opinion, nothing more.
> Yet you just dismiss my approach without providing solid argumentation
I've made several other comments on this item.
> you cannot use previous OOP experience
Shock horror, a developer needs to learn JavaScript before writing JavaScript. This is a positive not a negative. There are already too many "developers" who write javascript without understanding it.
> there are MANY ways to do OOP using prototypes (functions,direct prototyping,__proto__) vs ONE
your example of "many ways" is misleading: it's not "functions" or "direct prototyping" - you assign functions to a constructor's prototype, and they become instance methods.
__proto__ is non-standard, and no developer worth working with would use it for creating "classes".
There are also MANY ways to create "classes" using object literals - the article linked just gives one example of this.
> 'this' has different meanings depending on context -> requires you to process contextual information
you're making a mountain out of a molehill.
> prototypes make existing objects unreliable ->
How so?
> requires prior knowledge about libraries and APIs (leaky abstraction)
No more than an object literal with methods attached to it.
> building an object with prototypes requires far more mental steps: you have to know about objects, functions but also about the prototype chain, the hidden prototype property (why?), the logic behind the new keyword etc.
Yes, it's so horrible that developers using a language need to have some knowledge of that language before writing more than basic code, isn't it?
> If prototypes had been implemented in a very consistent way (without the new-keyword and only using __proto__) I would have advocated prototypes instead of literals
you still haven't actually given any reason the new operator is "bad".
and you keep mentioning __proto__ which makes me think you don't understand the language at all. __proto__ is a non-standard property that some implementations expose
> On top of this, many different 'sugar' solutions exist, most of them incompatible.
You don't need "sugar" at all - just a constructor function definition, any methods for the prototype and then as many calls to instantiate objects as required.
> conforms to established industry and business standards in OOP
buzzwords and "make the boss happy" phrases mean nothing here.
> requires less knowledge about language details, this leaves much more room to focus on application logic and design
if the developer doesn't understand javascript, which OOP pattern you use is the least of your worries.
> very transparent, no sugar code (beget) or libraries required as I said, no sugar or libraries are required for prototype "classes" - it's part of the language you are choosing to ignore.
> neat code: methods are grouped together
How is this different to assigning methods to a constructor's prototype?
> allows to implement well known design patterns easily
this is a pattern in and of itself, what are you talking about?
> no this, that or self variables
a lack of "this" is not an advantage - you mentioned earlier about not being able to re-use previous OOP experience, then argue that not using a standard OOP concept ("this" in instance methods) is a good thing?? Make up your mind!
> And all you are saying is: it's too easy, it must be for entry level developers.
No, I said "simpler is not always better". Your approach ignores huge parts of the way JavaScript works because "it's too hard".
It's nothing to do with puzzle contests or ridiculous languages - it's about learning to use a language to it's full potential, rather than sticking your head in the sand and saying "it's too hard, so I'm not going to do it!".
Because you said:
> I dont use new, prototype and this because I find them confusing
Why duplicate effort in describing the concept?
Functions are objects, so adding a static property to a constructor function is no harder than adding it to an object literal.
cls = function(){} cls.staticMethod = function(){}
vs
cls = { staticMethod:function(){} }
I prefer the latter because the static method can be defined within the body of the 'class' definition. Using the former method it's too easy to add static methods in different places.
You get an instance of your "class" by calling createNew(), which returns an object literal it just created.
This object literal has methods attached to it, but you can't call them before you "instantiate" an "instance" by calling createNew(), so you don't have any static methods at all - just a heap of "instance methods" that may or may not operate on the instance.
Using a Prototype "class" you can very easily have true "static" and "instance" methods, that are called without and with an instance respectively.
Also, I have no idea what you mean by "add static methods in different places"?? You either:
* add a method to the constructor directly, which makes it static
FooConstructor.method = function () {...};)
* or you add it to the constructor's prototype, which makes it an instance method
FooConstructor.prototype.method = function () {...};MyClass = { getInstance: function(){ ... } }
vs
MyClass = function(){ }
//outside function body... MyClass.prototype.getInstance = function(){ ... }
MyClass = function(){ }
//outside function body... MyClass.prototype.getInstance = function(){ ... }
but this makes no sense - why would you have a "static" method defined as an instance method?in this example you would do:
var MyClass = function (){}; // constructor
MyClass.getInstance = function (){}; // static method
MyClass.prototype.start = function (){}; // instance methodMyClass = {
staticMethod: function(){
}
createNew: function() {
obj = {};
obj.instanceMethod = function(){}
return obj;
}
}Also, I find it quite elegant to see that the constructor function (factory method) is implemented as a static method as well). Of course I know these aren't static methods like in Java/PHP but in practice they can treated as such.
why though? I don't see how being part of an object literal is inherently more maintainable?
> Also, I find it quite elegant to see that the constructor function (factory method) is implemented as a static method as well)
but you were harping on about "OOP standards" and "best practices" before - since when is the constructor for a class a static method? In a language with true classes, even with a Singleton, the constructor is an instance method.
A singleton uses a static factory method in PHP and Java. AFAIK there is no (mainstream) language where a singleton is an instance method.
function Cat() {
var private;
function meow() {
alert('meow');
}
return {
meow : meow
};
}var cat = Cat();
Makes as much sense to me as it did to you. That is, very little.
I wish the author would properly explain why he went to great lengths to avoid the tools available to him.
One of the reasons he likely didn't use the "this" keyword is because it's compromisable. "this" is anything that the invoker of the method wants it to be, so you can accidentally compromise information.
Also, if you have your variables in function scope and the function is created in that scope, the keyword is unnecessary.
Regarding not using new/constructor functions, some people think the non-constructor-based OOP model in JS is nicer.
Finally, regarding lack of prototypes, if you want to keep secrets then you can't re-use functions. On the other hand, if you are creating public/non-privileged methods, no reason not to re-use functions.
Due to the disastrous effects of constructor functions if used without the new keyword, only constructor functions should have a capitalised first letter such that they can be easily distinguished.
You mean ones with broken JS implementations?
Unless you mean the .prototype property, in which case you really mean re-use of functions. That's something you can only use for unsafe public methods, unfortunately. For safe (uncompromisable) public methods it can't be used, nor can it for private or privileged methods.