-2

In JavaScript (or TypeScript), objects are passed by reference, unlike primitives which are copied in the function. Therefore, isn't it the case that this:

sum(one: number, two: number): number {
    return one + two;
}

is always less efficient than this?

sum(input: { one: number, two: number}): number {
    return input.one + input.two;
}

In the first function, 'one' and 'two' are made copies of. In the second function, 'one' and 'two' are simply references to the original 'one' and 'two' encased in an object, so no copies will be made, which saves computation right?

Of course, if I would need to manipulate the values of 'one' and 'two' and wouldn't want the changes to persist, I would use the second function.

Therefore, shouldn't it always be an advised best practice for JavaScript developers to encase their function parameter primitives in an object (given that they don't want manipulations to persist)?

CoderApprentice
  • 425
  • 1
  • 6
  • 21
  • 1
    *"...unlike literals which are copied in the function..."* *primitives*, not *literals*. Objects have literals too. – T.J. Crowder Mar 06 '20 at 14:20
  • 1
    In your second one, you make a copy of one & two, and then place inside an object. IOW: both copy the number.. In theory your first is likely to be faster, but Javascript has such good support for object types the difference is unlikely to cause you any issues. – Keith Mar 06 '20 at 14:20
  • Don't even worry about optimisations at this level. Just assume that the engine is able to sufficiently optimise something as basic and common as *passing values to functions.* Don't try to outsmart Javascript. – deceze Mar 06 '20 at 14:21
  • 2
    Why would `sum(one, two)` produce copies but `sum({one, two})` not? Surely you'd get the *value* of `one` and `two` using the same mechanism - so if the first one produces copies (*and that is somehow a performance bottleneck!?*) then the second one would *surely* also produce copies *as well as* an object wrapper around them. Thus the second one would require *more* memory. With that said, this seems like one of the absolutely least significant attempts of micro-optimisations I've seen. Can you really show a real world example where performance is measurably impacted by parameter passing? – VLAZ Mar 06 '20 at 14:25
  • "Premature optimization is the root of all evil", this applies here very well. – ASDFGerte Mar 06 '20 at 14:32
  • @VLAZ Well because I thought if you make an object, you make two pointers 'one' and 'two' that point to the instances of 'one' and 'two' in memory. But apparently, if you add a primitive to an object, that means a copy of the primitive is added instead so you now have two instances of that primitive? – CoderApprentice Mar 06 '20 at 14:35
  • 1
    @ASDFGerte [Handy flowchart to help with deciding whether to prematurely optimise](https://xkcd.com/1691/) – VLAZ Mar 06 '20 at 14:37

1 Answers1

6

Therefore, it should be the case that this:

sum(one: number, two: number): number {
    return one + two;
}

is always less efficient than this:

sum(input: { one: number, two: number}): number {
    return input.one + input.two;
}

That doesn't follow.

What's passed to a function are the values of its arguments. There are a couple of different ways to interpret "value" in this context in computer science, so I'll use the really, really pragmatic one: The bits that go in a variable, on the stack, etc. at runtime.

Values are extremely efficient to pass to functions. You push them on the stack, and the function pops them off the stack. So

sum(1, 2);

does this:

  • Pushes 1 on the stack.
  • Pushes 2 on the stack.
  • Calls the function, which
    • pops the values off the stack
    • adds them together
    • pushes the return value on the stack
    • returns

(Handwaving away some details!)

In your second example, to call sum, you have to create an object:

sum({one: 1, two: 2});

so it does this:

  • Allocates memory for the object.
  • Creates a property, with slots to remember the name "one" and the value for it; puts the value 1 in the value slot.
  • Creates a second property, with slots to remember the name "two" and the value for it; puts the value 2 in the value slot.
  • Pushes that object's reference (a value saying, roughly, where it is in memory) on the stack.
  • Calls the function, which:
    • pops the object reference off the stack.
    • looks up the property "one" in the object and puts its value in a local variable (which, amusingly, will probably be on the stack).
    • looks up the property "two" in the object and puts its value in a local variable.
    • adds them together
    • pushes the result on the stack
    • returns

So you've saved one push/pop, but at the cost of allocating an object and filling in two properties on it, then looking up those values later; all of which is more expensive than a stack operation.

Now, in JavaScript, we create an release objects a lot, so engines are very good at it. If you have a function where it makes the most sense to pass it an object, by all means do that. (For instance: If the function needs more than three pieces of information in order to do its work, it's often a better developer experience to have them pass in an object with named properties rather than having them remember the order of parameters, although of course IDEs help).

Community
  • 1
  • 1
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875