DEV Community

Taylor Hunt
Taylor Hunt

Posted on • Updated on

I think I finally “get” JS objects

While learning JavaScript, at some point I looked up what “object-oriented” meant.

That was a mistake.

The first reason it was a mistake was because I was exposed to way too many unhelpful OOP diagrams. You know the kind:

A diagram about class vs. instance vs. extensions that looks like it was drawn in Powerpoint. It’s about faces and hats or something; if you really want to read the text, this links to the original SVG.

This diagram from Wikimedia Commons is typical: it models something you’d never code in a real program. (Why is that so common?)

The other reason it was a mistake was because it taught me about Java and C++ programmers’ opinions on objects… but those opinions barely mattered to the language I was learning. I read about “information hiding”, “encapsulation”, and “polymorphism”, but I didn’t understand how they were relevant.

And as it were, they mostly weren’t relevant!

  • Some jargon was for techniques difficult in other languages, but so easy in JavaScript that you don’t even think about them.

  • Some jargon was for how low-level languages would optimize performance, in ways that JavaScript is too high-level to care about.

  • Some jargon described techniques that JavaScript objects can’t even do.

  • And, lastly, some jargon was for bad ideas — even in the originating languages.

For years, I was dimly aware that it seemed unnecessary to mess with prototypes, classes, constructors, and this for like, 98% of my code — so why was I expected to use them for everything, or I wasn’t doing object-orientation “properly”?

It turns out object-oriented code is great at some things, but not all things

What I’ve since realized is that JavaScript reuses its full-power Objects for many non-OO purposes, most of which are much simpler. I’ve used JS objects for roughly 4 things:

  1. Grouping variables
  2. Namespacing
  3. Typed-ish data
  4. Actual full-power Objects

The first 3 aren’t “real” object-orientation (or whatever that means), but they reuse the object machinery JavaScript already has because it’s convenient.

1. Grouping variables

Sometimes, you want to collect related variables together. Other languages features for that are called dictionaries, hashes, maps, or structs. Perl, being Perl, has 6 ways to do it, with names like “BagHash”.

It’s like prefixing variable names that go together:

var player_x = 0;
var player_y = 0;
var player_health = 100;

// or:
var player = {
 x: 0,
 y: 0,
 health: 100
};
Enter fullscreen mode Exit fullscreen mode

Usually the specifics of how exactly to group variables in other languages is for performance reasons. For example, C’s structs group values together to make them efficient byte-wise, but if you want to look up the properties at runtime by name, you’ll need a hash function/table instead.

These sorts of grouped variables as objects are also used to 1.) return multiple values, 2.) implement optional arguments in a friendly way, and 3.) other places where the language doesn’t support passing around multiple variables at once. As a counterexample, Python has optional keyword function arguments and multiple return values, so it doesn’t use objects for either. But in JavaScript, we use object and object accessories for both:

function getCoordinates (
  target,
  { relativeTo } // Using object destructuring for optional arguments
) {
  let ret = { x: 0, y: 0, relativeTo };
  if (relativeTo) {
    ret.x = getHorizontalOffset(target, relativeTo[0]);
    ret.y = getVerticalOffset(target, relativeTo[1]);
  } else {
    ret.x = getHorizontalOffset(target);
  }

  return ret; // Returning 3 related variables in the same object
}
Enter fullscreen mode Exit fullscreen mode

2. Namespacing

There’s no technical reason that the Math object exists. But there is a human reason: it’s easy to remember math-related stuff lives in this object.

They could be separate identifiers with math_ prefixes — older languages did that all the time. But this seems a little cleaner; contrast the myriad functions in PHP’s global scope.

Modern-day JavaScript now handles namespacing with module imports and exports, but you can still wrap those up into an object, the syntax is similar, and many old browser APIs and libraries still use objects for namespacing.

(This is also known as static properties/methods, which kind of sucks as a name and infected JS from other, less fun programming languages. Go @ TC39 about it.)

3. Typed-ish data objects

Typed-ish data objects (a very rigorous term I just made up) bundle data with ways to read and manipulate said data, like Date, DOMRect, or Array.

  • Typed-ish data objects are when this and methods become useful — methods that don’t use this are just namespaced functions.
  • I think this is the level where instanceof would become useful, but JS/DOM design mistakes popularized duck typing in the JavaScript world anyway.

These objects let you change their data in different ways, and the different ways to read that data will update to match your changes:

var d = new Date();
d.toDateString(); // "Sat Apr 11 2020"
d.setMonth(7);
d.toDateString(); // "Tue Aug 11 2020"
Enter fullscreen mode Exit fullscreen mode

These kinds of objects can use (and often do use) information-hiding/internal representations, but it’s not required. For example, a Date only stores one UNIX timestamp as an integer, and it even lets you look at and change that integer directly if you want. And as for TypedArray, well, it’s in the name.

For example, a Color object where the constructor takes any string that CSS can parse as a color, then expands it into an object with R, G, B, and A properties. (This part is left as an exercise to the reader, because it’s surprisingly hard.)

At first, an object shaped like the following:

{
  R: 255,
  G: 40,
  B: 177,
  A: 255
}
Enter fullscreen mode Exit fullscreen mode

…doesn’t seem that much more useful than using hex codes directly in JS like 0xff28b1ff. But by making an actual color Object you can new up, we can add useful features, such as:

  • We could enforce that R, G, and B never exceed 255 or go lower than 0 with a setter.
  • It could have tint() and shade() methods that are easier to understand than manual RGB tinkering.
  • The Color object could return the numeric equivalent of its hexadecimal code for its .valueOf(), output a human-friendly hsla() syntax for its .toString(), or output to an array for data transfer with .toJSON().

4. Full-force capital-O internal state Objects, also known as Art

Finally, you have the full-force objects that act as discrete… well, objects. Like DOM elements, Document, and so on. They use the hell out of this, and need to. (It’s possible to model these sorts of things in functional programming, but it involves trying to avoid this so hard you end up inventing that.1)

HTMLInputElement.validity.validate(), though, is very good. It uses a bunch of properties on one specific user-visible object, then changes what the element is doing to reflect its calculations. And if you call the .focus() method on one <input>, it changes the properties of others and the activeElement property of the document that owns them all.

For an even more complex example, consider jQuery objects and their seemingly-simple API:

$('.widget').addClass('loaded').fadeIn().on('click', activateWidget);
Enter fullscreen mode Exit fullscreen mode

There is a great deal going on under the hood there. But because jQuery is doing those object operations under the hood, it turns into a wonderful API that has stood the test of time.

Anyway, I’m not a good enough programmer to really understand or work on level 4 yet.

So what?

My first draft originally tried to tie this all into a lesson for readers, but I suspect this post is too confusing for beginners and also too mundane for advanced devs, so I’m publishing it for me. As a treat.


  1. No, I will not explain what I mean. 

Top comments (17)

Collapse
 
talmoscovitz profile image
talmoscovitz

Excellent article! I rarely find wikipedia pages useful - it's almost like their writers intentionally obfuscate the subject using irrelevant examples and overtly technical jargon fit for experts (not beginners). Who watches the watchers?

Collapse
 
guibleme profile image
Guilherme Barbosa Leme

Came to say the same. Awesome article!

Collapse
 
csdswarm profile image
Chris Strolia-Davis

In the early days of JavaScript, we used to specify that JS was object "based" not object "oriented". It has the concept of properties and methods and "looks" like it's object oriented. Indeed, you can even make work, to some extent, like an actual object oriented language.

The problem. It has ever been a prototypal language, and a dynamic one at that. The result is that people who try to shoehorn it into the OOP paradigm often finds that it works until it completely falls apart. And at the point it completely falls apart, you start to realize that the seeds of that were sown months before and are nearly impossible to remove by the time you realize the problem.

Whenever possible, I've come to do anything I can to avoid the this keyword. With the exception of using libraries and frameworks that force me to, I've found that I never need to use classes in JS, and since doing this, my life has never been easier (except when I end up working with other JS devs that are class evangelists).

Understanding closures in JS takes a little getting used to, but once you get it, I find that the resulting ways of handling object creation with factories and composition are much easier to write, understand and maintain than trying to follow an OOP paradigm.

Collapse
 
squidbe profile image
squidbe

As someone who's been writing JavaScript since 1999, I've found it interesting to see the language change over the years. As for objects, the key is to understand and embrace prototypal inheritance. Adding classical syntax in es6 muddied the waters and made some people think that JS inheritance had actually become classical, but as Doug Crockford used to say, it was just syntactic sugar -- still prototypes under the hood.

Spend some time understanding prototypes and prototypal inheritance -- it's not very complicated, and it makes sense if you don't try to force the classical model onto it.

Collapse
 
steveknoblock profile image
Steve Knoblock

JavaScript objects have many qualities of objects from OOP such as they consist of groups of properties which consist of property name-property value pairs. They do have an object feel to them that is palpable because of this. Values are accessed by property name. The name is “called” on the name similar to object methods. The JavaScript objects feels coherent and contained although not encapsulated. By having essentially a namespace in front of the property name, a kind of mild encapsulation is achieved.

All this makes objects very flexible. Too flexible for OOP mavens, but others have other opinions. Objects just exist by their declaration in object notation or through computation.

The associative array data structure, popularly referred to nowadays as a dictionary, is equivalent to the structure of an object. Perl is a language that offers associative arrays, called hashes because of how values are stored and accessed randomly. In Perl you are free to build as much OOP as you care to.

Collapse
 
peerreynders profile image
peerreynders • Edited

“Tables are the main (in fact, the only) data structuring mechanism in Lua, and a powerful one.”

Sound familiar? Unfortunately JavaScript objects are typically introduced in learning materials as a poor excuse for class instances rather than a “data structuring mechanism.”

this gets similar treatment. With traditional functions it's just something like an implicit calling environment parameter. If a function is called as a method the environment happens to be the object it was called on.

But this can be something else entirely, e.g.

import server$, { type ServerFunctionEvent } from 'solid-start/server';

async function connectServerSource(this: ServerFunctionEvent) {
  //...
}

const source = server$(connectServerSource);

//...
Enter fullscreen mode Exit fullscreen mode

where

interface ServerFunctionEvent {
  request: Request;
  env: Env;
  clientAddress: string;
  locals: Record<string, unknown>;
  fetch(url: string, init: RequestInit): Promise<Response>;
}
Enter fullscreen mode Exit fullscreen mode

i.e. the function to be executed server side is (independently) user defined but when executed this holds information and capabilities that only are relevant (yet indispensable) for very occasional, advanced use cases.

Collapse
 
drazisil profile image
Molly Crendraven

For anyone confused by this syntax, it's helpful to note that Interface is Typescript, not quite JavaScript. 😊

Collapse
 
minatop10 profile image
Mina

…doesn’t seem that much more useful than using hex codes directly in JS like 0xff28b1ff. But by making an actual color Object you can new up, we can add useful features, such as:

From a technical standpoint, JavaScript's object-oriented code may not align with the expectations derived from other languages. What struck me as a relatively beginner-level javascript dev is how you tinkered with CSS. Now I can't unsee how CSS really looks like an object.

Great read Taylor.

Collapse
 
efpage profile image
Eckehard

One of the core principles of OO is NOT TO USE global scopes. It is hard to imagine CSS that is only defined inside an object. Maybe this can partly establish something like a "local" CSS.

Collapse
 
codingjlu profile image
codingjlu

The thing about JavaScript is that objects are not what other languages call objects. In most languages an "object" comes from OOP. In JS an object does (did) not have anything to do with OOP. It's mostly equivalent to a Python dictionary, but slightly more powerful. OOP was not added to JS until ES6. In OOP an object is an instance of a class. JavaScript kind of seriously intertwined all that and basically made an instance a normal JS object. Gets kind of confusing, but makes sense when you think of it. 🥔

Collapse
 
urielsouza29 profile image
Uriel dos Santos Souza

Prototype is OOP.
Prototype-based programming is a style of object-oriented programming

Collapse
 
efpage profile image
Eckehard

I totally agree, Javascript is confusing in that specific point. And while JS classes look very similar to classes in other languages, the class model lacks some important features like strong encapsulation and a good control of visibility. Slowly it catches up introducing "private" variables in ES2023. But in fact, classes are not that useful as they are in C++ or Object-Pascal. But using them only for grouping of variables is still a bit underrated. Using Setters and getters can in any way be helpful to create reliable and good protected structures in JS.

Collapse
 
randy5235 profile image
Randy Wressell

My favorite part of all this is when all the Java and C# devs start clamoring about typescript and then write Java like code is TS and then complain that is still isnt quite right

Collapse
 
ukiyo profile image
Sunandini

As a beginner,I got little confused 🤔 in

  • Namespacing
  • Typed-ish data
  • Actual full-power Objects 😞 If I want to understand more about above topics , from where should I start? 🫠
Collapse
 
peerreynders profile image
peerreynders

Written this way…

const names = [
  'sunday',
  'monday',
  'tuesday',
  'wednesday',
  'thursday',
  'friday',
  'saturday',
];
const name = (index) => names[index];
const number = (name) => names.indexOf(name.toLowerCase());

const index = number('wednesday');
console.log(index);
// → 3
console.log(name(index));
// → wednesday
Enter fullscreen mode Exit fullscreen mode

…the three variable names names, name and number are “occupied” at the top level (i.e. they “pollute” it as nothing else can now use these variable names).

Compare this to…

const weekDay = (() => {
  const names = [
    'sunday',
    'monday',
    'tuesday',
    'wednesday',
    'thursday',
    'friday',
    'saturday',
  ];

  return {
    name(index) {
      return names[index];
    },
    number(name) {
      return names.indexOf(name.toLowerCase());
    },
  };
})();

const index = weekDay.number('wednesday');
console.log(index);
// → 3
console.log(weekDay.name(index));
// → wednesday
Enter fullscreen mode Exit fullscreen mode

Only weekDay is used as a top level variable name. The name() and number() functions are “namespaced” behind weekDay and names isn't accessible at all. And names, name and number can still be used as variable names on the top level for something more important.

“Typed-ish data” is just a standardized grouping of data that may (or may not) have some associated methods that use or act on the data contained inside the object.
See objects, methods.

“Actual full-power Objects” typically deals with the whole Class-Responsibility-Collaboration thing, i.e. class-orientation.

See The Secret Life of Objects.

In JavaScript Classes primarily exist to keep people happy who learned programming the class-oriented way first.

You only really need to use them when implementing Web Components as custom elements have to extends HTMLElement

Collapse
 
ukiyo profile image
Sunandini

Thank you 😃 @peerreynders , You explained very nicely, now I understood JS object bit more, Thank you soo much for this explanation 🤩

Collapse
 
efpage profile image
Eckehard

This reminded me to finish this post about inheritance, which is something I am missing in your post. Combined with a fine grained scope control this is a strong feature and a core quality of OOP.