DEV Community

loading...
Cover image for A "Gotcha" of JavaScript's Pass-by-Reference

A "Gotcha" of JavaScript's Pass-by-Reference

bytebodger profile image Adam Nathaniel Davis ・9 min read

A few days ago, I posted an article talking about Pass By Reference Trolls. Specifically, I was calling out those people who stubbornly refuse to acknowledge JavaScript's native pass-by-reference behavior, despite any proof you might show them. (If you're so inclined, you can read the article here: https://dev.to/bytebodger/troll-hunting-101-javascript-passes-objects-by-reference-40c8)

Because I've grown so weary of the Pass By Reference Trolls, I put a note at the bottom of that article explaining that, in contrast to my normal behavior, I would not be replying directly to any comments. However, after reading a few replies, I realized that there is a critical area of ambiguity on this matter that probably fosters a good portion of the confusion.

Rather than reply to those commenters directly (which I swore I would not do), I realized that this might be a prime opportunity for a follow-on article. To be absolutely clear, the particular people who replied to my last article were not acting trollish in any way. They were presenting respectful and well-reasoned counter-points, which is why I thought it might be best just to clarify things in this article.

In fact, the more I thought about this over the weekend, the more I realized that this is kinda like the Monty Hall Problem. (If you're not well familiar with it, google it. No, seriously. Right now. Go google it. It's fascinating.)


Alt Text

JavaScript's Monty Hall Problem

I won't bore you with a thorough recap of the Monty Hall Problem. (But have I mentioned that, if you don't know about it, you should google it??)

The key thing that interests me about it is that, on one level, it's actually an extremely simple problem. There's no calculus. No advanced concepts of theoretical physics. No quantum mechanics. It's a very basic puzzle of elementary probabilities. And yet, if people haven't already been exposed to the problem, the vast majority will come to the absolutely wrong solution.

But it gets more interesting than that. There's something about the way that the problem is received in the brain that causes even advanced academics to become extremely defensive and/or combative about defending their erroneous solution.

Seriously. Learn about the Monty Hall Problem. Then find someone who's not familiar with it - preferably someone with advanced academic or professional status. Then, when they give you the wrong solution, watch as they protest, vehemently, about how mistaken they believe you are.

In this regard, pass-by-reference is strikingly similar to the Monty Hall Problem. Once someone gets it in their head that "JavaScript has no pass-by-reference!!!" it becomes nearly impossible to dislodge their erroneous conclusion.


Alt Text

The Setup

If you've been writing code for, oh... five minutes or so, nothing in this next example will surprise you. Nevertheless, it's important to illustrate the extremely simple concept at play:

// initialize our variables
let mostImportantNumber = 3.14;
let spanishNumbers = { one: 'uno', two: 'dos', three: 'tres' };

// use these variables to initialize some NEW variables
let answerToEverything = mostImportantNumber;
let germanNumbers = spanishNumbers;

// mutate the NEW variables to our liking
answerToEverything = 42;
germanNumbers.one = 'einz';
germanNumbers.two = 'zwei';
germanNumbers.three = 'drei';

// inspect the ORIGINAL variables
console.log(mostImportantNumber);  
// 3.14 - no surprise here
console.log(spanishNumbers); 
// {one: 'einz', two: 'zwei', three: 'drei'}
// wait a minute... that doesn't look like Spanish
Enter fullscreen mode Exit fullscreen mode

As I discussed in my previous article, there are some people who want to dive into pedantics with the argument that, "That's not passing by reference! Passing requires a function!"

Umm... no. It doesn't. You can "pass" a value into a new variable by using the value to initialize the variable. But even if we give in to the Passing Police, we can write this with a function and the effect is no different.

const mutate = (aNumber, numberNames) => {
  aNumber = 42;
  numberNames.one = 'einz';
  numberNames.two = 'zwei';
  numberNames.three = 'drei';
}

// initialize our variables
let mostImportantNumber = 3.14;
let spanishNumbers = { one: 'uno', two: 'dos', three: 'tres' };

// use these variables to initialize some NEW variables
let answerToEverything = mostImportantNumber;
let germanNumbers = spanishNumbers;

// mutate the NEW variables to our liking
mutate(mostImportantNumber, spanishNumbers);

// inspect the ORIGINAL variables
console.log(mostImportantNumber);  
// 3.14 - no surprise here
console.log(spanishNumbers); 
// {one: 'einz', two: 'zwei', three: 'drei'}
// wait a minute... that doesn't look like Spanish
Enter fullscreen mode Exit fullscreen mode

Strangely enough, I've never had anyone argue with me that the primitive value (mostImportantNumber) and the object (spanishNumbers) are treated the same. It's pretty clear to the naked eye that something different is happening with these variables. But I've still had multiple Language Nerds stubbornly tell me that both variables are passed by value - even though they are obviously processed at runtime in very different ways.

But as I mentioned above, there were two commenters on my previous article who gave interesting "gotcha" examples. And the more I thought about it, the more I became convinced that it may be examples like those that are causing some people confusion.

So let's explore the "counter examples" they gave...


Alt Text

Fumbled References

@iquardt gave this basic-yet-vexing example:

const foo = xs => {
  xs = [1];
};

let xs = [];
foo(xs);
console.log(xs);  // []
Enter fullscreen mode Exit fullscreen mode

On the surface, this seems to "break" my pass-by-reference position, right? I mean, the xs array is passed into foo(), where it's mutated. But the original xs array is unaltered. So... no pass-by-reference??

Well, let's dive a bit deeper.

First, the example is a bit convoluted because we have the same variable name outside and inside the function scope. This always makes it a bit harder to follow what's actually happening as we try to mentally organize the scopes. So I'll make it a little clearer by renaming the variable in the function signature.

const foo = someArray => {
  xs = [1];
};

let xs = [];
foo(xs);
console.log(xs);  // [1]
Enter fullscreen mode Exit fullscreen mode

This seems to "work" - but it has nothing to do with pass-by-value or pass-by-reference. It has to do with the fact that, inside the function, there is no definition for xs. But in JS, functions have access to variables in their calling scope. JS looks outside the function scope and finds a definition for xs and updates it accordingly.

To get a clearer view on pass-by-value/reference, let's complete the de-obfuscation of foo() by changing the name of the inner variable and also tweaking the outer one.

const foo = someArray => {
  someArray = [1];
};

let originalArray = [];
foo(originalArray);
console.log(originalArray);  // []
Enter fullscreen mode Exit fullscreen mode

This is functionally equivalent to @iquardt 's first example. And just like in that example, the outer-scoped array of originalArray remains unchanged. This is where people claim that JS objects are not passed by reference. The thinking goes like this:

If objects (and arrays are objects) are passed-by-reference, then, someArray will be a reference to originalArray. And if that were true, then when we set someArray = [1], that change should be reflected in originalArray, outside the function scope.

But that's not what happens. So... pass-by-reference is false??

Umm... no.

The problem here is that people seem to have completely juggled the idea of an object with a reference to that object. They're similar - but they're not identical, interchangeable concepts.

A reference to an object allows us to perform operations on the original object. But here's the critical part: If we destroy that reference, we shouldn't be surprised when we can no longer perform operations on the original object.

That's exactly what's happening in this example. When foo() enters its instruction body, someArray is absolutely a reference to originalArray. But then, in the first line of the function's code, that reference is destroyed (overwritten) by an entirely different object: [1]

And we can illustrate this concept just by adding a few lines of code:

const foo = someArray => {
  someArray.push('pass');
  someArray.push('by');
  someArray = [1];
  someArray.push('reference');
};

let originalArray = [];
foo(originalArray);
console.log(originalArray);  // ['pass', 'by']
Enter fullscreen mode Exit fullscreen mode

When the function first starts running, someArray is 100% a reference to originalArray. That's why push() updates the contents of originalArray - because as long as we have that reference, we can perform operations on the original object.

But on the third line of the function, we do this: someArray = [1]; That code doesn't overwrite the entire value of originalArray. That line of code overwrites the reference which originally pointed to originalArray. Therefore, the first two push() statements are the only ones that are reflected back on the original array.

The key takeaway is this:

A reference allows you to manipulate some original object. But if you overwrite the reference, you haven't overwritten the original object, you've just overwritten the reference to that object.


This might be clearer if I add some comments to the code:

const foo = someArray => {
  // someArray is currently A REFERENCE to 
  // originalArray
  someArray.push('pass'); // this uses THE REFERENCE to
  // originalArray to add an item to the array
  someArray.push('by'); // this uses THE REFERENCE to
  // originalArray to add an item to the array
  someArray = [1]; // this OVERWRITES the reference -
  // someArray is now [1] - with NO RELATION to
  // originalArray 
  someArray.push('reference'); // this adds an item to
  // the new [1] array, which has no relation to
  // originalArray
};

let originalArray = [];
foo(originalArray);
console.log(originalArray);  // ['pass', 'by']
Enter fullscreen mode Exit fullscreen mode

In the interest of completeness, I'll also show @devdufutur 's example:

function reassign(someStuff) {
  someStuff = { someInt: 42 };
}

let three = { someInt: 3 };
console.log("before reassign", three); // { someInt: 3 }
reassign(three);
console.log("after reassign", three); // { someInt: 3 }
Enter fullscreen mode Exit fullscreen mode

He used a traditional object, rather than an array, but the concept here is exactly the same. The original three object remains unchanged because, in the first line of his reassign() function, he overwrote (destroyed) the reference to three.

Notice that, in his example, he even used the term "reassign" - which is rather instructive. Because when he writes someStuff = { someInt: 3 };, that LoC isn't reassigning the original object. It's reassigning the someStuff variable from its reference to a brand new object, disconnected from the original object passed in the argument.

We can alter this example to highlight this same principle:

function reassign(someStuff) {
  someStuff.someInt = -1;
  someStuff.thisIsPassBy = 'reference';
  someStuff = { someInt: 42 };
  someStuff.lost = 'reference';
}

let three = { someInt: 3 };
console.log("before reassign", three); // { someInt: 3 }
reassign(three);
console.log("after reassign", three);  // { someInt: -1, thisIsPassBy: 'reference' }
Enter fullscreen mode Exit fullscreen mode

someInt is reassigned to -1. That works because it's an operation on the reference to three. We can even add new keys, because adding the key is also an operation on the same three object (using the still-functioning reference).

But our attempts to set someInt to 42 and lost to 'reference' are not reflected on the original three object. They can't be. Because, when we tried to set someInt to 42, we overwrote the reference to three.

Again, this might be a little clearer with some comments:

function reassign(someStuff) {
  // someStuff is currently A REFERENCE to 'three'
  someStuff.someInt = -1; // this uses THE REFERENCE to
  // 'three' to update the value of someInt
  someStuff.thisIsPassBy = 'reference'; // this uses THE
  // REFERENCE to 'three' to add the thisIsPassBy key
  someStuff = { someInt: 42 }; // this OVERWRITES the
  // reference - someStuff is now { someInt: 42 } - with
  // NO RELATION to 'three'
  someStuff.lost = 'reference'; // this adds the 'lost'
  // key to the new { someInt: 42 } object, which has no
  // relation to 'three'
}

let three = { someInt: 3 };
console.log("before reassign", three); // { someInt: 3 }
reassign(three);
console.log("after reassign", three);  // { someInt: -1, thisIsPassBy: 'reference' }
Enter fullscreen mode Exit fullscreen mode



Alt Text

Buckets

FWIW, I do understand that this is why some people (angrily) cling to the ideas that "Everything is passed by value!" and "Objects are passed by a value - that holds a reference." And if that nomenclature helps you understand what's going on, then great!

But when you look at the first example in this article and you stubbornly stamp your feet and declare, "They're both passed by value!" you're taking two examples, that clearly and demonstrably behave quite differently, and you're trying to wash away that difference with some unhelpful bromide.

I'm not asking anyone to think anything like me, but it helps me to think of variables not so much as "values" (which is already abstract as hell) or "references". Instead, I just think of them as... buckets.

Once you've accounted for space constraints, you can put pretty much anything you want inside the bucket. It can hold a quantifiable volume (number) or some printed text (a string) or nothing (null) or... many other things.

Buckets can also hold objects. But when we try to pass the bucket to someone else, the recipient receives a note that says, "Hey, the original thing you're looking for is over there." If you take the note out of the bucket and replace it with something else entirely (even if that "something else" is... another object), there's no way for the original object to reflect any of those changes.

A bucket (variable) can hold some thing (like, a value) or it can hold a note that refers to something else. If the bucket is holding a reference, and you overwrite that reference with an assignment operator, you will wipe out the reference. And you won't see any future changes reflected on the original object.

Discussion (18)

pic
Editor guide
Collapse
unchar1 profile image
Askara Novaru • Edited

I don't mean to troll but I can understand why a lot of people are adamant about the terminology. In languages like C++ or C#, you can actually pass a reference not just to the object (the value), but rather to the object's holder (the variable).

So I think a lot of people think like this

  1. Pass-by-value: Pass the value of this variable
  2. Pass-by-reference: Pass a reference of this variable

Since most languages agree that "Pass-by-reference" is sort of dangerous, they don't have it. Which can lead to many people re-purposing the term "pass-by-reference", changing the meaning from "pass a reference of the variable" to "this variable holds a reference which is being passed".Since passing a reference of the variable isn't even possible in JS, so why have a term for that anyway?

I think a good litmus test for whether a language can pass the reference of the variable would be whether you can write a swap function in that language.
Here's a sample code in C++

int a = 1, b = 2;
std::cout << a << " " << b << endl; // Output: 1 2 
std::swap(a, b):
std::cout << a << " " << b << endl; // Output: 2 1
Enter fullscreen mode Exit fullscreen mode

You can do something similar in C# (with some changes), but it isn't possible in JS or Java, without wrapping a variables inside some type of container, and then passing that container.

So in JS or Java, the references to variables can never be passed, rather variables can hold the value of a reference, and that value can get passed.

Collapse
bytebodger profile image
Adam Nathaniel Davis Author

I've been noticing that a lot of people who seem entrenched in the idea that "JS has no pass-by-reference" seem to give me examples from C/C++. So I'm honestly wondering if this is just an artifact of people trained in a particular paradigm then (naturally) clinging to that paradigm even when they move outside their original area??

I was curious about how C++ would define pass-by-reference. The IBM Knowledge Center is one of the first pages that comes up: ibm.com/support/knowledgecenter/SS...

It has some very... interesting detail. It starts off with this:

Pass-by-reference means to pass the reference of an argument in the calling function to the corresponding formal parameter of the called function. The called function can modify the value of the argument by using its reference passed in.

Hmm...

const callingFunction = () => {
  const importantNumber = 3.14;
  const spanishNumbers = { one: 'uno', two: 'dos', three: 'tres' };
  calledFunction(importantNumber, spanishNumbers);
  console.log('importantNumber', importantNumber); // 42
  console.log('spanishNumbers', spanishNumbers); // { one: 'einz', two: 'dos', three: 'tres' }
}

const calledFunction = (somePrimitive, someObject) => {
  somePrimitive = 42;
  someObject.one = 'einz';
}

callingFunction();

In this JS example, the calling function passes two arguments as parameters to the called function. The called function modifies the value of those arguments. But the change is only reflected on one of those arguments - the object. Why?? Because the object is passed by reference.

The IBM Knowledge Center definition goes on to state that:

The difference between pass-by-reference and pass-by-value is that modifications made to arguments passed in by reference in the called function have effect in the calling function, whereas modifications made to arguments passed in by value in the called function can not affect the calling function. Use pass-by-reference if you want to modify the argument value in the calling function. Otherwise, use pass-by-value to pass arguments.

So look at exactly what happened in my simple JS example. A modification made to an argument passed in by reference in the called function (someObject) has effect in the calling function. Whereas a modification made to an argument passed in by value in the called function (somePrimitive) does not affect the calling function.

There it is, defined by IBM with regard to C++. Even by that definition, JS is passing the object by reference.

Collapse
bytebodger profile image
Adam Nathaniel Davis Author • Edited

I'll admit that maybe I'm the one with the massive mental block on this one. Maybe I'm the one who's fighting against the simple solution of the Monty Hall Problem. But every single time someone tries to explain to me why JS has no pass-by-reference, they either ignore the simple, tactical, repeatable examples I've already provided, or they say/show something that actually only furthers my case.

In my first example, I show a basic process by which we 1) initialize two variables, 2) pass those variables into new variables, 3) mutate the new variables, and 4) output the values of the original variables. In the example, the first original variable (the primitive value) is unchanged - because it's passed by value. The second original variable is changed - because it's passed by reference. It's already shown above, but here's a stripped down version of it:

// initialize variables
let mostImportantNumber = 3.14;
let spanishNumbers = { one: 'uno', two: 'dos', three: 'tres' };
// pass variables 
let answerToEverything = mostImportantNumber;
let germanNumbers = spanishNumbers;
// mutate variables 
answerToEverything = 42;
germanNumbers.one = 'einz';
// output original variables
console.log(mostImportantNumber);  // 3.14
console.log(spanishNumbers); // { one: 'einz', two: 'dos', three: 'tres' }

It's obvious that something very different happens to the two original variables - mostImportantNumber and spanishNumbers.

We never performed any mutation directly on mostImportantNumber and, understandably, the value of mostImportantNumber remains constant. We never performed any mutation directly on spanishNumbers - yet the value of spanishNumbers is updated.

I don't know how I can make it any clearer than this. It's demonstrably, provably obvious that the newly-created variable germanNumbers maintains some kind of "link" back to its initializing variable spanishNumbers. It's demonstrable and provable because, when we update the members of germanNumbers, the change is reflected back on spanishNumbers.

As long as I've been programming, this "link" has been called a "reference". If you (or anyone else) wants to tell me that this "link" is not a "reference", then that's fine - but that leads me to your question:

Since passing a reference of the variable isn't even possible in JS, so why have a term for that anyway?

Because, in JS (and many other languages), the behavior of a passed primitive is demonstrably different than the behavior of a passed object.

It's really that simple. Why would we keep calling a "kick" a "throw" if it's demonstrably obvious that they're two different behaviors, and two different things are happening in those actions??? And why would we keep calling JS objects that have been passed "pass-by-value" when it's demonstrably obvious that they behave entirely differently from primitives that have been passed by value???

Collapse
unchar1 profile image
Askara Novaru • Edited

Because, in JS (and many other languages), the behavior of a passed primitive is demonstrably different than the behavior of a passed object.

Actually there's no practical difference between how JS passes a primitive value or an object. I'm sorry if I came off wrong. I was just pointing out where a lot of people who were arguing about it where coming from. As for the example you provided.

// initialize variables
let mostImportantNumber = 3.14;
let spanishNumbers = { one: 'uno', two: 'dos', three: 'tres' };
// pass variables 
let answerToEverything = mostImportantNumber;
let germanNumbers = spanishNumbers;
// mutate variables 
answerToEverything = 42; // This is an assignment, not a mutation
germanNumbers = { one: 'einz' } // This would be the equivalent operation for an object
// output original variables
console.log(mostImportantNumber);  // 3.14
console.log(spanishNumbers); // { one: 'uno', two: 'dos', three: 'tres' } <-- Unchanged
Enter fullscreen mode Exit fullscreen mode

The JS runtime has no reason to treat a primitive assignment differently to an object assignment. In fact, you can even try to mutate a primitive just like an object, and JS will allow you to do that as well. It just throws away any mutations you make, which is why it appears that you are operating on another copy of the primitive. But in reality both variables point to the same primitive as well.

answerToEverything.one = 42; // Perfectly valid, it just doesn't mutate 'answerToEverything', since primitives are immutable by default
germanNumbers.one = 'einz'; // Since objects are mutable by default, this mutates 'germanNumbers'
Enter fullscreen mode Exit fullscreen mode

In fact, if you freeze the object, you can make an object immutable as well, and essentially get the same behavior between objects and primitives, which demonstrates that you don't need to copy anything to make an object behave like a primitive.

// initialize variables
let mostImportantNumber = 3.14;
let spanishNumbers = { one: 'uno', two: 'dos', three: 'tres' };
spanishNumbers = Object.freeze(spanishNumbers); // We freeze the object i.e. make it immutable like a primitive
// pass variables 
let answerToEverything = mostImportantNumber;
let germanNumbers = spanishNumbers;
// mutate variables 
answerToEverything = 42;
germanNumbers.one = 'einz'; // This mutation doesn't do anything, since the object is 'frozen' i.e. immutable
// output original variables
console.log(mostImportantNumber);  // 3.14
console.log(spanishNumbers); // { one: 'uno', two: 'dos', three: 'tres' } <-- Unchanged
Enter fullscreen mode Exit fullscreen mode

I suppose you could say that primitives are like objects that are just frozen by default. While this is technically not true, unless you attempt any operation specific to a primitive (such as addition,subtraction,etc), for the JS Runtime, they are treated exactly in the same way (such as when assigning them to variables or passing them to functions)

I hope that helps clear things up a bit!

Collapse
darkwiiplayer profile image
DarkWiiPlayer • Edited

Weird. I think it's trivially obvious that javascript passes by reference. The examples kinda show that, at those developers, seem to misunderstand the definitions of call-by-reference and call-by-value rather than the nature of javascript.

The confusion seems to be about mixing up references and variable names, so maybe a better way of explaining would be translating the examples to SSA form?

It's also somewhat true that all languages are realy call-by-value, only sometimes the "value" is a reference. Just as it's true that all languages are strictly goto-based and javascript uses manual memory management and pointers. Under the hood, these all apply, it's just not a very useful way to think about it.


EDIT: Maybe people just need to use a language that is actually call-by-value like C, where they actually have to manually pass references as values to simulate call-by-reference. Seeing something for oneself is often the easiest way to understand it.


EDIT 2: So according to wikipedia, the undisputable source of truth on the internet, I am wrong, and what I call "call-by-reference" is really just "call-by-sharing", a special form of "call-by-value". So it's basically agreeing that JS is "call-by-value", with the values being references to objects.

So the real question should be:

Does call-by-reference mean a) passing references to values or b) passing references to variables?

The answer seems to differ from person to person, but the consensus might be tending towards b) from what I've seen.


EDIT 3: Thinking about it some more, I'm switching sides. Javascript is call-by-value. At first I was going at it from the perspective that objects are structs, so call-by-value would mean that mutation would return a new object. This is wrong though, objects are not structs, as they are not defined by the sum of their parts. To illustrate this:

let foo = { a: 20 }
let bar = { a: 20 }
console.log(foo == bar)
Enter fullscreen mode Exit fullscreen mode

So the "value" of an object is not just the combination of its structure and values of its members. Two objects can have the same shape and still be distinct. The "value", therefore, is the unique reference to the object. We can say it's a pointer that makes it unique, or a direct trace to the call to its constructor.

So passing the "value" of an object means passing that same memory reference, not just a copy of its data. Javascript passes objects by value.

Collapse
bytebodger profile image
Adam Nathaniel Davis Author

Weird. I think it's trivially obvious that javascript passes by reference. The examples kinda show that, at those developers, seem to misunderstand the definitions of call-by-reference and call-by-value rather than the nature of javascript.

Exactly. That's why I found it so odd when I realized that there's a (small) crowd out there that not only doesn't see this, but will argue rather belligerently against it.

The confusion seems to be about mixing up references and variable names, so maybe a better way of explaining would be translating the examples to SSA form?

Maybe. Although it's impractical to think that would ever be adopted on a grand scale.

It's also somewhat true that all languages are really call-by-value, only sometimes the "value" is a reference.

Bingo. And I do understand that JS references are saved in a value. But it's beyond confusing to try to shout everyone down by saying, "There is no pass-by-reference in JS!!!!" They can scream that all they want, but at the end of the day, the (very basic) examples illustrate that objects and scalar values are being handled very differently in memory once they're passed.

Collapse
darkwiiplayer profile image
DarkWiiPlayer • Edited

the [...] examples illustrate that objects and scalar values are being handled very differently in memory once they're passed.

Arguably. If you think about it, numbers kind of behave just like objects in assignments. They can't behave differently in mutation because they can't be mutated.

For example:

function(number, object) {
   number = 30
   object = { foo: "bar" }
}
function(40, { foo: "foo" })
Enter fullscreen mode Exit fullscreen mode

As you can see object and number both behave the same: a new value is assigned to both variables and neither is mutated and neither is changed outside the function.

Consider also this example:

function(object) {
   object.foo = 20
}
original = { foo: 10 }
reference = original
function(reference)
console.log(original == reference)
Enter fullscreen mode Exit fullscreen mode

As you can see, the variable reference still contains the same object as it did before, at least by how javascript itself defines object identity. The variable still points to the same value, but the value itself was mutated.

Additionally, consider this example of call-by-reference:

var pi : integer := 3.2; // Good enough for some people¹
...
procedure increment(var number : integer)
begin
   number := number + 1;
end;
...
increment(pi)
writeln(pi) // Lo and behold, pi is now 4.2
Enter fullscreen mode Exit fullscreen mode

If we try this in javascript, we will find that it's not possible:

let pi = 3.2
function increment(number) { number = number + 1 }
increment(pi)
console.log(pi) // As expected, pi remains 3.2 (well, our variable at least)
Enter fullscreen mode Exit fullscreen mode

Again, this comes down to how you define call-by-reference. Is the "reference" to the passed data, or to the variable from which the data is passed? Answering that core question is the only way to clarify whether JS supports call-by-reference or not.


EDIT: I haven't used pascal in a while, so I am not sure whether that program is completely correct, but it should nevertheless serve to illustrate the point.


¹ youtube.com/watch?v=bFNjA9LOPsg

Thread Thread
bytebodger profile image
Adam Nathaniel Davis Author • Edited

Excellent examples. And yeah, I've had the same kinda "aha!" moment when I realized that, in simple terms, an object is basically a mutable value. And a scalar (like: a number), is an immutable value.

This also helps to further illustrate the difference between a value, and the variable that holds that value. In most illustrations, the two concepts are interchangeable. But of course, they're not the same thing.

It took me years to really "grok" that anything (in JS, or any other language) could truly be immutable. Because I'd think of simple examples like this:

let count = 0;
count++;
console.log(count);
Enter fullscreen mode Exit fullscreen mode

My previous response would have been, "Obviously, numbers are mutable, otherwise, the 3rd line would return an error or 0." And honestly, that logic mostly "works" when trying to look at your code and determine what can be changed (mutated), and what cannot.

IMHO, this example "feels" much more like immutability:

const count = 0;
count++;
console.log(count);
Enter fullscreen mode Exit fullscreen mode

This obviously doesn't run. But of course, the error isn't spawned because the value is immutable. It's spawned because the variable itself cannot be reassigned.

Collapse
functional_js profile image
Functional Javascript • Edited

Again, I am in 100% agreement Adam

There are three stack allocation cases (Javascript supports the first two only):

pass value:

allocate a value type
(a value type = primitive type, eg. number, boolean, string, etc)
the developer's data (value) is IN the stack frame. There is no reference to the heap.
note: assume this includes strings because they have "value semantics", so their allocations behave like a primitive.
Applies to either a variable assignment or function invocation

pass reference:

allocate a reference type
(a reference type = heap objects, eg. array, object, function, etc)
Just like pass by value, a new stack frame is allocated, but the stack frame does not contain the developer's data (value), but only a reference to it on the heap. Therefore a REFERENCE was passed, no data (value) was passed, as the data continues to unknowingly sit on the heap untouched and unaffected by the stack allocation.
Summary: A reference is copied; the end value itself is not copied.
Applies to either a variable assignment or function invocation

pass a reference of reference

Just like the previous two, a new stack frame is allocated, but the newly created stack frame does not point to the heap, but to the stack frame below it which thus points to the data on the heap (one extra level of indirection)
note: Never use this. Using this is like using the Unsafe keyword in C#. You'd only use this for things like interop with DCOM. It would be an extremely, advanced, bizarre case. That is why there is a specialized keyword that must be placed on both the caller and callee to even be able to do it (a double red flag). It cannot be used by default, nonexplicitly, or accidently, for it's a massive antipattern if used outside of it's extreme, highly-specific edge case.
This is why it doesn't exist in Javascript. There'd be no need for it, and would only create a crap ton more load of bugs.
Applies to function invocation only

Collapse
bytebodger profile image
Adam Nathaniel Davis Author

I didn't even realize that there is such a thing as "pass a reference by reference" - in any language. Very interesting. At some point, when I'm done writing code for the day but my brain hasn't yet completely shut down, I'll probably read up on that - just as an interesting trivia point.

Thanks!

Collapse
functional_js profile image
Functional Javascript • Edited

Here's an example from the C# docs (though massively naive and antipattern-ish, because one would never have to do this). Notice two ref keywords must be used, in the caller and callee.
Btw, I've coded in C# for years and I've never used it, and never seen it used. It really shouldn't exist.

class PassingRefByRef
{
    static void Change(ref int[] pArray)
    {
        // Both of the following changes will affect the original variables:
        pArray[0] = 888;
        pArray = new int[5] {-3, -1, -2, -3, -4};
        System.Console.WriteLine("Inside the method, the first element is: {0}", pArray[0]);
    }

    static void Main()
    {
        int[] arr = {1, 4, 5};
        System.Console.WriteLine("Inside Main, before calling the method, the first element is: {0}", arr[0]);

        Change(ref arr);
        System.Console.WriteLine("Inside Main, after calling the method, the first element is: {0}", arr[0]);
    }
}
/* Output:
    Inside Main, before calling the method, the first element is: 1
    Inside the method, the first element is: -3
    Inside Main, after calling the method, the first element is: -3
*/

Enter fullscreen mode Exit fullscreen mode
Thread Thread
netional profile image
Netional

I recently came across a Typescript use case (admittedly seldom happens) for reference to the reference in which I wanted to have a function that disposes of passed in class members and set them to undefined. This is not possible in JS or Typescript as far as I can see.

private DisposeOrbitalControls(orbitControls: OrbitControls | undefined): void {
    if (orbitControls !== undefined) {
      orbitControls.dispose();
      orbitControls = undefined
    }
}

this.DisposeOrbitalControls(this.orbitControlsOrtho)
this.DisposeOrbitalControls(this.orbitControlsPerspective)
//this.orbitControlsOrtho and this.orbitControlsPerspective are not set to undefined
Enter fullscreen mode Exit fullscreen mode
Collapse
fiedlr profile image
Adam Fiedler • Edited

I think it's just a matter of terminology, really. In JS, if you think of passing an object to a function f as passing a reference by value to f, it is consistent with both your "counterexamples" (if I haven't overlooked anything) and what others claim (that JS just passes by value). You can change the outside object inside, but the formal parameter is not just substituted by the input variable.

The situation is supposed to be analogous in Java and well explained here: stackoverflow.com/questions/40480/..., although I'm no expert.

The reason why people from the C++ community can be so adamant in this regard is exactly because passing by reference does not work there this way.

Sometimes lines are blurry and there are many examples of that. Take weakly/strongly typed language debate.
To sum up: you've brought up some good points but I wouldn't make such a fuss about it.

Collapse
bytebodger profile image
Adam Nathaniel Davis Author

Hahaha, well... I tend to only "make a fuss" about things as a reaction to others continually making a fuss to me over a given issue. In this particular case, I was basically reacting to the fact that I've had multiple people try to shout me down on this over the last year. And their obstinance on this matter is, to me at least, rather baffling.

But I do agree with your points. I've finally come to realize that much of the Holy-War-ish fighting over this comes down to me standing on the north side of the mountain and insisting that it's "southerly", while others are standing on the south side of the mountain and insisting that it's "northerly". When I finally started analyzing all of their (adamant) arguments, I realized that much of this comes down to perspective. And... nomenclature.

The only reason why I care about this issue at all (other than it being good fodder for a few articles), is that, IMHO, the terminology, used by some of those who want to scream that JS only passes-by-value, is downright harmful to the understanding of new devs.

When you show someone two different variables, and setting those variables has completely different side effects, and then you get up on your soapbox about "JS only passes by value!!!" Well... it is, at best, extremely disorienting.

Collapse
devdufutur profile image
Rudy Nappée

Dude you completely cut my post and missed my point :(

Tldr I just pointed JS variables behave like c++ pointers and not c++ references.

Collapse
bytebodger profile image
Adam Nathaniel Davis Author • Edited

I sincerely apologize for any misrepresentation. I understand that you were, overall, talking about pointers-vs-references. But your last example (which I took verbatim) ends with the conclusion:

It seems parameters are passed by value in JS. Are we agree now ? :)

But... no. We don't agree. For the reasons that I outlined in this follow-on article.

FWIW, I found your response to be logical and well thought out. And I wasn't trying to twist your words or troll you with this article. But it struck me that you gave a putative counter example that was conceptually identical to another putative counter example that was given in the exact same comment section.

This got me to thinking that there are many JS devs who look at examples like the ones you provided, and use those to conclude that JS does not pass by reference.

Collapse
kelicia91 profile image
Kelicia

Thank u for letting me know what I missed. :-)

Some comments have been hidden by the post's author - find out more