Вопросы по EmberJS

Небольшая подборка вопросов по EmberJS для себя (время от времени дополняется).

В чем разница между Ember.computed('p1', 'p2', function() { ... }); и function () {
... }.property('p1', 'p2)
?

Оба варианта делают одно и тоже. Но первый отработает всегда. Второй — только если флаг EXTEND_PROTOTYPES установлен в true (такой по умолчанию).

В чем разница между {{partial "bar"}}, {{view "bar"}} и {{render "bar" <context>}}?

{{partial "bar"}} — отображает шаблон из templates/bar.hbs

{{view "bar"}} — отображает представление из views/bar.js

{{render "bar" <context>}} — отображает представление из views/bar.js используя контроллер controllers/bar.js и шаблон из templates/bar.hbs

В чем разница между Ember.View и Ember.Component?

Просто оставлю ссылку на stackOverflow. Описано толково.

Отличия между extend, reopen и reopenClass.

extend — используется для объявления подкласса от указанного. Пример:

App.Car = DS.Model.extend({...});

reopen — добавляет свойства и методы к экземплярам класса.

            App.Car = DS.Model.extend({...});
            var c = App.Car.create();
            c.reopen({
                m: Ember.K
            });
            c.m();

reopenClass — добавляет свойства и методы к самому классу.

            App.Car = DS.Model.extend({...});
            var c = App.Car.create();
            App.Car.reopenClass({
                m: Ember.K
            });
            App.Car.m();

Какая разница между [], @each и вложенным @each?

[] — меняется при любом изменении содержимого.

@each — используется для отслеживания изменений какого-то из свойств объектов в коллекции (например, observes('collection.@each.prop')). Вообще, такой обсервер сработает и при изменении всей коллекции (добавление/удаление элемента или же замена содержимого коллекции целиком). Использование в виде observes('collection.@each') не рекомендуется (лучше брать []).

Вложенный @each невозможен — нельзя делать так: collection.@each.obj.prop, collection.@each.objs.@each.prop.

Можно ли делать set для computedProperty? Если да, то можно ли это запретить?

Можно делать. Запретить это тоже можно:

            id: function() {
                return 'id';
            }.property().readOnly()

В таком случае, попытка установить значение через set приведет к ошибке.

Почему [].isAny('fake', 1) будет false, а [].isEvery('fake', 1) будет true?

isAny основан на any. Если посмотреть на его реализацию, то видно, что изначально возвращаемое значение метода установлено в false. Далее идет цикл по содержимому коллекции. Но, если коллекция пустая, то тело цикла не выполняется и будет возвращено начальное значение (false).

isEvery, в свою очередь, использует every, в котором выполняется find (см. исходник). Но вот этот поиск делается по пустому массиву. Соответственно он вернет false для любых допустимых ключей/значений. Логика every довольно проста — если find не нашел несоответствий, то коллекция удовлетворяет проверяемому критерию (у всех элементов есть поле с проверяемым значением). А вот, есть ли в массиве хоть что-то, не проверяется.

Как можно избежать многократного срабатывания обсервера для @each.prop за один «runLoop»?

Нужно использовать Ember.run.once. По ссылке доступен пример. Обратите внимание на то, сколько раз в консоли пишется obs1, obs2, obs3. obs1 — это «обычный» обсервер, в котором может быть какая-то логика по обработке коллекции. Он срабатывает при любом изменении свойства a какого-либо элемента массива list. obs2 -тоже вполне «обычный» обсервер, однако некоторая логика из него вынесена в obs3, который вызывается через Ember.run.once. Получается, что obs2 вызывается столько же раз, сколько и obs1. Но вот какие-то посчеты для obs2 выполняются только один раз, а не несколько, как с obs1.

В чем разница между router, route и resource?

router — карта приложения. Содержит в себе роуты и ресурсы. Отвечает за отображение нужных шаблонов, подгрузку данных и т.д. Делает это сопоставляя текущий URL с определенным роутом.

route — маршрут на карте приложения. Отвечает какой-то его странице. Вложенными быть не могут.

resource — группа роутов. Может быть вложенным в другой ресурс.

Подробно роутинг описан в документации.

В каком порядке вызываются эти методы роута и представления: model, willInsertElement, beforeRender, beforeModel, setupController, didInsertElement, afterRender, afterModel, renderTemplate.
  • beforeModel (route)
  • model (route)
  • afterModel (route)
  • setupController (route)
  • renderTemplate (route)
  • beforeRender (view)
  • afterRender (view)
  • willInsertElement (view)
  • didInsertElement (view)

Проверить можно по ссылке.

В чем разница между $(this) и this.$() (в контексте Ember.View)?

this.$() — возвращает jQuery-объект для этого View-элемента. Если в него передать какой-то селектор, то метод вернет jQuery-объект используя текущий элемент как «контейнер».

$(this) — это просто обертка Ember.View в jQuery-объект.

Как дождаться выполнения нескольких RSVP.Promise?

Необходимо использовать RSVP.all

            var promise1 = this.store.find('m1', 1);
            var promise2 = this.store.find('m2', 2);
            var promise3 = this.store.find('m3', 3);
 
            Ember.RSVP.all([ promise1, promise2, prmise3 ]).then(function() {
                //....
            });

Как правильно вызывать actions при тестирования контроллера/компонента?

Необходимо использовать send:

            test('testing controller', function() {
                var ctrl = this.subject();
                ctrl.send('actionName', 'someParameterValue');
            });

Как получить список роутов до текущего (включительно)?

В роуте:

            this.get('router.router.state.handlerInfos');

В контроллере:

            this.get('target.router.currentHandlerInfos');

См. консоль в примере.

В каком порядке вызываются listeners?

Они вызываются в порядке, обратном к объявлению. Пример:

            App.IndexView = Ember.View.extend({
 
              d: function () {
                console.log('Listener d');
              }.on('didInsertElement'),
 
              didInsertElement: function () {
                console.log('Did Insert Element');
              },
 
              a: function () {
                console.log('Listener a');
              }.on('didInsertElement'),
 
              b: function () {
                console.log('Listener b');
              }.on('didInsertElement'),
 
              c: function () {
                console.log('Listener c');
              }.on('didInsertElement')
 
            });

Подписчики выполнятся в таком порядке: c, b, a, d. См. исходники sendEvent и исходники CoreView.trigger. Получается, что вначале вызываются подписчики (через this._super), а потом уже сам didInsertElement. Живая демка доступна на jsbin.

Как можно сделать, чтоб какое-то поле объекта не затиралось при extend’е этого объекта, а «дополнялось» новым значением (пример — classNames в Ember.View)?

Делается это при помощи concatenatedProperties. Пример:

            App.ParentView = Ember.View.extend({
              concatenatedProperties: ['prop1'],
              prop1: ['val11'],
              prop2: ['val21'],
              prop3: ['val31']
            });
 
            App.ChildView = App.ParentView.extend({
              concatenatedProperties: ['prop2'],
              prop1: ['val12'],
              prop2: ['val22'],
              prop3: ['val32']
            });
 
            App.IndexView = App.ChildView.extend({
              concatenatedProperties: ['prop3'],
              prop1: ['val13'],
              prop2: ['val23'],
              prop3: ['val33']
            });

prop1, prop2, prop3 объявлены во всех трех View. Объявление concatenatedProperties в ParentView и ChildView имеют одинаковый смысл — значения из ParentView потеряны не будут. А вот concatenatedProperties в IndexView не уберегает от перезатирания prop3 в ChildView. Таким образом получается, что в результате в IndexView будет такое:

  • prop1 — [‘val11’, ‘val12’, ‘val13’]
  • prop2 — [‘val21’, ‘val22’, ‘val23’]
  • prop3 — [‘val32’, ‘val33’]

Живая демка доступна по ссылке.

Чем отличаются map, invoke и forEach?

map — проходит по коллекции, выполняя переданную функцию (не имя функции) над каждый элементов. Возвращает новый массив. Если callback не возвращает ничего, то в результате работы map будет массив с размером равным исходному, но состоящий из undefined.

invoke — проходит по коллекции. В качестве параметра принимает имя функции, которая должна быть вызвана у каждого элемента, где она реализована. Так же возвращает новый массив. Если метод, имя которого передано в invoke, не возвращает ничего, то на выходе invoke получится массив из undefined.

forEach — просто проходит по коллекции, выполняя переданную функцию (не имя функции) над каждым элементом. Возвращает текущую коллекцию.

Демо на jsbin. Как видно, разница между invoke и invoke2 состоит в том, что invoke2 не возвращает ничего. Как результат — invoked являет собой массив из трех undefined после вызова invoke2. Та же разница между map и map2. Во втором функции callback не возвращает ничего и в результате получается массив из трех undefined.

Можно ли получить из двухмерного массива Ember.Object’ов одномерный массив со значениями определенного поля каждого объекта? Доп. условие — если поле объекта undefined, то необходимо подставить значение по умолчанию. Второе доп. условие — не использовать методы, которые в качестве параметров приминают callback-функции.

Дано:

model: function() {
    return [
      [
        Em.Object.create({name: 'a'}),
        Em.Object.create({name: 'b'}),
        Em.Object.create({name: 'c'})
      ],
      [
        Em.Object.create({name: 'd'}),
        Em.Object.create({name: 'e'}),
        Em.Object.create({name: 'f'})
      ],
      [
        Em.Object.create({}),
        Em.Object.create({name: 'h'}),
        Em.Object.create({name: 'i'})
      ]
    ];
  }

Надо получить одномерный массив со значением name каждого объекта (вместо undefined выводить ‘-‘):

            ['a', 'b', 'c', 'd', 'e', 'f', '-', 'h', 'i']

Да, такое можно получить. Демка на jsbin. Для этого используется двойной invoke. Внешний проходит по подмассивам и выполняет внутренний invoke по каждому вложенному вектору. Вложеенный invoke выполняет getWithDefault для каждого объекта. В результате получается двухмерный массив, где хранятся нужные значения. В одномерный он перегоняется при помощи Array.prototype.concat.apply.

Чем отличаются push и pushObject?

push — «коробочный» метод массива по добавлению элементов в конец.

pushObject — Эмберовский метод для добавление элемента в конец массива. Отличается тем, что правильно информирует обсерверы об изменениях в массиве. Если посмотреть исходник миксины NativeArray, то видно, что она использует MutableArray, в котором реализован pushObject. Он использует insertAt, а тот ,в свою очередь, вызывает replace (который переопреден в NativeArray). В методе replace вызываются arrayContentWillChange и arrayContentDidChange, в которых указано, что было изменено (в нашем случае — что был добавлен один элемент).

В демо это наглядно показано. При клике на Invalid Push выполняется обычный push. При клике на Push With Trigger выполняется push вместе с вызовом методов arrayContentWillChange и arrayContentDidChange. Push Object — это Эмберовский pushObject, а Push Objects — pushObjects. Update выполняет rerender для текущей вью.

Если кликнуть на Invalid Push, то в шаблоне никаких изменений не будет (хотя элемент в массив будет добавлен). Если после этого кликнуть на Push With Trigger / Push Object / Push Objects, то новый элемент появится на странице. Однако, тот, что был добавлен при Invalid Push, так и не появится. Это вызвано тем, что триггеры для обновления массива в данном случае сработали только на последнюю вставку. А Handlebars не перерисовывал массив полностью (зачем лишнюю работать делать?), а обновил только то, что изменилось (как ему было сказано). Если нажать Update, то отобразится уже весь массив (так как вью было перерисовано полностью).

Чем отличается Ember.keys от Object.keys?

Особо ничем. Функционал одинаковый. Ember.keys использует Object.keys, если последний доступен. Если нет, то выполняется метод-полифилл (см. исходник). С версии 1.13 Ember.keys стал устаревшим.

Сколько раз сработает обсервер на два свойства, если они оба будут изменены через setProperties?

Обсервер сработает два раза. Если посмотреть на исходник setProperties, то видно, внутри используется цикл, где на каждой итерации (для каждого изменяемого свойства) выполняется set. Демо на jsbin.

Можно ли немного сократить запись параметров, за которыми «следит» метод? f: function() { ... }.observes('a.b.c', 'a.b.d')

Можно вынести «общую» часть двух свойств. Запись будет выглядеть так:

            function () {
                ...
            }.observes('a.b.{c,d}')

Демо на jsbin. Возможность такой записи была добавлена как фича «propertyBraceExpansion» (см. поиск по репозиторию Эмбера на github’е).

В каком случае отработает блок else в коде — {{#each item in collection}}...{{else}}...{{/each}}

Ветка else выполнится в ситуации, когда collection пустая.

Можно ли в #each получить индекс текущей записи в коллекции?
Можно (с версии 1.11). Пример:

            {{#each collection as |item index|}}
              {{index}}: {{item}}
            {{/each}}

В чем разница между send, trigger и sendAction?

trigger — генерирует именованное событие объекта (на который через Ember.on подписываются).

send — выполняет именованное действие (тот самый action в шаблонах).

sendAction — выполняет действие, переданное в компонент (удобно для «человеческих» имен — например не «click», а «startUpload»).

Документация — send, sendAction, trigger.

, , , , , ,

Оставить комментарий

Top ↑ | Main page | Back