I'm writing an online game which allows a user to progress from one puzzle to the next, and if the user makes mistakes, each puzzle has a start again button to allow the user to start just that puzzle from scratch. A simplified version of the code's structure is below:
function puzzle(generator) {
this.init = function() {
this.generator = generator;
...
this.addListeners();
}
//fires when the puzzle is solved
this.completed = function() {
window.theSequence.next();
}
this.empty = function() {
//get rid of all dom elements, all event listeners, and set all object properties to null;
}
this.addListeners = function() {
$('#startOver').click(function() {
window.thePuzzle.empty();
window.thePuzzle.init();
});
}
this.init();
}
function puzzleSequence(sequenceGenerator) {
this.init = function() {
//load the first puzzle
window.thePuzzle = new puzzle({generating json});
}
this.next = function() {
//destroy the last puzzle and create a new one
window.thePuzzle.empty();
window.thePuzzle = new puzzle({2nd generating json});
}
}
window.theSequence = new puzzleSequence({a sequence generator JSON});
The problem I have is that if the user has progressed to the second puzzle, if they click start over it loads the first puzzle rather than the second. After a bit of debugging I've worked out that 'this', when used in methods by the second puzzle, for some reason still holds a reference to the first puzzle, but 'window.thePuzzle' - which should be the same as this - correctly refers to the second puzzle.
Why is 'this' persisting in referrring to the first one?
Let me know if you need more code samples
$('#startOver').click(this.empty);
You've taken the empty method and detached it from this to pass as a plain unbound function to jQuery. When it gets called back, it will have no reference to the original value of this. In fact, when a function is called unbound, this will refer to window, so you'll be scribbling what you think are properties onto the globals.
JavaScript doesn't bind methods in the same way as other languages. See eg. this answer for an explanation of what it actually does. This confuses many people; personally I consider it one of JavaScript's worst flaws.
There is a very good (and clear) description of exactly how the this reference is treated in different contexts over at Quirksmode.
Related
I am extending mxgraph delete control example to add delete like controls to nodes which are generated dynamically in my graph. The source code for the example is available here
The problem is in this part of the code -
// Overridden to add an additional control to the state at creation time
mxCellRendererCreateControl = mxCellRenderer.prototype.createControl;
mxCellRenderer.prototype.createControl = function(state)
{
mxCellRendererCreateControl.apply(this, arguments);
var graph = state.view.graph;
if (graph.getModel().isVertex(state.cell))
{
if (state.deleteControl == null)
mxCellRendererCreateControl.apply inside the overridden call back of createControl seems to work as intended (calls the original function before creating additional controls) with the initial state of the graph on load. But, once I add nodes dynamically to the graph and the callback is invoked by mxgraph's validate/redraw, the control goes into an infinite loop, where 'apply' function basically keeps calling itself (i.e, the callback).
I am a bit clueless because when I debug, the context(this) looks fine, but I can't figure out why instead of invoking the prototype method, it just keeps invoking the overridden function in a loop. What am I doing wrong?
It looks like you are not cloning your original function the right way, please try the following :
Function.prototype.clone = function() {
var that = this;
return function theClone() {
return that.apply(this, arguments);
};
};
Add that new method somewhere in your main code so it will available in the whole application, now you can change your code to :
// Overridden to add an additional control to the state at creation time
let mxCellRendererCreateControl = mxCellRenderer.prototype.createControl.clone();
mxCellRenderer.prototype.createControl = function(state) {
mxCellRendererCreateControl(state);
var graph = state.view.graph;
if (graph.getModel().isVertex(state.cell)) {
if (state.deleteControl == null) {
// ...
}
}
// ...
};
This should work if I understood your problem correctly, if it does not, please change the old function call back to the apply. Otherwise let me know if something different happened after the Function prototype change.
It seems that your overriding code is being called multiple times (adding a simple console.log before your overriding code should be enough to test this)
Try to ensure that the code that overrides the function only gets called once, or validate whether the prototype function is the original or yours.
Here is an example of how you can check if the function is yours or not
if (!mxCellRenderer.prototype.createControl.isOverridenByMe) {
let mxCellRendererCreateControl = mxCellRenderer.prototype.createControl;
mxCellRenderer.prototype.createControl = function(state) { /* ... */ };
mxCellRenderer.prototype.createControl.isOverridenByMe = true;
}
There are other ways, like using a global variable to check if you have overriden the method or not.
If this doesn't fix your issue, please post more about the rest of your code (how is this code being loaded/called would help a lot)
I have put together a fun API for game creation. In the code I create a prototype for Mover and then extend it with several specific prototypes (Gold, Monster, and Hero). Each one is based on a img tag with a given ID. I use type-specific information in the constructor and a single template method for each type. Most of the functional code in Mover depends on those type-specific details. I have included one example for simplicity.
I use method calls in a separate script to create and destroy instances of the Mover child types. When I create and destroy one instance at a time everything works as intended. The image updates, the sound plays and it is removed after the correct delay. If I create two or more, however, only the last one works as expected. So if I make gold, moster, hero. Only the hero will remove correctly. The other two will play the audio, but don't appear to update.
I ran into the same problem when I tried to attach a function to the onclick event for more than one instance. Only the last one worked and the others did nothing. Obviously I'm missing something about the way java handles method assignments. Any explanation you can offer would help.
Thanks,
BSD
function Mover()
{
}
Mover.prototype.InitTag = function()
{
this.HTMLtag.src=this.imageURL;
this.HTMLtag.style.position="absolute";
this.HTMLtag.style.width=characterSize;
this.HTMLtag.style.height=characterSize;
this.Position(Math.floor(Math.random()*(MaxW-characterSize)+(characterSize/2)),Math.floor(Math.random()*(MaxH-characterSize)+(characterSize/2)));
}
Mover.prototype.Destroy = function()
{
var disp = this.HTMLtag.display;
this.HTMLtag.src=this.destroyURL
this.HTMLtag.display = disp;
this.destroyAudio.play();
this.RemoveTag();
}
function Monster(id)
{
this.MonsterID = id;
this.HTMLtag = document.getElementById("monster"+id);
this.imageURL = "monster1.jpg";
this.destroyURL = "monster2.jpg";
this.destroyAudio = monsterAudio;
}
Monster.prototype = new Mover();
Monster.prototype.RemoveTag = function()
{
var mID = this.MonsterID;
setTimeout(function() {field.DeleteMonster(mID)}, 1000);
}
function Hero()
{
this.HTMLtag = document.getElementById("hero");
this.imageURL = "hero1.jpg";
this.destroyURL = "hero2.jpg";
this.destroyAudio = heroAudio;
}
Hero.prototype = new Mover();
Hero.prototype.RemoveTag = function()
{
setTimeout(function() {field.DeleteHero()}, 5000);
}
function Gold(id)
{
this.GoldID = id;
this.HTMLtag = document.getElementById("gold"+id);
this.imageURL = "gold1.jpg";
this.destroyURL = "gold2.jpg";
this.destroyAudio = goldAudio;
}
Gold.prototype = new Mover();
Gold.prototype.RemoveTag = function()
{
var mID = this.GoldID;
setTimeout(function() {field.DeleteGold(mID)}, 1000);
}
---------UPDATE UPDATE UPDATE-----------
I have at least partially fixed the problem. I have gotten it to work, but I still don't know why it didn't function as intended. I noticed that while my browser's (Chrome) developer tools could visually identify the most-recently-added Mover when it was being destroyed, it could not do so with the any other movers.
Tag of most recently added Mover can be identified in Chrome developer tools.
This suggested that Mover.HTMLtag was not actually the same as document.getElementById('mover1'). I was able to confirm this by looking at the variables in the GoldField.DeleteMover. At the line indicated mover.src has not changed, but movers[id].HTMLtag.src has been correctly updated. In the most-recently-added case they were both the same.
GoldField.prototype.DeleteMover = function(id)
{
var isHero = false;
if(this.Hero!=null && id==this.Hero.myID)
{
this.Hero = null;
isHero = true;
}
else if(this.Tower!=null && id==this.Tower.myID)
{
this.Tower = null;
}
var mover = document.getElementById("mover"+id);
if(!isHero)
{
this.tag.removeChild(mover);//<<< HERE HERE HERE HERE
delete this.movers[id];
}
}
So, I changed one line in Mover.Destroy. By finding the tag by ID and setting the src. I was able to reliable behavior. It would appear that Mover.HTMLtag is not reliable the same after the second Mover is added. Any explanation?
Mover.prototype.Destroy = function()
{
document.getElementById(this.HTMLtag.id).src=this.destroyURL;
this.HTMLtag.src=this.destroyURL;//old method
this.destroyAudio.play();
this.RemoveTag();
}
On suspicion that this might extend to other updates to this.HTMLtag I set up some basic movement of the Hero. It works great, but if you add one additional Mover of any kind it no longer moves. That narrows down the question considerably. Why would constructing a second Mover cause the prototype members to change?
So I debug your code and I found the cause of your problem. The problem was when you create a new instance of monster you storing a reference to it on the monster var. And when you delete it you don't delete / update the reference to it. So your delete function myField.DeleteMover(id) try to delete a monster already deleted. How to solve this.
// create an array to keep ref to our instances
var monsters= [];
// var monster = null;
function addMonster()
{
// monster = goldField.AddMonster();⏎
// push every monster in array
monsters.push(goldField.AddMonster());
}
function killMonster()
{
// if array.length is true
if (monsters.length) {
// call the destroy function on the last ref
monsters[monsters.length - 1].Destroy();
// remove the last ref from array using pop
monsters.pop();
}
//monster.Destroy();
}
This is working however I think all of this should be done in the objects itself. And you should not care about it here.
Another advice try to use more array methods. Avoid using delete on array index because it mess with index and count instead use splice(index, 1) same for add item in array use push instead of arbitrary index.
Anyway funny game! Good luck to finish it.
Edit, after your answer I go back an test.
To make it work I do this.
// First go inGoldField.prototype.DeleteMover and replace the ugly delete index by
this.movers.splice(id, 1);
// Then in the Mover.prototype.Destroy
// This part is a a little blurred for me.
// the current HTMLtag looks good but when I console.log like this
console.log('before', this.HTMLtag);
this.HTMLtag = document.querySelector("#mover" + this.myID);
console.log('after', this.HTMLtag);
// They are not equal look like the first is outdated
You should convert all your delete and add to splice and push methods.
This is just a quick debug I don't know why the selector is outdated.
So I check the code again and I make it work without refreshing the selector. The problem is caused by the creation of dom element with innerHTML.
First reset
this.HTMLtag.src=this.destroyURL
Then instead of
//Mover.prototype.Destroy
this.tag.innerHTML+="<img id='mover"+this.moverCount+"'>";
I create a img dom el.
var img = document.createElement("img");
img.setAttribute('id', 'mover' + this.moverCount);
this.tag.appendChild(img);
All Monsters are now deleted with the image.
I don't check for the hero but first you should update your innerHTML and reply if there is still a problem. I don't think there is any problem with some prototype.
I'm reading the Google Drive Realtime API documentation on Building a Collaborative Data Model.
I really like the way gapi.drive.realtime.databinding.bindString behaves. It doesn't mess up your cursor placement when multiple people are typing in the same text box. But it requires that you pass it a CollaborativeString.
But if you register a custom type, you have to use gapi.drive.realtime.custom.collaborativeField no matter what type of field you are defining, and you can't pass one of these to bindString. In fact, the collaborativeField type does not appear to be documented anywhere, and inspecting it in the console shows that it has no methods. That means there's no registerReference method, which CollaborativeString uses to keep track of cursor positions.
How frustrating. So I guess I have to work around it. I see a few options:
Ignore the fact that the cursor gets messed up during collaboration
Use a CollaborativeMap instead of a custom type, and wrap it with my custom type at runtime
Probably going to do option 2.
I think you misunderstand how this site works, the onus is not on other people to show you how to do something - you're asking other people to take time from their day and help you.
That being said, taking a quick look at the page that you linked shows that what you want to do is not only possible but quite straightforward and compatible with bindString. Stealing from the example code from that page:
// Call this function before calling gapi.drive.realtime.load
function registerCustomTypes()
{
var Book = function () { };
function initializeBook()
{
var model = gapi.drive.realtime.custom.getModel(this);
this.reviews = model.createList();
this.content = model.createString();
}
gapi.drive.realtime.custom.registerType(Book, 'Book');
Book.prototype.title = gapi.drive.realtime.custom.collaborativeField('title');
Book.prototype.author = gapi.drive.realtime.custom.collaborativeField('author');
Book.prototype.isbn = gapi.drive.realtime.custom.collaborativeField('isbn');
Book.prototype.isCheckedOut = gapi.drive.realtime.custom.collaborativeField('isCheckedOut');
Book.prototype.reviews = gapi.drive.realtime.custom.collaborativeField('reviews');
Book.prototype.content = gapi.drive.realtime.custom.collaborativeField('content');
gapi.drive.realtime.custom.setInitializer(Book, initializeBook);
}
and
// Pass this as the 2nd param to your gapi.drive.realtime.load call
function onDocLoaded(doc)
{
var docModel = doc.getModel();
var docRoot = docModel.getRoot();
setTimeout(function ()
{
var book = docModel.create('Book');
book.title = 'Moby Dick';
book.author = 'Melville, Herman';
book.isbn = '978-1470178192';
book.isCheckedOut = false;
book.content.setText("Call me Ishmael. Some years ago - never mind how long precisely - having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world.");
docRoot.set('tbook', book);
debugger;
}, 0);
}
Good luck and have fun with the Realtime API - it's a lot of fun to play with.
I know this question and answer are getting old, but for reference's sake, just the last part of Grant Watters' very good answer, the onDocLoaded routine, is rather misleading. That function as written, is more suited for the 3rd parameter to the gapi.drive.realtime.load call, the onInitializeModel callback.
The 2nd parameter is called every time the Doc is loaded. You wouldn't normally add the same object over and over as the above routine would... Instead, you would normally set up your event handling, your dataBinds etc. This version might clarify somewhat:
// Pass this as the 2nd param to your gapi.drive.realtime.load call
function onDocLoaded(doc)
{
var docModel = doc.getModel();
var docRoot = docModel.getRoot();
var text = doc.getModel().getRoot().get("text");
// Add an event listener...
text.addEventListener(gapi.drive.realtime.EventType.TEXT_INSERTED, onStringChanged);
// ...and/or bind to collaborative objects:
var textArea = document.getElementById('textArea1')
textBinding = gapi.drive.realtime.databinding.bindString(text, textArea);
etc...
}
Not incidentally, bindString returns the binding object, which is needed to "unbind" later, preventing an AlreadyBound error or other unexpected behavior when the next Doc is loaded. Do something like this:
function onDocLoaded(doc)
{
// Clear any previous bindings etc:
if (textBinding) { textBinding.unbind() };
textBinding = null;
etc...
I have a static class which contains an array of callback functions, I then have a few other classes that are used to interact with this static class...
Here is a simple example of the static class:
var SomeStaticInstance = {};
(function(staticInstance) {
var callbacks = {};
staticInstance.addCallback = function(callback) { callbacks.push(callback); }
staticInstance.callAllCallbacks = function() { /* call them all */ }
}(SomeStaticInstance));
Then here is an example of my other classes which interact with it:
function SomeClassOne() {
this.addCallbackToStaticInstance = function() { SomeStaticInstance.addCallback(this.someCallback); }
this.someCallback = function() { /* Do something */ }
this.activateCallbacks = function() { SomeStaticInstance.callAllCallbacks(); }
}
function SomeClassTwo() {
this.addCallbackToStaticInstance = function() { SomeStaticInstance.addCallback(this.someOtherCallback); }
this.someOtherCallback = function() { /* Do something else */ }
this.activateCallbacks = function() { SomeStaticInstance.callAllCallbacks(); }
}
Now the problem I have is that when I call either class and tell it to activateCallbacks() the classes only activate the callbacks within their own scope, i.e SomeClassOne would call someCallback() but not someOtherCallback() and vice versa, now I am assuming it is something to do with the scope of the closures, however I am not sure how to get the behaviour I am after...
I have tried turning the static class into a regular class and then passing it into the 2 classes via the constructor, but still get the same issue...
So my question is how do I get the classes to raise all the callbacks
-- EDIT --
Here is an example displaying the same issue as I am getting on my actual app, I have put all script code into the page to give a clearer example:
http://www.grofit.co.uk/other/pubsub-test.html
It is a simple app with 2 presenters and 2 views... one view is concerned with adding 2 numbers at the top of the page, the 2nd view is concerned with taking that total and multiplying it and showing a result.
The 3rd party library I am using is PubSubJS, and the first presenter listens for an event to tell it that the one of the boxes has changed and re-totals the top row. The 2nd presenter listens for when the multiply or total at the top changes, then recalculates the bottom one. Now the first presenter recalculates correctly, and the 2nd presenter will correctly recalculate whenever the multiply box changes, HOWEVER! It will NOT recalculate when the total on the top changes, even thought it should receive the notification...
Anyway take a quick look through the source code on the page to see what I mean...
First, I think you want var callbacks = [] (an array instead of an object) since you're using callbacks.push().
I'm not sure I understand your problem. The way your classes are structured, you can achieve what you want by instantiating both classes and calling addCallbackToStaticInstance() on both new objects. E.g.,
var one = new SomeClassOne();
var two = new SomeClassTwo();
one.addCallbackToStaticInstance();
two.addCallbackToStaticInstance();
one.activateCallbacks();
Then, as above, you can call activateCallbacks() from either object.
If you're saying you want to be able to call activateCallback() after instantiating only one of the classes, you really have to rethink your approach. I'd start with moving addCallbackToStaticInstance() and activateCallbacks() into their own class.
This is a very odd way of doing things, but your main problem is that your callbacks object it not part of SomeStaticInstance, it is defined within an anonymous closure. Also your callbacks object {} should be an array [].
try staticInstance.callbacks = []; instead of var callbacks = {};
and
staticInstance.addCallback = function(callback) {
this.callbacks.push(callback);
}
The example below, is just an example, I know that I don't need an object to show an alert box when user clicks on div blocks, but it's just a simple example to explain a situation that frequently happens when writing JS code.
In the example below I use a globally visible array of objects to keep a reference to each new created HelloObject, in this way events called when clicking on a div block can use the reference in the arry to call the HelloObject's public function hello().
1st have a look at the code:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html><head><meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
<title>Test </title>
<script type="text/javascript">
/*****************************************************
Just a cross browser append event function, don't need to understand this one to answer my question
*****************************************************/
function AppendEvent(html_element, event_name, event_function) {if(html_element) {if(html_element.attachEvent) html_element.attachEvent("on" + event_name, event_function); else if(html_element.addEventListener) html_element.addEventListener(event_name, event_function, false); }}
/******************************************************
Just a test object
******************************************************/
var helloobjs = [];
var HelloObject = function HelloObject(div_container)
{
//Adding this object in helloobjs array
var id = helloobjs.length; helloobjs[id] = this;
//Appending click event to show the hello window
AppendEvent(div_container, 'click', function()
{
helloobjs[id].hello(); //THIS WORKS!
});
/***************************************************/
this.hello = function() { alert('hello'); }
}
</script>
</head><body>
<div id="one">click me</div>
<div id="two">click me</div>
<script type="text/javascript">
var t = new HelloObject(document.getElementById('one'));
var t = new HelloObject(document.getElementById('two'));
</script>
</body></html>
In order to achive the same result I could simply replace the code
//Appending click event to show the hello window
AppendEvent(div_container, 'click', function()
{
helloobjs[id].hello(); //THIS WORKS!
});
with this code:
//Appending click event to show the hello window
var me = this;
AppendEvent(div_container, 'click', function()
{
me.hello(); //THIS WORKS TOO AND THE GLOBAL helloobjs ARRAY BECOMES SUPEFLOUS!
});
thus would make the helloobjs array superflous.
My question is: does this 2nd option in your opinion create memoy leaks on IE or strange cicular references that might lead to browsers going slow or to break???
I don't know how to explain, but coming from a background as a C/C++ coder, doing in this 2nd way sounds like a some sort of circular reference that might break memory at some point.
I also read on internet about the IE closures memory leak issue http://jibbering.com/faq/faq_notes/closures.html (I don't know if it was fixed in IE7 and if yes, I hope it does not come out again in IE8).
Thanks
Aside:
var HelloObject = function HelloObject(div_container)
In general try not to use named inline function expressions. It doesn't usually get you anything and there are serious problems with them in JScript (IE). Either use a function HelloObject() { ... } statement, or a var HelloObject= function() { ... }; anonymous expression.
does this 2nd option in your opinion create memoy leaks on IE
‘Create’? No, your existing code already had leaks on IE.
The listener you applied to the click event has a closure, keeping a reference to the parent function scope. In both examples, the div_container object is in that scope, so you've got a circular reference:
div -> onclick ->
listener function -> parent scope ->
parent function -> reference to div
A reference loop containing a mixture of native JS objects and host objects (such as the div, a DOM Node) is what causes the memory leaks in IE6-7.
To stop this happening, you can pull the click function definition out of the parent:
function HelloObject(div_container) {
AppendEvent(div_container, 'click', HelloObject_hello);
}
function HelloObject_hello() {
alert('hello');
}
Now there is no closure, the div_container is not remembered, and there is no loop/leak. However, the hello function is just a function, with no idea to which div_container it belongs (other than by looking at the event/this it gets on click).
More typically you do need to remember the div, or, if you're doing things in an objecty way, the this:
function HelloObject(element) {
this.element= element;
AppendEvent(element, 'click', this.hello.bind(this));
}
HelloObject.prototype.hello= function() {
alert('Hello, you clicked on a '+this.element);
};
(About function.bind.)
Which of course brings back the reference loop:
element -> onclick
bound hello function -> bound this
Helloer instance -> 'element' member
reference to element
Do you really care about this kind of refloop? Maybe not. It only really affects IE6-7; it's bad in IE6 as the memory isn't given back until you quit the browser, but then there's a growing school of thought that says anyone still using IE6 deserves everything they get.
On IE7, the memory leaks pile up until you leave the page, so it only matters for very long-running pages where you're throwing away old HelloObjects and binding new ones repeatedly. (In that case, a linear Array of HelloObjects that doesn't discard old values would also be a memory leak until page unload, in itself.)
If you do care, because you're working for some dingy corporate that runs IE6 and no-one ever closes their browsers, then (a) my condolences, and (b) yes, you will indeed have to implement something like the lookup object you had, to act as a decoupling layer between the DOM Nodes and the Function object you're using as an event listener.
Some frameworks have their own decoupling used for events. For example, jQuery attaches the event handler functions to a data lookup, indexed by an id that it drops into each Element object; this gives it decoupling for free, though it does have problems of its own...
If you're using plain JavaScript, here's some example helper code.
// Event binding with IE compatibility, and decoupling layer to prevent IE6-7
// memory leaks
//
// Assumes flag IE67 pre-set through eg. conditional comments
//
function EventTarget_addEventListener(target, event, listener) {
if ('addEventListener' in target) {
target.addEventListener(event, listener, false);
} else if ('attachEvent' in target) {
if (IE67)
listener= listener.decouple();
target.attachEvent('on'+event, listener);
}
}
Function.decouple_bucket= {};
Function.decouple_factory= function() {
function decoupled() {
return Function.decouple_bucket[decoupled.decouple_id].apply(this, arguments);
}
return decoupled;
};
Function.prototype.decouple_id= null;
Function.prototype.decouple= function() {
var decoupled= Function.decouple_factory();
do {
var id= Math.floor(Math.random()*(Math.pow(2, 40)));
} while (id in Function.decouple_bucket);
decoupled.decouple_id= id;
Function.decouple_bucket[id]= this;
return decoupled;
};
Function.prototype.release= function() {
delete _decouple_bucket[this.decouple_id];
};
if (IE67) {
EventTarget_addEventListener(window, 'unload', function() {
Function.decouple_bucket.length= 0;
});
}
// Add ECMA262-5 method binding if not supported natively
//
if (!('bind' in Function.prototype)) {
Function.prototype.bind= function(owner) {
var that= this;
if (arguments.length<=1) {
return function() {
return that.apply(owner, arguments);
};
} else {
var args= Array.prototype.slice.call(arguments, 1);
return function() {
return that.apply(owner, arguments.length===0? args : args.concat(Array.prototype.slice.call(arguments)));
};
}
};
}
With all that tedious plumbing out of the way, you can then just say:
function HelloObject(element) {
this.element= element;
EventTarget_addEventListener(element, 'click', this.hello.bind(this));
}
HelloObject.prototype.hello= function() {
alert('Hello, you clicked on a '+this.element);
};
that might lead to browsers going slow or to break???
No, it's only really memory leaks (and then mostly in IE) that we're worried about when talking about refloops.
The answer is in the body of the question. It seems weird to you because of your C++ background. The 2nd option is the Javascript-ish way of doing it.