Who will dispose of a computed created on-the-fly? - javascript

I'm building a SPA using knockout.js. In my viewmodels I often use the "on-the-fly" computeds trick to track changes and maintain state. Here is an example:
var fileBrowserViewModel = function() {
this.currentFolder = ko.observable("/");
this.folderPath = ko.observableArray(["/"]);
// on-the-fly created computed to maintain folderpath when currentFolder changes
ko.computed(function() {
var index = this.folderPath.indexOf(this.currentFolder());
if (index < 0)
this.folderPath.push(this.currentFolder());
else
this.folderPath.splice(index+1, this.folderPath().length-index);
}, this);
}
I have a logic which disposes every publicly exposed computed property of the viewmodel. But what happens with these implicitly declared computeds after the wrapping viewmodel has been released?
How and when exactly are computeds disposed of? Could it cause memory leaks?

How and when exactly are computeds disposed of?
They aren't, in the sense that, their .dispose functions are never called.
Could it cause memory leaks?
Maybe, but it won't always. The reason computed's need to be .dispose'd, is because they subscriptions on their dependencies; which means that each dependency is holding a reference to the computed. The computed will get garbage collected when there are no longer any references to it. In the above example, that will happen when the ViewModel itself is no longer referenced anywhere, so the computed will get garbage collected along with its ViewModel, as intended.
However, if the computed depends on anything outside of the ViewModel that contains it, it will not get garbage collected until all of the things it depends on are themselves ready to be garbage collected. In the worst case, where the computed depends on something global that is never garbage collected, the computed itself will never be garbage collected.
So, the real answer is that you only need to dispose a computed when it has dependencies that will be longer-lived than the computed itself. However, as there's no harm to disposing a computed that would get garbage collected anyways, it's often just easier to make sure all computeds created by ViewModels get disposed; rather than trying to split hairs over which ones need to be disposed.
How to handle computed disposal
In any case, you'll want to have a .dispose method on your viewModel or some equivalent. There's no way some external logic is going to be able to know about the privately created computeds, so the ViewModel itself will need to handle it itself. The simple way to handle this is just to call .dispose on each privately created computed in the VM's .dispose method:
var fileBrowserViewModel = function() {
// on-the-fly created computed to maintain folderpath when currentFolder changes
var folderPathComputed = ko.computed(function() {
//implementation omitted
}, this);
this.dispose = function () {
folderPathComputed.dispose();
}
}
If you want something less manual; you can create a utility to handle the disposal for you e.g.:
function ComputedManager() {
var computedsToDispose = [];
this.computed = function() {
//create a computed normally, with the provided arguments
var computed = ko.computed.apply(ko, arguments);
computedsToDispose.push(computed);
return computed;
}
this.dispose = function() {
computedsToDispose.forEach(function(computed) {
computed.dispose();
});
}
}
var fileBrowserViewModel = function() {
var computedManager = new ComputedManager();
// on-the-fly created computed to maintain folderpath when currentFolder changes
computedManager.computed(function() {
//implementation omitted
}, this);
this.dispose = computedManager.dispose;
}
It's a bit awkward to call computedManager.computed instead of ko.computed all the time, but it's easier than manually cleaning up every computed, and a benefit is that you could create all computeds this way, and then eliminate the need for some other logic to go through and find and dispose all publically exposed computeds.

Related

ko.pureComputed - clear cached value on sleep

I have some ko.pureComputed properties that usually hold a big amount of data inside themselves.
When those ko.pureComputed properties go to sleeping state (noone is subscribe to them) I don't need that data anymore until they go back to listening state (someone is subscribe to them).
During that time while they are in the sleeping state I'd like the ko.pureComputed properties to clear their values so that the garbage collector can remove that computed data from memory, then when I need the computed data again, that is, when the ko.pureComputed go back into listening state, I'd like to reevalute the computed data.
Is that possible?
Further details about my use-case scenario:
My site is a Single Page Application, meaning a Javascript framework (Durandal) switches pages (HTML and JS) in display for the user.
Some pages have a need for computed properties which would store large amount of data. I'd like to use ko.pureComputed for that purpose, because it will stop updating itself once the user goes off its page, i.e. once the ko.pureComputed goes into sleep state because it has no more listeners.
(Durandal deattaches and reattaches the page's JS viewmodel from and into the HTML view when the user goes away or visits the page)
The problem is that the ko.pureComputed keeps its latest value cached.
In my case those values are large arrays of large objects, which take up a noticeable amount of memory. I'd like to dispose of that data once it's not needed anymore.
Is there a way to clear the cached value from the ko.pureComputed once it goes into the sleeping state (when the user leaves the page), and then later reinitialize it when the ko.pureComputed goes back to listening state (when the user revisits the page)?
Using a pure computed's state change events, we can tell the computed to clear its value while it's sleeping. Here's a wrapper function that sets it all up:
function computedValueOnlyWhenActive(readFunction) {
var isAwake = ko.observable(false),
theComputed = ko.pureComputed(function () {
if (isAwake()) {
return readFunction();
}
});
theComputed.subscribe(function() {
isAwake(true);
}, undefined, "awake");
theComputed.subscribe(function() {
isAwake(false);
theComputed.peek(); // force reevaluation
}, undefined, "asleep");
return theComputed;
}
Demo: https://jsfiddle.net/mbest/gttosLzc/
This isn't an answer to the specific question you asked, but it might be a more helpful answer depending on your situation.
In Durandal the router plugin navigates by asynchronously loading the specified module with a requireJS call. Once it retrieves the module it checks if the result is either an object or a function, and if it's a function it will instantiate a new object from the function. If it is an object it just uses the object.
RequireJS automatically caches the modules it retrieves in that it doesn't bother re-fetching a module from the server if it's already downloaded it. So if your module definition is a plain object then that same object will get displayed each time.
This module definition will save its state between navigations:
define(['durandal/app'], function (app) {
var title = 'myView';
var vm = {
title: title;
};
return vm;
});
This module definition will create a new object and will re-bind all knockout bindings resulting in a freshly loaded screen on each navigation.
define(['durandal/app'], function (app) {
var title = 'myView';
var vm = function(){
this.title = title;
};
return vm;
});
EDIT:
For a more granular durandal solution that also works with older versions of knockout (i.e. before pureComputed) you can combine the concept in michael best's answer of using an isAwake observable with durandal's view activation and deactivation lifecycle hooks.
function viewModel(){
var self = this;
this.isAwake = ko.observable(true);
this.theComputed = ko.computed(function () {
if (isAwake()) {
return myValue();
}
return "";
});
this.activate = function(){
self.isAwake(true);
}
this.deactivate = function(){
self.isAwake(false);
}
}
var vm = new viewModel();
return vm; //return the instance not the function
http://durandaljs.com/documentation/Hooking-Lifecycle-Callbacks.html

How can I enforce privacy on State variables in a Flux Store?

I have a small, home-brewed implementation of the Flux pattern that I'm working with, just to get a better understanding of the pattern. Its working quite well, and I'm learning a ton! But I've run into an issue I can't wrap my head around at the moment. Apologies in advance if this has an obvious solution.
Imagine my simple Store called ExampleStore, and my simple Component ExampleComponent. In it there are:
_exampleState
getExampleState()
setExampleState()
in ExampleComponent, to stay updated:
_onChange: function() {
setState({exampleState: ExampleStore.getExampleState()})
}
in ExampleStore, after a dispatched action I use the setter:
setExampleState(newStateFromDispatchedAction);
This works perfectly. The data is flowing as it should. But I have a question, because it seems easy to break the pattern because there is no privacy enforced on my _exampleState within my Store. Since I have a getter and private setter method for _exampleState, it seems natural to me that somehow I want to enforce more privacy on the variable. Is there a nifty way to do this, that I am overlooking?
As it is now, if, for example, inside ExampleComponent I do (which I wouldn't, but I could):
this.state.exampleState = {field:'example'}
//doing this
this.state.exampleState.field = 'example2';
//changes the store _exampleState to {field:'example2'}
I have changed the value of _exampleState within ExampleStore directly, without making use of the setter. This seems dangerous (and makes me question why I'd have a private setter/public getter to begin with). This question comes after dealing with a pesky bug where a library I was using modified the state variable directly, and thereby within the Store.
Is there some good way I'm overlooking to enforce privacy on the state variables in my Store, so that they may not be changed directly through their references in ExampleComponent? Sorry if this is a dumb question and I'm overlooking something simple, thanks for the help!
Be aware that one of the basic principles of the Flux philosophy is that stores should have no (public) setters. That means you should not be able to modify the store's state if not inside the store itself.
One way of enforcing the privacy could be by keeping state variables as private, only letting the store managing them.
EDIT: to "enforce" privacy, you could also return a deep copy of your state, as it is shown in the code.
The following code, based on the official flux GitHub repository's flux-todomvc example, highlights the idea:
var AppDispatcher = require('../dispatcher/AppDispatcher');
var AppConstants = require('../constants/AppConstants');
var EventEmitter = require('events').EventEmitter;
var assign = require('object-assign');
// This is a private state variable that can only be accessed in this file
var _exampleState = {/*...*/};
var ExampleStore = assign({}, EventEmitter.prototype, {
EXAMPLE_STATE_CHANGED: 'EXAMPLE_STATE_CHANGED',
// return a deep copy of your state so there is no way
// to modify the store's state by reference
getExampleState: function() {
return deepCopy(_exampleState);
}
/*...*/
};
// this is a private method (setter)
var _setExampleState = function(newExampleState) {
_exampleState = newExampleState;
};
ExampleStore.dispatchToken = AppDispatcher.register(function(action) {
switch(action.actionType) {
case AppConstants.CHANGE_EXAMPLE_STATE:
_setExampleState(action.newExampleState);
ExampleStore.emit(ExampleStore.EXAMPLE_STATE_CHANGED);
break;
}
});
// the implementation of deepCopy is a developer's choice
// this version of it is very inefficient
var deepCopy = function(obj) {
return JSON.parse(JSON.stringify(obj));
}
module.exports = ExampleStore;
Facebook official examples are a good way to understand how to implement the core Flux concepts.
EDIT: this is a way of "enforcing" privacy of a state variable, but it is discouraged due to the clear loss of efficiency. I guess that the main idea here is that, even though you are able to do so in some situations, changing the store's state through reference is just against Flux. It is important to notice that this enforcement is not a reality in many big libraries. In React, for instance, it is possible to modify the state of a component directly, even though that is completely not recommended.
you can wrap your store in a closure, and provide getters and setters, to prevent accidental modification of your state.
https://facebook.github.io/immutable-js (or Mori or seamless-immutable) provides the means to prevent modifications to nested data, while avoiding the need to make defensive deep clones in your getExampleState method. However, it has a huge impact on your coding style and code base. It possibly works best with a functional coding style, as is encouraged by some flux implementations, like https://github.com/rackt/redux.
Another option is to make it clearer that you don't want the state to be modified by ensuring that non-library code only sees the store state in React 'props', rather than React 'state' - not modifying props should be second nature to a React developer anyway (other bugs will occur if they modify it). This can be done using a generic flux wrapper component, such as Facebook's 'Container' - https://facebook.github.io/flux/docs/flux-utils.html

Memory Management in Backbone.js

Consider the following:
SomeView = Backbone.View.extend({
render0: function () {
var view0 = new View0();
view0.setElement("#right-block");
view0.render();
},
render1: function(event) {
var view1 = new View1();
view1.setElement("#right-block");
view1.render();
},
});
If I call render0() and then render1, what will happen to the object view0? Do I have to explicitly destroy the old view?
Your view0 will stay in memory as long as the DOM element #right-block exists. Because the event handlers on the DOM element is pointing to methods of your view, so it won't be garbage collected.
Ideally you should invoke view0.remove(), which will remove the element from DOM and also calls stopListening().
But in your sample code, if you do that, the element #right-block will be removed from DOM, and view1.setElement("#right-block"); won't work as expected.
In this case try invoking view0.undelegateEvents(); view0.stopListening();, and if nothing else is referring to the view instance, it'll be garbage collected
No, you don't have to destroy the old view. Variables in objects that themselves go out of scope do not have to be manually cleared. When they go out of scope or when the parent object is deleted, the data contained within will also be eligible for garbage collection.

Circular reference memory leak?

I am in doubt if the following design pattern would cause a memory leak.
I have been using it for some time with success, but I haven't seen this pattern used by others, so I'd like some confirmation if you see something wrong with it.
As from next month I have to start working on a large project, and I want to know for sure that I can use this without problems, or if I should use another strategy.
controller.js:
var Controller = function(options){
};
Controller.prototype.makeView = function(options){
options.controller = this;
options.otheroption = options.otheroption;
var view = new View(options);
};
Controller.prototype.getModel = function(options){
//--- Get model ---
var model = new Model();
var promise = model.fetch();
return promise;
});
view.js:
var View = Backbone.View.extend({
initialize: function(options){
this.controller = options.controller;
this.otheroption = options.otheroption;
},
getModel: function(){
var promise = this.controller.getModel();
promise.done(_.bind(function(model){
//Do something with the returned model instance
}, this));
};
});
Instantiate controller, eg. from the router, or another controller:
//--- Instantiate the controller and build the view ---//
var controller = new Controller();
controller.makeView(options)
To me, this doesn't look like a circular reference, because both the controller and view are declared as a local variable.
Yet the instantiated view can access the controller functions, which allows me to isolate the RESTful server interactions via models / collections that the view uses.
For me it would seem as if the only reference remaining would be the view that keeps a reference to the controller object.
What I do afterwards is clean up the view (I destroy the instance and its references when I don't need it anymore.
Your opinion on this pattern is highly appreciated.
My purpose is to isolate creation of views / server interactions in separate controller files: if you see holes in my method and have a better way of doing it, please share.
Thanks.
Short answer: There is no memory leak problem in the code you have posted. The view holds a reference to the controller, but not vice versa. So as long as the controller lives longer than the view, that reference does not keep your objects from being garbage-collected. I don't see a circular reference anywhere in your code.
Longer answer: The pitfalls would be in the code you haven't posted. In particular, any event handlers in your view must be cleaned up properly, otherwise your views never fade into oblivion. But you have said in your question that you clean up your view, so I guess you are aware of that sort of problem.
What controller doing is here looks like a utility to me. Could have been easily managed by a global level singleton. I see some issues in first glance.
Code repetition, assuming you would creating separate Controller for different types of Models and Views, makeView and getModel code needs to be repeated for each controller. If you extending from a BaseController, then you need to pass View and Model Class to getModel and makeView functions.
How do you handle a use-case where you have to use same model in different Views?
makeView and getModel is designed assuming for each makeView there would be a getModel call, in assumed order
I would rather write a utility function which can create and deploy views for me.
var deployView = function(view, config){
//do the view rendering
view.render();
view.$el.appendTo(config.el);
}
var createView = function(config) {
var view;
var viewType = 'model';
if (config.collection || config.Collection) {
viewType = 'collection';
}
if (viewType === 'model') {
if (config.Model) {
config.model = new config.Model(config.modelAttributes);
//fetch if needed
}
} else {
if (config.Collection) {
config.collection = new config.Collection(config.items);
//fetch if needed
}
}
var filteredConfig = _.omit(config, 'Collection', 'Model', 'View');
view = new config.View(filteredConfig);
deployView(view, filteredConfig)
}
JavaScript implementations haven't had a problem with circular references for a long time. (IE6 did have a memory leak from circular references if I recall correctly, which wasn't shared by any other major browser from that period.)
Modern JavaScript implementations perform garbage collection through a "mark and sweep" algorithm. First they scan through your web app's entire memory structure starting from the global object, and mark everything they find. Then they sweep through every object stored in memory and garbage collect anything that wasn't marked. As long as there isn't a reference to your object from the global object or any stored function, it can be garbage collected.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management#Mark-and-sweep_algorithm
You're probably thinking of a reference counting-based implementation, which does have issues with memory leaks from circular references. In that implementation as long as one object contained a reference to another, that second object can't be garbage collected. That method was once used in web browsers but not anymore.
Nowadays, most memory leaks are from globally-accessible objects you forget to clean up and accidentally retaining data in function closures (a function that creates another function and passes/saves it somewhere). Since the closure's local variables can be accessed by the function created inside of them, they have to be retained as long as that function exists.
So go ahead and add all the circular references you want. Unless you need to target IE6, your code's fine.

Why does my ko computed observable not update bound UI elements when its value changes?

I'm trying to wrap a cookie in a computed observable (which I'll later turn into a protectedObservable) and I'm having some problems with the computed observable. I was under the opinion that changes to the computed observable would be broadcast to any UI elements that have been bound to it.
I've created the following fiddle
JavaScript:
var viewModel = {};
// simulating a cookie store, this part isnt as important
var cookie = function () {
// simulating a value stored in cookies
var privateZipcode = "12345";
return {
'write' : function (val) { privateZipcode = val; },
'read': function () { return privateZipcode; }
}
}();
viewModel.zipcode = ko.computed({
read: function () {
return cookie.read();
},
write: function (value) {
cookie.write(value);
},
owner: viewModel
});
ko.applyBindings(viewModel);?
HTML:
zipcode:
<input type='text' data-bind="value: zipcode"> <br />
zipcode:
<span data-bind="text: zipcode"></span>?
I'm not using an observable to store privateZipcode since that's really just going to be in a cookie. I'm hoping that the ko.computed will provide the notifications and binding functionality that I need, though most of the examples I've seen with ko.computed end up using a ko.observable underneath the covers.
Shouldn't the act of writing the value to my computed observable signal the UI elements that are bound to its value? Shouldn't these just update?
Workaround
I've got a simple workaround where I just use a ko.observable along side of my cookie store and using that will trigger the required updates to my DOM elements but this seems completely unnecessary, unless ko.computed lacks the signaling / dependency type functionality that ko.observable has.
My workaround fiddle, you'll notice that the only thing that changes is that I added a seperateObservable that isn't used as a store, its only purpose is to signal to the UI that the underlying data has changed.
// simulating a cookie store, this part isnt as important
var cookie = function () {
// simulating a value stored in cookies
var privateZipcode = "12345";
// extra observable that isnt really used as a store, just to trigger updates to the UI
var seperateObservable = ko.observable(privateZipcode);
return {
'write' : function (val) {
privateZipcode = val;
seperateObservable(val);
},
'read': function () {
seperateObservable();
return privateZipcode;
}
}
}();
This makes sense and works as I'd expect because viewModel.zipcode depends on seperateObservable and updates to that should (and does) signal the UI to update. What I don't understand, is why doesn't a call to the write function on my ko.computed signal the UI to update, since that element is bound to that ko.computed?
I suspected that I might have to use something in knockout to manually signal that my ko.computed has been updated, and I'm fine with that, that makes sense. I just haven't been able to find a way to accomplish that.
sigh, I found someone with my exact same problem
If dependentObservables don't notifySubscribers on write, why do they
even bother to do it on read? They get added to the observables list
and subscribed to, but then they never trigger on updates. So what is
the point of subscribing to them at all?
Ryan Niemeyer answers:
I think that for your scenario, dependentObservables may not be the
right tool for the job. dependentObservables are set up to detect
dependencies in the read function and re-evaluate/notify whenever any
of those dependencies change. In a writeable dependentObservable, the
write function is really just a place to intercept the write and allow
you to set any observables necessary, such that your read function
would return the proper value (write is typically the reverse of read
in most cases, unless you are transforming the value).
For your case, I would personally use an observable to represent the
value and then a manual subscription to that observable to update the
original value (the one that you may not have control over).
It would be like: http://jsfiddle.net/rniemeyer/Nn5TH/
So it looks like this fiddle would be a solution
var viewModel = {};
// simulating a cookie store, this part isnt as important
var cookie = function () {
// simulating a value stored in cookies
var privateZipcode = "12345";
return {
'write' : function (val) {
console.log("updated cookie value with: " + val);
privateZipcode = val;
},
'read': function () {
return privateZipcode;
}
}
}();
viewModel.zipcode = ko.observable(cookie.read());
// manually update the cookie when the observable changes
viewModel.zipcode.subscribe(function(newValue) {
cookie.write(newValue);
});
ko.applyBindings(viewModel);​
That makes sense and its somewhat simpler to use. Overall I'm not sure how great of an idea it is to treat a cookie as an observable since the server could edit it in an ajax request, etc.
Try making your internal privatezipcode an observable. See here: http://jsfiddle.net/KodeKreachor/fAGes/9/

Categories