views:

250

answers:

3

I have a problem with dereferencing a Javascript object and setting it to NULL.

Here, I have a Folder implementation that supports recursive subdirectory removal. Please see my comments to understand my dilemma.

function Folder(name, DOM_rows) {
    this.name = name;
    this.files = [].concat(DOM_rows);
    this.subdirs = [];
}

Folder.prototype.AddDir(name, DOM_rows) {
   this.subdirs.push(new Folder(name, DOM_rows));
}

Folder.prototype.RemoveDir(folder) {
   var stack = [folder];
   while(stack.length > 0) {
      var cur = stack.pop();
      // do a post-order depth-first traversal, so dig to the deepest subdir:
      if(cur.subdirs.length > 0) {
          while(cur.subdirs.length > 0) { stack.push(cur.subdirs.pop()); }
      } else {
          // arrived at a leaf-level:
          cur.files = null;
          // now how do I delete cur from it's parent's subdirs array?
          // the only way I know how is to keep a "cur.parentDir" reference,
          // then find parent.subdirs[ index of cur ] and slice it out.
          // How can I do the JS-equivalent of *cur = NULL?
      }
   }
}
+1  A: 

Everything is javascript is passed by value, so "*cur=NULL" is not possible. You basically have the following options here

  • use parentID as you suggested
  • if your Folder hierarchy has a well-known root, browse from that root to find the parent object
  • use something like DOM removeChild (which is called on parent), instead of removeNode (which is called on the node itself).
stereofrog
Do you mean passed-by-reference?
Jeff Meatball Yang
No, there are no references (in c++ sense) in javascript - function arguments are always values.
stereofrog
To expand: Javascript is pass-by-value, but objects are stored by reference. It's sometimes called "pass by reference value". This is why the affect of `function foo(obj) {obj.qux = 1;}` is visible outside a call to `foo`, but not `function bar(obj) {obj = {qux: 1};}`.
outis
@outis: Agree, only primitive arguments (`string`, `number`, `boolean`, `null` and `undefined`) are passed "by value", objects are passed "by reference" (or as you said, the reference is passed as value :).
CMS
I really really wish we had pass by reference reference. :)
Jeff Meatball Yang
+1  A: 

Note that you don't have as big a problem as you suspect, since all subdirectories but folder in your RemoveDir will be deleted from their parent's subdir by the stack.push(cur.subdirs.pop()); line

To find a subdirectory in a parent, you could make use an object-as-dictionary rather than an array for subdirs:

function Folder(name, DOM_rows, parent) {
    this.name = name;
    this.parent = parent;
    this.files = [].concat(DOM_rows);
    this.subdirs = {};
    this.subdirCount = 0;
}

Folder.prototype.AddDir = function (name, DOM_rows) {
    if (this.subdirs[name]) {
        return null;
    }
    ++this.subdirCount;
    return this.subdirs[name] = new Folder(name, DOM_rows, this);
}

Given a folder, you can remove the folder from the parent with:

delete folder.parent.subdirs[folder.name];

Here's a preorder version:

Folder.prototype.RemoveDir = function (folder) {
  if (this.subdirs[folder.name]) {
      var stack = [folder];
      while(stack.length > 0) {
          var cur = stack.pop();
          // pre-order
          delete cur.files;
          // if there's other processing to be done, now's the time to do it
          for (subdir in cur.subdirs) {
              stack.push(cur.subdirs[subdir]);
              delete cur.subdirs[subdir];
          }
          // it's unnecessary to set subdir count, since 'cur' has been deleted
          //cur.subdirCount = 0;
      }
      delete this.subdirs[folder.name];
      --this.subdirCount;
  }
}

And the recursive post-order version:

Folder.prototype.RemoveChildren = function () {
    for (subdir in this.subdirs) {
        this.RemoveDir(this.subdirs[subdir]);
    }
}

Folder.prototype.RemoveDir = function (folder) {
    if (this.subdirs[folder.name]) {
        folder.RemoveChildren();
        folder.files = [];
        delete this.subdirs[folder.name];
        --this.subdirCount;
    }
}

And the iterative post-order version:

Array.prototype.top = function () { return this[this.length-1]; }

Folder.prototype.RemoveDir = function (folder) {
  if (this.subdirs[folder.name]) {
      var stack = [folder];
      while(stack.length > 0) {
          var cur = stack.top();
          if (cur.subdirCount > 0) {
              for (subdir in cur.subdirs) {
                  stack.push(cur.subdirs[subdir]);
                  delete cur.subdirs[subdir];
              }
              cur.subdirCount = 0;
          } else {
              stack.pop();
              delete cur.files;
              // other post-order processing
          }
      }
      delete this.subdirs[folder.name];
  }
}

Though, unless you need to take additional steps when processing deleted files & folders, a simple:

Folder.prototype.RemoveDir = function (folder) {
  delete this.subdirs[folder.name];
}

should suffice.

outis
Thanks outis - You went to town on this - and +1 for the .top() prototype. I need to release references to DOM elements, so I can't just delete the reference to the folder, lest I risk memory leaks in IE.
Jeff Meatball Yang
+1  A: 

I was trying to do the same thing today. I've worked around it by storing the object's index as a property of the object itself.

When you add it:

myObj.ID = myArr.push(myObj);

So to remove it you

myArr[myObj.ID] = null;

I guess you solved it by now, but you could do almost the same; and it's simpler than using objects.

DasRakel