55+1 прием для улучшения JavaScript-кода

Бабич Татьяна

Руководитель Frontend отдела компании Simbirsoft

На старт. Внимание. Марш.

Проект на старте всегда:

  • кажется меньше, чем он будет в итоге
  • небольшим ТЗ
  • с требования сделать как можно быстрее

1. Соблюдайте LIFT - принципы.

L - locate

I - identify

F - flat

T- try to stay DRY

Спустя пару месяцев

controllers/
    LoginController.js
    RegistrationController.js
    ProfileController.js
    SearchResultsController.js
    FeedController.js
directives.js
filters.js
models/
    Feed.js
    CartModel.js
    ProfileModel.js
    SearchResultsModel.js
    UserModel.js
services/
    FeedService.js
    CartService.js
    UserService.js
    ProfileService.js

Спустя полгода

controllers/
    LoginController.js
    RegistrationController.js
    ProductDetailController.js
    SearchResultsController.js
    UsersController.js
    UsersProfileController.js
    OpinionController.js
    InstrumentsController.js
    FeedController.js
    RecoveryController.js
    NavigationContoller.js
directives.js
filters.js
models/
    CartModel.js
    ProductModel.js
    SearchResultsModel.js
    UserModel.js
    Navigation.js
    Recovery.js
    Feed.js
    Instruments.js
    Opinion.js
    Users.js
    UsersProfile.js
services/
    ...

Спустя полгода

views/
    partials/
              ...
    users.html
    userprofile.html
    search.html
    register.html
    recovery.html
    profile.html
    opinionPage.html
    login.html
    instruments.html
    instrumentsPage.html
    myinstruments.html
    feed.html
    oauth.html
    404.html
    index.html    

шаблоны....

Модульность

product/
    search/
        SearchResultsController.js
        SearchResultsModel.js
    ProductDetailController.js
    ProductModel.js
    ProductService.js
user/
    LoginController.js
    RegistrationController.js
    RecoveryController.js
    UserModel.js
    UserService.js
instruments/   
    InstrumentsController.js
    InstrumentsModel.js
    InstrumentsService.js
feed/
    FeedController.js   
    FeedModel.js
    FeedService.js   

2. Принцип единой ответственности

/* directives.js */

angular
   .module('app.project')

   .directive('orderCalendarRange', orderCalendarRange)

   .directive('salesCustomerInfo', salesCustomerInfo)

   .directive('sharedSpinner', sharedSpinner);

function orderCalendarRange() {
   /* implementation details */
}

function salesCustomerInfo() {
   /* implementation details */
}

function sharedSpinner() {
   /* implementation details */
}
/* calendarRange.directive.js */

angular
   .module('sales.order')
   .directive('acmeOrderCalendarRange', orderCalendarRange);

function orderCalendarRange() {
   /* implementation details */
}

/* customerInfo.directive.js */
   
angular
   .module('sales.widgets')
   .directive('acmeSalesCustomerInfo', salesCustomerInfo);

function salesCustomerInfo() {
   /* implementation details */
}

/* spinner.directive.js */

angular
   .module('shared.widgets')
   .directive('acmeSharedSpinner', sharedSpinner);

function sharedSpinner() {
   /* implementation details */
}

3. Соблюдайте правила именования переменных

profile.controller.js

ProfileController.js

4. Применяйте единый codestyle. Используйте анализаторы кода. Включите их в систему контроля версий

6. Научитесь писать "велосипеды", тестировать, а главное использовать и документировать 

5. Создайте константы для всех переменных от сторонних библиотек в вашем проекте

Модульность

  • приватность
  • слабая связность
  • независимость

1 модуль = 1 задача

Модульность

10. Используйте IIFE

9. Создавайте ре-используемые модули

7. Создавайте главный модуль и не загромождайте его

8. Создавайте много небольших независимых модулей

;(function() {
  ...
  var app = angular.module('myApp');
  ...
})();

console.log(app) // not defined

Контроллеры

12. Переносите логику из контролера в фабрики и сервисы

11. Формируйте $scope в определенном месте

13. Используйте синтаксис controllerAs

<div ng-controller="UserController as user">
  {{ user.name }}
</div>

для шаблонов

function UserController() {
   this.name = {};
   this.someFuncion = function() { };
}

для контроллеров

function UserController() {
   var vm = this;
   vm.name = {};
   vm.sumeFunc = function() { };
}

или

Замена vm синтаксиса

function User() {
  angular.extend(this, {
    someVar: {
      name: 'Name'
    },
    anotherVar: [],
    doSomething: function doSomething() {

    }
  });
}

angular
  .module('app')
  .controller('User', User);

angular.extend

Контроллеры

14. Задавайте отдельный контроллер для каждого шаблона

// route-config.js
angular
   .module('app')
   .config(config);

function config($routeProvider) {
   $routeProvider
       .when('/user', {
           templateUrl: 'user.html',
           controller: 'UserController',
           controllerAs: 'vm'
       });
}

<!-- user.html -->
<div></div>

Минимизация количества $watcher - ов

16. Используйте одноразовую привязку данных {{:: ... }}

17. Не используйте ng-class для установки CSS классов, если это возможно сделать средствами css

18. Не храните ссылки на Dom элементы в scope

19. Переносите манипуляции с DOM в директивы

15. Правильно используйте $rootScope

20. Создавайте директивы с изолированным $scope

Минимизация количества $watcher - ов

21. Избегайте работы с большими данными

23. Удаляйте ненужные фильтры

24. Переносите тяжелую логику из фильтров в контроллеры и сервисы

22. Используйте track by для циклов

Минимизация количества $watcher - ов

26. Избегайте установки флага objectEquality в true.


$scope.$watch(…, …, true);

25. Используйте $watchCollection вместо $watch (с 3-им параметром)

27. Отписывайтесь от событий и watch-еров

var stopFunction = $scope.$on('someEvent', function() { ... }); // Обработчик добавлен
stopFunction(); // Обработчик удален

Оптимизация $digest вызовов

28. Если это возможно, сокращайте количество вызовов ng-model

ng-model-options=”{debounce: 250}”

29. Старайтесь избегать использования ng-mouse-over и подобных директив

30. Избегайте использования сокращенного синтаксиса объявления зависимостей без учета минификации кода

31. Используйте $inject для задания зависимостей

Аннотация внедрения зависимостей

angular
   .module('app')
   .controller('Dashboard', Dashboard);

function Dashboard(common, dataservice) {
}
angular.module('app').controller('Dashboard', d);function d(a, b) { }
angular
   .module('app')
   .controller('Dashboard', Dashboard);

Dashboard.$inject = ['$location', '$routeParams', 'common', 'dataservice'];

function Dashboard($location, $routeParams, common, dataservice) {
}

32. Используйте ng-annotate с /** @ngInject */

33. Замените всплытие событий использованием Медиатора

Паттерн "Фасад"

34. Создавайте "врапперы" над вашими библиотеками

скрытие деталей реализации конкретного функционала

DOM - оптимизация

35. Упрощайте DOM

39. Клонируйте ваши узлы, изменяйте копии, а затем заменяйте ими оригиналы

40. Используйте только быстрые обертки над DOM операциями jquery

36. Переключайте CSS- классы вместо перестроения DOM

37. Кэшируйте выборки node, вносите изменения

38. Генерируйте элементы отдельно от страницы 

41. Не чередуйте запись и чтение DOM

Оптимизация CSS

42. Избавляйтесь от тяжелых css свойств:

border-radius, box-shadow, rotate. Заменяйте их на svg

43. Тестируйте css-transitions

44. Отключайте сложные :hover анимации во время скроллинга

.disable-hover {
  pointer-events: none;
}

45. Указывайте четкие размеры изображениям, тем самым ускоряя reflow и repaint

function signUpQ() {
    $ionic Loading.show({
                template: ’Registration...’
            }; firebaseService.checkUsername(vm.user.userName).then(function(exist) {
                    if (!exist) {
                        firebaseService.createUser(vm.userCred.email, vm.userCred.password).then(function() {
                                firebaseService.authByPassword(vm.userCred.email, vm.userCred.password).then(function(response) {
                                        firebaseService.pushl) serDetails(response.uid, vm.user), then(function(error) {
                                            firebaseService.reserveUsername(vm.user.userName, response.uid).then(function(error) {
                                                    $ionicLoading.hide();
                                                    if (error) {
                                                        var promise = SionicPopup.alert({
                                                            title: 'Something wrong',
                                                            template: 'Your account is created but you have to change your User Name directly from from your profile'
                                                        });
                                                        promise.then(function() {
                                                            Sstate.go('main.login');
                                                        });
                                                    } else {
                                                        Sstate.go('main.login');
                                                    }
                                                },
                                                function(error) {
                                                    $ionicLoading.hide();
                                                    var promise = SionicPopup.alert({
                                                        title: 'Something wrong',
                                                        template: 'Your account is created but you have to change your User Details directly from your profile'
                                                    });
                                                    promise.then(function() {
                                                        Sstate.go('main.login');
                                                    };);
                                                })
                                        },
                                        function(error) {
                                            SionicLoading.hide();
                                            SionicPopup.alert({
                                                title: 'Registration error, template: error'
                                            });
                                        }
                                    }
                                }
                            } else {
                                SionicLoading.hide();
                                SionicPopup.alert({
                                    title: 'Registration error',
                                    template: 'Nickname already in use'
                                });
                            }
                        };
(function(module) {
    'use strict';
    module.factory('signUpService', signUpService);
    signUpService.$inject = ['$ionicLoading', 'firebaseService', '$ionicPopup', '$state', '$q'];
    function signUpService($ionicLoading, firebaseService, $ionicPopup, $state, $q) {

        return {
            signUp: function(user, userCred) {
                var newUser = {
                    user: user,
                    userCred: userCred
                };

                /* Registration chain */
                checkUsername(newUser)
                    .then(createUser)
                    .then(authByPassword)
                    .then(pushUserDetails)
                    .then(reserveUsername)
                    .catch(throwError);
            }
        };
        [...]
    }
}(angular.module('starter')));

46. Выстраивайте цепочки методов

Получение данных

50. При последовательном переборе элементов массива отдавайте предпочтение циклу for, а не циклу for...in

48. Используйте объекты вместо массива аргументов

var alert = new Alert(id, {
 x: 100, y: 75,
 width: 300, height: 200,
 title: "Error", message: message,
 titleColor: "blue", bgColor: "white",
 textColor: "black",
 icon: "error", modal: true
});

49. Не изменяйте массив arguments. Копируйте его в настоящий, используя [].slice.call(arguments)

47. Получайте общие данные не блокируя загрузку основных данных

JS ES6

51. Используйте let и const. Не забывайте про ReferenceError

52. Оставьте декларации VAR внутри унаследованного кода, чтобы обозначить, что он должен быть тщательно переработан

(function () {
    var food = 'Meow Mix';
}());

console.log(food); // Reference Error
Using ES6 Blocks:

{
    let food = 'Meow Mix';
}

console.log(food); // Reference Error

54.  Используйте экспорт модулей. Помещайте export в конце модуля.

53. Используйте стрелочные функции

  • когда вам нужно сохранить лексическое значение this
  • вместо функциональных выражений, когда это возможно
function sumTwo(a, b) {
    return a + b;
}

function sumThree(a, b, c) {
    return a + b + c;
}

let api = {
    sumTwo,
    sumThree
};

export default api;

55. Экспортируемые элементы - это связи, а не ссылки. Избегайте изменения общего интерфейса этих экспортируемых значений

+1 совет

Не учите код. Учите архитектуру.

Вопросы?

55+1

By tatyana_babich

55+1

  • 496