3

I had a question, and wondered if you called setTimeout from an object, then deleted the object, would the setTimeout callback still be called?

Apparently yes.

var container = {
    timer: { // the object to be deleted
        start: function() {
            console.log('start');
            setTimeout(this.end, 2000);
        },
        end: function() {
            console.log('end');
        },
    },

    timerStart: function() { 
        this.timer.start();
        setTimeout(this.timerDelete, 1000);
    },

    timerDelete: function() {
        console.log(delete this.timer);
        console.log('deleted timer');
    },
};

After calling container.timerStart(); I recieve the following:

> container.timerStart();
  start
< undefined
  true
  deleted timer
  end

Therefore showing that the object container.timer was successfully deleted, but also that container.timer.end was also called after container.timer was deleted. I understand that delete only removes the reference, and once all references of an object are removed it is removed from memory, but does this mean that setTimeout also stores a reference to its callback?

In essence, my questions are:

  1. Is container.timer actually deleted?
  2. Why does the setTimeout callback container.timer.end still run?
  3. How does setTimeout actually work with reference to this behaviour?

Any feedback or reading resources are greatly appreciated. Thanks!

dwb
  • 2,136
  • 13
  • 27
  • I think this has to do with how garbage collector works in modern languages. A var is deleted only if there are no references left to it. You may here call `delete` on the var object, but a previous call to `setTimeout` with the same var object still keeps a reference to the object. It will be deleted (in memory) when there will be no more references to it – Pierre Mar 31 '20 at 11:38
  • Even though `timerDelete` is no longer part of the object, it still exists on the heap and the callback still has a reference to it. – phuzi Mar 31 '20 at 11:38
  • 1
    @Pierre that's not how garbage collectors work, or cyclic references not accessible from the GC root would create memory leaks. Modern garbage collectors use mark and sweep algorithm. Also another nit. Memory is not collected immediately when it becomes inaccessible, it is only _subject_ to collection, and is collected at a later non-deterministic time when the garbage collector decides that it's a good time to sweep. – Patrick Roberts Mar 31 '20 at 11:41
  • 1
    FYI, because you called `setTimeout(this.timerDelete, 1000)` instead of `setTimeout(() => this.timerDelete(), 1000)`, `this` in the body of `timerDelete` refers to `window` instead of `container`, so after 1 second, the `timer` property won't actually be deleted from `container`. Even if you change that though, the log output will be the same. – Patrick Roberts Mar 31 '20 at 11:53

1 Answers1

4

does this mean that setTimeout also stores a reference to its callback?

Yes, that's exactly what's going on. It's somewhat similar to:

const obj = {
  fn: () => console.log('fn')
};
const fn = obj.fn;
delete obj.fn;
fn();

Is container.timer actually deleted?

In the sense that container no longer has a timer property, yes, but the timer function still exists, since setTimeout still holds a reference to it.

Why does the setTimeout callback container.timer.end still run?

Because it, too, was queued up in setTimeout. The start runs immediately, which does

setTimeout(this.end, 2000);

so setTimeout saves a reference to the function in this.end and calls it after a couple seconds.

Something will only be garbage collected once nothing holds a reference to it anymore (except possibly a circular reference). If something is stored in a closure or in a callback, and the callback can still be referenced, it won't be GC'd, at least not until nothing can reference it anymore.

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320