proper method wrt object encapluation and DOM events...

J

James

I am learning HTML/JavaScript... Okay...

Let me start out with some code... The following describes a very simple
"MyImage" object that has but a single "public" method "load(src, alt)".
However, it has two internal methods "prvOnLoad(e)/prvOnError(e)" that
belong to an internal object "this.image" in a sense: Once a user calls
`MyImage.load(src, alt)', the object will setup internal listeners via
`MyImage.prvAttachEventListeners()'. In addition, once an event fires, it
"disables" further events by setting `this.image.onload/error/abort'
callbacks to null:


// MyImage.js
-------------
function MyImage(something)
{
this.image = new Image();
this.something = something;
}


MyImage.prototype.prvAttachEventListeners =
function()
{
var this_ = this;
this.image.onload = function(e) { this_.prvOnLoad(e); };
this.image.onerror = function(e) { this_.prvOnError(e); };
this.image.onabort = function(e) { this_.prvOnError(e); };
}


MyImage.prototype.prvRemoveEventListeners =
function()
{
this.image.onload = null;
this.image.onerror = null;
this.image.onabort = null;
}


MyImage.prototype.prvOnLoad =
function(e)
{
alert(this + "::prvOnLoad() - says:" + this.something);
this.prvRemoveEventListeners();
}


MyImage.prototype.prvOnError =
function(e)
{
alert(this + "::prvOnError() - says:" + this.something);
this.prvRemoveEventListeners();
}


MyImage.prototype.load =
function(src,
alt)
{
this.prvAttachEventListeners();
this.image.alt = alt;
this.image.src = src;
}
-------------




Here is how one can use it:
-------------
function foo()
{
var i = new MyImage("Hello World: Images!");
i.load("http://www.google.com/intl/en_ALL/images/srpr/logo1w.png", "alt
text 1");
}
-------------


My focus is on the "private" method
`MyImage.prototype.prvAttachEventListeners'... Will create a closure that
leaks `this'?
 
R

RobG

I am learning HTML/JavaScript... Okay...

Let me start out with some code... The following describes a very simple
"MyImage" object that has but a single "public" method "load(src, alt)".
However, it has two internal methods "prvOnLoad(e)/prvOnError(e)" that
belong to an internal object "this.image" in a sense: Once a user calls
`MyImage.load(src, alt)', the object will setup internal listeners via
`MyImage.prvAttachEventListeners()'. In addition, once an event fires, it
"disables" further events by setting `this.image.onload/error/abort'
callbacks to null:

// MyImage.js
-------------
function MyImage(something)
{
this.image = new Image();
this.something = something;
}

MyImage.prototype.prvAttachEventListeners =


Starting a property name with "prv" doesn't make it private. This
method and all the other functions added to MyImage.prototype are
public.

function()
{
var this_ = this;

Interesting version of this/that/_theOther. ;-).

this.image.onload = function(e) { this_.prvOnLoad(e); };
this.image.onerror = function(e) { this_.prvOnError(e); };
this.image.onabort = function(e) { this_.prvOnError(e); };
}

MyImage.prototype.prvRemoveEventListeners =
function()
{
this.image.onload = null;
this.image.onerror = null;
this.image.onabort = null;
}

Ok, a bit simplistic.

MyImage.prototype.prvOnLoad =
function(e)
{
alert(this + "::prvOnLoad() - says:" + this.something);
this.prvRemoveEventListeners();
}

MyImage.prototype.prvOnError =
function(e)
{
alert(this + "::prvOnError() - says:" + this.something);
this.prvRemoveEventListeners();
}

MyImage.prototype.load =
function(src,
alt)
{
this.prvAttachEventListeners();
this.image.alt = alt;
this.image.src = src;
}
-------------

Here is how one can use it:
-------------
function foo()
{
var i = new MyImage("Hello World: Images!");
i.load("http://www.google.com/intl/en_ALL/images/srpr/logo1w.png", "alt
text 1");}

There are no private methods, they're all public.

`MyImage.prototype.prvAttachEventListeners'... Will create a closure that
leaks `this'?

I don't understand the phrase "a closure that leaks 'this'".

The listeners have a closure to the instance of MyImage that set them.
That kind of structure (circular references involving DOM objects)
used to cause memory leaks in IE. While those issues have been fixed
(more or less), javascript developers tend to be wary of using
approaches that keep unnecessary references. Given the simplistic
approach of the above, a "minimise references" approach might be:

MyImage.prototype.attachEventListeners = function() {
var this_ = this;
this.image.onload = this_.prvOnLoad;
this.image.onerror = this_.prvOnError;
this.image.onabort = this_.prvOnError;
};


There are other approaches, e.g. the following uses a button element
but it shows the concept:

function Button(id) {
this.element = document.createElement('button');

// No closure here as an object is returned, not a function
this.element.id = id;
}

Button.prototype.setCaption = function(caption) {
var el = this.element;
while (el.firstChild) {
el.removeChild(el.firstChild);
}
el.appendChild(document.createTextNode(caption));

// Memory leak paranoia - remove unnecessary reference
// However there should be no closure as nothing is returned
// and there is no local or inner function object involved to
// create a closure with el
el = null;
}

Button.prototype.addListener = function(type, listener) {

// Assign a function reference so don't cause a closure.
this.element['on' + type] = listener;
}

Button.prototype.removeListener = function(type) {
this.element['on' + type] = null;
}

Button.prototype.appendTo = function(element) {
element.appendChild(this.element);
}

// Function expects to be attached to on<event> property so - this -
// will reference the element it's attached to
function showID() {
alert(this.id);
}

// Call after document loaded, say body@onload
function addButton(id) {
var oButton = new Button(id);
oButton.setCaption('Click me!!');
oButton.addListener('click', showID);
oButton.appendTo(document.getElementsByTagName('body')[0]);
}

If you want to have some kind of persistant reference to the element
added this way, keep them in an array or object structure perhaps
indexed by id or other unique property. HTML5 makes that easy with
data- attributes:

<URL: http://www.w3.org/TR/html5/elements.html#embedding-custom-non-visible-data-with-the-data-attributes
jQuery uses such a scheme with a non-standard jQuery<numeric string>
property (I wonder how long it will be before it has a data- prefix),
David Mark's MyLibrary avoids that approach (apparently, I haven't
checked it out that thoroughly).
 
D

David Mark

Starting a property name with "prv" doesn't make it private. This
method and all the other functions added to MyImage.prototype are
public.


Interesting version of this/that/_theOther. ;-).



Ok, a bit simplistic.







There are no private methods, they're all public.


I don't understand the phrase "a closure that leaks 'this'".

The listeners have a closure to the instance of MyImage that set them.
That kind of structure (circular references involving DOM objects)
used to cause memory leaks in IE. While those issues have been fixed
(more or less), javascript developers tend to be wary of using
approaches that keep unnecessary references. Given the simplistic
approach of the above, a "minimise references" approach might be:

  MyImage.prototype.attachEventListeners = function() {
      var this_ = this;
      this.image.onload = this_.prvOnLoad;
      this.image.onerror = this_.prvOnError;
      this.image.onabort = this_.prvOnError;
  };

There are other approaches, e.g. the following uses a button element
but it shows the concept:

  function Button(id) {
    this.element = document.createElement('button');

I hope you don't mind if I throw in an obscure and unrelated piece of
advice. Oddly enough, the above line should always be followed by
something like this:-

this.element.setAttribute('type', 'button');

....but not:-

this.element.type = 'button';

This is that rarest of situations where setAttribute is needed. I
haven't looked into why this might be. Perhaps the type property is
not standard for BUTTON elements (but that's just a guess). The
reality is that at least some Webkit-based browsers will treat the
element as a submit button.
    // No closure here as an object is returned, not a function
    this.element.id = id;
  }

  Button.prototype.setCaption = function(caption) {
    var el = this.element;
    while (el.firstChild) {
      el.removeChild(el.firstChild);
    }
    el.appendChild(document.createTextNode(caption));

    // Memory leak paranoia - remove unnecessary reference
    // However there should be no closure as nothing is returned
    // and there is no local or inner function object involved to
    // create a closure with el

Yes, that paranoid pattern has been copied everywhere. The next line
is superfluous.
    el = null;
  }

  Button.prototype.addListener = function(type, listener) {

    // Assign a function reference so don't cause a closure.
    this.element['on' + type] = listener;
  }

  Button.prototype.removeListener = function(type) {
    this.element['on' + type] = null;
  }

  Button.prototype.appendTo = function(element) {
    element.appendChild(this.element);
  }

  // Function expects to be attached to on<event> property so - this -
  // will reference the element it's attached to
  function showID() {
    alert(this.id);

What a cool coincidence. Now I can bring up the other obscure Webkit
but I've observed recently. Watch out for timeouts firing prematurely
after this host method returns. I really should report that one.
Only way around it is with primitive semaphores. One school of
thought says to always substitute an uber-cool, scripted DIV for the
built-in alert. But I didn't attend that school. The built-in alert
is often the best choice (works well almost everywhere, with virtually
no effort).
  }

  // Call after document loaded, say body@onload
  function addButton(id) {
    var oButton = new Button(id);
    oButton.setCaption('Click me!!');
    oButton.addListener('click', showID);
    oButton.appendTo(document.getElementsByTagName('body')[0]);
  }

If you want to have some kind of persistant reference to the element
added this way, keep them in an array or object structure perhaps
indexed by id or other unique property. HTML5 makes that easy with
data- attributes:

<URL:http://www.w3.org/TR/html5/elements.html#embedding-custom-non-visible...



jQuery uses such a scheme with a non-standard jQuery<numeric string>
property (I wonder how long it will be before it has a data- prefix),
David Mark's MyLibrary avoids that approach (apparently, I haven't
checked it out that thoroughly).

Not entirely. It has an elementUniqueId function that is used by some
functions and it will create an expando in all but IE. Of course, it
could have been written to use the ID (if present) and fall back to
the expando.

Personally, I'd just make sure that any element used in such a way has
an ID, which eliminates the need for the additional code. Of course,
that's not what GP libraries are about (which is why they are
generally inappropriate for browser scripting).
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,767
Messages
2,569,572
Members
45,045
Latest member
DRCM

Latest Threads

Top