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:

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 šŸ˜„

šŸ‘‰ Newer: Job networking for developers šŸ‘ˆ Older: A Rubyist learns Haskell, part 1 šŸš€ Back to top