Открыть меню    

Полезные практики javascript

Компонентный подход

Компонентный подход — код разбивается на повторно используемые модули с одинаковым интерфейсом. Для этого нужно описать их каких модулей состоит код, что они умеют делать и как они взаимодействуют друг с другом.

Какие сущности у нас есть?

  • список отелей
  • отель (как один из элементов списка)
  • фотогалерея

Что умеют отели (каждый в отдельности)?

  • создаваться из шаблона
  • добавляться на страницу
  • обрабатывать клики
  • удалять обработчики
  • удаляться со страницы

Что умеет галерея?

  • показываться (в разметке уже есть галерея)
  • принимать набор фотографий
  • показывать нужную фотографию
  • прятаться

Как связаны между собой галерея и отель?

Через список отелей. Он создает и то и другое и может контролировать изменения в каждом из них.

Объекты связываются друг с другом через общего предка, который слушает их события и вызывает их методы

Как связать два компонента (класса) между собой

Передаем свойству form setOnSubmitHandler callback, который переопределяет настройки экземпляра presenterInfo и затем отрисовывает presenterInfo.render(). Обратите внимание, что значения элементов формы мы передаем только непосредственно внутри класса Form: this._onSubmitHandler(data).

let form = new Form({
    el: document.querySelector('.form')
});

let presenterInfo = new Presenter({
    el: document.querySelector('.presenter')
});

form.setOnSubmitHandler(formData => {
    presenterInfo.setData({
        login: formData.login,
        password: formData.password
    });

    presenterInfo.render();
});


// in form.js:

setOnSubmitHandler (handler) {
    this.onSubmitHandler = handler;
}

// при submit формы выполняем установленные ранее callback
onSubmit (e) {
    e.preventDefault();
    let data = this.getData();

    this.onSubmitHandler(data);
}

&& вместо if

//...
next: (x) => conditionFn(x) && obs.next(x)
//...

2-й операнд (obs.next(x)) выполнится только тогда, когда первый операнд (conditionFn(x)) true.

Если первое значение false, то второе не имеет смысла проверять.

Оптимизация (throttle - ускорение)

Оптимизация любых функций, который повторяются слишком часто.

Проверку запустим не чаще, чем в 100мс:
То есть на каждой следующей прокрутке Timeout очищается и только при останове и по прошествии 100мс, то только тогда будет вызван scroll.

var scrollTimeout;
// на каждый запуск скролла в эту переменную записываем новый Timeout,
// который отработает через 100мс

window.addEventListener("scroll", function(event){

    clearTimeout(scrollTimeout);
    scrollTimeout = setTimeout(function() {
        //....
    }, 100);

})

Фасад

Работаем например над общими компонентами по смыслу в одной папке.

Далее импортируем их в одном файле и экспортируем так:

//task.ts
import TasksComponent from './tasks.component';
import TaskEditorComponent from './task-editor.component';
import TaskMyDirective from './task-tooltip.directive';

export {
    TASKS_DIRECTIVES,
    TasksComponent,
    TaskEditComponent,
    TaskMyDirective
};

Далее мы можем экспортировать по отдельности или все вместе в другие модули:

//app.ts
import { TasksComponent, TaskEditComponent } from './tasks/tasks';

Конструкция !!

Конструкция !! позволяет преобразовать любой выражение JS в его логический эквивалент.

!!"ЭЭЭЭЭ" === true //true

!!0 === false //true

Альтернатива свойству функции arguments

function log(target, name, descriptor) {
  const original = descriptor.value;
  if (typeof original === 'function') {
    descriptor.value = function(...args) {
      console.log(`Arguments: ${args}`);
      try {
        const result = original.apply(this, args);
        console.log(`Result: ${result}`);
        return result;
      } catch (e) {
        console.log(`Error: ${e}`);
        throw e;
      }
    }
  }
  return descriptor;
}

Обратите внимание на то, что здесь мы использовали оператор расширения для того, чтобы автоматически создать массив со всеми переданными методу аргументами. Это — современная альтернатива свойству функции arguments.

Oператор расширения в действии:

var log = function(a, b, ...rest) {
  console.log(a, b, rest);
};

log(1, 2, 3, 4, 5, 6); // 1 2 [ 3, 4, 5, 6 ]

Асинхронность

console.time('loop');

setTimeout(function() {
    console.log('test');
}, 2000);

for (var i = 0; i < 3000000000; i++) {
    var a = i/i;
}

console.timeEnd('loop');
Отсчет начинается с момента установки таймера, но обработка начнется только в конце основного потока, то есть при обработке цикле прошло, например, 2 секунды и значит надпись 'test' будет выведена сразу же, без всяких ожиданий. jsfiddle.net

Занижаем скорость интернета в chrome

Вкладка no throttling (throttle - душить, дросселировать, мять; дроссель) и выбираем нужный девайс с соответствующей скоростью.

Как правильно организовывать асинхронный код?

Promise

Fetch API https://developer.mozilla.org/ru/docsFetch_API - это обертка над XMLHttpRequest, которая полностью работает на Promise.

try catch

Парсим ответ сервера в try catch: если данные от сервера не JSON, то отдаем cb('Извините в данных ошибка');

let xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = function (e) {
    let result;
    try {
        result = JSON.parse(xhr.responseText);
    } catch (e) {
        cb('Извините в данных ошибка');
    }
    cb(result.status);
};
xhr.send(JSON.stringify(data));

Callback-функцию, асинхронная операция

/* upload.js */

// cb - собственно и есть наш callback
export default function (url, data, cb) {
    let xhr = new XMLHttpRequest();
    xhr.open('POST', url, true);

    xhr.onload = function (e) {
        let result = JSON.parse(xhr.responseText);
        cb(result.status);
    };

    xhr.send(data);
}
/* index.js */

import fileUpload from './upload';

const formUpload = document.querySelector('#upload');

formUpload.addEventListener('submit', prepareSendFile);

function prepareSendFile(e) {
    e.preventDefault();
    let resultContainer = formUpload.querySelector('.status');
    let formData = new FormData();

    // передаем callback, который по scope видит и resultContainer и formUpload
    fileUpload('/admin/upload', formData, function (data) {
        resultContainer.innerHTML = data;
        formUpload.reset();
    });
}

Суть проста - наш callback будет выполнен лишь при определенных условиях. При этом callback-функция работает с нашим текущим кодом (resultContainer, formUpload) и мы заранее не знаем будет ли она выполнена и в точности какие данные придут в нее.

Комментарии к статье