Knockout ViewModel computed garbage collection

烂漫一生 提交于 2020-01-24 04:56:11

问题


I've been trying to track down any issues with garbage collection within my application code. I've stripped it down to pure knockout code and there seems to be an issue collecting created objects depending on how a computed property is created.

Please see the following JS fiddle: http://jsfiddle.net/SGXJG/

  1. Open Chrome profiler.
  2. Take a heap snapshot
  3. Click Make All
  4. Take another heap snapshot
  5. Compare the snapshots
  6. Test2 & Test3 still remain in memory while Test1 is correctly collected.

Please see the following code for the viewmodel:

function ViewModel() {
    this.test1 = null;
    this.test2 = null;
    this.test3 = null;
}
ViewModel.prototype = {
    makeAll: function () {
        this.make1();
        this.make2();
        this.make3();
    },
    make1: function () {
        this.test1 = new Test1();
        this.test1.kill();
        delete this.test1;
    },
    make2: function () {
        this.test2 = new Test2();
        this.test2.kill();
        delete this.test2;
    },
    make3: function () {
        this.test3 = new Test3();
        this.test3.kill();
        delete this.test3;
    },
};
ko.applyBindings(new ViewModel());

And here are the three tests classes:

function Test1() {
    var one = this.one = ko.observable();
    var two = this.two = ko.observable();
    this.three = ko.computed(function () {
        return one() && two();
    });
}
Test1.prototype = {
    kill: function () {
        this.three.dispose();
    }
};

function Test2() {
    this.one = ko.observable();
    this.two = ko.observable();
    this.three = ko.computed(function () {
        return this.one() && this.two();
    }, this);
}
Test2.prototype = {
    kill: function () {
        this.three.dispose();
    }
};

function Test3() {
    var self = this;
    self.one = ko.observable();
    self.two = ko.observable();
    self.three = ko.computed(function () {
        return self.one() && self.two();
    });
    self.kill = function () {
        self.three.dispose();
    };
}

The difference being that Test1 'three' computed does not use this or self to reference the 'one' & 'two' observable properties. Can someone explain what's happening here? I guess there's something in the way the closures contain the object reference but I don't understand why

Hopefully I've not missed anything. Let me know if I have and many thanks for any responses.


回答1:


Chrome, like all modern browsers, uses a mark-and-sweep algorithm for garbage collection. From MDN:

This algorithm assumes the knowledge of a set of objects called roots (In JavaScript, the root is the global object). Periodically, the garbage-collector will start from these roots, find all objects that are referenced from these roots, then all objects referenced from these, etc. Starting from the roots, the garbage collector will thus find all reachable objects and collect all non-reachable objects.

The garbage collector doesn't run right away, which is why you still see the objects in Chrome's snapshot even though you've dereferenced them (edit: As mentioned here, running the heap snapshot first runs the garbage collector. Possibly it's not processing everything and so doesn't clear the objects; see below.)

One thing that seems to generally trigger the garbage collector is to create new objects. We can verify this using your example. After going through the steps in your question, click on "Make1" and take another heap snapshot. You should see that Test2 is gone. Now do it again, and you'll see that Test3 is also gone.

Some more notes:

  1. Chrome doesn't clean up everything during garbage collection. "[Chrome]...processes only part of the object heap in most garbage collection cycles." (google.com) Thus we see that it takes a couple runs of the garbage collector to clear everything.

  2. As long as all external references to a object are cleared, the object will be cleaned up by the garbage collector eventually. In your example, you can even remove the dispose calls, and all the objects will be cleared.




回答2:


I think this is a classical loop reference problem.

Let's call:

var test2 = new Test2();

now test2.three holds a reference of test2! Because you literally asked knockout to bind a function(){...} with that "this" object, the test2 object.

Since test2 naturally holds a reference of test2.three, you now got a loop reference between the two objects!

You can see this is same for your Test3.

But for Test1, let's call:

var test1 = new Test1();

test1.three holds references of two objects (test1.one and test2.two), test1 holds three references (test1.one, test1.two and test1.three), there is no loop reference.

In some other languages like Java and Objective-C, the language supports weak reference to deal with this kind of issue. But so far, weak reference not implemented in Javascript.

+1 thanks for your question! It gave my brain some spin, helped me to understand Javascript more :)




回答3:


I think the problem is that you use && in your code, it will return a boolean, properly "true".

this.three = ko.computed(function () {
    //                 !
    return this.one() && this.two();
},

So this.three == true and not self.one + self.two if this was the intention? and when you dispose

this.three.dispose();

You just get rid of a boolean.

Is there a reason why you have an extra "this" in "function Test2()"?



来源:https://stackoverflow.com/questions/22730292/knockout-viewmodel-computed-garbage-collection

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!