Using private methods in CoffeeScript
I am a big fan of CoffeeScript. Its simplicity and expressiveness made me more productive and helped me better understand JavaScript. Its syntax emphasizes lambdas and promotes best JavaScript practices, hiding away unneeded hackery. It also addresses many common issues with vanilla JavaScript, like making extending objects easier or providing super
method.
But CoffeeScript stays true to the nature of JavaScript, combining brilliant solutions with highly questionable ones. What I particularly dislike about CoffeeScript is how it obscures JavaScript’s native scoping mechanism and makes using private object properties inconvenient and counter-intuitive. As a result, many developers who use CoffeeScript don’t even know they can use private properties and methods. Private methods are virtually extinct in the world of CoffeeScript, and this is not a good practice. Search by word ‘private’ on http://coffeescript.org yields no results, and https://github.com/polarmobile/coffeescript-style-guide recommends emulating private methods by prepending _
to method name.
Here’s a simple class declaration in CoffeeScript:
class SomeClass
publicProperty: 'foo'
publicMethod: ->
@publicProperty
sc = new SomeClass();
sc.publicProperty # 'foo'
sc.publicMethod() # also 'foo'
This compiles to:
var SomeClass, sc;
SomeClass = (function() {
function SomeClass() {}
SomeClass.prototype.publicProperty = 'foo';
SomeClass.prototype.publicMethod = function() {
return this.publicProperty;
};
return SomeClass;
})();
...
As we can see, class properties and methods are added to prototype of SomeClass
and made public. We can declare private properties, although it’s not very intuitive and we’ll have to remember some JavaScript.
class SomeClass
publicMethod: ->
privateMethod()
privateProperty = 'foo'
privateMethod = ->
privateProperty
sc = new SomeClass();
sc.publicMethod() # 'foo'
sc.privateProperty # undefined
sc.privateMethod() # TypeError: undefined is not a function
Compiles to:
var SomeClass, sc;
SomeClass = (function() {
var privateMethod, privateProperty;
function SomeClass() {}
privateProperty = 'foo';
SomeClass.prototype.publicMethod = function() {
return privateMethod();
};
privateMethod = function() {
return privateProperty;
};
return SomeClass;
})();
As you can see by using =
operator instead of :
we didn’t actually define any properties of SomeClass
. Instead we created local variables within scope of SomeClass
‘s constructor function. Because public methods of SomeClass
are functions defined within the same scope, local variables are available within these functions but not exposed to the outside world.
This is Javascript way of hiding implementation of an object. Instead of declaring class properties and methods as private we hide them in function scope, unavailable from outside, and return object that acts as interface to that scope. It feels weird for people used to conventional OOP. Private attributes are not referenced with this
and instead of declaring private properties you must have a declaration for every property you want to be exposed.
CoffeeScript does nothing to make this more usable. In fact, it makes things even worse. In JS you have to manually add properties to object’s prototype to expose them. This is an inconvenience, but it provides some introduction to JavaScript’s prototypal inheritance. In CoffeeScript it’s buried deep under syntactic sugar. Fortunately, CoffeeScript also provides a really short and awesome way to add properties to object’s prototype via the ::
operator.
class SomeClass
# this line is identical to
# publicMethod: ->
this::publicMethod = ->
privateMethod()
privateProperty = 'foo'
privateMethod = ->
privateProperty
I find this notation to be much more transparent. CoffeeScript makes objects look like Ruby classes, clearly to make writing CoffeeScript easier for Ruby on Rails developers. I find this ironic, because this is exactly the logic that lead to many of JavaScript’s drawbacks. JavaScript was designed to look like Java, so Java developers could feel themselves at home.
But JavaScript doesn’t work the way Java (or Ruby) work. This resulted in Java developers being among the most passionate haters of JavaScript. For them, JavaScript is deceiving. It’s built on totally different principles, so language constructs that look familiar would work differently or stack together in unexpected ways. The only way to write good JavaScript is to learn and embrace its differences, especially the ones that are considered its 'weirdnesses’.