Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
269 views
in Technique[技术] by (71.8m points)

javascript - Is there a way to tell knockout to wait to recalculate computed values until after the view model is defined?

I have a complex view model which is a couple hundred lines of javascript code with a good amount of observable properties, computed observable properties, writable computed observable properties and functions. So managing this is quite a bit of a challenge.

An annoying problem that I've had to deal with is that computed observables are calculated immediately right when you define it. So using variables that haven't been defined yet in the view model at the point of defining the observable lead to errors stating that the variable hasn't been defined. It is... just later in the file.

Here's a contrived example:

function ViewModel1?(args) {
    var self = this;

    self.firstName = ko.observable(args.firstName);
    self.lastName = ko.observable(args.lastName);
    self.fullName = ko.computed(function () {
        return self.firstName() + ' ' + self.lastName();
    });
}

function ViewModel2?(args) {
    var self = this;

    self.fullName = ko.computed(function () {
        // uh oh, where's firstName?
        return self.firstName() + ' ' + self.lastName();
    });
    self.firstName = ko.observable(args.firstName);
    self.lastName = ko.observable(args.lastName);
}

Using ViewModel1 will work with no problems. At the point fullName is defined, firstName and lastName is defined so it works as expected. Using ViewModel2 will not work. There would be an Error in the computed function stating firstName is not defined.

What I've been doing until now was to ensure that all computed observables are defined after all dependent variables have been defined. The problem with this is that in doing so, things are defined in seemingly random places when I would rather keep related variables defined close together.

One nice solution that I've come up with is to define an "initializing" observable set to true and make all computed observables test if it's still initializing and calculate and return the value when it isn't. That way, the attempts to access the currently undefined variable will not be made.

function ViewModel3(args) {
    var self = this;
    var initializing = ko.observable(true);

    self.fullName = ko.computed(function () {
        if (!initializing()) {
            return self.firstName() + ' ' + self.lastName();
        }
    });
    self.firstName = ko.observable(args.firstName);
    self.lastName = ko.observable(args.lastName);

    initializing(false);
}

But this won't be very practical in my case however. I have many computed observables, so doing this in all of them will make it very bloated, remember I have a lot of these. Throttling it doesn't seem to make a difference.

Is there a way to tell knockout to wait before trying to calculate the values of computed observables? Or is there a better way for me to structure my code to deal with this?

I could probably make some helper functions to manage the initialization logic, but I'd still have to alter all of the computed observable definitions. I suppose I can monkey patch knockout to add this initializing logic as I'm not aware knockout has such options which I might just do. I've looked at the source for computed observables before but I don't know if there's already a setting elsewhere.

jsfiddle demo

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Computed observables accept a deferEvaluation option that prevents the initial evaluation from happening until something actually tries to retrieve the computed's value.

You would define it like:

self.fullName = ko.computed({
   read: function() {
       return self.firstName() + " " + self.lastName();
   },
   deferEvaluation: true
});

Just for completeness, you could also specify it like:

this.fullName = ko.computed(function() {
       return this.firstName() + " " + this.lastName();
 }, this, { deferEvaluation: true });

Or you could wrap it like:

ko.deferredComputed = function(evaluatorOrOptions, target, options) {
   options = options || {};

   if (typeof evaluatorOrOptions == "object") {
       evaluatorOrOptions.deferEvaluation = true;   
   } else {
       options.deferEvaluation = true;
   }

   return ko.computed(evaluatorOrOptions, target, options);
};

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...