55+1 прием
для улучшения
JavaScript-кода
Бабич Татьяна
Руководитель Frontend отдела
компании SimbirSoft
На старт! Внимание! Марш!
Проект будет больше, чем предполагается
Состав команды может поменяться
Время реализации сильно ограничено
Соблюдайте LIFT - принципы1
locate
identify
flat
try to stay DRY
L
I
F
T
-
-
-
-
Соблюдайте LIFT - принципы.
Соблюдайте LIFT - принципы.
Спустя пару
месяцев
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
Соблюдайте LIFT - принципы.
Спустя полгода
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/
...
Модульность
Модульность
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 */
}
Применяйте единый codestyle. Используйте анализаторы
кода. Включите их в систему контроля версий
4
Модульность
Соблюдайте правила именования переменных3
profile.controller.js ProfileController.js
Создайте константы для всех переменных от сторонних
библиотек в вашем проекте
5
Модульность
Оптимизируйте ваши «велосипеды», тестируйте их, а
главное документируйте
6
1 модуль = 1 задача
Модульность
приватность
слабая связность
Модульность
независимость
Создавайте много небольших независимых модулей
Создавайте ре-используемые модули9
8
Модульность
Создавайте главный модуль и не загромождайте его7
Модульность
Используйте IIFE10
;(function() {
...
var app = angular.module('myApp');
...
})();
console.log(app) // not defined
Переносите логику из контроллера в фабрики и сервисы12
Контроллеры
Формируйте $scope в определенном месте11
Контроллеры
Используйте синтаксис 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() { };
}
Для шаблонов Для контроллеров
или
13
function User() {
angular.extend(this, {
someVar: {
name: 'Name'
},
anotherVar: [],
doSomething: function doSomething() {
}
});
}
angular
.module('app')
.controller('User', User);
Контроллеры
Замена vm синтаксиса
Задавайте отдельный контроллер для каждого шаблона
// 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>
14
Контроллеры
Используйте одноразовую привязку данных {{:: ... }}
Не используйте ng-class для установки CSS свойств,
если это возможно сделать средствами только CSS
17
16
Минимизация количества $watcher - ов
Правильно используйте $rootScope15
Минимизация количества $watcher-ов
Минимизация количества $watcher - ов
Переносите манипуляции с DOM в директивы19
Не храните ссылки на DOM элементы в $scope18
Избегайте работы с большими данными21
Создавайте директивы с изолированным $scope20
Используйте track by для циклов22
Минимизация количества $watcher - ов
Переносите тяжелую логику из
фильтров в контроллеры и сервисы
24
Удаляйте ненужные фильтры23
Используйте $watchCollection вместо $watch (с 3-им параметром).
Избегайте установки флага objectEquality в true
$scope.$watch(…, …, true);
26
Минимизация количества $watcher - ов
Откажитесь от использования $watch там, где это возможно25
Отписывайтесь от watches и event listeners27
Минимизация количества $watcher - ов
var stopFunction = $scope.$on('someEvent', function() { ... }); // Обработчик добавлен
stopFunction(); // Обработчик удален
Если это возможно, сокращайте количество вызовов ng-model
ng-model-options=”{debounce: 250}”
Старайтесь избегать использования ng-mouse-over и подобных
директив
29
28
Оптимизация $digest вызовов
Оптимизация $digest вызовов
Избегайте использования сокращенного синтаксиса объявления
зависимостей без учета минификации кода
angular
.module('app')
.controller('Dashboard', Dashboard);
function Dashboard(common, dataservice) {
}
30
Аннотация внедрения зависимостей
Аннотация внедрения зависимостей
angular.module('app').controller('Dashboard', d);function d(a, b)
{ }
Используйте $inject для задания зависимостей
angular
.module('app')
.controller('Dashboard', Dashboard);
Dashboard.$inject = ['$location', '$routeParams', 'common', 'dataservice'];
function Dashboard($location, $routeParams, common, dataservice) {
}
Используйте ng-annotate с /** @ngInject */32
31
Аннотация внедрения зависимостей
Архитектура
Замените всплытие событий использованием Медиатора33
Архитектура
Паттерн «Фасад»
скрытие деталей реализации конкретного функционала
Архитектура
Создавайте «врапперы» над вашими библиотеками34
Переключайте CSS классы вместо перестроения DOM
Скрывайте элементы перед изменением37
36
Оптимизация CSS
Упрощайте DOM35
DOM - оптимизация
Генерируйте элементы отдельно от страницы38
Оптимизация CSS
Клонируйте ваши узлы, изменяйте копии, а затем
заменяйте ими оригиналы
Используйте только быстрые обертки над DOM
операциями jquery
40
39
Не чередуйте запись и чтение DOM41
Тестируйте css-transitions43
Оптимизация CSS
Избавляйтесь от тяжелых CSS свойств: border-radius,
box-shadow, rotate
42
Оптимизация CSS
.disable-hover {
pointer-events: none;
}
Отключайте сложные :hover анимации во время скроллинга44
Оптимизация CSS
Указывайте четкие размеры изображениям, тем самым
ускоряя reflow и repaint
45
Выстраивайте цепочки методов46
Получение данных
Получение данных
(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')));
Используйте объекты вместо массива аргументов
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
});
48
Получение данных
Получайте общие данные не блокируя загрузку основных данных47
Не изменяйте массив arguments. Копируйте его в настоящий,
используя [].slice.call(arguments)
49
Получение данных
Не применяйте цикл for in для массивов50
class ExampleController {
constructor() {
this.controllerName = 'Example Controller';
}
}
export { ExampleController }
ES6
ES6
Используйте классы для контроллеров и сервисов51
Помещайте export в конец модуля
function sumTwo(a, b) {
return a + b;
}
function sumThree(a, b, c) {
return a + b + c;
}
let api = {
sumTwo,
sumThree
};
export default api;
52
ES6
Оставьте декларации VAR внутри унаследованного
кода, чтобы обозначить, что он должен быть тщательно
переработан
53
ES6
Используйте стрелочные функции
когда вам нужно сохранить лексическое значение this
вместо функциональных выражений, когда это возможно
54
myModule.component('appFooter', {
template: '<footer></footer>',
bindings: { entityName: '=' },
controller: function() {
this.getCurrentYear = function() {};
}
});
ES6
Используйте API компонент55
+1 совет
Пишите тесты.
Вопросы?

55+1 прием для улучшения Javascript-кода / Татьяна Бабич (Simbirsoft)

  • 1.
    55+1 прием для улучшения JavaScript-кода БабичТатьяна Руководитель Frontend отдела компании SimbirSoft
  • 2.
    На старт! Внимание!Марш! Проект будет больше, чем предполагается Состав команды может поменяться Время реализации сильно ограничено
  • 3.
    Соблюдайте LIFT -принципы1 locate identify flat try to stay DRY L I F T - - - - Соблюдайте LIFT - принципы.
  • 4.
    Соблюдайте LIFT -принципы. Спустя пару месяцев 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
  • 5.
    Соблюдайте LIFT -принципы. Спустя полгода 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/ ...
  • 6.
  • 7.
  • 8.
    /* 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 */ }
  • 9.
    Применяйте единый codestyle.Используйте анализаторы кода. Включите их в систему контроля версий 4 Модульность Соблюдайте правила именования переменных3 profile.controller.js ProfileController.js
  • 10.
    Создайте константы длявсех переменных от сторонних библиотек в вашем проекте 5 Модульность Оптимизируйте ваши «велосипеды», тестируйте их, а главное документируйте 6
  • 11.
    1 модуль =1 задача Модульность приватность слабая связность Модульность независимость
  • 12.
    Создавайте много небольшихнезависимых модулей Создавайте ре-используемые модули9 8 Модульность Создавайте главный модуль и не загромождайте его7
  • 13.
    Модульность Используйте IIFE10 ;(function() { ... varapp = angular.module('myApp'); ... })(); console.log(app) // not defined
  • 14.
    Переносите логику изконтроллера в фабрики и сервисы12 Контроллеры Формируйте $scope в определенном месте11 Контроллеры
  • 15.
    Используйте синтаксис controllerAs Контроллеры <divng-controller="UserController as user"> {{ user.name }} </div> function UserController() { this.name = {}; this.someFuncion = function() { }; } function UserController() { var vm = this; vm.name = {}; vm.sumeFunc = function() { }; } Для шаблонов Для контроллеров или 13
  • 16.
    function User() { angular.extend(this,{ someVar: { name: 'Name' }, anotherVar: [], doSomething: function doSomething() { } }); } angular .module('app') .controller('User', User); Контроллеры Замена vm синтаксиса
  • 17.
    Задавайте отдельный контроллердля каждого шаблона // 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> 14 Контроллеры
  • 18.
    Используйте одноразовую привязкуданных {{:: ... }} Не используйте ng-class для установки CSS свойств, если это возможно сделать средствами только CSS 17 16 Минимизация количества $watcher - ов Правильно используйте $rootScope15 Минимизация количества $watcher-ов
  • 19.
    Минимизация количества $watcher- ов Переносите манипуляции с DOM в директивы19 Не храните ссылки на DOM элементы в $scope18 Избегайте работы с большими данными21 Создавайте директивы с изолированным $scope20
  • 20.
    Используйте track byдля циклов22 Минимизация количества $watcher - ов Переносите тяжелую логику из фильтров в контроллеры и сервисы 24 Удаляйте ненужные фильтры23
  • 21.
    Используйте $watchCollection вместо$watch (с 3-им параметром). Избегайте установки флага objectEquality в true $scope.$watch(…, …, true); 26 Минимизация количества $watcher - ов Откажитесь от использования $watch там, где это возможно25
  • 22.
    Отписывайтесь от watchesи event listeners27 Минимизация количества $watcher - ов var stopFunction = $scope.$on('someEvent', function() { ... }); // Обработчик добавлен stopFunction(); // Обработчик удален
  • 23.
    Если это возможно,сокращайте количество вызовов ng-model ng-model-options=”{debounce: 250}” Старайтесь избегать использования ng-mouse-over и подобных директив 29 28 Оптимизация $digest вызовов Оптимизация $digest вызовов
  • 24.
    Избегайте использования сокращенногосинтаксиса объявления зависимостей без учета минификации кода angular .module('app') .controller('Dashboard', Dashboard); function Dashboard(common, dataservice) { } 30 Аннотация внедрения зависимостей Аннотация внедрения зависимостей angular.module('app').controller('Dashboard', d);function d(a, b) { }
  • 25.
    Используйте $inject длязадания зависимостей angular .module('app') .controller('Dashboard', Dashboard); Dashboard.$inject = ['$location', '$routeParams', 'common', 'dataservice']; function Dashboard($location, $routeParams, common, dataservice) { } Используйте ng-annotate с /** @ngInject */32 31 Аннотация внедрения зависимостей
  • 26.
    Архитектура Замените всплытие событийиспользованием Медиатора33 Архитектура
  • 27.
    Паттерн «Фасад» скрытие деталейреализации конкретного функционала Архитектура Создавайте «врапперы» над вашими библиотеками34
  • 28.
    Переключайте CSS классывместо перестроения DOM Скрывайте элементы перед изменением37 36 Оптимизация CSS Упрощайте DOM35 DOM - оптимизация Генерируйте элементы отдельно от страницы38
  • 29.
    Оптимизация CSS Клонируйте вашиузлы, изменяйте копии, а затем заменяйте ими оригиналы Используйте только быстрые обертки над DOM операциями jquery 40 39 Не чередуйте запись и чтение DOM41
  • 30.
    Тестируйте css-transitions43 Оптимизация CSS Избавляйтесьот тяжелых CSS свойств: border-radius, box-shadow, rotate 42 Оптимизация CSS
  • 31.
    .disable-hover { pointer-events: none; } Отключайтесложные :hover анимации во время скроллинга44 Оптимизация CSS Указывайте четкие размеры изображениям, тем самым ускоряя reflow и repaint 45
  • 32.
  • 33.
    (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')));
  • 34.
    Используйте объекты вместомассива аргументов 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 }); 48 Получение данных Получайте общие данные не блокируя загрузку основных данных47
  • 35.
    Не изменяйте массивarguments. Копируйте его в настоящий, используя [].slice.call(arguments) 49 Получение данных Не применяйте цикл for in для массивов50
  • 36.
    class ExampleController { constructor(){ this.controllerName = 'Example Controller'; } } export { ExampleController } ES6 ES6 Используйте классы для контроллеров и сервисов51
  • 37.
    Помещайте export вконец модуля function sumTwo(a, b) { return a + b; } function sumThree(a, b, c) { return a + b + c; } let api = { sumTwo, sumThree }; export default api; 52 ES6
  • 38.
    Оставьте декларации VARвнутри унаследованного кода, чтобы обозначить, что он должен быть тщательно переработан 53 ES6 Используйте стрелочные функции когда вам нужно сохранить лексическое значение this вместо функциональных выражений, когда это возможно 54
  • 39.
    myModule.component('appFooter', { template: '<footer></footer>', bindings:{ entityName: '=' }, controller: function() { this.getCurrentYear = function() {}; } }); ES6 Используйте API компонент55
  • 40.
  • 41.