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, 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, 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 below. Almost all 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.
There's a lot in the bottom two-thirds of the list, only because I haven't gone through it yet and weeded out the less-than-awesome resources.
Also, note that this list is copied straight from the "JS" section of my learning road map, and the latest version may have evolved from what you see here.
- Basics:
- Practice:
- DOM and forms:
- Going deeper:
- Functional JS:
-
Web components:
- Rob Eisenberg - "Hello Web Components"
- Dave Rupert - HTML with Superpowers: The Guidebook or the course version
- MDN - Web Components
- The Modern JavaScript Tutorial - Web Components
- Web Components Today
- Build a UI following Jared White - How Ruby and Web Components Can Work Together
- SSR web components in Ruby with the upcoming Heartml (see this Spicy Web article for context)
- Experiment using Turbo to drive front-end behavior: "Turbo 7.2.0 (currently in beta) allows you to define your own Stream actions which can be any JS code you want. By combining a custom Stream action or two with web components, you can essentially drive reactive frontend behavior from the backend stupidly easily. Loooove it! 😍 […] For a turnkey example, you could check out https://github.com/hopsoft/turbo_ready " —Jared White on The Spicy Web Discord
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 😄
Top comments (7)
Eloquent Javascript is also a nice learning ressource and it is available for free.
Javascript often includes different and sometimes opposing concepts to do the same thing, which has mainly historical reasons. You do not necessarily need to learn all this concepts to be productive. Even though it is sometimes helpful to know some of the older concepts, it is generally a good Idea to focus on the newer parts. Sometimes you can just select your style and ignore all the other options the language might provide.
Thanks @efpage for the recommendation!
Yeah, the historical/backwards-compatibility aspect is one reason JS can be daunting. Fortunately, recent JS resources (at least the ones I've seen) generally do a good job of pointing out those stylistic and historical differences.
The "multi-paradigm-approach" in Javascript has surely historical reasons, but it is also one of the interesting aspects of Javascript. It allows to select just the style that is most appropriate for a problem.
I have been working with procedural and OOP code for most of my life, but in some cases functional code provides shorter and more elegant solutions. On the other hand, using only functional code can also be limiting (see here), slower and more resource-consuming. With Javascript it is easy to mix different styles. Why not build classes, that follow internally the principles of functional programming and immutability? Absolute possible.
When I started using Javascript, I thought, that the lack of static types would cause more problems, but in fact, it did not. Ok, there are cases when a strong type definition is helpful, but errors like a not initialized variable are not a question of type definition. Many errors that are found by a C++- of Delphi-compiler will not be detected, even if you use Typescript.
Another weak point of Javascript is the lack of tree shaking, which is usually done by the compiler/linker of a compiled language. Tools like vite/rollup can help a lot, but it is easy to break tree shaking if you are not very careful.
Overall, it is sometimes demanding, but also much fun to learn and use Javascript. And if you think of a browser more as a programming environment, you will get a lot of most interesting API´s for free. See this page as a reference. It references about 100 different API´s that can be used in a modern browser that give you access to tools like a well designed and powerful 2D- and 3D graphic systs, multimedia- and MIDI-API´s, Sound generation and analysis and so on. But the key to this ecosystem is - Javascript!
I've run across that MDN page on Web APIs, but thanks for reminding me of it! I'll add it to my list for later reference.
The lack of static types doesn't bother me (coming from Ruby), but I've definitely noticed the cultural difference that you mentioned, of focusing on classes/objects vs. only functions. JS functions can have mutable state too, via closure, and to me that's a less clear/explicit approach than classes and objects. So I don't completely understand the reasons behind that cultural difference, other than maybe historical.
But that's one reason I want to learn more about functional programming, to better understand its strong points. And even in the Ruby world there's a sizable group of FP enthusiasts and libraries, so it would be fun to take advantage of those wherever it makes sense.
The discussion about FP vs OOP has often something of a religious dispute and I´m still not clear if these "styles" are actually opposites at all. Classes - as implemented in C++ - are just a way to organize your code and nobody tells you, how to write this code. You can build a class filled with spaghetti code or in an organized way. There is no reason not to use the principles of FP inside classes.
But to write OOP code in Javascript is no fun. The term "object" is used very different from what other OOP languages define. Somehow the creators of Javascript managed to define classes, that fit both definitions, but the current Javascript classes are still awkward. They are not really encapsulated, they lack any visibility qualifiers, metods are not clearly marked to be part of a class, and the overly use of "this" can drive you nuts. But there are situations, in which classes are valuable to use.
Knowing both paradigms is helpful, but making a religion of it, is truely not. The DOM is a good example. You can access the DOM via the HTML-DOM-API. In it´s core, the DOM follows an object oriented approach, DOM objects are stateful, and like childs of any OOP heirarchy, they give access to the whole DOM through their properties (what people call the Jungle).
It is very easy to manage the DOM using classes, that are derived from the DOM classes (See some examples in my DML-project), but this is a classical OOP-approach. If you try the same with a functional approach, you end up writing something like REACT that needs a virtual DOM to make the stateful DOM stateless.
Don´t get me wrong: There have been good reasons to build tools like REACT. But they come at a price that you need to know.
That's great context on classes and objects in JS vs. in other languages.
And yeah, it has struck me as odd before that React takes a functional approach against the grain of the DOM. But I'm still fairly new to all this, and I feel like I haven't yet seen for myself the problems / pain points that led to the design decisions of React and other libraries. But that's why I'm starting with the basics and learning them thoroughly rather than skipping ahead to frameworks, because I suspect things will make more sense in that order. (And if they still don't, I'll feel more justified in saying they don't, lol.)
Transitioning from Ruby to JavaScript can be quite the journey, and it's great to see developers embracing new challenges! While you're diving deep into JavaScript, take a moment to explore the beauty of Urdu Poetry on our website. Poetry can provide a refreshing perspective and inspiration as you navigate this transition. If you're interested in enriching your experience, learn more on our site!