Coming to grips with JS
A Rubyist's deep dive
December 29, 2023 Ā· Felipe Vogel Ā·tl;dr Iām systematically learning JavaScript using these resources.
Why?
Because JS is inescapable in web development.
Sure, you can use any number of JS-avoidance libraries. Iām a fan of Turbo, and thereās also htmx, htmz Unpoly, Alpine, hyperscript, swup, barba.js, and probably others.
Then there are stack-specific libraries: StimulusReflex for Rails, Phoenix LiveView, Laravel Livewire, Unicorn and Tetra for Django, Blazor for .NET, ā¦ and the list goes on.
You get the picture. Lots of people would rather not build a JS front end.
I myself avoided the JS ecosystem a few years ago when it would have been the default path for me as a beginning second-career developer. But I was going the self-taught route, so I needed an ecosystem with strong conventions. I didnāt know how to choose from a dozen popular JS frameworks. And none of them is an all-in-one, ābatteries-includedā framework, so it looked like Iād need to make many decisions about how to put together an app, which would mean (for me at that time, as a beginner who lacked context) lots of frustration and stabbing in the dark.
It was in the Ruby world that I found the conventions I needed. Plus, I found (and still find) Ruby to be more enjoyable.
But now Iāve had enough web development experience that I can circle back and learn JS thoroughly, confidently, and without wasting as much time on rabbit trails.
Not that I canāt get around in JS. At my last job, I was comfortable building full-stack features in Rails and React.
Oh, and speaking of my last jobārecently I was laid off as part of a massive reduction in force. (Stay tuned for a future post on my job search and what Iām learning from it.)
Being unemployed and seeing so many jobs involving a JS front endāthatās ultimately what gave me the push I needed to get serious about JS.
Thatās what I mean when I say JS is āinescapableā: not that we canāt build anything without itāin fact, I quite enjoy making sites with minimal JS and plenty of interactivity. I only mean that JS skills are mandatory for someone like me who has only a few years of experience, and therefore fewer job options. Even if I could find a backend-only position (which is doubtful), Iām not sure I want to pigeonhole myself like that.
Plus, I really do enjoy full-stack development. And even if in some utopian universe I were able to land a full-stack position using some of the above-mentioned libraries instead of a heavy JS front end, it would still be important to understand whatās going on behind the scenes. After all, those libraries are JS thatās running on the page allowing my non-JS code to do cool, interactive things.
So many reasons to learn JS!
How?
Iām using the resources listed in the āJSā section of my (ironically) āLearn Rubyā list. Almost all the resources are free. Besides a comprehensive look at JS syntax, I made sure to include a few other areas:
- Guided practice and projects, to turn knowledge into skills.
- Web APIs, especially the DOM, forms, and web components.
- Deep dives into how JS works, and the rationale (or at least reasons) behind its quirks.
- Functional JS, because Iām interested in functional programming. I recently started learning Haskell, but JS will be useful as an example of how to apply functional concepts in a not-really-functional language.
A word on JS frameworks
You may be wondering why my learning plan doesnāt include any JS frameworks. No React deep dives? Not even the more hip Vue or Svelte??
I do plan on familiarizing myself with popular front-end frameworks, including the parts of React that I havenāt used. Learning the patterns that are common across frameworks will be valuable, I think.
But if thereās anything I focus on, I want it to be JS itself (along with other web standards) because theyāre a more durable investment, changing more slowly than JS frameworks.
Learning JS, re-learning Ruby
Readers who arenāt into Ruby can feel free to leave now (itās OK, I wonāt feel bad), but I wanted to conclude by showing how learning JS has helped me re-learn Ruby features that I rarely use. Here are two examples.
Object destructuring
In JS:
const obj = { first: "Willard", middle: "Wilbur", last: "Wonka" }
const { first, last } = obj
Did you know Ruby can do something similar with hash destructuring?
obj_hash = { first: "Willard", middle: "Wilbur", last: "Wonka" }
# `=>` is the rightward assignment operator.
obj_hash => { first:, last: }
This is thanks to Rubyās pattern matching, which is actually a lot more flexible than JS destructuring. (For more complex examples, see āEverything You Need to Know About Destructuring in Ruby 3ā.)
Note, however, that there is a proposal to add pattern matching to JS.
Object literals
In JS:
const obj = {
first: "Willard",
last: "Wonka",
full() {
return `${this.first} ${this.last}`;
},
}
In Ruby, every object has a class, so thereās no concise way to define a one-off object, right?
My first attempt to prove this wrong was to add a method to an OpenStruct
:
require "ostruct"
obj = OpenStruct.new(first: "Willard", last: "Wonka") do
def full = "#{first} #{last}"
end
# Uh oh, that didn't work as intended!
# The `#full` method isn't actually defined.
obj.full
# => nil
It turns out this only works with a Struct
:
Person = Struct.new(:first, :last) do
def full = "#{first} #{last}"
end
obj = Person.new(first: "Willard", last: "Wonka")
obj.full
# => "Willard Wonka"
But now weāre nearly in the territory of an explicit class definition, far from a JS-style one-off object.
OK, then just for fun, how about we expand OpenStruct
so that it actually does something with that block?
require "ostruct"
class OpenStruct
def self.create_with_methods(**kwargs, &methods)
open_struct = new(**kwargs)
open_struct.instance_eval(&methods)
open_struct
end
# Now add a shortcut syntax.
class << self
alias_method :[], :create_with_methods
end
end
# Or, OpenStruct.create_with_methods(...)
obj = OpenStruct[first: "Willard", last: "Wonka"] do
def full = "#{first} #{last}"
end
This still doesnāt look as uniform as JS object literals, and performance-wise Iām sure Ruby is not optimized for this sort of object. Thatās because it goes against the grain of Ruby, where classes play a central role, as distinct from instances of them. In JS, with its prototype-based object model, āclassesā are syntactic sugar, and individual objects are more central than in Ruby. (On how and why this is so, itās helpful to read about JSās early history.)
But we shouldnāt overstate the difference: the JS and Ruby object models are actually similar in how dynamic both of them are. This makes Ruby-to-JS compilers like Opal easier to implement, according to an Opal maintainer.
In the end, learning more JS has given me a deeper appreciation of both JS and Ruby: JS for the ingeniously simple idea behind its object model, and Rubyā¦ for everything else š