Возможно ли вычисление наблюдаемых на прототипах в KnockoutJS?

У меня есть множество предметов, возвращающихся из службы. Я пытаюсь определить вычисляемый наблюдаемый объект для каждого экземпляра Item, поэтому мой инстинкт подсказывает мне поместить его в прототип.

Один случай для вычисляемой наблюдаемой: система вычисляет точки, но пользователь может переопределить вычисленное значение. Мне нужно сохранить рассчитанное значение на случай, если пользователь удалит переопределение. Мне также нужно объединить назначенные пользователем и рассчитанные точки и сложить итоги.

Я использую отображение, чтобы сделать следующее:

var itemsViewModel;
var items = [
    { 'PointsCalculated' : 5.1 },
    { 'PointsCalculated' : 2.37, 'PointsFromUser' : 3 }
];

var mapping = {
    'Items' : {
        create : function(options) {
            return new Item(options.data);
        }
    }
};

var Item = function(data) {
    var item = this;
    ko.mapping.fromJS(data, mapping, item);
};

Item.prototype.Points = function () {
    var item = this;
    return ko.computed(function () {
        // PointsFromUser may be 0, so only ignore it if the value is undefined/null
        return (item.PointsFromUser != null) ? item.PointsFromUser : item.PointsCalculated;
    });
};

ko.mapping.fromJS(items, mapping, itemsViewModel);

Как это работает сейчас, я должен вызвать анонимную функцию, чтобы вернуть вычисленное наблюдаемое. Похоже, что создается новый экземпляр вычисляемого наблюдаемого объекта для каждой привязки, что лишает большей части смысла помещать его в прототип. И немного раздражает необходимость расшифровывать, сколько скобок использовать каждый раз, когда я обращаюсь к наблюдаемому.

Он также несколько хрупкий. Если я попытаюсь получить доступ к Points() в коде, я не смогу

var points = 0;
var p = item.Points;
if (p && typeof p === 'function') {
    points += p();
}

потому что это меняет контекст Points() на DOMWindow, а не на item.

Если бы я поместил вычисляемый в create() в сопоставление, я мог бы захватить контекст, но тогда была бы копия метода для каждого экземпляра объекта.

Я нашел сообщение Майкла Беста о группах Google (http://groups.google.com/group/knockoutjs/browse_thread/thread/8de9013fb7635b13). Прототип возвращает новую вычисленную наблюдаемую при «активации». Я не понял, что вызывает «активировать» (может быть, Objs?), но я предполагаю, что это все еще происходит один раз для каждого объекта, и я понятия не имею, какую область действия получит «это».

На данный момент, я считаю, что я прошел то, что доступно в опубликованных документах, но я все еще работаю над расшифровкой того, что происходит из источника.


person Chris Hance    schedule 24.04.2012    source источник


Ответы (1)


Вы упоминаете, что не хотите иметь экземпляр функции ko.computed в каждом экземпляре вашего класса javascript, однако это не будет работать с тем, как была построена функциональность ko. Когда вы используете ko.computed или ko.observable, они создают специальные указатели памяти на закрытые переменные внутри, которые вы обычно не хотели бы использовать совместно между экземплярами класса (хотя в редких случаях вы могли бы).

Я делаю что-то вроде этого:

var Base = function(){
    var extenders = [];

    this.extend = function(extender){
        extenders.push(extender);
    };

    this.init = function(){
        var self = this; // capture the class that inherits off of the 'Base' class

        ko.utils.arrayForEach(extenders, function(extender){

             // call each extender with the correct context to ensure all
             // inheriting classes have the same functionality added by the extender
             extender.call( self );
        });
    };
};

var MyInheritedClass = function(){
    // whatever functionality you need

   this.init(); // make sure this gets called
};

// add the custom base class
MyInheritedClass.prototype = new Base();

затем для вычисляемых наблюдаемых (которые ДОЛЖНЫ быть функциями экземпляра в каждом экземпляре вашего MyInheritedClass) я просто объявляю их в extender следующим образом:

MyInheritedClass.prototype.extend(function(){

     // custom functionality that i want for each class 
     this.something = ko.computed(function() {
         return 'test';
     });
});

Учитывая ваш пример и класс Base, определенный выше, вы можете легко сделать:

var Item = function(data) {
    var item = this;

    ko.mapping.fromJS(data, mapping, item);

    this.init(); // make sure this gets called
};
Item.prototype = new Base();

Item.prototype.extend(function () {
    var self = this;

    this.Points = ko.computed(function () {

        // PointsFromUser may be 0, so only ignore it if the value is undefined/null
        return (self.PointsFromUser != null) ? 
               self.PointsFromUser : self.PointsCalculated;
    });
};

Тогда все экземпляры вашего класса Item будут иметь свойство Points, и он будет правильно обрабатывать логику ko.computed для каждого экземпляра.

person ericb    schedule 25.04.2012
comment
Спасибо. Я думаю, что цель .extend() просто щелкнула. Это прекрасно решает мои проблемы с областью видимости/вызовом двойных функций, и я поверю вам на слово, что Knockout не позволит мне сохранить фактические вычисленные наблюдаемые на прототипе. - person Chris Hance; 26.04.2012
comment
Эрик, не могли бы вы предоставить более подслащенную версию? Что-то вроде childClass = BaseClass.extend(function(){/*...*/}), которое уже выполняет настройку цепочки прототипов? Делать это вручную для каждого экземпляра класса немного странно... - person fbuchinger; 17.10.2012
comment
Я не вижу причин, по которым вы не могли бы поместить статическую функцию «расширить» в унаследованный конструктор, если это в основном так: SubClass.extend = function(extender){ SubClass.prototype.extend(extender); }; - person ericb; 18.10.2012