When a language passes data by value, a copy of the actual data is made and passed to the function. Any changes made to the parameter inside the function do not affect the original data outside the function. For example, in many programming languages, primitive data types like numbers, strings, and booleans are typically passed by value.
In pass - by - reference, instead of copying the data, a reference to the original data is passed to the function. Any changes made to the parameter inside the function directly affect the original data outside the function. This is commonly used for complex data structures like objects and arrays.
In TypeScript, primitive types such as number
, string
, boolean
, null
, and undefined
are passed by value. Here is an example:
function increment(num: number) {
num = num + 1;
return num;
}
let originalNum = 5;
let newNum = increment(originalNum);
console.log(originalNum); // Output: 5
console.log(newNum); // Output: 6
In this example, the increment
function receives a copy of the originalNum
. So, when we modify num
inside the function, the originalNum
remains unchanged.
Object types (including arrays, functions, and custom objects) in TypeScript are passed by reference. Consider the following example:
function addElement(arr: number[]) {
arr.push(4);
return arr;
}
let originalArray = [1, 2, 3];
let newArray = addElement(originalArray);
console.log(originalArray); // Output: [1, 2, 3, 4]
console.log(newArray); // Output: [1, 2, 3, 4]
Here, the addElement
function receives a reference to the originalArray
. So, when we push a new element into arr
inside the function, the originalArray
is also modified.
When working with primitive types, you can use functions to perform operations on them without worrying about accidentally modifying the original data. For example:
function multiplyByTwo(num: number): number {
return num * 2;
}
let myNumber = 10;
let result = multiplyByTwo(myNumber);
console.log(result); // Output: 20
When working with object types, you need to be aware of the reference behavior. If you want to keep the original object unchanged, you can create a copy before passing it to a function.
function modifyObject(obj: { name: string }) {
obj.name = 'New Name';
return obj;
}
let originalObj = { name: 'Old Name' };
let copiedObj = { ...originalObj };
let modifiedObj = modifyObject(copiedObj);
console.log(originalObj.name); // Output: Old Name
console.log(modifiedObj.name); // Output: New Name
When passing object types, it’s common to modify the parameters inside the function. However, this can lead to unexpected behavior if not handled carefully. For example:
function removeLastElement(arr: number[]) {
arr.pop();
return arr;
}
let myArray = [1, 2, 3];
let modifiedArray = removeLastElement(myArray);
console.log(myArray); // Output: [1, 2]
To avoid unintended side - effects, you can use techniques like returning new objects instead of modifying the original ones.
function removeLastElementWithoutSideEffect(arr: number[]): number[] {
return arr.slice(0, arr.length - 1);
}
let anotherArray = [1, 2, 3];
let newAnotherArray = removeLastElementWithoutSideEffect(anotherArray);
console.log(anotherArray); // Output: [1, 2, 3]
console.log(newAnotherArray); // Output: [1, 2]
Using immutable data structures can make your code more predictable and easier to debug. For example, instead of modifying an array in - place, you can create a new array with the desired changes.
function addElementImmutably(arr: number[], element: number): number[] {
return [...arr, element];
}
let immutableArray = [1, 2, 3];
let newImmutableArray = addElementImmutably(immutableArray, 4);
console.log(immutableArray); // Output: [1, 2, 3]
console.log(newImmutableArray); // Output: [1, 2, 3, 4]
When you need to pass an object to a function without modifying the original, make sure to create a proper copy. For simple objects, you can use the spread operator (...
). For more complex objects, you may need to use a deep - copy function.
function deepCopy(obj: any): any {
return JSON.parse(JSON.stringify(obj));
}
let complexObj = { a: 1, b: { c: 2 } };
let copiedComplexObj = deepCopy(complexObj);
copiedComplexObj.b.c = 3;
console.log(complexObj.b.c); // Output: 2
console.log(copiedComplexObj.b.c); // Output: 3
In TypeScript, primitive types are passed by value, while object types are passed by reference. Understanding this difference is essential for writing correct and efficient code. By following the common and best practices discussed in this blog, such as using immutable data structures and making proper copies of objects, you can avoid many common pitfalls and make your code more reliable.