Is JavaScript Pass-By-Reference or Pass-By-Value?

Note: throughout this document, the terms ‘references’ and ‘points [to]’ are used interchangeably.

The terms “Pass-By-Reference” and “Pass-By-Value” are regularly thrown around Stack Overflow; yet, many of these discussions are crippled by confusion. Before we can clear up this confusion, we must understand what these terms actually mean. Pass-By-Reference and Pass-By-Value are types of evaluation strategies. Evaluation strategies are the rules that a programming language uses to govern how arguments are passed into functions: do the parameters by which objects and primitives are passed into functions hold copies of those objects and primitives? Or do they hold references? Different languages handle argument passing differently. This clarification concerns only JavaScript’s evaluation strategy.

To understand JavaScript’s evaluation strategy, it’s useful to first understand Pass-By-Value and Pass-By-Reference.

The Pass-By-Value Evaluation Strategy

In this strategy, the value of the arguments that are passed into the functions are copies of the values of the objects and primitives passed by the invocation of the function. The changes made to the parameters inside the function do not influence the objects and primitives that correspond to the arguments. Generally, memory is allocated to copy the values of the external objects and primitives, and this memory allocation is used only within the function; it does not affect the objects and primitives outside the function.

Here is an example of what would happen if JavaScript adhered to a pure Pass-By-Value strategy:

If you run this code in repl.it, you’ll see that console.log(obj1.item) evaluates to ‘changed’, which is different from what we expect with Pass-By-Value. Clearly, then, JavaScript does not adhere to a purely Pass-By-Value evaluation strategy.

The Pass-By-Reference Evaluation Strategy

When this strategy is in effect, the function does not receive a value copy, but, in fact, receives direct pointers to the objects and primitives that exist outside the function. If the objects or primitives which are passed into the function as arguments are changed within the function, then these changes affect the objects and primitives that exist outside the function. This occurs because the arguments that the function invocation receives are direct pointers to the objects and primitives outside the function (as opposed to copies of those objects and primitives). We can think of the arguments as aliases for the objects and primitives that are passed in from the outside.

So, to take the same example — if JavaScript were strictly Pass-By-Reference for both objects and primitives:

Check out the gist for a deeper explanation.

Again, running this code in repl.it yields console.log’s of 10, ‘changed’, ‘unchanged’ in that order. Clearly, then, JavaScript does not adhere to a pure Pass-By-Reference evaluation strategy.

So what’s up with JavaScript?

JavaScript adheres to an evaluation strategy that is referred to as ‘Call-By-Sharing’ (this term is not widely used).

Call-By-Sharing

With Call-By-Sharing (aka Call-By-Object or Call-By-Object-Sharing), which is the official evaluation strategy of ECMAScript, the function receives a copy of the reference (i.e. a pointer) to the passed in object. (Aside: Call-By-Sharing is an evaluation strategy first named by Barbara Liskov for the language CLU in 1974. It is used by languages such as Python, Iota, Java (for object references), Ruby, JavaScript, Scheme, OCaml, AppleScript, and many others; source). In other words, the value of the argument is not the direct alias, but the copy of the address.

The Call-By-Sharing strategy primarily differs from the Pass-By-Value and Pass-By-Reference strategies in that the assignment of a new value to an object argument passed to the function does not affect the corresponding object that exists outside the function (as it would with the Pass-By-Reference evaluation strategy; recall that we already know that when we pass in primitive arguments to function calls, those function calls create a copy of that argument and so whatever happens within the function does not affect the corresponding primitive value outside the function). Instead of receiving a direct pointer (Pass-By-Reference) or an exact copy (Pass-By-Value) of the object, the function receives a copy of a reference to the passed in object.

This is the key insight. Here’s how it works:

BUT, if we change b not by reassignment but by mutation (i.e. changing the properties, that change does affect a whether we are in a function or outside a function).

In other words, changes to the properties of the local object that corresponds to the argument passed to the function are in fact reflected in the external object. As we already know, changes to primitives are not reflected because primitives are passed by value in JavaScript; passed primitives create copies (new allocations of memory).

To receive a quick refresher on the difference between object and primitive assignments, check out this gist.

So to take the same example in the context of JavaScript’s Call-By-Sharing evaluation strategy:

obj1 changes because we mutate its property. obj2 does not change because we assign it to a new object. num does not change because the function receives a copy of the value stored in num. To be completely clear: if JavaScript adhered to a pure Pass-By-Value strategy, the operations/mutations/reassignments within the changeStuff function would have no affect on the objects and primitives that exist outside the function. If JavaScript adhered to a pure Pass-By-Reference strategy, everything would have changed: num would be 100 and both obj1.item and obj2.item would read ‘changed’.

Instead, JavaScript is a bit of both. If you reassign/rebind object arguments, the changes won’t affect the objects that exist outside the function. But if you change the properties of the object arguments, those changes will affect the objects that exist outside the function scope as we saw with obj1.

We can think about Call-By-Sharing with regards to objects in terms of Rebinding vs Mutating.

Rebinding

Rebinding is the process by which a variable that previously pointed to an object now points to another object via reassignment.

Rebinding is often conflated with assignment-by-reference. One might mistakenly believe that after assigning {x: 20} to the foo variable in the example above, the bar variable would also point to the new object. However, as we see if we repl this code, the bar variable still points/refers to the old object. Only foo is rebound to the new memory block.

MUTATING

In contrast with rebinding, mutating only affects the content of the object, not the connections between the variable pointers and the object itself.

Consider the following example:

When person is passed into mutate, the parameter obj is given a reference to the same object in memory that person references. So now person and obj both point to {name: 'ayana'}. Inside the mutate function, the name is changed to ‘mutated’. The object that person points to has been mutated by the function.

The changeStuff example came from this stack overflow discussion

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.