Doing it via events, I'd think of adding an array per instance, with strings that then call mapped functions per string. If a string doesn't exist, nothing is called or acted upon, so no further overhead.
That does seem to be the only way. In vanilla c3, there are 2 concerns for a project with high object count:
1.) The simple act of calling a Function has overhead. It does in any programming context, but the overhead in construct is significant at scale, (a function call that simply sets a variable from a parameter will take 3-5x longer than simply running that event outside of a function. Since you can't call a mapped function without a router function, (if you want to use parameters), every dynamic function calls becomes 2 function calls (6x-10x) the time of simply running the simple event, requires a way to manage tokens, etc... So the net result is that dynamic function routing when used liberally comes at a huge cost.
2.) Managing per instance arrays, or dictionaries requires an amount of repetitive boiler plate every time you use it. For each object, pick array by uid, for each element, function call .... This is itself a perfect thing to abstract away, and you could use functions to do it, but now you have functions inside of foreach loops. The performance loss of foreach loops is sizable when object counts are high, as it duplicates the overhead of simply running a blank event. So things get messy where you prefer the for each loop inside of the function and not the other way around.
The solution is as you say, put as much of the nec looping in javascript and eliminate all that you can editor side. Overboy's UID to anything is helpful, but it can only go straight to common expressions (like variables, position, etc, and can't hook into a behavior. I often create simple behaviors simply to hold data, so that iterating with that data, etc can happen in in the behavior instead of events. You can also create getters and setters that run at a tiny fraction the cost of doing that with a eventsheet function. Overboy's Data+ essentially does that as well and is a fine tool - much better than authoring it yourself.
Overboy's Advanced signals, also function like the old c2 functions, so you don't need to call two functions to route a signal. They also function per instance in lovely fashion, allow for polymorphism with out setting up boilerplate code. And he has custom expressions. Working in c3 with Overboy's tools makes it much more scalable and easy to work with. But, if performance is still the issue, Js is probably the only solution, either plugin sdk or the editor side JS.
I have a post about it, but the takeaway is that using editor js has a lot of restrictions in how you use it, or you might end up with worse performance than not using it at all. For example, I see many people say calling a function from js is easier, than using mapped functions. I agree. But the overhead of a JS block is huge compared to basic actions or c3 function calls. Like 300x worse. Again, this isn't a problem if your JS contains more than one line of code. But putting JS blocks in any kind of loop destroys any performance gained from the logic being JS instead of ACES.
Basically, every ACE has an overhead. But a JS block does too, just way more. So in a way it's much like the function. If the JS block is eliminating the need for editor side loops via event sheets, then you are winning, but if you are using JS more like fancy functions to wrap utilities in, you are probably going to have worse performance. Behaviors on the other hand, for whatever reason, do not invoke the same expensive overhead when they are called. Ashley seemingly brushed off the concern with the usual, "but no real game..." He isn't wrong, so long as the valid selection of real games are only those that aren't object heavy in the way a vampire survivor game is. He also isn't particularly wrong to be unconcerned about it, as its not a bottleneck many users will face. But imo, using eventside js, is worse than behavior side, though it is easier to author.