在 KnockoutJS 中更改模型数据时发生 Javascript 内存泄漏

标签 javascript knockout.js memory-leaks

我们正在构建一个相当大的单页应用程序,使用 KnockoutJS 作为“数据处理程序”。

问题在于,当更改模型数据时,旧模型不会被垃圾收集器处理(看起来如此)。

该应用程序有大约 12 个不同的模型,这些模型具有您可以检索关系的计算可观察值。

在 ViewModel 中,每个模型都有一个可观察数组。假设我用一个模型的 100 个实例填充了一个数组。当我稍后想要更改这 100 到 100 个不同的实例时,内存会增加并且永远不会减少(我正在使用 Chrome 并在 Windows 任务管理器中检查)。

该应用程序非常复杂,但我会给出一些我正在做的事情的例子(代码被复制和简化以仅显示一些例子,一些代码可能看起来很奇怪但它可能是因为我删除了命名空间和其他东西)。

View 模型:

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
    };

})();

部门模型

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;

};

地址模型

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;

};

还有很多代码,但我正在寻找一种开始查找泄漏的方法(我已经尝试了几个小时才找到它)。我的例子中有什么可能导致它吗?或者有没有其他人遇到过这种问题?该应用程序还具有多个自定义绑定(bind),但我已尝试删除使用这些绑定(bind)的 HTML,而且似乎是“原始”javascript 对象占用了内存。

如果我将模型中的可观察和计算变量更改为返回静态值的函数,则对象似乎已被处置。

最佳答案

每个 DepartmentAddress 中的 computed's 将保持对 View 模型的订阅,直到您换出部门和地址。例如,我希望这会泄漏内存:

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 */ ] });

在这种情况下,所有“旧”部门仍将绑定(bind)到您的 ViewModel.Addresses。更改 Addresses 应该会释放内存:

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

解决此问题的一种方法是修改您的 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);
        });

        // 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
};

})();

阅读Knockout Computed Documentation .具体滚动到底部并阅读 dispose 的文档。确保您处理您创建的任何计算,如果它们被删除但它们所依赖的可观察对象没有被删除。

关于在 KnockoutJS 中更改模型数据时发生 Javascript 内存泄漏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20049338/

相关文章:

memory-leaks - 为什么 Java11 将 java.util.zip.ZipFile$Source 保留在堆上?

javascript - 确定字符串是否在 JavaScript 中的列表中

javascript - Jquery 是如何使用美元符号的?

jquery-templates - 如何获取要在 {{if}} 中使用的数据项的值?

asp.net-mvc - Upshot.js 的当前状态

c - free() 是在所有深度还是仅在最高级别释放内存?

c - C中的内存泄漏,函数内部的malloc

javascript - 无法访问 JS 对象中的值

asp.net - 如何捕获提交表单响应 "different domain"?

javascript - Knockout.js + Bootstrap - 奇怪的事情发生