Strange memory leak in knockout display plugin

It is impossible to understand why disposing of computed observables does not remove subscriptions from global variables in the case view model created using the knockout.mapping plugin.
First, let's see what happens when a model is created directly:

// Global variable. var Environment = { currencyStr: ko.observable("usd.") }; // Item model, used intensively. function ItemModel(price) { var self = this; this.price = ko.computed(function () { // Computed is subscribed to global variable. return price + ' ' + Environment.currencyStr(); }); }; ItemModel.prototype.dispose = function () { // Dispoing subscription to global variable. this.price.dispose(); }; function ViewModel() { var self = this; self.items = ko.observableArray([]); // Simply adds 1000 new items to observable array directly. self.addItems = function () { for (var i = 0; i < 1000; i++) { self.items.push(new ItemModel(i)); } }; // Disposes and removes items from observable array this.removeItems = function () { ko.utils.arrayForEach(self.items(), function (item) { item.dispose(); }); self.items.removeAll(); }; }; ko.applyBindings(new ViewModel()); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script> <button data-bind="click: addItems">Add items</button> <button data-bind="click: removeItems">Remove items</button> <div data-bind="foreach: items"> <div> <span data-bind="text: price"></span> </div> </div> 

I used Chrome dev tools to record heap distributions while adding and removing items. After each addition, previously selected objects were successfully cleaned, I got the following image:

enter image description here

Now the same functionality using the mapping plugin:

 // Global variable. var Environment = { currencyStr: ko.observable("usd.") }; // Item model, used intensively. function ItemModel(price) { var self = this; this.price = ko.computed(function () { // Computed is subscribed to global variable. return price + ' ' + Environment.currencyStr(); }); }; ItemModel.prototype.dispose = function () { // Dispoing subscription to global variable. this.price.dispose(); }; function ViewModel() { var self = this; self.items = ko.observableArray([]); self.itemsMapping = { 'create': function (options) { return new ItemModel(options.data); } }; // Simply adds 1000 new items to observable array using mapping plugin. self.addItems = function () { var itemsPrices = new Array(1000); for (var i = 0; i < 1000; i++) { itemsPrices[i] = i; } // Mapping new array to our observable array. ko.mapping.fromJS(itemsPrices, self.itemsMapping, self.items); }; // Disposes and removes items from observable array this.removeItems = function () { ko.utils.arrayForEach(self.items(), function (item) { item.dispose(); }); self.items.removeAll(); }; }; ko.applyBindings(new ViewModel()); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script> <button data-bind="click: addItems">Add items</button> <button data-bind="click: removeItems">Remove items</button> <div data-bind="foreach: items"> <div> <span data-bind="text: price"></span> </div> </div> 

Using the same method to write heap distributions, this is what I see:

enter image description here

I know pureComputed , but would like to avoid using them for two reasons:

  • Switching to clean computed interrupt code for legacy code by throwing exceptions:

    "Computed" net "should not be called recursively

Solving these problems will take a long time.

  1. Clean calculations are calculated more often, which creates some performance overhead that I would like to avoid, and again, this will unpredictably affect legacy code.

I would also like to use the mapping plugin because of its ability to control the state of the collection (using the key display property) and because it creates all observables for me.

So, is there something I missed and what is the right way to free up resources if using a map plugin?

+6
source share
1 answer

Digging into the display plugin, it does some hacking calculated and obviously breaks them in this case.

It seems that setting a price calculated on deferEvaluation makes the display plugin basically leave it alone.

 this.price = ko.computed(function () { // Computed is subscribed to global variable. return price + ' ' + Environment.currencyStr(); }, this, { deferEvaluation: true }); 
+4
source

Source: https://habr.com/ru/post/979652/


All Articles