如果两个依赖路径改变了 observable,则敲除计算会在单个动作上多次触发

Knockout computed triggering multiple times on a single action if two dependence paths to changed observable

我有一个绑定到 UI 元素的可观察 A。 我还有一个依赖于 A 的计算 B。 我有一个计算出的 С,它同时依赖于 A 和 B。 我订阅了 C

当 UI 元素中的值发生变化时,会计算两次并调用两次订阅。

我认为原因是A有两个订阅: 答:[B,C]
Knockout 通知 B 关于 A 的变化。
在 B 被评估后,它通知 C 关于 B
的变化 然后它回到开始并调用 A 的第二个订阅 C.
这里我们有两次调用 C.

有什么办法可以避免这种情况吗?

var viewModel = {
    firstName: ko.observable("Andrew"),
    lastName: ko.observable("King"),
};

viewModel.fullName = ko.computed(function() {
    return viewModel.firstName() + " " + viewModel.lastName();
});

viewModel.user = ko.computed(function() {
    return {
    fullName: viewModel.fullName(),
    lastName: viewModel.lastName()
  };
});

viewModel.user.subscribe(function() {
    // This is called once if I change first name
    // It is called twice if I change last name
});

http://jsfiddle.net/jngxwf5v/

可观察对象在其依赖项之一发生更改时重新计算。由于您要在每个 运行 上创建一个新对象,因此 knockout 无法判断是否确实发生了变化。

使用延迟计算修复它

为了防止它在其两个依赖项都发生变化时多次 运行ning,您可以将其延迟:

var viewModel = {
    firstName: ko.observable("Andrew"),
    lastName: ko.observable("King"),
};

viewModel.fullName = ko.computed(function() {
    return viewModel.firstName() + " " + viewModel.lastName();
});

viewModel.user = ko.computed(function() {
    return {
    fullName: viewModel.fullName(),
    lastName: viewModel.lastName()
  };
}).extend({ deferred: true });

viewModel.user.subscribe(function(user) {
  console.log(user);
});

viewModel.firstName("John");
viewModel.lastName("Doe");
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

使用自定义相等比较器修复它

解决此问题的另一种方法是添加自定义相等比较器。这让 knockout 检查,当依赖关系发生变化时,新结果是否真的与前一个不同。仅当两者不同时,订阅者才会更新。

var viewModel = {
    firstName: ko.observable("Andrew"),
    lastName: ko.observable("King"),
};

viewModel.fullName = ko.computed(function() {
    return viewModel.firstName() + " " + viewModel.lastName();
});

viewModel.user = ko.computed(function() {
  return {
    fullName: viewModel.fullName(),
    lastName: viewModel.lastName()
  };
});

viewModel.user.equalityComparer = (x, y) => x === y || x.fullName === y.fullName && x.lastName === y.lastName;

viewModel.user.subscribe(console.log);

viewModel.lastName("Doe");
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

两种方法的区别

在延迟示例中,敲除排序将计算的重新执行推到 setTimeout。它只会 运行 一次,但你不会知道 "when".

在第二个例子中,计算函数被调用了两次(和之前一样)。唯一的区别是不会通知订阅者,因为这两个结果被认为是相等的。