There are two takeaways here: initialize assets and reuse them, and if you want to play event-triggered sounds that could overlap (laser firing, for example), you'll want multiple copies of the asset available.
I learned pretty early to initialize static assets once and then reuse them. The original implementation was adding a new asset for each game object. This meant every laser fired would add another sound to the DOM. As you can imagine, this allows you to figure out the upper limit on the quantity of elements in the DOM pretty quickly.
The solution was to not tie the sounds directly to objects, but rather to create a pool of sounds, then cycle through the pool when a sound was triggered.
Here is the implementation:
init() will get called by the loader script, and will loop on the keys of the tracks object, creating the sounds and adding them to the DOM.
To play the sound, we just call
playSound() with the type matching the key we want.
playSound() will call
getSound(), which will use mod to set the target index to the next index, or
0 if previous index was the end of the array. Then the sound element at that index will get returned and played.