DEV Community

Josh Holbrook
Josh Holbrook

Posted on

Object Literals are More Interesting than you Think

During the last few months I've been reacquainting myself with JavaScript after three years of writing Python professionally. I spent a few weekends hacking with a Next.js-based electron app framework, finally wrapping my head around React; and more recently I did a spike on using TypeScript to do galaxy brain functional programming using a library that reminds me a lot of Scalaz (TypeScript is Good, Actually - remind me to write about this later). I've been really enjoying it. I gotta say, JavaScript whips ass.

Python is a good language too - in many ways it's much more practical, and its library support for data stuff is fantastic. There are many real-world problems out there that Python can solve easily (and practically 🙂), for which using JavaScript would be difficult to the point where any author using JavaScript to solve them must have been acting purposefully obtuse.

But while Python was technically the first language I learned, the first language I fell in love with was JavaScript - and picking it up again has let me fall in love again, both remembering the good times that we had together and understanding it better for having grown. Don't get me wrong, JavaScript is Extremely Cursed - I found myself typing a isNaN check from muscle memory and started foaming at the mouth a little bit. If JavaScript is Good, it is certainly Chaotic Good.

But after having truly learned Python as a senior developer instead of a frazzled inexperienced college student, I can compare it to JavaScript and be enlightened about both. The differences between Python and JavaScript make the interesting features and design decisions of both stand out.

One of the JavaScript features that nobody really talks about is object literals. Native JavaScripters use object literals all the time and think little of it. Many Pythonistas when writing JavaScript use them as well, again without much consideration. But if you really dig, it turns out that the way these two audiences think about their objects are very different and I think it's worth following this thread through to its conclusions.

A literal in both Python and JavaScript is a value that in the code represents itself, rather than acting as a reference to - or operations upon - other things in the code. Some simple examples include string and numerical literals in both Python and JavaScript (1, "foo bar baz", 3.1415 and so on). More complicated examples include Arrays in JavaScript ([1, 2, 3, 4, 5]), or sets in Python ({1, 2, 3, 4, 5}).

Python includes a data type called a dict, which is designed to contain keys and values as a fairly straightforward representation of a hashmap-like thing. Any given key matches to exactly one unordered1 value. In Python, the keys can be anything that is hashable. That data type has a literal syntax: {"foo": "bar"}.

There's a construction in JavaScript that has a literal syntax that looks just like this: object literals. An object literal in JavaScript may look like {"foo": "bar"} - very similar to a dict literal in Python.

However, while object literals are often used as dicts, they're actually true objects the same way that instances of classes are objects in Python. This is easy to miss, because objects in JavaScript are used for both the use cases of Python objects as well as the use cases for Python dicts. For example, the following look really similar:

They both create a data structure with string keys and, in this case, string values. They both show access, updates and condition checking. I haven't benchmarked it but have to assume that the performance of these abstractions are vaguely O(1)2. If you have this kind of string-based lookup problem, both of these languages have you covered.

But this obscures the true nature of JavaScript objects. JavaScript objects are more like Python instances of classes and allow for some mind-bending levels of flexibility. To demonstrate this property, consider the following:

You can see that we have something a little different. The key here is the this keyword. Much like Python methods, a JavaScript object literal has a sense of self - which is something that Python dicts don't actually have.

The idiomatic way to express this in Python is to create a class:

In JavaScript, the prior snippet isn't strictly idiomatic, but it's not non-idiomatic either. JavaScript allows objects to be constructed in many different ways - with object literals like I did here, via its class syntax, using the methods on Object such as setPrototypeOf, or by writing a constructor function and setting properties on its prototype. This scratches the surface.

In Python, however, there is one obvious and clear way to do it, and that is with classes. This is a good property of Python, and in fact is a great argument for it being a better fit for first language than JavaScript. It does, however, mean that the power of this flexibility can be hard to imagine for the Pythonista.

All that said, Python's object model is itself quite flexible, though many things require working extremely against the grain. For illustration, here is the closest I was able to get to a Python snippet that "did the same thing" as the JavaScript snippet:

This snippet follows the same overall structure as the object literal snippet in JavaScript. It exposes a function which takes a sound, which constructs and returns an object which has a "sound" method that prints that sound to the screen.

However, we don't have object literals in Python, so we need to dynamically construct our animal. We instantiate a bare object, attach the _sound attribute to the animal, and give it a method, which is bound to the animal. We finally return it. It's this action that JavaScript does with an object literal syntax.

It's interesting that in this case the word Animal still shows up in this code. The call to type here is analogous to calling Object.create in JavaScript. In JavaScript, an empty object literal ({}) is equivalent to calling Object.create(Object.prototype). The Python equivalent to Object.create, type, takes arguments for a type name, classes to inherit from, and a dict of class properties and methods. In Python's case, modeling classes (instead of prototypes) means needing to give that class a name. In practice, the Python programmer looking to create a throwaway type like this would create an empty class, or at least dynamically assemble the third argument.

JavaScript isn't the only language that has object literals (Scala has a form of them though they manifest very differently). Python could in an alternate universe support object literals, if they so wanted - in fact, one can fake this pattern with a class decorator (a feature JavaScript doesn't have):

Rather than using a true object literal syntax, we fake it using some properties of Python classes. When Python classes are instantiated, they inherit the attributes from their classes - and Python class syntax supports non-method class properties. By using a decorator to instantiate the class in-place (only to be exposed on korben.__class__), we're able to emulate the pattern. One could imagine creating a syntax around this that added some sort of instance keyword. It wouldn't be a stretch.

But it's worth noting that this pattern isn't very useful in Python. In JavaScript, we rarely if ever use class inheritance or type information - instead, we lean on duck typing. In JavaScript it's all well and good to type if (korben.sound) {, but in Python you would ask, if isinstance(korben, Animal):. This difference in approach means that different abstractions are going to be useful. This can be seen in other places, such as Python's early adoption of decorators as compared to its single-expression lambda syntax.

Ultimately, object literals are a very powerful feature in JavaScript. They allow authors to construct objects - true objects - in a very lightweight way. While they're often used for data structure use cases, their true nature is much richer. But this doesn't imply that Python should have them too. Features that a language has exist in the context of the overall design decisions that a language has made. The beauty of JavaScript object literals can get lost in the noise of overlapping use cases and similar syntax, and by comparing JavaScript to Python, which made very different design decisions, we can hear its strengths more clearly.

  1. In truth, Pythons 3.7+ have ordered keys by spec and 3.6 and v8 have ordered keys by implementation. In all cases, as far as I know, this is insertion order. 

  2. It's worth remembering that while whiteboarding interviews would have you say a dict is O(1) by assuming it's a naive hash-map that the actual data structures underneath these base types are more complex than the model used for winning LeetCode. 

Top comments (0)