Search code examples
typescriptloopsenumsiteration

Why is iterating enums so counter intuitive in typescript?



enum Direction {
    Down = -1,
    Up = 1
}

let i = 1;
for (const direction of Object.values(Direction)) {
    console.log('iteration ' + i++);
    console.log(direction);
}

iteration 1

Up

iteration 2

-1

iteration 3

Down

iteration 4

1

I was expecting:

iteration 1

-1

iteration 2

1

Generated js:

var Direction;
(function (Direction) {
    Direction[Direction["Down"] = -1] = "Down";
    Direction[Direction["Up"] = 1] = "Up";
})(Direction || (Direction = {}));
var i = 1;
for (var _i = 0, _a = Object.values(Direction); _i < _a.length; _i++) {
    var direction = _a[_i];
    console.log('iteration ' + i++);
    console.log(direction);
}

I'm sure there's a workaround but I really just want to know what causes this to go against every single expectation I had


Solution

  • Just take a look at the generated JavaScript output:

    var Direction;
    (function (Direction) {
        Direction[Direction["Down"] = -1] = "Down";
        Direction[Direction["Up"] = 1] = "Up";
    })(Direction || (Direction = {}));
    

    We're only going to focus on the two lines inside:

    Direction[Direction["Down"] = -1] = "Down";
    Direction[Direction["Up"] = 1] = "Up";
    

    Assignments are really just expressions that "return" the new value assigned to the target. So, after we assign -1 to Down and 1 to Up, we assign "Down" to -1 and "Up" to 1, which gives us a reverse mapping that allows you to look up the name of an enum member:

    console.log(Direction[Direction.Down]); // "Down"
    

    Basically, your enum looks like this at runtime:

    {
      "1": "Up",
      "Down": -1,
      "-1": "Down",
      "Up": 1
    }
    

    However, this behavior is only seen in numeric enums. For example, a string enum like this will not produce a reverse mapping:

    enum Direction {
        Down = "down",
        Up = "up",
    }
    

    As for the fix, you can check if the value is a number or not:

    let i = 1;
    for (const direction of Object.values(Direction)) {
        if (typeof direction !== "number") continue;
    
        console.log('iteration ' + i++);
        console.log(direction);
    }
    

    Playground