Search code examples
javascriptsplice

Why doesn't decrementing an argument in a function invocation work in javascript?


In a JavaScript project I was looping through a list and sometimes removing the current element with splice(). Now after that obviously the iterating variable need be decremented to not miss an element. Looks like this:

for (var i = 0; i < list.length; ++i) { 
  if (...){
    list.splice(i,1); 
    --i
  }
}

Now, coming from Java, I figured I'd save a line by decrementing the variable after passing it as an argument like this:

for (var i = 0; i < list.length; ++i) { 
  if (...)
    list.splice(i--,1)
}

The answers show that it should work. The issue happened using paper.js - I opened an issue on github for that. It seems that paper.js was decrementing i before passing its value to splice() resulting in an actual call of splice(-1,1). With an index of -1, splice() would remove the list's last item. On the next loop iteration, i would be incemented back to 0, the check on list[0] would still evaluate to true as the element is still there, the same call splice(-1,1) would happen deleting the last element, and so on.

See the broken behavior here: Tapping keys creates circles which shrink and, when below area 1, are removed from canvas and the array holding them. The issue happens when splice() gets called in line 17.

Quite curious, that issue - thanks for the help!


Solution

  • No difference between two ways (decrementing i argument on splice call or in the next line) because you are using a post-decrement operator.

    By post-decrement I mean when the -- operator is used as a postfix (i.e. i--): in this case it returns the current value before decrementing. And, that's why both alternatives used in your questions are equivalents.

    Regarding to type of i variable, you can check with typeof operator. In this case is a number object, which belongs to the scalar primitive group (Number, String, Boolean, undefined, null, Symbol). All primitive values are assigned by value. Only compound values in javascript are assigned by reference (Object, Array).

    You can read more about javascript arithmetic operators.

    Check the outputs for the two cases:

    function test(list, condition) {
        const list1 = [...list];
        for (var i = 0; i < list1.length; i++) {
          if (condition(list1[i], i)) {
            list1.splice(i--, 1);
          }
        }
        console.log('output using decrement on splice arg', list1);
        
        const list2 = [...list];
        for (var i = 0; i < list2.length; i++) {
          if (condition(list2[i], i)) {
            list2.splice(i, 1);
            i--;
          }
        }
        console.log('output decrementing after splice', list2);
    }
    
    const list = [1, 2, 3, 4];
    
    test(list, (elem, index) => elem % 2 === 0);
    // -> [1, 3]
    // -> [1, 3]
    
    test(list, (elem, index) => index % 2 !== 0);
    // -> [1]
    // -> [1]


    The problem I see with your code is with the if condition inside the loop. If your test includes the index in some way that could cause you to remove extra elements (not intended in the first place).

    Second example, using use a condition based on the element indexes, and exemplifies a possible situation when we try to remove all elements on odd positions. This happens because on the second iteration (i=1) the tests evaluates to true and the decrement of i-- steps back to zero (i=0). Then the for increment acts and again we are testing the previous scenario: i=1.

    Previous behavior remains the same whether you decrement the iteration variable inline in the splice call or after.