4. References: two names, one object
A variable never holds an object. It holds an arrow to one. Once you see the arrows, half of JavaScript’s surprises disappear, and the hardest problem in this book comes into view.
- Predict what happens when two variables refer to the same object and one of them changes it.
- Explain why === on objects compares identity, not contents.
- Explain how a function can change an object its caller owns.
- Make a shallow copy with spread syntax, and say what it does not copy.
A puzzle
Predict what each of these prints before you read on:
let m = 1;
let n = m;
n = 99;
console.log(m);
const a = { x: 1 };
const b = a;
b.x = 99;
console.log(a.x);
The first prints 1. Changing n didn’t touch m. The second prints
99. Changing b did change a. Same-looking code, opposite
results. Why?
A number in a variable is like a number written on a sticky note. Copy the
note and you have two separate notes. An object is more like a house,
and the variable holds only its address. const b = a copies the
address onto a second card. Now two cards lead to the same house, and if
you repaint the house using one card, the other card leads to a repainted
house too.
The arrows
Step through it. On the left are the variables; on the right, the objects living in memory.
const a = { x: 1 };const b = a;b.x = 99;console.log(a.x);const c = { x: 99 };console.log(a === b);console.log(a === c);
A variable that holds an object holds a reference to it: an arrow
pointing at the object, not a copy of it. When two references point at
the same object, they are aliases. Changing an object in place (like
b.x = 99) is a mutation, and every alias sees it. For objects,
=== compares identity: “are these the same object?”, not “do they
have the same contents?”.
This also solves a mystery from chapter 2: how can you push onto a
const array? const freezes the arrow: the name can never point at
a different array. It says nothing about the array the arrow points at, and
that array can still be changed.
Arrays are objects too
Everything above holds for arrays, because arrays are objects:
const list = [1, 2, 3];
const same = list;
same.push(4);
console.log(list); // [ 1, 2, 3, 4 ]
console.log(list === same); // true
console.log([1] === [1]); // false
Two separately written [1]s are two different arrays, so === says
false even though they look identical.
const a = { x: 1 };
const b = a;
b.x = 99;
console.log(a.x);
const c = { x: 99 };
console.log(a === b, a === c);
const list = [1, 2];
const same = list;
same.push(3);
console.log(list);Functions get arrows too
When you pass an object to a function, the parameter receives a copy of the arrow. Function and caller now share one object:
function birthday(person) {
person.age = person.age + 1;
}
const ada = { name: 'Ada', age: 36 };
birthday(ada);
console.log(ada.age); // 37
That’s why step(particles) in the last chapter could move the caller’s
particles without returning anything. But notice what a function can’t
do: point the caller’s variable somewhere else.
function replace(person) {
person = { name: 'Bob', age: 1 };
}
replace(ada);
console.log(ada.name); // Ada
person = ... re-aims only the function’s own local copy of the arrow.
The caller’s ada arrow never moved.
Making a real copy
Sometimes you want a separate object. Spread syntax, {...obj}, builds
a brand-new object and copies the properties into it:
const p = { x: 1, y: 2 };
const q = { ...p, x: 50 };
console.log(p.x); // 1
console.log(q.x); // 50
console.log(p === q); // false
Properties listed after the spread override the copied ones, so
{ ...p, x: 50 } means “a copy of p, but with x set to 50”.
Spread is shallow. It copies the properties of the outer object, but if
a property holds an object, only the arrow is copied. So
const c = { ...a } where a.pos is an object leaves c.pos === a.pos,
and mutating c.pos.x still changes a.pos.x.
Why this is Viscosity’s hardest problem
Remember the particle loop. Suppose Viscosity decides your particles would
be faster as a struct of arrays: one array of all the x values, one of
all the y values. Now look at this ordinary code:
const first = particles[0];
first.x = 100;
console.log(particles[0].x); // must be 100
first and particles[0] are aliases. They’re the same object, and the
program relies on that. If Viscosity had quietly moved x into a
separate array, first.x = 100 would have to update that array too.
Otherwise the program prints the wrong answer, and an optimizer that
changes answers is worse than no optimizer at all.
So before it can change how data is laid out, Viscosity has to answer: where can arrows to these objects end up? Who else might be looking at them? Compilers call this alias analysis and escape analysis, and they’re what make automatic layout changes safe. Research systems that switch JavaScript arrays to columns automatically face exactly this requirement[3]. You’ll build these analyses yourself in Parts VI and VII. For now, you can see the problem, and that’s the first step to solving it.
Your turn
Write withX(p, x) that returns a new object just like p but with its x property set to x. The original p must not change.
function withX(p, x) {
// your code here
}
const p = { x: 1, y: 2 };
const q = withX(p, 9);
console.log(p, q);Write indexOfSame(xs, target) that returns the index of the slot holding the very same object as target (identity, not equal contents), or -1 if there is none.
function indexOfSame(xs, target) {
// your code here
}
const a = { id: 1 };
const xs = [{ id: 1 }, a];
console.log(indexOfSame(xs, a));What you learned
- A variable holding an object holds a reference: an arrow, not the object.
- Copying the variable copies the arrow. Two arrows to one object are aliases, and a mutation through either is seen by both.
===on objects compares identity: same object, not same contents.- Functions receive arrows, so they can mutate the caller’s objects, but they can’t re-aim the caller’s variables.
{ ...obj }makes a new, shallow copy.- Aliasing is why changing data layout automatically is hard. It is the problem at the core of Viscosity.
That’s the end of Day 1. Tomorrow: functions as values, and closures.
Check your understanding
0/6- 1.
What does this print?
const a = { n: 1 }; const b = a; b.n = 2; console.log(a.n); - 2.
What is
[1, 2] === [1, 2]? - 3.
What does this print?
function f(o) { o = { v: 2 }; } const x = { v: 1 }; f(x); console.log(x.v); - 4.
const c = { ...a };anda.posis an object. Which is true? - 5.
Why is aliasing a problem for an optimizer that changes an array of objects into a struct of arrays?
- 6.
Which line creates a new object rather than another alias of
p?
References
- [1] MDN contributors. Working with objects (JavaScript Guide). MDN Web Docs. developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_objects (checked 2026-10-31)
- [2] MDN contributors. Equality comparisons and sameness. MDN Web Docs. developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness (checked 2026-10-31)
- [3] Lukas Makor, Sebastian Kloibhofer, David Leopoldseder, Daniele Bonetta, Lukas Stadler, Hanspeter Mössenböck (2022). Automatic Array Transformation to Columnar Storage at Run Time. Proceedings of the 19th International Conference on Managed Programming Languages and Runtimes (MPLR ’22), ACM, pp. 16–28. doi:10.1145/3546918.3546919 (checked 2026-10-31)