I will be using knockoutjs in a single page application and I am concerned whether the observables remain in memory even if any DOM elements that the bindings were applied to, are removed.
What I think is that I will need to handle this in my application, by calling
ko.cleanNode(DOMElement)
on each DOM element that used observables, before removing them from the document.
I just need someone to confirm that this is the case
Thanks
CleanNode doesn't remove the observables, it just unbinds them from the UI elements. You'd also need to remove any references to your view models by setting them to null in order for them to be garbage collected, something like:
var myVM = new myViewModel();
ko.applyBindings(myVM, DOMElement);
//All your other stuff
ko.cleanNode(DOMElement);
myVM = null;
Related
How bad would it be if I could cache DOM selections and store them as property directly on DOM parent element. Say i have
(function(element){
element['content'] = element.querySelector(".tooltip__content");
//maybe event this
element['content']['handlers']['click'] = function(e){...};
}(document.querySelector("tooltip"));
This way I can "cache" DOM selection and have references to event handlers to use with removeEventListener.
How bad is it? What are the alternatives? Thank you.
Of course i can store it in seperate variables but maybe there is more structural organized way?
In knockout I want to write an ComputedObservable which is computed from values which are not observable. I want to manually trigger the Notification.
Is this possible somehow?
A computed variable is nothing but a function that registers custom event on all observables inside it.
When ever your internal observable variable changes, it broadcasts a notify event and all listeners catches the event and process accordingly.
The process sounds simple but isn't when you plan to use on plain Javascript variable. You can refer Listening for variable changes in JavaScript or jQuery.
Now if you wish to achieve this manually, basic challenges:
A variable can be changed anywhere, anytime. You will have to manually trigger their notify event everywhere. If you miss, you will have incorrect data.
You will also have to add eventListeners for bindings. Like a total variable which should be computed.
Browser support. IE8 or before does not support custom events and you will have to add hacks for it.
My suggestion, use computed (or pureComputed if on or above KO3.2) with observables. This way you will save a lot of code. Knockout team must have gone through these issues and have added handling for it in their code. You reinventing the wheel will add a lot of code in your codeBase and without proper documentation, will be difficult to maintain.
Following is a Fiddle where I replicated textInput binding for number input. If you see, they have separate handling of IE10, IE9 and IE8 or below. They even have special handling for safari below 5.
I fully agree with the other answer on just using ko.observable variables in your computed. I don't see why you'd use a computed if its re-evaluation isn't automated. I'd advice to create a plain function instead.
The closest approach I'd see is to create a dummy observable that the computed subscribes to. You can call valueHasMutated to force re-evaluation:
var a = "A";
var b = "B";
var subscriptionObs = ko.observable();
var ab = ko.pureComputed(function() {
subscriptionObs();
return a + b;
});
ko.applyBindings({ab: ab });
b = "C";
subscriptionObs.valueHasMutated();
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<p data-bind="text: ab"></p>
Does the remove child method provided by polymer takes care of cleaning all the dependencies such as event listeners, binded vars, etc. ?
Polymer.dom(parent).removeChild(node)
I want to add/remove many elements programmatically and want to make sure nothing is leaking or nothing is still watching for objects that do not exist anymore.
To add:
// Create the element
var paperListbox = document.createElement('paper-listbox');
paperListbox.setAttribute('depth', depth);
this.listen(paperListbox, 'iron-select', 'selectionChanged');
// add it
Polymer.dom(this.$.container).appendChild(paperListbox);
To remove:
// get node of interest
var node0 = Polymer.dom(this.$container).childNodes[0];
Polymer.dom(this.$.container).removeChild(node0)
Does such a pattern would allow me to add/remove lot of elements properly?
I know jQuery doesn’t cache collections of element, f.ex calling:
$('.myclass').html('hello');
$('.myclass').html('bye');
Will make jQuery climb the DOM twice.
But how about cached DOM nodes?
var elems = document.querySelectorAll('.myclass');
$(elems).html('hello');
$(elems).html('bye');
Will jQuery cache those internally, or will they be equally slow as the first example?
To clarify: will jQuery keep a reference to elems and cache $(elems) internally so it won’t have to apply the same $() wrapper every time?
Something like:
cache = {}
constructor = function(collection)
if collection in cache
return cache[collection]
else construct(collection)
Assuming I've understood your question correctly, then no, jQuery won't keep a reference to the selected nodes beyond the statement that uses them:
$('.myclass').html('hello'); //Select all .myclass elements, then change their HTML
$('.myclass').html('bye'); //Select all .myclass elements, then change their HTML again
If you maintain a reference to those selected nodes separately, it will be faster:
var elems = document.querySelectorAll('.myclass'); //Select all .myclass elements
$(elems).html('hello'); //Change their HTML (no need to select them)
$(elems).html('bye'); //Change their HTML (no need to select them)
The difference won't be massive (unless your DOM is very complicated) but there will be a difference:
Update
will jQuery keep a reference to elems and cache $(elems) internally so
it won’t have to apply the same $() wrapper every time?
No, it won't. As stated above, the reference to the matched set of elements will not be maintained beyond the statement to which it applies. You can improve the performance of your code by keeping a reference to jQuery objects that are used throughout, rather than selecting them again each time, or even wrapping a stored set of native DOM nodes in a jQuery object each time.
If "cache" means "keep the DOM elements in the internal collection for that jQuery object" .. then yes.
Imagine
jq = $(elementListOrSelector)
where jq[0..jq.length-1] evaluate to the respectively DOM element. For instance, jq[0] evaluates to the first DOM element represented by the jQuery object, if any. Note that this collection is not magically changed once it has been built (and the way in which it was built does not matter).
However there is no "cache" outside of simply keeping the immediate results for that particular jQuery object.
I am building a MooTools class and I have this in my initialize function:
this.css = null;
window.addEvent('domready', function(){
this.document = $(document);
this.body = $(document.body);
this.head = $(document.head);
}.bind(this));
Ok and now to the questions ...
Should I declare this.css = null or any other empty variable in the init:
this.css = null; // Maybe this.css = '' - empty string?
Next thing is about window and document ... Should I put it into $() or not because it works both way, so I just want to know which way is preferred? So to summarize:
window.addEvent() // or should I use $(window).addEvent()
this.document = $(document) // or this.document = document
this.body = $(document.body) // or this.body = document.body
I stored these values into object to avoid multiple DOM queries, is this ok? Or would it be better to call $(selector) / $$(selector) every time?
Two more things left ... About binding ... Is it ok to use .bind(this) every time or would it be better to use .bind(this.myDiv) and use it inside function as eg.: this.setStyle(...); instead of this.myDiv.setStyle(...)
(function(){
this.setStyle('overflow-y', 'visible');
}.bind(this.body)).delay(100);
or
(function(){
this.body.setStyle('overflow-y', 'visible');
}.bind(this)).delay(100);
And the last thing is about garbage collection ... Do I have to garbage myself and how to do it (as far as I know MooTools does it on its own on unload). The confusing part is that I found function in MT docs:
myElement.destroy();
They say: Empties an Element of all its children, removes and garbages the Element. Useful to clear memory before the pageUnload.
So I have to garbage on my own? How to do that? When to use .destroy()? I am working on a huge project and I notice that IE gets slow over multiple executions of the script (so how to handle that? probably some cleaning needed, memory leaks?).
pff, this is a bit long.
first, initial variables. this.css = null... the only time i'd set 'empty' variables are: typecast; when it's a property of an object i may reference and don't want undefined; when it's a string i will concatenate with or a number i will incre/decrement; null is not really useful at this point.
a common / good practice when writing a mootools class is to use the Options class as a mixin. this allows you to set default options object with your default settings that can be overridden upon instantiation. similarly, Object.merge({ var: val}, useroptions); can override a default val if supplied.
now, iirc, there are times when you'd have to use $(document.body) and it's not because document.body does not work, it's because applying $() also applies Element prototypes in IE (since Element prototype is not extended there, the methods are applied to the elements directly instead, which happens when you $ them). Also, $ assigns an internal UID of the target element and allows for element storage to be used for that element. I don't see a point to using $(document) or $(window) - they are 'extended' as much as needed by default. In any case, even in IE, you only need to $(something) the one time and can continue using it as just 'something' afterwards. check my document.getElementById("foo").method() example - you can just run $("foo"); on it's own and then try document.getElementById("foo").method() again - it will work in IE too.
window.addEvent(); // is fine.
document.body.adopt(new Element("div")); // not fine in IE.
new Element("div").inject(document.body); // fine.
and on their own:
$(document.body).adopt(new Element("div")); // fine.
document.body.adopt(new Element("span")); // now fine, after first $.
see this in ie8: http://www.jsfiddle.net/AePzD/1/ - first attempt to set the background fails but the second one works. subsequently, document.body.methods() calls are going to work fine.
http://www.jsfiddle.net/AePzD/2/ - this shows how the element (which $ also returns) can have methods in webkit/mozilla and not in trident. however, replace that with $("foo") and it will start working. rule of thumb: $ elements you don't dynamically create before applying methods to them.
storing selectors can be a good performance practice, for sure. but it can also fill your scope chain with many variables so be careful. if you will use a selector two or more times, it's good to cache it. failing to do so is not a drama, selector engines like sizzle and slick are so fast these days it does not matter (unless you are animating at the time and it impacts your FPS).
as for binding, whichever way you like.
keep in mind delay has a second argument, BIND:
(function(){
this.setStyle('background', 'blue');
}).delay(100, $("foo"));
so do quite a few functions. this particular bind is not very useful but in a class, you may want to do
(function(){
this.element.setStyle('background', 'blue');
this.someMethod();
}).delay(100, this));
GC. mootools does it's own GC, sure. however, .destroy is a very good practice, imo. if you don't need something in the DOM, use element.dispose(). if you won't attach it to the DOM again, use .destroy() - removes all child nodes and cleans up. more memory \o/
advice on IE... dodgy. you can use drip if you can to trace memory leaks, there are things like dynatrace that can be very good in profiling. in terms of practices... make sure you don't use inline js, you always clean up what you don't need (events, elements) and generally, be careful, esp when you are stacking up events and dealing with ajax (bring new elements that need events - consider event delegation instead...). use fewer dom nodes - also helps...