【发布时间】:2026-01-10 15:20:06
【问题描述】:
如果视图模型是使用 knockout.mapping 插件创建的,则无法弄清楚为什么处理计算的 observable 不会从全局变量中删除订阅。
首先让我们看看直接创建模型时会发生什么:
// 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>
我在多次添加和删除项目时使用 Chrome 开发工具记录堆分配。每次添加后,之前分配的对象都被清理成功,得到如下图:
现在使用映射插件具有相同的功能:
// 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>
使用相同的技术记录堆分配这是我所看到的:
我知道pureComputed,但出于两个原因想避免使用它们:
- 切换到纯计算会通过抛出异常来破坏遗留代码:
'纯'计算不能被递归调用
解决这些问题需要很长时间。
- 更频繁地评估纯计算,这会产生一些我想避免的性能开销,并且这再次对遗留代码产生不可预测的影响。
此外,我仍然想使用映射插件,因为它能够监控集合状态(使用 key 映射属性)并且因为它为我创建了所有可观察对象。
那么有什么我错过的吗?在使用映射插件的情况下释放资源的正确方法是什么?
【问题讨论】:
标签: javascript knockout.js knockout-mapping-plugin