This relates to the concept of identity. ECMAScript has various kinds of values. Objects have identity, but undefined, null, booleans, numbers, bigints, and strings do not. (Symbols are complicated).
The value 42
is the same value as 42
, the values cannot be distinguished. You cannot modify these values, that just creates a different value.
The only ECMAScript value that you can modify is an Object value. You can make observable modifications by creating, setting, or deleting properties. Variables are similar to properties – they are bindings in an "environment record" that represents a scope, though scopes other than the global-this and with-clauses are not connected to actual ECMAScript Objects.
You can have multiple bindings that refer to the same primitive (non-Object) value, but there's nothing you can do to tell the difference. It is likely that an ECMAScript implementation will just copy values for bools or numbers, and it is free to decide whether or not to copy or share strings.
You can also have multiple bindings that refer to the same Object value. Because Objects have identity, you can tell whether two bindings refer to the same Object, or to two distinct Objects. When you modify an Object through one binding, you can observe that change through all other bindings as well.
Taken together, this means:
- ECMAScript can be interpreted to have primitive types (anything that is not an Object, in particular strings and numbers).
- ECMAScript is very much a by-value language, which is clear in the spec through its careful distinction of values and references (which always refer to bindings/properties).
- But Objects are pointer-like values. Creating a new binding to an Object value does not clone its contents.
- This approach is sometimes called a "by-sharing" rather than a "by-value" (copying) or "by-reference" evaluation strategy.
A conforming ECMAScript implementation can represent Objects as a pointer to some data structure, and it can copy those pointers around. It cannot generally copy the contents of those objects around, though. Of course, an implementation might be able to show that an object is only used locally, and that such optimizations would be safe.
These kinds of semantics are thoroughly mainstream, and are used in a very similar way e.g. by Java or Python (and by their common ancestor, Smalltalk). Function calls just involve normal bindings of argument values to parameter names, with no special by-name or by-reference semantics. This behaviour is in contrast to languages like C/C++ that also operate by-value, but have explicit pointers and would copy struct contents by default. C++ optionally also supports by-reference calls.