Топ10 ошибок, совершаемых при разработке на AngularJS

https://www.airpair.com/angularjs/posts/top-10-mistakes-angularjs-developers-make
  • Перевод
На настоящий момент AngularJS — один из самых популярных javascript фреймворков. Его использование упрощает процесс разработки, делая AngularJS великолепным инструментом для создания небольших веб-приложений, но возможности фреймворка не ограничиваются этим и позволяют разрабатывать большие, наполненные разнообразным функционалом приложения. Комбинация легкости в разработке и большое количество возможностей привели к широкому распространению, а вместе с распространением появились типичные, часто встречающиеся ошибки. В этом топике описаны наиболее распространенные ошибки, встречающиеся при разработке на AngularJS больших проектов.

1. Структура папок, соответствующая MVC приложениям

AngularJS является MVC фреймворком. Несмотря на то, что модели в нем определяются не настолько явно, как в случае с backbone.js, общий архитектурный стиль остается тем же. Часто используемой практикой при использовании MVC фреймворков является группировка файлов по следующему шаблону:
templates/
    _login.html
    _feed.html
app/
    app.js
    controllers/
        LoginController.js
        FeedController.js
    directives/
        FeedEntryDirective.js
    services/
        LoginService.js
        FeedService.js
    filters/
        CapatalizeFilter.js
Подобный подход встречается часто, особенно у разработчиков, имеющих опыт разработки на RoR. Тем не менее при росте приложения использование подобной структуры папок приводит к тому, что в каждый момент времени приходится держать открытыми несколько папок. Чем бы вы не пользовались — Sublime, Visual Studio или Vim с NerdTree — при перемещении по дереву каталогов вы постоянно будете тратить время на скроллинг. Чтобы избежать этого, вы можете группировать файлы по функционалу, а не по типу:
app/
    app.js
    Feed/
        _feed.html
        FeedController.js
        FeedEntryDirective.js
        FeedService.js
    Login/
        _login.html
        LoginController.js
        LoginService.js
    Shared/
        CapatalizeFilter.js
Подобная структура папок делает гораздо более простым поиск связанных файлов, относящихся к одной и той же фиче, что способно ускорить процесс разработки. Да, это может показаться спорным — хранить в одной папке html файлы вместе с js, но эффект от экономии времени может оказаться более важным.

2. Модули (или их отсутствие)

Часто, в начале разработки проекта весь функционал складывается в единый модуль. До какого-то момента подобный подход работает, но по мере развития проекта код становится неуправляемым.
var app = angular.module('app',[]);
app.service('MyService', function(){
    //service code
});

app.controller('MyCtrl', function($scope, MyService){
    //controller code
});
Следующим по распространенности подходом является группировать объекты по их типу:
var services = angular.module('services',[]);
services.service('MyService', function(){
    //service code
});

var controllers = angular.module('controllers',['services']);
controllers.controller('MyCtrl', function($scope, MyService){
    //controller code
});

var app = angular.module('app',['controllers', 'services']);
Подобный подход масштабируется тоже не лучшим образом, подобно структуре каталогов из пункта 1. Чтобы добиться лучшей масштабируемости, мы последуем той же самой концепции разбиения кода по фичам:
var sharedServicesModule = angular.module('sharedServices',[]);
sharedServices.service('NetworkService', function($http){});

var loginModule = angular.module('login',['sharedServices']);
loginModule.service('loginService', function(NetworkService){});
loginModule.controller('loginCtrl', function($scope, loginService){});

var app = angular.module('app', ['sharedServices', 'login']);
Разнесение функционала по различным модулям также дает возможность повторного использования кода в различных проектах.

3. Внедрение зависимостей

Внедрение зависимостей (dependency injection) одна из лучших возможностей, предоставляемых AngularJS. DI облегчает процесс тестирования и делает код чище. AngularJS очень гибок в вопросе того, как зависимости могут быть внедрены. Самый простой способ — это передать зависимость в функцию в качестве параметра:
var app = angular.module('app',[]);
app.controller('MainCtrl', function($scope, $timeout){
    $timeout(function(){
        console.log($scope);
    }, 1000);
});
Из кода ясно, что MainCtrl зависит от $scope and $timeout. Это прекрасно работает до того момента, как проект пойдет в продакшн и вы захотите минифицировать ваш код. Использование UglifyJS к вышеуказанному коду приведет к следующему:
var app=angular.module("app",[]);app.controller("MainCtrl",function(e,t){t(function(){console.log(e)},1e3)})
Теперь AngularJS никак не узнает от чего в реальности зависит MainCtrl. Чтобы этого не происходило, есть очень простое решение — передавать зависимости как массив строк, с последним элементом в виде функции, принимающей все перечисленные зависимости в виде параметров:
app.controller('MainCtrl', ['$scope', '$timeout', function($scope, $timeout){
    $timeout(function(){
        console.log($scope);
    }, 1000);
}]);
Код выше будет преобразован минификатором в код, который AngularJS уже сможет корректно интерпретировать:
app.controller("MainCtrl",["$scope","$timeout",function(e,t){t(function(){console.log(e)},1e3)}])

3.1. Глобальные зависимости

Часто, при разработке AngularJS приложения, появляется необходимость в использовании объектов, доступных в любой точке приложения. Это ломает стройную модель, основанную на внедрении зависимостей, и приводит к возникновению багов и усложнению процесса тестирования. AngularJS позволяет оборачивать подобные объекты в модули так, что они могут быть внедрены подобно обычным AngularJS модулям. К примеру, великолепная библиотека Underscore.js может быть завернута в модуль следующим образом:
var underscore = angular.module('underscore', []);
underscore.factory('_', function() {
  return window._; //Underscore must already be loaded on the page
});
var app = angular.module('app', ['underscore']);

app.controller('MainCtrl', ['$scope', '_', function($scope, _) {
    init = function() {
          _.keys($scope);
      }

      init();
}]);
Это позволяет приложению использовать единый стиль с обязательным внедрением зависимостей и оставляет возможность тестировать модули в отрыве от функционала их зависимостей.

4. Раздувание контроллеров

Контроллеры — это основа AngularJS. И часто, особенно новички, пишут в контроллерах слишком много логики. Контроллеры не должны осуществлять манипуляции с DOM или содержать DOM селекторы, для это существуют директивы. Точно также и бизнес-логика должна находиться в сервисах. Данные также должны храниться в сервисах (за исключением случаев когда данные привязаны к $scope), поскольку сервисы, в отличие от контроллеров, синглтоны, чье время жизни совпадает со временем жизни самого приложения.При разработке контроллеров лучше всего следовать принципу единственной ответственности (SRP) и считать контроллер координатором между представлением и моделью, в этом случае логики в нем будет минимум.

5. Service vs Factory

Эти именования приводят в конфуз каждого новичка в AngularJS, хотя в реальности они почти одинаковы.Посмотрим исходные коды AngularJS:
function factory(name, factoryFn) { 
    return provider(name, { $get: factoryFn }); 
}

function service(name, constructor) {
    return factory(name, ['$injector', function($injector) {
      return $injector.instantiate(constructor);
    }]);
}
Функция service просто вызывает функцию factory, в которую обернут вызов функции provider. Если service просто вызывает функцию factory, в чем разница между ними? Смысл в $injector.instantiate, внутри которой $injector создает новый экземпляр функции конструктора сервиса. Пример сервиса и фабрики, выполняющих одинаковые действия:
var app = angular.module('app',[]);

app.service('helloWorldService', function(){
    this.hello = function() {
        return "Hello World";
    };
});

app.factory('helloWorldFactory', function(){
    return {
        hello: function() {
            return "Hello World";
        }
    }
});
В момент, когда helloWorldService или helloWorldFactory будут инжектированы в контроллер, они обе будут иметь единственный метод, возвращающий «Hello World». Поскольку все провайдеры синглтоны, у нас всегда будет только один экземпляр сервиса и один экземпляр фабрики. Так почему же существуют одновременно и фабрики и сервисы, если они выполняют одну и ту же функцию? Фабрики предоставляют больше гибкости, поскольку они могут возвращать функцию, которая может создавать новые объекты. В ООП фабрика представляет собой объект, создающий другие объекты:
app.factory('helloFactory', function() {
    return function(name) {
        this.name = name;

        this.hello = function() {
            return "Hello " + this.name;
        };
    };
});
Вот пример контроллера, использующего сервис и две фабрики:
app.controller('helloCtrl', function($scope, helloWorldService, helloWorldFactory, helloFactory) {
    init = function() {
      helloWorldService.hello(); //'Hello World'
      helloWorldFactory.hello(); //'Hello World'
      new helloFactory('Readers').hello() //'Hello Readers'
    }

    init();
});
Фабрики также могут быть полезны при разработке классов с приватными методами:
app.factory('privateFactory', function(){
    var privateFunc = function(name) {
        return name.split("").reverse().join(""); //reverses the name
    };

    return {
        hello: function(name){
          return "Hello " + privateFunc(name);
        }
    };
});

6. Неиспользование Batarang

Batarang — это расширение браузера Chrome для разработки и дебага AngularJS приложений. Batarang позволяет:
  • просматривать модели привязанные к скоупам
  • строить граф зависимостей в приложении
  • производить анализ производительности приложения
Несмотря на то, что производительность AngularJS неплохая «из коробки», при росте приложения, с добавлением кастомных директив и сложной логики, приложение может начать подтормаживать. Используя Batarang легко разобраться, какая из функций тратит много времени при вызове. Batarang также отображает дерево наблюдателей (watch tree), которое может быть полезным при использовании большого числа наблюдателей (watchers).

7. Слишком много наблюдателей

Как было отмечено выше, AngularJS довольно производителен из коробки. Но, когда количество наблюдателей достигнет числа 2000, $digest цикл, в котором происходит проверка изменения данных, может начать замедлять работу приложения. Хотя достижение числа 2000 не гарантирует замедления, это хорошая стартовая точка, с которой уже можно начинать беспокоиться. С помощью следующего кода можно узнать количество наблюдателей на странице:
(function () { 
    var root = $(document.getElementsByTagName('body'));
    var watchers = [];

    var f = function (element) {
        if (element.data().hasOwnProperty('$scope')) {
            angular.forEach(element.data().$scope.$$watchers, function (watcher) {
                watchers.push(watcher);
            });
        }

        angular.forEach(element.children(), function (childElement) {
            f($(childElement));
        });
    };

    f(root);

    console.log(watchers.length);
})();
Используя код выше и дерево наблюдателей батаранга, вы можете посмотреть, есть ли у вас наблюдатели-дубликаты или наблюдатели над неизменяемыми данными. В случае с неизменяемыми данными вы можете использовать директиву bindonce, чтобы не увеличивать количество наблюдателей на странице.

8. Наследование скоупов ($scope's)

Наследование в JS основанное на прототипах отличается от классического наследования на классах. Обычно это не является проблемой, но эти нюансы могут проявляться при работе со скоупами. В AngularJS обычный (не изолированный) $scope наследован от родительского до самого старшего предка $rootScope. Общая модель данных, разделяемая родительским скоупом с дочерним, организуется легко благодаря наследованию на прототипах. В следующем примере мы хотим, чтобы имя пользователя одновременно отображалось в двух элементах span, после того как пользователь введет свое имя.
<div ng-controller="navCtrl">
   <span>{{user}}</span>
   <div ng-controller="loginCtrl">
        <span>{{user}}</span>
        <input ng-model="user"></input>
   </div>
</div>
Теперь вопрос: когда пользователь введет свое имя в текстовое поле, в каких элементах оно будет отображаться:navCtrl, loginCtrl, или в обоих? Если ваш ответ — loginCtrl, вы понимаете, как работает наследование основанное на прототипах. В поиске по строковым полям цепочка прототипов не используется. Чтобы добиться желаемого поведения, нам желательно использовать объект для корректного обновления имени пользователя в дочернем и родительском $scope. (Напоминаю, что в JS функции и массивы также являются объектами.)
<div ng-controller="navCtrl">
   <span>{{user.name}}</span>
   <div ng-controller="loginCtrl">
        <span>{{user.name}}</span>
        <input ng-model="user.name"></input>
   </div>
</div>
Теперь, поскольку переменная user — объект, цепочка прототипов будет работать и элемент span в navCtrl будет корректно обновлен вместе с loginCtrl. Это может выглядеть неестественным примером, но при работе с директивами, создающими дочерние скоупы (подобно ngRepeat), подобные моменты будут возникать.

9. Использование ручного тестирования

До тех пока вы не начнете использовать TDD в своей работе, вам придется каждый раз запускать проект и проводить ручное тестирование, чтобы удостовериться, что ваш код работает. Нет оправданий для использования подобного подхода в случае с AngularJS. AngularJS был изначально спректирован таким образом, чтобы разработанный на нем код был тестируемым. DI, ngMock — ваши лучшие помощники в этом. Также есть несколько инструментов, способных вас перенести на следующий уровень.

9.1 Protractor

Юнит-тесты — это основа для построения полноценно покрытого тестами приложения, но с ростом проекта использование интеграционных тестов может быть более эффективным для проверки, насколько жизнеспособен код в приложении. К счастью, команда AngularJS разработала замечательный инструмент — Protractor, способный имитировать взаимодействие с пользователем. Protractor использует фреймворк Jasmine для написания тестов и обладает хорошим API для описания различных сценариев взаимодействия. Среди множества различных инструментов для тестирования у Protractor есть преимущество в понимании внутреннего устройства AngularJS, что особенно полезно, когда вы имеет дело с чем-то наподобие $digest циклов.

9.2. Karma

Команда проекта AngularJS не ограничилась написанием инструментария для разработки тестов. Также был разработан исполнитель тестов (test runner) Karma. Karma позволяет выполнять тесты каждый раз при изменении файлов с исходниками. Karma способна выполнять тесты параллельно в нескольких браузерах. Различные устройства также могут быть нацелены на сервер кармы для более полного покрытия реальных сценариев использования.

10. Использование jQuery

jQuery замечательная библиотека. Она стандартизовала кросс-платформенную разработку и стала стандартом в современной веб-разработке. Несмотря на то, что jQuery обладает большим количеством фич, её философия далека от философии AngularJS. AngularJS — это фреймворк для построения приложений, в то время как jQuery — это просто библиотека, упрощающая процесс взаимодействия JavaScript и HTML и предоставляющая удобный API для работы с AJAX. В этом заключается фундаментальное различие между ними. Ангуляр — это подход к построению приложений, а не способ управления разметкой документа. Чтобы действительно осознать принципы построения AngularJS приложений, вам стоит перестать использовать JQuery. jQuery заставляет вас соответствовать существующему HTML стандарту, в то время как ангуляр позволяет вам расширять стандарт HTML под нужды вашего приложения. Манипуляции с DOM в AngularJS должны производиться в директивах, но при этом вполне допустимо размещать в директивах обертки над существующими jQuery компонентами, если вы не смогли найти аналог на angular.

Заключение

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

Похожие публикации

Комментарии 31

    +7
    вам стоит перестать использовать JQuery

    Только jQuery — это ещё множество готовых плагинов, так что нельзя просто взять и перестать использовать jquery. Приходится оборачивать jquery-контролы в директивы.
      +2
      Стоит заменить, что сейчас многие JQuery плагины сразу предоставляют готовые директивы.
      0
        –2
        Я, когда читал, удивился. Если посмотреть в раздел Function Declarations:
        /*
        * recommend
        * Using function declarations
        * and bindable members up top.
        */
        function Avengers(dataservice, logger) {
            var vm = this;
            vm.avengers = [];
            vm.getAvengers = getAvengers;
            vm.title = 'Avengers';
        
            activate();
        
            function activate() {
                return getAvengers().then(function() {
                    logger.info('Activated Avengers View');
                });
            }
        
            function getAvengers() {
                return dataservice.getAvengers().then(function(data) {
                    vm.avengers = data;
                    return vm.avengers;
                });
            }
        }
        


        При использовании strict режима нельзя использовать getAvengers до объявления.
          +4
          Почему же нельзя?
            +1
            Думаю это происки jshint'a. Есть там такое правило.
              0
              Это тяжелое наследие JSLint Крокфорда и его нелюбви к FD, и особенно определенным ниже использования. Нужно выставлять latedef: 'nofunc' и радоваться.
        0
        Сомнительные ошибки, я бы даже сказал слишком субъективные.

        1) Зависит от предпочтений пользователя, смешивать шаблоны и скрипты в одной папке, на мой взгляд не лучший вариант, но зато так код легко выносится в модули. То есть свои плюсы и минусы.

        2) Делить логику на модули это всегда хороший тон, но тем не менее, для небольших проектов это может не потребоваться вообще.

        3) Это согласен, но с этим столкнется любой человек, который минифицирует свой код (и не особо разбирается в настойках минификатора).

        3.1) Сомнительное удовольствие. Очень сомнительное.

        4) В программировании вообще по возможности не стоит ничего раздувать, это и без того понятно.

        5-9) Это все в документации прекрасно описано.

        10) Сами и пишите что jQ это либа, а angular фреймворк и концепция. Да и angular сам внутри использует jQuery light, а если подключен jQ то и его.
          +1
          3. Вместо того, чтобы явно прописывать все зависимости и тем самым дублировать код, в 2015 году стоит уже использовать grunt-ng-annotate (https://www.npmjs.com/package/grunt-ng-annotate) или gulp-ng-annotate (https://www.npmjs.com/package/gulp-ng-annotate)

          3.1. Да, только вместо

          underscore.factory('_', function() {
            return window._; //Underscore must already be loaded on the page
          });
          


          лучше так:

          underscore.factory('_', function($window) {
            return $window._;
          });
          
            +2
            <irony>
            А то и так:
            loDash.factory('_', function($window) {
              return $window._;
            });
            

            </irony>
              +5
              image
            –6
            «Но, когда количество наблюдателей достигнет числа 2000, $digest цикл, в котором происходит проверка изменения данных, может начать замедлять работу приложения.»

            Зачем все этот бред повторяют?
            НЕТ и НЕ БЫЛО никакого ограничения, один из создателей ангуляра сказал что может быть замедление взял это число с потолка.
            Если выносить код в директивы и более менее его структурировать, то даже 50 вотчеров сложно увидеть в одном цикле.
              +2
              Для отчетов был грид, который биндился ангуляром. Грид на 3000 строк и 20 колонок.
              Браузер просто перестает отвечать на секунд 5-7. Замедляется очень сильно.
                +2
                А вы часто встречаете приложения где это нужно?

                Я вот слабо себе представляю задачу где понадобится такая таблица с обновлением в реал-тайме.
                А для остальных случает есть одноразовый вывод, без создания вотчеров

                  <span>{{data}}</span> // создает вотчер
                
                  <span>{{::data}}</span> //  не создает вотчер
                


                  0
                  у нас админка на ангуляре. Грустно, печально и совестливо.
                    +1
                    а можно подробнее с чем именно у вас проблемы? уж админки то писать на ангуляре по-моему вообще самое то =)
                      0
                      Возможно, об этом речь: habrahabr.ru/post/246905/#comment_8259067
                      0
                      проблемы с производительностью на большом количестве данных и большой частоте их апдейта.

                      проблемы с лагами даже на небольшом количестве данных

                      проблемы с идеологическими методами коммуникации компонентов: ангуляр подталкивает шарить данные
                  +1
                  Ну и как с такой таблицей работать, это экранов на 10-20 наверное табличка? Такие таблицы и на чистом HTML шустро не работают, особенно на мобильных устройствах…
                  Опять же я мало где видел такие таблицы, насколько это распространённый случай чтобы его обсуждать? А то так можно договориться до того что кроме ассемблера, ни на чём писать нельзя (вдруг я таблицу на 100500 строк и 50 столбцов захочу вывести на экран?)
                    0
                    IE 5.0 вполне неплохо рендерил список всех российских БИКов в виде таблицы, а это 80 мегабайт чистого текста, который ещё конвертируется в HTML.
                  +2
                  а если еще пользоваться двусторонним связыванием только там где нужно, то вообще проблем не будет.
                    0
                    Кто минусует, приведите пример из кода ангуляра с ограничением, благо он opensource.
                    0
                    В основном согласен, кроме:

                    1. Имхо, меняется шило на мыло. Лучше использовать хороший редактор с функциями «Go to File...» и следовать конвенции именования.
                    3.1. Вообще не понимаю, зачем. Если вдруг понадобится потестировать, правильно ли работает underscore.js?
                    10. Иногда все же приходится.
                      0
                      ngAnnotate для Grunt.js решает проблему №3 один раз и навсегда.
                        0
                        Я уже переводил(и дополнял эту статью), ссылка была на хабре.

                        Но все равно спасибо. Очередной раз подняли активное и что немаловажно конструктивное обсуждение в комментариях.
                          0
                          Еще можно было бы засовывать понимание ангулара в головы старых программистов, не дожидаясь когда до них дойдет!
                            0
                            Сколько раз читаю про разницу сервиса и фабрики, все равно не доходит пока. Подскажите опытным взглядом, пожалуйста, куда копать.

                            Я недавно начал использовать Angular и делаю примерно так:

                            var app = angular.module('Resources', []);
                            app.controller('RecordsController', ['$scope', 'resService', function($scope, resService){
                                $scope.records = [];
                                $scope.search = '';
                            
                                $scope.load = function() {
                                    resService.load($scope.search).then(function(){
                                        $scope.records = resService.getRecords();
                                    });
                                };
                            }]);
                            
                            app.service('resService', function($http){
                                var url = 'ajax.php';
                                var records = {};
                            
                                return {
                                    load: function(search, status){
                                        var vars = {
                                            m: 'getResources',
                                            search: search
                                        };
                                        var respons = $http({method: 'POST', url: url, data: vars}).
                                            success(function (data, status, headers, config) {
                                                records = data.records;
                                            }).
                                            error(function (data, status, headers, config) {
                                                alert('Error!');
                                            });
                                        return respons;
                                    },
                                    getRecords: function(){
                                        return records;
                                    }
                                };
                            });
                            


                            Можете подсказать на этом примере, что стоит сделать иначе? И в чем будет разница и смысл использования service и factory?
                              0
                              Artima, все просто. Они очень похожи, за исключением того, что в фабрике вы можете возвращать как объект, так и функцию-конструктор, с помощью которой внутренний механизм ангуляра будет каждый раз создавать новый экземпляр объекта и передавать в виде зависимости в контроллеры или другие сервисы.
                                0
                                внутренний механизм ангуляра будет каждый раз создавать новый экземпляр объекта и передавать в виде зависимости в контроллеры или другие сервисы


                                Это вы про что? Про это: jsfiddle.net/5ds0to0j/?
                                0
                                Между service и factory по-большому счету только синтаксическая разница — какой вариант объявления больше подходит вашему стилю, тот и используйте.
                                  0
                                  А можете пример привести? Я немного изменил ваш (http://jsfiddle.net/0zttL5ft/2/) и меня не покидает ощущение, что поведение у фабрики и сервиса абсолютно идентичное. А в каком месте начинается различие так и не могу понять.
                                    0
                                    Мой пример был про то, что при возвращении из factory функции, Angular не будет каждый раз с помощью нее новый объект создавать.

                                    Сервис стоит использовать, если вместо:
                                    app.factory('foo', function() {
                                      var thisIsPrivate = "Private";
                                      function getPrivate() {
                                        return thisIsPrivate;
                                      }
                                    
                                      return {
                                        variable: "This is public",
                                        getPrivate: getPrivate
                                      };
                                    });
                                    


                                    при объявлении вы хотите использовать this:
                                    app.service('foo', function() {
                                      var thisIsPrivate = "Private";
                                      this.variable = "This is public";
                                      this.getPrivate = function() {
                                        return thisIsPrivate;
                                      };
                                    });
                                    


                                    или хотите, чтобы инстанс какого-то ранее созданного класса мог инжектироваться, ну или просто вам удобнее такая запись:
                                    function Foobar() {
                                      var thisIsPrivate = "Private";
                                      this.variable = "This is public";
                                      this.getPrivate = function() {
                                        return thisIsPrivate;
                                      };
                                    }
                                    
                                    ...
                                    
                                    app.service('foo3', Foobar);
                                    


                                    При этом надо не забывать, что какой бы вы не использовали способ для объявление инжектируемой сущности, в результате всегда будет синглтон — Angular один раз, в момент первой встретившейся инъекции этого конкретного сервиса (не путать с конкретным способом объявления service), создаст экземпляр (для constant и value это будет просто значение, для factory то, что вернулось из функции fn при factory('ServiceName', fn), для service это будет результат от new fn() при service('ServiceName', fn)). Этот экземпляр ляжет в кеш и потом всегда будет отдаваться при последующих инъекциях.
                                      0
                                      Спасибо за пример! Кажется дошло, наконец. Буду пробовать на практике. :)
                                0
                                По поводу п.7 (слишком много наблюдателей) — создаваемые вручную элементы и слинкованные одним и тем же скоупом будут иметь один один и тот же скоуп (родительский). Для N таким образом созданных элементов функция из статьи посчитает наблюдателей N+1 раз, соответственно, результат будет в N+1 раз больше.

                                Правильная функция здесь: stackoverflow.com/questions/18499909/how-to-count-total-number-of-watches-on-a-page

                                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                Самое читаемое