0

Coming from languages like C++, Java or PHP I've learned that there are Value Types and Reference Types. Value Types store values directly in the variable (in a box / memory location). Whereas Reference Types store a reference to an Object in memory.

This makes perfect sense and seems like a simple mental model to deal with.

About primitives in JavaScript, MDN has following to say:

All primitives are immutable; that is, they cannot be altered. It is important not to confuse a primitive itself with a variable assigned a primitive value. The variable may be reassigned to a new value, but the existing value can not be changed in the ways that objects, arrays, and functions can be altered. The language does not offer utilities to mutate primitive values.

While this is undoubtedly true, why do you have to emphasize immutability when it basically comes for free implicitly? You could just say that primitives are stored directly in a variable. Such a value could never take any effect on other variables. No need to even mention it.

My guess is, that the spec wants to explicitly allow references to immutable objects (like in Python).

So my question is:

Why does the specification refer to immutable values rather than just saying "the value gets stored directly in the variable" (aka value type)?

Is it just to ensure that engine developers can implement it however they like? Or is it indeed just a very basic explanation that each variable is assigned to a value without being explicit about how this is technically implemented - but therefore you at least need the value to be immutable?

9
  • this says you can't do 5=6; Commented Apr 14, 2023 at 13:27
  • Yeah, so I guess my question was: Isn't that obvious?
    – tweekz
    Commented Apr 14, 2023 at 13:33
  • 1
    Suppose you have var someString = 'FooBar';. You also can't do: someString[5] = 'z', or someString.replace('Bar', 'Baz'), in the sense that these operations won't affect the original string (but both will happily execute and may initially appear to work that way). But you can do someString = 'FooBaz', or someString = someString.replace('Bar', 'Baz'). Commented Apr 14, 2023 at 13:56
  • Yeah that's true. It might also serve to clarify that wrapper classes are immutable as well.
    – tweekz
    Commented Apr 14, 2023 at 14:11
  • 1
    @Craig interesting side note, thanks for sharing. On the other hand, specification like the one of PHP go the other route and say that VSTOREs are always holding copies of values (may it be primitives or references). But maybe Javascript just wants to be more precise here and rule out any edge-cases / allow any implementation.
    – tweekz
    Commented Apr 14, 2023 at 18:02

2 Answers 2

2

ECMAScript is specified at a higher level than languages like C++, giving more implementation freedom as long as it conforms to the required semantics.

For example, ECMAScript does not specify whether values are stored on heap or stack and whether they are stored as pointers or direct values. The implementation can choose the most appropriate strategy, and may even change strategy at runtime.

Mutability is significant because it affects what strategy is appropriate. For an immutable value, there is no observable difference between copying a pointer to the value or copying the value, so the implementation may choose a strategy based on performance. For mutable values, however, the implementation is basically required to use pointers when assigning, since changes to a mutable value should be observable through all variables the value is assigned to.

For an object which is ostensibly mutable, but not actually mutated during the course of the code, the engine might store it as value rather than pointer, but have a fallback strategy in case the object is mutated.

Modern JavaScript implementation use very sophisticated optimizations, where code is profiled at runtime, and "hot" areas recompiled to more optimized code based on statistics. This could easily mean a temporary variable was entirely eliminated and a short-lived value purely lived in a register. An implementation requirement that variables should be stored in memory in a certain form would prevent such optimization.

So in short, ECMAScript does not specify such implementation details, since it would put unnecessary constraints on the implementation.

3
  • Thank you. That explanation helped me a lot. So, the emphasize on immutability lies in the fact, that primitives might be implemented as pointers? Because for direct storage - immutability is a given and would not need mentioning. Also I would like to ask you, what mental model you use when copying a primitive - do you imagine coyping a value or a pointer?
    – tweekz
    Commented Apr 17, 2023 at 11:10
  • @tweekz: You can have mutable value types in some languages. ECMAScript focus on semantics like identity and mutability because it gives the implementation greater freedom. In JavaScript and similar languages, I think of variables as bindings between a name and a value.
    – JacquesB
    Commented Apr 19, 2023 at 19:04
  • So if I do something like let a = 4 and let b = a -> do think of a and b pointing to the same value or to separate instances of a value that are equal?
    – tweekz
    Commented Apr 22, 2023 at 18:55
1

"You could just say that primitives are stored directly in a variable. Such a value could never take any effect on other variables"

That's not true. This is where the metaphor that describes a variable as a little box that stores values breaks down. Speaking in general terms (that is, the following is not specific to a particular language), variables are abstractions. Under the hood, the actual value resides somewhere in memory, and during execution of various low level instructions, at certain points registers or other memory locations contain values that represent offsets to that address, or store the address itself, etc. The process might involve a whole bunch of copying of values back and forth, to and from temporary locations, operating on these temporary values, and so on - this whole intricate dance that helps maintain the illusion that has been dreamt up by the designers of the higher level language. The details of this are different between languages and compilers. Variables as you know them from the source code might not exist as such in the compiled code.

But coming back to the higher-lever notion: you can have variables that are aliases. Two technically distinct named labels that refer to the same value (and thus both "see" the changes to the underlying value). That's because the value is not "in" the variable. In JavaScript, you might create an alias by creating a closure over a variable.

Note that aliases are not pointers. A pointer would be something like a variable that refers to a value (a number) that itself represents an address to a completely different memory location.

Value Types vs Reference Types

It's a little tricky because the meaning of these terms can vary to some extent across languages, and can be tied to the idiosyncrasies of each language. But roughly speaking, it is not given that a value type (or a type with value copy semantics) is immutable.

The Java/C# notion of value vs reference types does not cleanly map to how types work in C++, in that in C++ these semantics have to some extent be built by you. In C++, if you create an instance of a class, it's a value type (think of the case where you're not using new). However, you can normally mutate its state (you can change parts of it in memory, and the object itself will retain its identity and "continuity of existence", Ship of Theseus–style). If you want instances of a class to be immutable by design, you have to, well, design it that way yourself.

If the type is made immutable, you can't modify the instance - if you wanted a slightly altered version, you'd have to replace the entire instance with a different object that has the desired state. That is, you'd have to make the variable refer to a different object.

Now, in a way, the combo of {a pointer + the pointed-to instance} is a reference type (in the Java/C# sense), but the pointer itself (viewed on its own) has value semantics and is passed around by copy. As before, if the pointed-to type is immutable, you can't modify the underlying instance itself - if you want to change something about it, you have to replace it with a different object, and change the value of the pointer (change what the value refers to).

Immutable primitive types are conceptually unchangeable in the same sense. E.g., consider an integer: under "normal" circumstances, you don't mutate parts of an integer value in place (furthermore, conceptually, it doesn't even have parts). You just make the variable refer to some other integer. Now, in reality, an integer is typically a multi-byte value; C++ allows you to bypass the primitive abstraction, and fiddle with the underlying bytes in place. JavaScript does not ("The language does not offer utilities to mutate primitive values"). You can fiddle with the bits, but it results in a modified copy.

And the fact that something is immutable by design also allows compilers/interpreters to assign the same underling value to different variables under various circumstances (e.g. in a type system where strings are immutable, two unrelated string variables that happen to have the same content might refer to the same underlying memory, precisely because the content can't be changed). Again, coming back to the initial point, the value is not literally "in the variable".

8
  • Thanks for your elaborate answer. I do understand that internally a lot of magic might happen. Especially with abstract / interpreted languages (and even with compiled ones). But speaking as a general mental model, I find it way more intuitive to just think of variables holding a value, rather than referencing immutable objects (in case of primitives). It just adds an additional layer of indirection. So basically I am confused, if my mental model is acceptable for JavaScript.
    – tweekz
    Commented Apr 14, 2023 at 13:31
  • 1
    @tweekz For the most purposes, that mental model is fine. Where you have to be careful is in some specific scenarios and edge cases, where the higher level abstractions become "leaky". Like the alias being created in a closure, and you weren't expecting that. Or, say, when doing something perf. critical with strings and you run into performance issues - if you're not aware strings are immutable, and every change creates a new string, so you get slowdowns, you might now know how to begin to tackle the problem. (The string thing is not JavaScript specific, though. E.g. its true of C# as well). Commented Apr 14, 2023 at 13:44
  • With values stored directly in variables you will end up copying those values anyway. Actually, implementations like V8 apply both models. In case of small integers, they store them directly in the variable (as a tagged pointer). Strings actually are immutable Objects on the Heap.
    – tweekz
    Commented Apr 14, 2023 at 14:02
  • @tweekz - that's what I mean by "variables are an abstraction" and "primitives are an abstraction". What's actually happening under the hood depends on implementation details. Strings are counted/conceptualized as "primitives" on the language level, but implementation-wise, things are more complicated. Commented Apr 14, 2023 at 14:53
  • @tweekz - I guess you may be interpreting the MDN article as a text that's describing how things work under the hood, but its not doing that - it describes what these things mean on the language specification level. E.g. they don't consider strings to be objects at the language levels. For example, you can't add a property to a string the way you can add it to proper JS objects, arrays and functions. Commented Apr 14, 2023 at 15:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.