Pull to refresh

Comments 27

На самом деле можно сделать проще, но не бесплатно. Можно просто вместо ko.computed использовать функцию.
vm.symbolsLength=function(){
        alert(1);
        return vm.symbols().length;
    }; 

Конечно, тогда в биндингах надо будет добавлять скобочки. Но эффект будет тот же, как и от этой кучи кода. Почему тогда везде не использовать просто функцию вместо computed? А на нее нельзя подписаться. Ну так и на ko.recompute этот тоже нельзя подписаться.
А впрочем вот так даже без скобочек должно работать:
ko.recompute=function(callback){
   callback.__ko_proto__=ko.observable;
   return callback
}
Спасибо за комментарий! Честно скажу для меня было неожиданностью, но этот вариант заработал. Для простого случая — это отличный вариант, но тогда теряется преимущество computed объекта — он вычисляется только один раз если исходные данные не изменились.
Например
vm.symbolsLength=ko.recompute(function(){
    alert(1);
    return vm.symbols().length;
});
vm.x1=ko.recompute(function(){
    return vm.symbolsLength()+'x1';
});
vm.x2=ko.recompute(function(){
    return vm.symbolsLength()+'x2';
});

jsfiddle
Мой вариант вычисляет symbolsLength только одни раз, Ваш вариант будет вычислять его два раза
Да кеширование значения теряется, но в последнем примере смысла применять recompute для symbolsLength тоже нет. Поскольку последующие вызовы все равно создадут постоянные зависимости. Но вобщем согласен — мой вариант не совсем эквивалентен.
Постоянных зависимостей не будет. Допустим в темплейте мы вывели переменную x1. Затем мы поменяли темплейт. Произойдет следующее:
При первом изменении symbols будет пересоздан x1, т.к. у него нет подписчиков (темплейт удален).
При втором изменении symbols будет пересоздан symbolsLength, т.к. подписчик x1 был удален.
Следующий раз при изменении symbols вычислений не будет, пока symbolsLength или x1 не появятся в DOM.
Да согласен. Вот еще вариант, чуть красивее:
ko.recompute=function(callback){
       var c;
        function create(){
            c = ko.computed({
                read: callback,
                deferEvaluation:true,
                disposeWhen:function(){
                     return (c.getSubscriptionsCount()==0)&& setTimeout(create,0);
                }
            });
        }
        create();
        function read(){
            return c();
        }
        read.__ko_proto__=ko.observable;
        return read;
    };
Да, использование disposeWhen будет более правильнее.
Спасибо. Дополню статью вашим вариантом.
Сначала был в восторге от Backbone. Потом от Knockout. Потом от Knockback. С развитием приложений и усложнением интерфейсов я начал сталкиваться с сюрпризами и ограничениями, которые приходилось решать набором всяческих самописных костылей. Иногда конечно оказывалось, что я протупил и проблема решалась просто заменой computed на fn(), но все равно в итоге почему-то получалась каша. Работающая, но каша.

Недавно наткнулся на эту статью и решил попробовать AngularJS. Ребята, так это же совсем другое дело. Порог вхождения, конечно, выше — нужно сначала въехать в ряд сомнительных на первый взгляд понятий и принципов — но в итоге получается нечто более структурированное, стабильное, «бескостыльное».

Если не знакомы, советую поиграться, как минимум, для осведомленности :)
С описанной в статье проблемой столкнулся уже на этапе оптимизации проекта и переписывать на другой фреймворк уже было поздно. Тем более хотелось все-таки разобраться в чем была причина проблемы. По правде говоря, решив ее, все стало работать довольно шустро и меня вполне устроило. Но если будет подобная задача, то для сравнения воспользуюсь AngularJS.
Просто Knockout не дает готового решения как структурировать свое приложение. Это оставлено на откуп разработчика.
Кому то достаточно сделать простую модель, а кому то надо делать сложное приложение. И с Knockout можно избежать каши, просто рецепта нет. В этом есть как положительные так и отрицательные моменты. То же относится и к AngularJS — вам дают готовый рецепт структурирования и у этого подхода тоже есть плюсы и минусы.
> И с Knockout можно избежать каши, просто рецепта нет.
Возможно, но есть пара нюансов.

> То же относится и к AngularJS — вам дают готовый рецепт структурирования
Это, пожалуй, самый главный плюс для меня.
Вы так ссылаетесь на эти нюансы, как будто там написана истина в последней инстанции. К сожалению не могу привести ответную ссылку. Могу сказать только что утверждения там спорные:

1. Насчет POJO — это не совсем верно, поскольку в контроллер нам подсовывают клон объекта с предыдущей итерации. Или я не понимаю как это работает. Про клонирование, кстати, ни слова не сказано. Впрочем, да может быть POJO это лучше чем необходимость объявлять объекты особого типа, но опять же это не объект, который мы создаем сами. И кстати эти контроллеры в глобальном пространстве имен выглядят подозрительно. Надеюсь, их можно спрятать.
2, Насчет того что множественное обновление массива вызывает обновление UI на каждой итерации. Так это легко обходится. И есть выбор — обновлять для каждого или для всех. Что в Angular на этот счет?

В общем я думаю подход Angular vs Knockout это тема скорее для холивара. Потому что объективных преимуществ нет ни у того ни у другого. Я говорю именно о подходе. Angular несомненно более развитый фреймфорк в целом.
1. Насчет POJO — это не совсем верно, поскольку в контроллер нам подсовывают клон объекта с предыдущей итерации.

Не клон, а наследник, имеющий родительский scope в качестве prototype.

Дело в том, что POJO — это то, что мы объявляем в этом scope (который подсунули). Scope — это, по сути, контейнер: jsfiddle.net/W3aMj/

$scope.model — это объект, который мы создали сами, с ним мы и работаем.

И кстати эти контроллеры в глобальном пространстве имен выглядят подозрительно. Надеюсь, их можно спрятать.

Да, меня это тоже сначала обескуражило, а потом оказалось, что можно по-нормальному. Все равно удивляюсь, почему в документации примеры с глобальными переменными. Наверное, чтоб продемонстрировать, как все просто :) jsfiddle.net/ayJqy/1/

Насчет того что множественное обновление массива вызывает обновление UI на каждой итерации… Что в Angular на этот счет?

Дык он на следующем тике все обновит, делая «dirty check». В этом же и фишка. В текущем тике можно менять хоть 1000 раз — ни чего не произойдет.
Как такое сделать в Knockout кстати?

В общем я думаю подход Angular vs Knockout это тема скорее для холивара. Потому что объективных преимуществ нет ни у того ни у другого.

Для меня объективное преимущество Angular — отсутствие недостатотков Knockout. Недостатков такого масштаба у Angular пока не вижу. Наверное, это единственная причина, по которой я продолжаю этот разговор :)

А вообще действительно, давайте закругляться. Если у вас есть вопросы касательно того, как устроен Angular, постараюсь на них ответить.
У меня есть вопрос.
Если вас не затруднит, хотелось бы увидеть и пощупать аналог описанной в статье ситуации (jsfiddle). Интересно посмотреть как переключать темплейты и как часто будут пересчитываться значения.
Конечно, jsfiddle.net/m3Z8r/1/

К моему удивлению, Angular ведет себя в данном случае довольно непредсказуемо:

— при переключении на template1 он вызывает функцию symbolsLength() дважды
— при переключении на template1 — единожды (а нужно, чтоб вообще не вызывал)

Но если установить изначальное значение templateName в 'template2' — тогда не вызовет symbolsLength() ни разу, что правильно.

Я сам знаком с Angular не больше недели, и мне интересно, почему так получается. Думаю, задам вопрос на stackoverflow. Видимо, на то есть свои причины, но xdenser все-таки прав касательно отсутствия объективных преимуществ обоих подходов.
Цикл $digest будет выполняться пока все watch-и не стабилизируются (новое значение равно пердыдущему для каждого watch-а). Когда устанавливается template1, он компилируется, создается новая область видимости. Предыдущее значение для watch-а «symbolsLength()» еще не заполнено. Поэтому на первом проходе оно заполняется, потом делается второй проход и т.к. ничего не изменилось, он является последним. Как то так.
Первый проход — длина изменилась, на втором — стабилизировалась.
А что Вы пытаетесь измерять? Просто измерение получается в том числе источником изменения.
Вот так обычно более «чисто» измеряют: jsfiddle.net/m3Z8r/4/
Осознал. Теперь понятно, почему геттеры настоятельно рекомендуется делать максимально быстрыми и свободными от побочных эффектов.
aav, этот вариант от octave больше соответствует описанной в статье ситуации, так как у вас не вычисляется значение symbolsLength. Но благодаря вашему комментарию я начинаю улавливать отличие между knockaut и angular.
Например knockaut кеширует геттеры по используемым данным. То есть если используемые данные не изменились, то возвращается последнее вычисленное значение.
Но я не до конца понял как кешируются данные в angular? Как вы сказали, значения пересчитываются каждую итерацию, пока меняется результат. Но как закешировать геттер в пределах одной итерации?
Например, в этом примере, я так понял, значение пересчитывется в переделах одной итерации столько раз, сколько используется геттер. И, если knockaut делает одно вычисление, то angular 3*2=6!
Angular делает так, как Вы ему сказали делать.
Если у Вас какие-то тяжелые вычисления, которые при этом не зависят от других данных Ваших моделей, которые в рамках итерации, вообще говоря, тоже могли измениться, то Вам же никто не запрещает их закешировать самому?
А если в геттере какое-то элементарное действие, то чего его кешировать то?
В моем проекте как раз и были сложные вычисления в геттерах. Я многое могу сделать сам, в том числе и закешировать, но фреймверки как раз и нужны, чтобы меньше писать самому. И если в knockout уже есть кеширование, то это ему огромный плюс (правда пришлось допиливать — это конечно минус).
Ну тут, как я понимаю, самый главны вопрос — насколько часто эта необходимость возникает. У меня она возникает очень редко.
Я не очень хорошо знаю knockout, но, насколько могу судить по примерам и статьям, в AngularJS как раз писать приходится меньше.
Дык он на следующем тике все обновит, делая «dirty check». В этом же и фишка. В текущем тике можно менять хоть 1000 раз — ни чего не произойдет.
Как такое сделать в Knockout кстати?

Вот так (используя «throttle» extender).

Либо можно написать свой укороченный extender, если нужен только read метод:
	ko.extenders.async = function(target, timeout) {
		target['throttleEvaluation'] = timeout;
		return target;
	};
Еще тут есть пост об этом. Можно просто взять весь массив изменить, потом вызвать событие обновления.
Все равно я не понимаю как это работает с прототипами. Ну, допустим, если у меня простое свойство, тогда все понятно изменяя scope мы изменим только его свойство, а старое останется в прототипе. Но если у меня в scope массив и я сделаю в него push, я же изменю массив в прототипе, и вся магия развалится.
Вот здесь об этом автор говорит: www.youtube.com/watch?v=ZhfUv0spHCY#t=1871s

Но если у меня в scope массив и я сделаю в него push, я же изменю массив в прототипе, и вся магия развалится.

Не развалится, если эксплицитно объявить этот массив в текущем $scope, что и подразумевается.
По-моему где-то я видел красивое решение этой проблемы, но вспомнить не могу где.

А можете вопрос продублировать на stackoverflow.com? Там есть несколько активных контрибутеров, может у них какие идеи есть?
Sign up to leave a comment.

Articles