Why does a function in a child view model causes a

2019-08-23 09:56发布

问题:

Pushing the button in the following code does 2 things: It executes a function in a nested view model, and also makes a computed in the parent view model to execute.

var MasterViewModel = function () {
  var self = this;
  self.nested = new FirstViewModel();
  self.func = ko.computed (function() {
    var items = self.nested.array();
    alert("executed");
  });
}
var FirstViewModel = function () {
  var self = this;
  self.array = ko.observableArray([]);
  self.push = function () {
    self.array.push(new SecondViewModel());
    alert("pushed");
  }
}

var SecondViewModel = function () {
  var self = this;
  self.z = ko.observable();
}

var mvm = new MasterViewModel();
ko.applyBindings(mvm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
  <div data-bind="with: nested">
    <button data-bind="text: 'push me', click: push"></button>
  </div>

However, if the computed is changed to a simple function, it doesn't execute when the button is pushed. Why?

var MasterViewModel = function () {
  var self = this;
  self.nested = new FirstViewModel();
  self.func = function() {
    var items = self.nested.array();
    alert("executed");
  };
}
var FirstViewModel = function () {
  var self = this;
  self.array = ko.observableArray([]);
  self.push = function () {
    self.array.push(new SecondViewModel());
    alert("pushed");
  }
}

var SecondViewModel = function () {
  var self = this;
  self.z = ko.observable();
}

var mvm = new MasterViewModel();
ko.applyBindings(mvm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
  <div data-bind="with: nested">
    <button data-bind="text: 'push me', click: push"></button>
  </div>

回答1:

First: The data-bind="func" in <div data-bind="func"> raises a red flag for me. Normally a binding is in the form bindingName: boundValue.

But answering the question: A computed's value is recomputed whenever any of the observables it depends on is changed. You're changing self.array in FirstViewModel#push, so naturally the value of the computed that uses it in MasterViewModel (as self.nested.array) is recomputed.



回答2:

As an addition to the accepted answer, I'm posting here the relevant parts of the documentation:

How dependency tracking works

[...]

It’s actually very simple and rather lovely. The tracking algorithm goes like this:

  1. Whenever you declare a computed observable, KO immediately invokes its evaluator function to get its initial value.
  2. While the evaluator function is running, KO sets up a subscription to any observables (including other computed observables) that the evaluator reads. The subscription callback is set to cause the evaluator to run again, looping the whole process back to step 1 (disposing of any old subscriptions that no longer apply).
  3. KO notifies any subscribers about the new value of your computed observable.

If you'd like to prevent a dependency creation, use peek:

Knockout’s automatic dependency tracking normally does exactly what you want. But you might sometimes need to control which observables will update your computed observable, especially if the computed observable performs some sort of action, such as making an Ajax request. The peek function lets you access an observable or computed observable without creating a dependency.

So the given code will be changed into:

var MasterViewModel = function () {
  var self = this;
  self.nested = new FirstViewModel();
  self.func = ko.computed (function() {
    var items = self.nested.array.peek();
    alert("executed");
  });
}
var FirstViewModel = function () {
  var self = this;
  self.array = ko.observableArray([]);
  self.push = function () {
    self.array.push(new SecondViewModel());
    alert("pushed");
  }
}

var SecondViewModel = function () {
  var self = this;
  self.z = ko.observable();
}

var mvm = new MasterViewModel();
ko.applyBindings(mvm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
  <div data-bind="value: func">
     <div data-bind="with: nested">
       <button data-bind="text: 'push me', click: push"></button>
     </div>
  </div>

Note that now, when pushing the button, only an alert of "pushed" is displayed.