Enums in TypeScript: what is the JavaScript code d

2020-02-12 03:13发布

问题:

The following TypeScript:

enum PrimaryColors { Red, Green, Blue };

Produces the following JavaScript:

var PrimaryColors;
(function (PrimaryColors) {
    PrimaryColors[PrimaryColors["Red"] = 0] = "Red";
    PrimaryColors[PrimaryColors["Green"] = 1] = "Green";
    PrimaryColors[PrimaryColors["Blue"] = 2] = "Blue";
})(PrimaryColors || (PrimaryColors = {}));
;

I am embarrassed to admit that I don't understand what the JavaScript is doing.
The function in parentheses is assigning string values using another assignment as the index/key. I have not seen anything like this before.
And what is the purpose of the (PrimaryColors || (PrimaryColors = {}) following the function?
If the answer is to learn JavaScript properly, I will readily accept it, provided it comes with a suggested source that clearly explains what I am seeing here.

回答1:

I believe:

PrimaryColors[PrimaryColors["Red"] = 0] = "Red";

is equivalent to:

PrimaryColors[0] = "Red";
PrimaryColors["Red"] = 0;

See this reference.

The expression x = 7 is an example of the first type. This expression uses the = operator to assign the value seven to the variable x. The expression itself evaluates to seven.

For example:

console.log((x = 7));

outputs:

7

Similarly:

var x = {};
console.log((x["hi"] = 7));

Also outputs 7.


As for the second thing, PrimaryColors is initially undefined.

var x;
console.log(x); // undefined

In a boolean context, undefined evaluates to false:

console.log(!undefined); // true
console.log(!!undefined); // false

Sanity check:

console.log((!undefined) === true); // true
console.log((!!undefined) === false); // true
console.log(undefined === false); // false

This is a common usage of short circuiting. Because PrimaryColors is initially undefined (false), it will pass {} to the function.

PrimaryColors || (PrimaryColors = {})


回答2:

Maybe this will help.

(function() {})();

This is an 'immediately executing function'. It defines a function as an expression, and then invokes it.

var x = y || y = {};

If a common pattern for initializing something to a default value. If y does not have a value, the 1st part of the or-statement is false, so it executes the 2nd part, which assigns a value to y. The value of that 2nd expression is the new value of y. So x becomes that value of y -- which is the new value if it wasn't already defined.

x[y] = z;

Objects in JS are associative arrays. In other words, string-object pairs, like IDictionary(string,object). This expression is setting the key with value y to the value of z, in the dictionary x;

x[x["a"] = 0] = "a";

So, same thing here, but with a nested expression, which is:

x["a"] = 0;

So that just sets the value of key "a". Nothing fancy. But this is also an expression, whose value is 0. So substitute that in the original expression:

x[0] = "a";

Keys need to be strings, so it's actually the same thing as:

x["0"] = "a";

Which just sets yet another key in the dictionary. Result is that these statements are true:

x["0"] === "a";
x["a"] === 0;


回答3:

I found this question because I was wondering why use an IIFE at all when you can just init the var with {} right off. The previous answers don’t cover it, but I’ve found my answer in the TypeScript Deep Dive.

The thing is, enums can be split into multiple files. You just have to explicitly initialize the first member of second, third, etc. enums, so this:

enum Colors {
    Red,
    Green,
    Blue
}

enum Colors {
    Cyan = 3,
    Magenta,
    Lime
}

transpiles to this:

var Colors;
(function (Colors) {
    Colors[Colors["Red"] = 0] = "Red";
    Colors[Colors["Green"] = 1] = "Green";
    Colors[Colors["Blue"] = 2] = "Blue";
})(Colors || (Colors = {}));
var Colors;
(function (Colors) {
    Colors[Colors["Cyan"] = 3] = "Cyan";
    Colors[Colors["Magenta"] = 4] = "Magenta";
    Colors[Colors["Lime"] = 5] = "Lime";
})(Colors || (Colors = {}));

As you probably know, redeclaring a variable within the same scope is harmless, but reinitialization is not.

I think they could probably just go:

var Colors;
Colors || (Colors = {});
Colors[Colors["Cyan"] = 3] = "Cyan";
// ...

and skip the closure, but maybe I’m still missing something.



回答4:

It is used to create an associated map (in other words an object) where you will retrieve the 'name' of the enum value by using the index as key and vice versa. In other words: PrimaryColors["Red"] (or PrimaryColors.Red using dot notation) will yield 0. PrimaryColors[0] (dot notation would be invalid here) will yield "Red".

Understanding the implementation is actually not that hard if we consider three concepts:

  1. The assignment of values to existing variables in javascript evaluates to a value (so it's an expression rather than a statement in spirit)
  2. Object attributes (keys) can be accessed via brackets given their key
  3. Object attributes need to be of type string or Symbol but other values will be propagated to a string if possible.

Therefore:

PrimaryColors[PrimaryColors["Red"] = 0] = "Red";

is equivalent to

const valueToBeUsedAsIndex = PrimaryColors.Red = 0; // assignment evaluates to 0, i. e. valueToBeUsedAsIndex has value 0
PrimaryColors[valueToBeUsedAsIndex] = "Red"; // PrimaryColors[0] is "Red". Technically this assignment yields a value too ("Red" in this particular case) but the value is discarded as it's not needed anymore
// at this point PrimaryColors looks like this: { Red: 0, "0": "Red" }