"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".
5=6;
var someString = 'FooBar';
. You also can't do:someString[5] = 'z'
, orsomeString.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 dosomeString = 'FooBaz'
, orsomeString = someString.replace('Bar', 'Baz')
.