Fuite de mémoire Javascript lors de la modification des données du modèle dans KnockoutJS

Nous construisons une application assez large d'une page avec KnockoutJS comme «gestionnaire de données».

Le problème est que lorsque vous modifiez les données du modèle, les anciens modèles ne sont pas éliminés par le collecteur d'ordures (tel qu'il le semble).

L'application a environ 12 modèles différents avec des obserts calculés dont vous pouvez récupérer les relations.

Dans le ViewModel, j'ai un tableau observable pour chacun des modèles. Disons que je remplis un tableau avec 100 instances d'un modèle. Lorsque je souhaite modifier ces 100 à 100 instances différentes, la mémoire augmente et elle ne descend jamais (j'utilise Chrome et vérifie le Gestionnaire des tâches Windows).

L'application est assez complexe, mais je vais donner quelques exemples de ce que je fais (le code est copié et simplifié pour ne montrer que quelques exemples, certains du code peuvent sembler étranges, mais cela est problablement parce que j'ai supprimé les espaces de noms et autres) .

ViewModel:

var ViewModel = (function () { var _departments = ko.observableArray(), _addresses = ko.observableArray(); var UpdateData = function (data) { if (typeof data.departments != 'undefined') { var mappedData = ko.utils.arrayMap(data.departments, function(item) { return new Department(item); }); _departments(mappedData); } if (typeof data.addresses != 'undefined') { var mappedData = ko.utils.arrayMap(data.addresses , function(item) { return new Address(item); }); _addresses (mappedData ); } }; return { departments: _departments, addresses: _addresses, UpdateData: UpdateData }; })(); 

Modèle de département

 var Department = function (data) { var Department = { Id: ko.observable(data.ClusterId), Number: ko.observable(data.Number), Name: ko.observable(data.Name) }; var Addresses = ko.computed(function () { return ko.utils.arrayFilter(ViewModel.addresses(), function (address) { return address.DepartmentId() === Department.Id(); }).sort(function (a, b) { return a.Number() < b.Number() ? -1 : 1; }); }, Department); var Update = function (data) { Department.Id(data.Id); Department.Number(data.Number); Department.Name(data.Name); }; $.extend(Department, { Addresses: Addresses, Update: Update }); return Department; }; 

Modèle d'adresse

 var Address = function (data) { var Address = { Id: ko.observable(data.Id), Number: ko.observable(data.Number), Text: ko.observable(data.Text), DepartmentId: ko.observable(data.DepartmentId) }; var Department = ko.computed(function () { return ko.utils.arrayFirst(ViewModel.departments(), function (item) { return item.Id() == Address.DepartmentId(); }); }, Address); var Update = function (data) { Address.Id(data.Id); Address.Number(data.Number); Address.Text(data.Text); Address.DepartmentId(data.DepartmentId); }; $.extend(Address, { Department: Department, Update: Update }); return Address; }; 

Il y a beaucoup plus de code, mais ce que je cherche, c'est une façon de commencer à trouver la fuite (j'ai essayé de le trouver depuis quelques heures maintenant). Y at-il quelque chose dans mon exemple qui pourrait l'entraîner? Ou quelqu'un d'autre a-t-il eu ce type de problème? L'application dispose également de plusieurs liaisons personnalisées, mais j'ai essayé de supprimer le code HTML qui utilise les liaisons et il semble que l'objet javascript "brut" soit ce qui prend la mémoire.

Si je modifie les variables observables et calculées dans les modèles à des fonctions qui renvoient une valeur statique, les objets semblent être éliminés.

Les computed's dans chaque Department et Address garderont leur abonnement au modèle de vision jusqu'à ce que vous permettez d'échanger les deux ministères et les adresses. Par exemple, je m'attends à ce que cela fasse de la mémoire:

 ViewModel.UpdateData({ departments: [ /* some new departments */ ] }); ViewModel.UpdateData({ departments: [ /* some new departments */ ] }); ViewModel.UpdateData({ departments: [ /* some new departments */ ] }); ViewModel.UpdateData({ departments: [ /* some new departments */ ] }); ViewModel.UpdateData({ departments: [ /* some new departments */ ] }); ViewModel.UpdateData({ departments: [ /* some new departments */ ] }); ViewModel.UpdateData({ departments: [ /* some new departments */ ] }); ViewModel.UpdateData({ departments: [ /* some new departments */ ] }); 

Dans ce cas, tous les «anciens» départements seront toujours liés à votre ViewModel.Addresses . La modification des Addresses devrait ensuite libérer la mémoire:

 ViewModel.UpdateData({ addresses: [ /* some new addresses */ ]}); // all the old departments should get GC'd now. 

Une façon de résoudre ceci est de modifier votre ViewModel pour disposer les anciens objets avant de les supprimer.

 var ViewModel = (function () { var _departments = ko.observableArray(), _addresses = ko.observableArray(); var UpdateData = function (data) { if (typeof data.departments != 'undefined') { var mappedData = ko.utils.arrayMap(data.departments, function(item) { return new Department(item); }); // dispose of the computeds in the old departments ko.utils.arrayForEach(_departments(), function (d) { d.Addresses.dispose(); }); _departments(mappedData); } if (typeof data.addresses != 'undefined') { var mappedData = ko.utils.arrayMap(data.addresses , function(item) { return new Address(item); }); // dispose of the computeds in the old addresses ko.utils.arrayForEach(_addresses(), function (a) { a.Department.dispose(); }); _addresses (mappedData ); } }; return { departments: _departments, addresses: _addresses, UpdateData: UpdateData }; })(); 

Lisez la documentation calculée Knockout . En fait, faites défiler vers le bas et lisez la documentation pour dispose . Assurez-vous d' dispose les calculs que vous créez s'ils sont enlevés, mais les éléments d'observation dont ils dépendent ne sont pas supprimés.