JavaScript for the Rubyist
Background
Some time ago I took a new position at a different company. At the time of the move, I was fully expecting to give up ruby almost completely. Not because my love for the language faltered in any way, it was just that the company was predominantly Python at the time, and I was hired mainly for front-end work. I was committed to making my world javascript.
I’m writing this because the world of javascript did not welcome me with the same open arms that ruby did years ago. And, to be honest, I found that a little frustrating. This post will, hopefully, make things a little easier on the next rubyist to follow my path.
Disclaimer: I very, very much love javascript, and have since the beginning. However, due to the nature of this topic, I anticipate that my tone might be perceived as negative towards the language, code, or libraries. I absolutely do not mean this to be the case. I recognize that when I originally set down this path, I did not know what I do now, and was probably not looking in the right places. Additionally, I don’t have a time-machine to look over past me’s shoulder, so I can only recount how I felt at the time.
Disclaimer tl;dr: this is not a commentary on the javascript language or community.
First Issues
One issue I had with the javascript was that, to my eyes, I didn’t see a similar level of structure/organization (read: nesting and indentation) in the code I was reading.
For example:
// Don't read too much into this, // it's just an illustration of flavor/look/feel var SomethingAwesome = { someMethod: function () { var someStuff = 42; return operation(someStuff); }, anotherMethod: function (withArgument) { var temp = this.method(withArgument).replace(/whatever/, 'something'); } } SomethingAwesome.prototype.protoMethodOne = function () { var awesomeVariable = 'some string'; // stuff and things... return something; }; SomethingAwesome.prototype.protoMethodTwo = function (argument) { var intermediate = operation(argument), anotherAwesomeVariable = this.protoMethodOne(intermediate); // stuff and things... return somethingElse; }; var SomethingAwesome.ns = { isAnObjLiteral: true, withMethods: function () { return 'totally'; } }; SomethingAwesome.ns.addSomeMore = function (moreArgs) { // more things and stuff... };
My ruby accustomed brain had a hard time immediately locking onto the structure of code that looked like this. However, the above code would roughly translate to ruby like:
# Don't read too much into this, # it's just an illustration of flavor/look/feel module SomethingAwesome def some_method some_stuff = 42 operation(some_stuff) end def another_method(with_argument) temp = method(with_argument).gsub(/whatever/, "something") end def proto_method_one awesome_variable = "some string" # stuff and things... something end def proto_method_two(argument) intermediate = operation(argument) another_awesome_variable = proto_method_one(intermediate) # stuff and things... something_else end module NS def is_an_object_literal true end def with methods "totally" end def add_some_more(more_args) # more things and stuff... end end end # sidenote: translating nonsense js into coherent ruby was not as simple as I thought..
That’s a good enough try I think, an exact or idiomatic translation would miss the point: coming from ruby I expected a nested structure to denote relationships.
Start in the Middle
Let’s go from ruby to javascript showing how to code using a familiar ruby style:
class SomeClass attr_accessor :one, :two, :three def initialize(opts) @one = opts[:one] @two = opts[:two] @three = opts[:three] end def useful_method @four = @one + @three end def another_method(with_arg) @n = @two + with_arg end end
and now the js:
function SomeClass (opts) { // assigning "this" to "self" allows us to reference the // class/object later when "this" may have changed var self = this; // we don't need to pass in "opts", // the function has access to it in outer scope function initialize () { // these will now be accessible as SomeClass.one/two/three from outside, // or self.one/two/three from inside self.one = opts.one; self.two = opts.two; self.three = opts.three; } self.useful_method = function () { self.four = self.one + self.three; // remember to explicitly return in javascript return self.four; } self.another_method = function (with_arg) { self.n = self.n || 0; // I wish javascript had ||= self.n += with_arg; return self.n; } // we have to explicitly call initialize method initialize(); // and also explicitly return the object return self; }
As long as you remember to explicitly return values and call the initialize method, this style can be a very clean, effective way of writing javascript. Some additional notes:
When using SomeClass in javascript be aware of the “new” operator. If you want to use the class in the most natural way, analogous to an instance of a class in ruby, you would want to use it like so:
// remember to use "new" var some_class = new SomeClass({ one: 1, two: 2, three: 3 }); some_class.useful_method(); some_class.another_method(5);
One “gotcha” is that if you don’t (or forget to) use “new”, SomeClass will behave as if all of its methods are class methods and all of its instance variables are class variables. Changing one assignment of SomeClass will change all others:
// not using "new"... var some_class_a = SomeClass({ one: 1, two: 2, three: 3 }); some_class_a.one === 1 // true // not using "new"... var some_class_b = SomeClass({ one: 3, two: 4, three: 5 }); some_class_a.one === 1 // false -- it's now 3
Inheritance
Ok, now what if you want to create a class that inherits from another? In ruby, this is trivial:
class SecondClass < SomeClass def new_public_method # code for the new public method... end private def new_private_method # cod for the new private method... end end
In javascript, using the pattern above, this is also easy. One can do it by cleverly changing the statement “self = this” in the new class to “self = new OldClass()”. This is called “Parasitic Inheritance”:
function SecondClass (opts) { var self = new SomeClass(opts); self.new_public_method = function () { // code for the new public method... } function new_private_method () { // code for the new private method... } return self; }