- Reduce Closures - All registration is done with a closure function. Instead, create an entry instance that captures the data and understands explicitly instance, singleton and custom. This will use a switch case to determine the registration strategy. Instance and singleton instances will then be stored on a state property of the container. By doing this we can drastically reduce the number of closures involved in registration.
- Child Container Efficiency - We may be able to re-implement child containers by using object inheritance. The idea is that when we create a child container, we simply create a new instance with all the same registrations, but we create a new state object that inherits from the original. When the child container has a service registered with it, we ensure that the corresponding property is set to null on the state object. This allows us to use the native JS prototype chain lookup. We need to be sure to keep a reference to the root state so that if the lookup fails across the hierarchy we can ensure that the state is pushed to the top level.
- Remove Subscription Arrays - Internally, all observers use arrays of subscribers. Creating arrays and pushing/lookup with arrays is both a performance and memory issue. Used a field strategy to store subscribers. Add fields for subscriber1, subscriber2 and subscriber3. Store the callback in these fields. Check them when adding and invoking. In order to support n number of subscribers, after 3 is reached, fallback to an array. This would result int he most common cases of observers being handled without needing to create arrays or puse/lookup in arrays.
- Switch Callbacks to Callables for Observers - Invoke all "callbacks" using
call(context)instead. This allows us to pass objects to the observables that implement acallmethod instead of using a closure. Since we already have existing binding instance objects that could serve this purpose, this will reduce the number of closures dramatically. If a binding instance needs to subscribe to different observers, such as in the case of two-way binding, it can pass a context object to the observer which will be passed to the call method allowing the binding expression to differentiate between them. This design would continue to support subscribing with functions, but all internal subscriptions should be done with callables in order to reduce the number of closures needed to set up bindings. Note that this affects the "Remove Subscription Arrays" implementation as it will also need to store context1, context2, context3 and a potential array of contexts for fallback. It may be possible to implement this with a mixin to reduce repetitive code. (It should not be done with inheritance for perf reasons though.) - Do Not Return Unsubscribe Functions - When subscribing to an observable, we return a function that can be used to unsubscribe. This creates another closure. We should not return anything, but instead add an unsubscribe method to our observable implementation. Passing the original callable should enable it to be unsubscribed.
- AST Connect Should Notify the Binding Expression - Currently each AST creates a chain of observables. This creates lots of intermediary classes for combining observables and then creates lots of closures. Instead, the binding should be passed through each node of the AST. The node should then treat the binding as a callable object and all observers should point to it, using the AST node as the context. Then, whenever part of the AST changes, the binding will be called back and passed the node and the new value. This can be used to update the target as well as re-connext the AST based on the changed value.
- Unify O.o and Setter Handlers - Could we pass the same callback for all calls to O.o and then dispatch from there to the correct object, thus reducing closures further?
- Integrate DirtyChecking with O.o and Setters - If we can have a single handler for O.o and Setters, then after all observed changes are dispatched, we should run the dirty checking. This allows us to more quickly update dirty checked values, based on the assumption that most of the time they are based on computed observed types. When this is done, it should push out the polling farther. Also, we may be able to poll at a slower rate if we implement this.
- Template Caching - Allow any view or dynamic template to specify that it should cache instances. After a view is done with, it can then be returned back to the view factory. If caching is enabled, the factory will keep the view instance around and re-provision it the next time that it is asked to create a view. Caching can be on, off or set to a size.
- Preconfigured Child Containers - When we compile an element that contains behaviors, we currently set up a container id so that we can later on connect the DI hierarchy. Could we also pre-configure a DI container with the behaviors already registered, then simply copy it and position it within the hierarchy via the new state mechanism above?
Ultimately, each library should result in a single JS file. Our build process should be updated so that the individual modules have their relative path imports stripped. The modules should then be concatenated and the non-relative imports should be placed on the top of the file. This will like require some gulp and Regex foo. The end result should then be compiled with Babel and the plugin or TS compiler should be used to generate the d.ts file. This procedure should not be done with any library that contains view resources, mainly the templating-resource library.