It's common practice to mutate the parameters in JS. But there are some good reasons not do to. And ways how to do it better.
Javascript passes object to a function by Call by sharing. Can be said to pass by copy of reference.
JavaScript does not Call By Reference. However, JavaScript does not make copies of objects when they are passed or assigned. As such, it is the same object with a different name - changes made to the object (from any name) affect said object.
The outside object is changed by a function.
const object = {
a: "hello"
}
function modify(o) {
o.a += " world"
}
modify(object)
console.log(object.a)
//prints "hello world"
Run code: https://repl.it/@replmat/parameter-changes-visible-outside
Not that the object is not modified by +=
like in the example above but fully re-assigned with a new value. The reason is, that the copy of a reference is another name for the reference. That name is kinda "hi-jacked" and assigned to a new value.
var obj = {
a: "hello"
};
function modify(o) {
o = {
a: "hello world"
};
}
modify(obj);
console.log(obj.a); //prints just "hello"
Run code: https://repl.it/@replmat/parameter-hi-jacked
Functions modify objects and objects are passed to the function as parameter.
Thesis: Functions use and modify objects. If you are passing an object to a function, then you would expect that the function does something with the object and might change it. So it is intentional, that the outside object gets modified. That the programm intentionally changes the object might become more visible, if setters are used. In TypeScript it's not always easy to identiyfy setters. See some examples below.
class Person {
name?: string
age?: number
_weight?: number
constructor(anyObject?: any) {
if (anyObject) {
Object.assign(this, anyObject)
}
}
// setter in flavor a
set weight(weight: any) {
this._weight = weight
}
// setter in flavor b
setWeight(weight: any) {
this._weight = weight
}
}
const paul: Person = new Person({name: "paul"})
console.log(paul) // Person { name: 'paul' }
//
// mutate parameter/ object directly
//
function evaluateAge(person: Person){
person.age = 5
}
evaluateAge(paul)
console.log(paul) // Person { name: 'paul', age: 5 }
//
// change object with setter a
//
function calculateWeight(person: Person){
// calculate something and finally set the weight
person.weight = 80
}
calculateWeight(paul)
console.log(paul) // Person { name: 'paul', age: 5, _weight: 80 }
//
// change object with setter b
//
function updateWeight(person: Person, kilos: number){
person.setWeight(kilos)
}
updateWeight(paul, 70)
console.log(paul) // Person { name: 'paul', age: 5, _weight: 70 }
Run code: https://repl.it/@replmat/Object-setter About setters and getter in Typescript see here: Documenation Classes
Functional-style programming promotes the idea that a program is about transforming data through functions, and that mutation should be avoided
Pure Functions
If you'd like functions to be pure, then do not change the value of the input or any data that exists outside the function's scope.
This makes the function we write much easier to test. As it does not change the state of any variable, we are guaranteed to get the same output every time we run the function with the same input.
function foo (_bar) {
var bar = _.clone(_bar) // lodash lib
bar.k1 = "bananas"
return bar
}
https://stackoverflow.com/questions/28792326/functional-style-javascript-good-practice-to-avoid-argument-mutation https://stackabuse.com/functional-programming-in-python/
- decide what your objects and program stats are and put them into the store
- use strickt in order to get warnings if mutatins happens outside the mutations.js
- if you need to transform a vuex object, then use
_.cloneDeep()
from lodash or similar expressions to make a real copy of your object before you modify it. A typical use case would be: the object should get stored in the database and the properties need mappig.
Options
- always use values
- always use objects
- always use values but objects if the parameters are optional
- use "class" objects if paramters get complex
Pro values
- paramaters don't need to be named
greet(name) {
console.log("Hello ", name)
}
greet("Paul")
- you can rename the parameters, depending of the context
function markdownToJson(markdown) { /* implementation */ }
const message = "I **love** you"
markdownToJson(message)
- very common approach
- low overhead
Pro objects
- function call names parameter explicitly. This can bring clearity
- order of parameters doesn't matter
- if parameter list gets too long
function getRegisteredUsers ({ fields, include, fromDate, toDate }) { /* implementation */ }
getRegisteredUsers({
fields: ['firstName', 'lastName', 'email'],
include: ['invitedUsers'],
fromDate: '2016-09-26',
toDate: '2016-12-13'
})