DEV Community

Discussion on: A "Gotcha" of JavaScript's Pass-by-Reference

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 • 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
 
bytebodger profile image
Adam Nathaniel Davis

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
 
michael_gaddis_538bd6aaff profile image
Michael Gaddis • Edited

If C++ prevents reassignment of a reference to another reference through strong typing enforcement, (been decades since I programmed in C++ so those memories are lost, but a brief survey of the web would indicate that references are static once created in C++.) Maybe that difference is what is confusing to C++ folks about JS reference passing and maybe would get at the heart of why they are stuck on the idea that JS doesn't pass by reference. The difference is in strong versus loose typing not passing by reference. Does that make any sense?

If you CAN destroy a reference and reassign it to another object in C++ then an example that shows it's the same would be equally enlightening.

Thread Thread
 
bytebodger profile image
Adam Nathaniel Davis

Yes, this does make sense. I do think that the loose/inferred/runtime typing is what twists everyone's heads in knots.

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