ES5 керівництво по JavaScript


JavaScript quality guide

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

Від перекладача

Всім привіт, з вами Максим Іванов, і сьогодні ми поговоримо про правила оформлення коду на мові JavaScript. Ніколя Бэвакуа (Nicolás Bevacqua), автор книги «Дизайн JavaScript-додатків» (JavaScript Application Design), розробник з Аргентини, опублікував дане керівництво досить давно, перший запис з'явилася ще в 2014 році, багато написано за стандартом ES5, проте, в наші дні це все одно актуально зараз, коли ES6 ще ніде повноцінно не працює без babel та інших транспайлеров. Хоча ми бачимо прогрес у топових десктопних браузерах (Google Crhome, Firefox), де вже реалізовано 70-90% задуманого, ми бачимо, що вони прагнуть підтримувати новий стандарт, але, на жаль, ще немає браузерів, які цілком могли б підтримувати ES6. До речі, я буду дуже радий вашим коментарям. Загалом, удачі і давайте почнемо.


Введення

Ці рекомендації не висічені на камені, це базові правила, які допоможуть вам навчитися писати більш послідовний і зрозумілий код. Щоб ефективно застосовувати на практиці запропоновані правила, почніть прямо зараз, діліться ними зі своїми колегами і впроваджуйте у виробництво. Однак, не зациклюйтеся на цих правилах, це може бути безплідним і контрпродуктивним. Спробуйте знайти золоту середину, щоб усім було комфортно у вашому колективі. Автоматизована перевірка стилю коду це досить корисна річ, так як стиль написання коду це справа кожного.

Зміст

  1. Модулі
  2. Суворий режим
  3. Форматування прогалин
  4. Точка з комою
  5. Стиль коду
  6. Аналіз коду на помилки
  7. Рядка
  8. Ініціалізація змінних
  9. Умовні конструкції
  10. Порівняння
  11. Тернарний оператор
  12. Прототипи і спадкування
  13. Об'єкти
  14. Масиви
  15. Регулярні вирази
  16. Консоль розробника
  17. Коментарі
  18. Назва
  19. Polyfill-бібліотеки
  20. Щоденні хитрості
  21. Керівництво по ES6


Модулі

Цей пункт передбачає, що ви використовуєте модульні системи, такі як CommonJS, AMD, ES6 Modules, або будь-які інші. Модульні системи працюють з окремою областю видимості, не зачіпаючи глобальні об'єкти, також вони забезпечують більш організовану структуру коду за рахунок автоматичної генерації залежностей, звільняючи вас від самостійної вставки тега <script>.

Модульні системи можуть довантажувати шаблони, які в свою чергу корисні для локальних рішень, коли мова йде про тестування окремого ізольованого компонента.

До прочитання:
1. Що таке модуль?
2. Модулі CommonJS
3. Модулі в JS

Суворий режим

Завжди використовуйте 'use strict'; у верхній частині свого модуля. Суворий режим дозволяє відловлювати помилки мови, хоч це і трохи страшне назву, такий режим дозволяє інтерпретатору стежити за якістю коду, тим самим ви витрачаєте менше часу на виправлення помилок.

До прочитання
1. Директива use strict
2. Strict Mode

Форматування прогалин

Контролюйте кількість пробілів при форматуванні коду. Для цього рекомендується використовувати файл конфігурації .editorconfig. Я пропоную використовувати такі налаштування.

# editorconfig.org
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false


Використання табуляції та пропусків ваша особиста справа, однак я рекомендую робити відступи в два пробілу. Файл .editorconfig подбає про все, процес форматування буде контролюватися і тоді, коли ви натиснете клавішу Tab.

Можна настроїти не тільки правильну вкладку, можна також налаштувати і відступи в потрібних місцях, наприклад, до, після або між чим-небудь. Правда виконати контроль над такими речами досить трудомістке заняття.

function () {}
function( a, b ){}
function(a, b) {}
function (a,b) {}

Намагайтеся звести до мінімуму відмінності в оформленні свого коду.

Там, де це можливо, покращуйте читабельність коду, намагайтеся писати в одному рядку максимум 80 символів.

До прочитання:
1. EditorConfig
2. Одні налаштування для всіх редакторів
3.Установка EditorConfig в Sublime text 3
4. 80-characters
5. Хороший стиль програмування

Крапка з комою

Більшість JavaScript-програмістів воліють використовувати крапку з комою. Крапку з комою необхідно завжди ставити для того, щоб уникнути автоматичної підстановки (ASI). Є мови, в яких крапка з комою не є обов'язковою, і її там ніхто не ставить. В JavaScript переклад рядки її замінює, але лише частково, тому краще її ставити, якщо ви розумієте правила ASI.

Незалежно від того, що ви впевнені у своєму коді, використовуйте валідатор (linter), щоб відловлювати непотрібні точки з комою.

До прочитання:
1. JavaScript Semicolon Insertion
2. Поради по стилю коду
3. Підвищення якості javascript коду
4. Все, що треба знати про точки з комою
5. Відкритий лист лідерам JS щодо точок з комою

Стиль коду

Не варто заморочуватися на цьому, адже це сверхболезненный і напружений момент, не приносить помітного успіху при дотриманні такої ж суворої політики в інших областях.

Зрозуміло, певний стандарт оформлення коду (стиль програмування) (англ. coding convention) існує. У світі JavaScript є навіть інструменти для перевірки вашого коду на дотримання цього стандарту — JSCS (JavaScript Code Style). Наявність загального стилю програмування полегшує розуміння і підтримку вихідного коду, написаного більш ніж одним програмістом, а також спрощує взаємодію декількох осіб при розробці програмного забезпечення.

До таких стандартів приходять як програмісти, так і кращі команди, так і цілі компанії:
1. Airbnb
2. Дуглас Крокфорд
3. Google
4. Grunt
5. Idiomatic
6. jQuery
7. <a href=«github.com/mrdoob/three.js/wiki/Mr.doob's-Code-Style%E2%84%A2>MDCS
8. Node.js
9. Wikimedia
10. WordPress
11. Яндекс

До прочитання:
1. Стиль програмування
2. JavaScript Code Style

Аналіз коду на помилки

З іншого боку, іноді необхідна валідація. Якщо ви не використовуєте валідатор, значить ви точно впевнені в тому, як працює ваш код, і вам не потрібно використовувати, наприклад, JSLint (інструмент перевірки якості програмного коду). І все ж, я рекомендую користуватися JSHint або ESLint.

Кілька порад у використанні JSHint:
  • Оголосіть .jshintignore файл в node_modules, bower_components та інших
  • Використовуйте правила нижче у файлі .jshintrc


{
"curly": true,
"eqeqeq": true,
"newcap": true,
"noarg": true,
"noempty": true,
"nonew": true,
"sub": true,
"undef": true,
"unused": true,
"trailing": true,
"boss": true,
"eqnull": true,
"strict": true,
"immed": true,
"expr": true,
"latedef": "nofunc",
"quotmark": "single",
"indent": 2,
"node": true
}


Звичайно, ви не зобов'язані суворо дотримуватися цих правил, але важливо знайти золоту середину між валідатором та іншими стилями програмування.

До прочитання:
1. JSLint
2. JSHint
3. Кілька порад по налаштуванню ESlint
4. Налаштування JSHint в sublime text 3
5. JavaScript-линтинг

Рядка

Всі рядки повинні мати один і той же тип лапок. Використовуйте одинарні або подвійні лапки у всьому коді.
// погано
var message = 'oh hai' + name + "!";

// добре
var message = 'oh hai' + name + '!';

Це буде працювати швидше, якщо скористаєтеся функцією форматування схожою на util.format Node.js. Таким чином, вам буде простіше форматування рядка, а код буде виглядати набагато чистіше.

// краще
var message = util.format('oh hai %s!', name);

Ви можете реалізувати щось подібне, скориставшись шматком коду нижче.
function format () {
var args = [].slice.call(arguments);
var initial = args.shift();

function replacer (text, replacement) {
return text.replace('%s', replacement);
}
return args.reduce(replacer, initial);
}

Для оголошення мультистрок (багаторядкових), особливо коли мова йде про HTML фрагментах, іноді краще використовувати масив як буфера даних, елементи якого надалі можна об'єднати в рядок. Звичайно, конкатенувати (об'єднувати) рядки набагато легше в звичному стилі і, звичайно, це буде працює швидше, але іноді це важче відстежувати.
var html = [
'<div>',
format('<span class="monster">%s</span>', name),
'</div>'
].join(");

У объединяемый масив можна вставляти свої фрагменти і в кінці об'єднувати їх для кращого читання коду. Саме так працюють деякі шаблонизаторы, наприклад Jade.

До прочитання:
1. Строки
2. Робота з рядками
3. Продуктивність конкатенації рядків
4. C-подібне уявлення рядків на основі типізованих масивів

Ініціалізація змінних

Завжди оголошуйте змінні за змістом і семантиці, фіксуючи їх у верхній частині області видимості. Вітається ініціалізація змінних в кожному рядку по одинці. Звичайно, можна один раз оголосити оператор var, а потім через кому вказувати змінні, однак будьте послідовні у масштабах цілого проекту.
// погано
var foo = 1,
bar = 2;

var baz;
var pony;

var a
, b;


// погано
var foo = 1;

if (foo > 1) {
var bar = 2;
}

У всякому разі, ви бачите, де оголошено конкретна мінлива
// добре
var foo = 1;
var bar = 2;

var baz;
var pony;

var a;
var b;


// добре
var foo = 1;
var bar;

if (foo > 1) {
bar = 2;
}


Оголошувати порожні змінні можна одному рядку через кому.

// прийнятно
var a = 'a';
var b = 2;
var i, j;


Умовні конструкції

Використовувати фігурні дужки. У деяких випадках це допоможе вам уникнути критичного бага у продукції Apple під час роботи протоколу безпечного з'єднання SSL/TLS.

// погано
if (err) throw err;


// добре
if (err) { throw err; }


Заради розуміння змісту, намагайтеся тримати вміст блоку умови не на одному рядку
// краще
if (err) {
throw err;
}


До прочитання:
1. apple's SSL/TLS bug
2. Ось це fail

Порівняння

Уникайте використання операторів == і !=, використовуйте більш сприятливі оператори порівняння === і! ==. Ці оператори називаються жорсткими операторами порівняння, в той час як їх альтернативи (== і !=) перетворять операнди до одного і того ж типу даних.

// погано
function isEmptyString (text) {
return text == ";
}

isEmptyString(0);
// <- true


// добре
function isEmptyString (text) {
return text === ";
}

isEmptyString(0);
// <- false


Тернарний оператор

Тернарний оператор чітко підходить для конкретних умов і абсолютно не підходить для многокомпонетных. Як правило, якщо ваш досвідчене око не може розібрати умова в тернарном операторі настільки швидко, наскільки ваш мозок може швидко прочитати текст та інтерпретувати його, заради вашого блага, не використовуйте тернарний оператор.

jQuery є яскравим прикладом, який наповнений неприємними трикомпонентними операторами.

// погано
function calculate (a, b) {
return a && b ? 11 : a ? 10 : b ? 1 : 0;
}


// добре
function getName (mobile) {
return mobile ? mobile.name : 'Generic Player';
}


У випадках, коли умова може виявитися заплутаним просто використовуйте if else.

При ініціалізації функції, використовуйте функціональну декларацію замість функціональних виразів. Все це впливає на «підняття» змінних і оголошенні функцій.

// погано
var sum = function (x, y) {
return x + y;
};


// добре
function sum (x, y) {
return x + y;
}


Немає нічого поганого в функціональних виразах, якщо ви їх використовуєте при карринге.

Майте на увазі, що функціональна декларація функцій буде доступна і в області видимості вище, так що все це не має значення, в якому порядку була оголошена ця функція. Як правило, ви завжди повинні оголошувати функції в глобальній області видимості, при цьому ви повинні намагатися уникати розміщення таких функцій всередині умовних операторів.

// погано
if (Math.random() > 0.5) {
sum(1, 3);

function sum (x, y) {
return x + y;
}
}


// добре
if (Math.random() > 0.5) {
sum(1, 3);
}

function sum (x, y) {
return x + y;
}


// добре
function sum (x, y) {
return x + y;
}

if (Math.random() > 0.5) {
sum(1, 3);
}


Якщо вам потрібен порожній метод (no-opви можете використовувати або розширення з допомогою прототипу Function.prototype або використовувати функцію function noop () {}. Також, в ідеалі, у вас повинна бути одна посилання на власний метод, що використовується у всьому додатку. Замість того, щоб писати однотипні конструкції коду, ваш метод буде шаблоном для подібних конструкції.

// погано
var divs = document.querySelectorAll('div');

for (i = 0; i < divs.length; i++) {
console.log(divs[i].innerHTML);
}


// добре
var divs = document.querySelectorAll('div');

[].slice.call(divs).forEach(function (div) {
console.log(div.innerHTML);
});


Прив'язка може бути здійснена за допомогою функції .call() з прототипу функції Function.prototype, також запис може бути скорочена до [].slice.call(arguments) замість використання Array.prototype.slice.call(). У будь-якому випадку, вона може бути спрощена за допомогою використання функції bind().

Тим не менш, слід пам'ятати, що існує значна зниження продуктивності в движку V8 при використанні такого підходу.

// погано
var args = [].slice.call(arguments);


// добре
var i;
var args = new Array(arguments.length);
for (i = 0; i < args.length; i++) {
args[i] = arguments[i];
}


Не розказуйте функції усередині циклів

// погано
var values = [1, 2, 3];
var i;

for (i = 0; i < values.length; i++) {
setTimeout(function () {
console.log(values[i]);
}, 1000 * i);
}


// погано
var values = [1, 2, 3];
var i;

for (i = 0; i < values.length; i++) {
setTimeout(function (i) {
return function () {
console.log(values[i]);
};
}(i), 1000 * i);
}


// добре
var values = [1, 2, 3];
var i;

for (i = 0; i < values.length; i++) {
setTimeout(function (i) {
console.log(values[i]);
}, 1000 * i, i);
}


// добре
var values = [1, 2, 3];
var i;

for (i = 0; i < values.length; i++) {
wait(i);
}

function wait (i) {
setTimeout(function () {
console.log(values[i]);
}, 1000 * i);
}


Або ще краще, просто використовуйте .forEach, який оптимізований для випадків використання функції в циклі.
// краще
[1, 2, 3].forEach(function (value, i) {
setTimeout(function () {
console.log(value);
}, 1000 * i);
});


Для того, щоб перегляд стека викликів був значно простіше необхідно скоротити використання анонімних функцій. Тобто, якщо ви звикли призначати всі ваші зворотні дзвінки в якості анонімних функцій, ви можете спробувати присвоїти їм імена. Це дозволить визначити основну причину виключення при аналізі трасування стека.

// погано
function once (fn) {
var ran = false;
return function () {
if (ran) { return };
ran = true;
fn.apply(this, arguments);
};
}


// добре
function once (fn) {
var ran = false;
return function run () {
if (ran) { return };
ran = true;
fn.apply(this, arguments);
};
}


Уникайте вкладених умов функції, іноді краще використовувати порожнє значення, що повертається, тим самим ваш код буде читабельний.

// погано
function foo (car, black, turbine) {
if (car) {
if (black) {
if (turbine) {
return 'batman!';
}
}
}
}


// погано
function fn (condition) {
if (condition) {
// 10+ рядків коду
}
}


// добре
function foo (car, black, turbine) {
if (!car) {
return;
}

if (!black) {
return;
}

if (!turbine) {
return;
}

return 'batman!';
}


Якщо функція нічого не повертає або повертає порожній return, то вона повертає значення undefined.

// добре
function fn (condition) {
if (!condition) {
return;
}

// 10+ рядків коду
}


До прочитання:
1. Область видимості в JavaScript і «підняття» змінних і оголошень функцій
2. Каррирование
3. Використання порожньої функції, на прикладі jQuery
4. Массивоподобные об'єкти
5. Трасування стека
6. Поради щодо налагодження JavaScript в асинхронних стеках викликів
7. JavaScript return у функції повертає undefined

Прототипи і спадкування

За будь-яку ціну уникайте розширення стандартних прототипів. Якщо вам необхідно розширити функціональні можливості нативних типів даних (структур) мови, скористайтеся бібліотекою poser або розробіть свою власну реалізацію структури даних.

До стандартних типів даних відносяться:
  • String
  • Number
  • Boolean


До складових типів даних відносяться:
  • Object
  • Function
  • Array


// погано
String.prototype.half = function () {
return this.substr(0, this.length / 2);
};


// добре
function half (text) {
return text.substr(0, text.length / 2);
}


Уникайте прототипную модель успадкування, адже це може позначитися на продуктивності. Однією з частих помилок при програмуванні на мові JavaScript є розширення базових прототипів. Ця технологія, називана monkey patching, вона порушує принцип інкапсуляції. Незважаючи на те, що вона використовується в широко поширених фреймворках, таких як Prototype.js на даний момент не існує розумних причин для її використання, так як в даному випадку вбудовані типи «захламляются» додаткової нестандартної функціональністю. Єдиним виправданням розширення базових прототипів є лише емуляція нових можливостей, таких як Array.forEach, для неподдерживающих їх старих версій мови.

  • Прототипних спадкування змушує вас використовувати ключове слово this завжди
  • Тут більше абстракції, ніж при використання простих об'єктів
  • Це причина головного болю при створенні нових об'єктів
  • Намагайтеся використовувати прості об'єкти


До прочитання:
1. Типи даних JavaScript і структури даних
2. Визначення типу даних
3. Що таке прототип?
4. Прототипно-орієнтоване програмування
5. Спадкування і ланцюжок прототипів
6. Що таке this і визначення контексту на практиці

Об'єкти

Для ініціалізації об'єкту ми можемо використовувати фігурні дужки { }, які будуть литералом об'єкта. Використовуйте підхід створення фабрики замість використання чистого конструктора для створення об'єкта.

Створення об'єкта:
var World = {}; // порожній об'єкт, що створюється за допомогою літерала фігурних дужок

console.log(World); // Object {}


var World = {
people: "~ 7 млрд.",
country: "~ 258 країн"
};

console.log(World); // Object {people: "~ 7 млрд.", country: "~ 258 країн"}


Створення об'єкта за допомогою інтерфейсу прототипу (підхід у використанні конструктора):
// погано
// згадуємо, що прототип - це спочатку порожній об'єкт
var TemplateWorld = function(_people, _country) {
// в загальному сенсі, як тільки ви використовуєте this всередині функції або створюєте внутрішні методи
// TemplateWorld перестає бути звичайною функцією і стає прототипом
this.people = _people;
this.country = _country;
} // конструктор (і в теж час прототип)

var World = new TemplateWorld("~ 7 млрд.", "~ 258 країн");

console.log(World); // TemplateWorld {people: "~ 7 млрд.", country: "~ 258 країн"}


Створення об'єкта за допомогою інтерфейсу прототипу (підхід у використанні фабрик):
// добре
var TemplateWorld = function(_people, _country) {
return {
people: _people,
country: _country
};
}

var World = TemplateWorld("~ 7 млрд.", "~ 258 країн");

console.log(World); // Object {people: "~ 7 млрд.", country: "~ 258 країн"}


Наочний приклад створення прототипу:
// добре
function util (options) {
// приватні методи і властивості прототипу
var foo;

function add () {
return foo++;
}

function reset () { // зверніть увагу, що цей метод не стає публічним
foo = options.start || 0;
}

reset();

return {
// публічні методи і властивості прототипу
uuid: add
};
}


Копіювання за посиланням і передача за значенням
Більш наочно і прозоро, як дані передаються між осередками пам'яті видно в С/С++. Але в JavaScript, дуже бажано пам'ятати про механізм роботи об'єктів і тоді відпадуть деякі питання відразу.

Змінні
// це називається передача за значенням
// один шматок пам'яті, копіюється в іншу комірку пам'яті

var a = 'text';
var b = a; // передача за значенням

a = 'update'; 

console.log(a); // update
console.log(b); // text


Об'єкти
// це називається передача за посиланням
// в JavaScript це робиться неявно

var a = {foo: 'bar'};
var b = a; // передача за посиланням
// b - посилання на a

a.foo = 'foo';

console.log(a); // Object {foo: 'foo'}
console.log(b); // Object {foo: 'foo'}

b.foo = 'bar';

console.log(a); // Object {foo: 'bar'}
console.log(b); // Object {foo: 'bar'}


До прочитання:
1. Об'єкти: передача за посиланням

Масиви

Для ініціалізації масиву ми можемо використовувати квадратні дужки [ ], які будуть литералом масиву. Для збільшення продуктивності, ви можете створити масив за допомогою конструкції new Array(length) з зазначенням його довжини (розміру масиву).

Для того, щоб правильно маніпулювати з елементами масиву необхідно знати стандартні методи. Все набагато простіше, ніж ви можете собі уявити.

Стандартні методи:


Більш просунуті методи:


Копіювання по посиланню
// Масив - це той же об'єкт

var a = [1, 2, 3];
var b = a; // передача за посиланням
// b - посилання на a

a[2] = 4;

console.log(a); // [1, 2, 4]
console.log(b); // [1, 2, 4]

b[2] = 3;

console.log(a); // [1, 2, 3]
console.log(b); // [1, 2, 3]


Приступимо до вивчення стандартних методів:
  • Перебір елементів робиться за допомогою .forEach
  • Перевірки за допомогою .some і .every
  • Об'єднання за допомогою .join і .concat
  • Робота зі стеком і чергою за допомогою .pop .push, .shift, і .unshift
  • Перебирающий метод (mapping) .map
  • Виконання запитів .filter
  • Сортування за допомогою .sort
  • Обчислення спільно с .reduce, .reduceRight
  • Копіювання за допомогою .slice
  • Більш потужний засіб для видалення і додавання елементів .splice
  • Пошук елемента у масиві .indexOf
  • На допомогу оператор in
  • Перебір в зворотному порядку або переворот масиву з допомогою .reverse


image

Перебір елементів робиться за допомогою .forEach
Це один з найпростіших методів в JavaScript. Не підтримується в IE7 та IE8.
Приймає функцію зворотного виклику (сallback), яка викликається один раз для кожного елемента в масиві, і пропускається через три основних аргументи:
  • value — значення, яке містить поточний елемент масиву
  • index — позиція елемента у масиві
  • array — являє собою посилання на масив


Крім того, ми можемо передати необов'язковий другий аргумент (об'єкт, масив, змінну, що завгодно), цей аргумент буде доступний в callback-функції і представлений у вигляді контексту (this).

['_', 't', 'a', 'n', 'i', 'f', ']'].forEach(function (value, index, array) {
this.push(String.fromCharCode(value.charCodeAt() + index + 2))
}, out = [])

out.join(")
// <- 'високий'


Ми схитрували, .join ми ще не розглядали, але скоро дійдемо до цього. В нашому випадку, він об'єднує різні елементи в масиві, цей метод набагато ефективніше працює, ніж робити це вручну out[0] + „+ out[1] + “+ out[2] + » + out[n]. До речі, ми не можемо розірвати foreach-цикл, як це робиться у звичайних переборах за допомогою break. На щастя, у нас є інші способи.

Перевірки за допомогою .some і .every
Ці методи відносяться до assert-функцій. Якщо ви коли-небудь працювали з перерахуваннями в .NET, то зрозумієте, що вони схожі на своїх кузенів .Any(x => x.IsAwesome).All(x => x.IsAwesome).

Ці методи аналогічні .forEach тим, що вони також беруть callback функцію з такими ж аргументами, але дещо відрізняються за своєю природою.

Метод some() викликає передану функцію callback один раз для кожного елемента, присутнього в масиві до тих пір, поки не знайде такий, для якого callback поверне істинне значення (значення, стає рівним true при приведенні його до типу Boolean). Якщо такий елемент знайдений, метод some() негайно поверне true. В іншому випадку, якщо callback поверне false для всіх елементів масиву, метод some() поверне false. Функція callback викликається тільки для індексів масиву, мають присвоєні значення; вона не викликається для індексів, які були видалені або яким значення ніколи не надавалися.

max = -Infinity
satisfied = [10, 12, 10, 8, 5, 23].some(function (value, index, array) {
if (value > max) max = value
return value < 10
})

console.log(max)
// <- 12

satisfied
// <- true


Метод .every() працює таким же чином, але замикання трапляються лише тоді, коли ваша callback-функція повертає false, а не правду.

Об'єднання за допомогою .join і .concat
Метод .join часто плутають з методом .concat, як ви знаєте, метод приймає роздільник в аргументі .join(separator), він створює рядок, в результаті чого він бере кожен елемент масиву і поділяє їх роздільником у цьому рядку. Якщо роздільник не вказано, за замовчуванням separator = ', '. Метод concat() повертає новий масив, що складається з масиву, на якому він був викликаний, сполученого з іншими масивами та/або значеннями, переданими в якості аргументів.

  • .concat працює за таким принципом: array.concat(val, val2, val3, valn)
  • .concat повертає новий масив
  • array.concat() без аргументів повертає неповну копію масиву


Неповна копія означає, що копія буде містити ті ж посилання на об'єкти, що і вихідний масив.
Як правило, і оригінал, і новий масив посилаються на один і той же об'єкт.
Тобто, якщо об'єкт за посиланням буде змінений, зміни помітні і в новому, і в вихідному масивах.

var a = { foo: 'bar' }
var b = [1, 2, 3, a]
var c = b.concat()

console.log(b === c)
// <- false

a.foo = 'foo';
console.log(b); // [1, 2, 3, Object {foo: 'foo'}]
console.log©; // [1, 2, 3, Object {foo: 'foo'}]

b[3] === a && c[3] === a
// <- true


Робота зі стеком і чергою за допомогою .pop .push, .shift, і .unshift
В даний час, всі знають, що додавання елементів в кінець масиву здійснюється за допомогою .push. А чи можете ви відразу додати кілька елементів, використовуючи таку конструкцію [].push('a', 'b', 'c', 'd', 'z')?

var a = [].push('a', 'b', 'c', 'd', 'z');
console.log(a); // 5

var a = [];
a.push('a', 'b', 'c', 'd', 'z');
console.log(a); // ['a', 'b', 'c', 'd', 'z']


Метод pop() видаляє останній елемент масиву і повертає його значення. Якщо масив порожній, повертається undefined (void 0).

Використовуючи .push і .pop ми можемо розробити свою структура LIFO стека.

function Stack () {
this._stack = []
}

Stack.prototype.next = function () {
return this._stack.pop()
}

Stack.prototype.add = function () {
return this._stack.push.apply(this._stack, arguments)
}

stack = new Stack()
stack.add(1, 2, 3)

stack.next()
// <- 3

stack.next()
// <- 2

stack.next()
// <- 1

stack.next()
// <- undefined


Навпаки, ми можемо створити FIFO чергу використовуючи .unshift і .shift.

function Queue () {
this._queue = []
}

Queue.prototype.next = function () {
return this._queue.shift()
}

Queue.prototype.add = function () {
return this._queue.unshift.apply(this._queue, arguments)
}

queue = new Queue()
queue.add(1,2,3)

queue.next()
// <- 1

queue.next()
// <- 2

queue.next()
// <- 3

queue.next()
// <- undefined


Використання .shift (або .pop ) у циклі для набору елементів масиву, ми можемо очистити його в процесі роботи.

list = [1,2,3,4,5,6,7,8,9,10]

while (item = list.shift()) {
console.log(item)
}

list
// <- []


Перебирающий метод (mapping) .map
Метод map() створює новий масив з результатом виклику зазначеної функції для кожного елемента масиву.

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

Метод Array.prototype.map має ту ж сигнатуру, що і у .forEach, .some, .every:
.map(fn(value, index, array), thisArgument).

values = [void 0, null, 'false', "]
values[7] = 'text'
result = values.map(function(value, index, array){
console.log(value)
return value
})

// <- [undefined, null, 'false', ", undefined × 3, undefined]

console.log(result[7]); // text


undefined × 3 — пояснюють тим, що метод .map не відпрацював для віддалених або нерозподілених елементів масиву, але вони все одно як і раніше включені в результуючий масив. Метод .map використовується для трансформації масиву.

// перебір
[1, '2', '30', '9'].map(function (value) {
return parseInt(value, 10)
})
// 1, 2, 30, 9

[97, 119, 101, 115, 111, 109, 101].map(String.fromCharCode).join(")
// <- 'високий'

// використовується шаблон відображається в нових об'єктах
items.map(function (item) {
return {
id: item.id
name: computeName(item)
}
})


Виконання запитів .filter
Метод filter() створює новий масив з усіма елементами, що пройшли перевірку, що задається в передаваної функції.

Метод filter() викликає передану функцію callback один раз для кожного елемента, присутнього в масиві, і конструює новий масив з усіма значеннями, для яких функція callback повернула true або значення, стає true при приведенні у boolean. Функція callback викликається тільки для індексів масиву, мають присвоєні значення; вона не викликається для індексів, які були видалені або яким значення ніколи не надавалися. Елементи масиву, що не пройшли перевірку функцією callback, просто пропускаються і не включаються в новий масив.

Як зазвичай: .filter(fn(value, index, array), thisArgument). Враховуючи, що .filter повертає тільки ті елементи, які проходять перевірку callback-функції, є деякі цікаві варіанти використання.

[void 0, null, 'false', ", 1].filter(function (value) {
return value
})
// <- [1]

[void 0, null, 'false', ", 1].filter(function (value) {
return !value
})
// <- [void 0, null, 'false', "]


Сортування за допомогою .sort
Метод sort() на місці сортує елементи масиву і повертає відсортований масив. Сортування необов'язково стійка. Порядок сортування за замовчуванням відповідає порядку кодових точок Unicode.

Сигнатура така ж як і більшість сортувальних функцій: Array.prototype.sort(fn(a,b)), метод приймає зворотний виклик, який перевіряє два елемента, в результаті в залежності від завдання обчислене значення може бути:

  • return value < 0, a передує b
  • return value === 0, якщо обидва а і Ь вважаються еквівалентні
  • return value > 0, якщо a b після


[9,80,3,10,5,6].sort()
// <- [10, 3, 5, 6, 80, 9]

[9,80,3,10,5,6].sort(function (a, b) {
return a - b
})
// <- [3, 5, 6, 9, 10, 80]


Обчислення спільно с .reduce, .reduceRight
Метод reduce() виконує функцію callback один раз для кожного елемента, присутнього в масиві, за винятком порожнин, приймаючи чотири аргументи: початкове значення (значення від попереднього виклику callback), значення поточного елемента, поточний індекс і масив, по якому відбувається ітерація.

В цілому, методи reduce() і reduceRight() ідентичні, за винятком того, що перебір елементів у методі reduce() йде зліва направо, а в методі reduceRight() справа наліво.

Обидва методи мають наступну сигнатуру: reduce(callback(previousValue, currentValue, index, array), initialValue).

При першому виклику функції параметри previousValue і currentValue можуть приймати одне з двох значень. Якщо при виклику reduce() переданий аргумент initialValue, то значення previousValue буде рівним значенню initialValue, а значення currentValue буде рівним першого значення в масиві. Якщо аргумент initialValue не задано, то значення previousValue буде рівним першого значення в масиві, а значення currentValue буде рівним другого значення в масиві.

Один з типових випадків використання .reduce функція суми:
Array.prototype.sum = function () {
return this.reduce(function (partial, value) {
return partial + value
}, 0)
};

[3,4,5,6,10].sum()
// <- 28


Скажімо, ми хочемо конкатенувати кілька рядків разом. Ми могли б використовувати .join для цієї мети. Але у випадку об'єктів це не спрацює, тому:
function concat (input) {
return input.reduce(function (partial, value) {
if (partial) {
partial += ', '
}
return partial + value.name
}, ")
}

concat([
{ name: 'George' },
{ name: 'Sam' },
{ name: 'Pear' }
])
// <- 'George, Sam, Pear'


Копіювання за допомогою .slice
Подібно .concat, виклик .slice без яких-небудь аргументів справить неповну копію вихідного масиву. Функція приймає два аргументи — початкове і кінцеве положення. Array.prototype.slice може бути використаний для перетворення масивів в об'єкти і навпаки в масиви.

Array.prototype.slice.call({ 0: 'a', 1: 'b', length: 2 })
// <- ['a', 'b']


Але це не буде працювати .concat:
Array.prototype.concat.call({ 0: 'a', 1: 'b', length: 2 })
// <- [{ 0: 'a', 1: 'b', length: 2 }]


Крім цього, ще один поширений спосіб використання методу .slice — видалення кількох елементів за списком аргументів:
function format (text, bold) {
if (bold) {
text = '<b>' + text + '</b>'
}
var values = Array.prototype.slice.call(arguments, 2)

values.forEach(function (value) {
text = text.replace('%s', value)
})

return text
}

format('some%sthing%s %s', true, 'some', 'other', 'things')
// < <b>somesomethingother things</b>


Більш потужний засіб для видалення і додавання елементів .splice
Метод splice() змінює вміст масиву, видаляючи існуючі елементи та/або додаючи нові. Якщо кількість зазначених вставлених елементів буде відмінним від кількості видалених елементів, масив змінить довжину після виклику. Зверніть увагу, що ця функція змінює вихідний масив, на відміну від .concat або .slice.

var source = [1,2,3,8,8,8,8,8,9,10,11,12,13]
var spliced = source.splice(3, 4, 4, 5, 6, 7)

console.log(source)
// <- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ,13]

spliced
// <- [8, 8, 8, 8]


Можливо, вже наголошувалося, що вона також повертає елементи і іноді це може стати в нагоді.
var source = [1,2,3,8,8,8,8,8,9,10,11,12,13]
var spliced = source.splice(9)

spliced.forEach(function (value) {
console.log('removed', value)
})
// <- removed 10
// <- removed 11
// <- removed 12
// <- removed 13

console.log(source)
// <- [1, 2, 3, 8, 8, 8, 8, 8, 9]


Пошук елемента у масиві .indexOf
Метод indexOf() порівнює шуканий елемент searchElement з елементами в масиві, використовуючи суворе порівняння (той же метод використовується оператором ===, потрійне одно).

var a = { foo: 'bar' }
var b = [a, 2]

console.log(b.indexOf(1))
// <- -1

console.log(b.indexOf({ foo: 'bar' }))
// <- -1

console.log(b.indexOf(a))
// <- 0

console.log(b.indexOf(a, 1))
// <- -1

b.indexOf(2, 1)
// <- 1

b.indexOf(2, a)
// <- 1

b.indexOf(a, 2)
// <- -1


В допомогу оператор in
Поширеною помилкою новачків на співбесіді є їх заплутане думка про однакову роботі методу .indexOf і оператора in.
var a = [1, 2, 5]

1 in a
// <- true 
// так як існує елемент a[1], який дорівнює 2!

5 in a
// <- false
// так як a[5] не існує


Справа в тому, що оператор in перевіряє ключ об'єкта, а не робить пошук за значенням. Такий варіант набагато швидший, ніж при використанні .indexOf:

var a = [3, 7, 6]

1 in a === !!a[1]
// <- true


Перебір в зворотному порядку або переворот масиву з допомогою .reverse
Цей метод буде приймати елементи в масиві і розташовувати їх у зворотному порядку.

var a = [1, 1, 7, 8]

a.reverse()
// [8, 7, 1, 1]


До прочитання:
1. Array
2. Array vs. Object
3. Продуктивність масивів в JavaScript
4. Змушуємо масиви працювати швидше
5. Продуктивність Objects/Arrays в JavaScript
6. Перебирающие методи
7. Бібліотека, що реалізує додаткову функціональність для роботи з масивами, об'єктами і функціями

Регулярні вирази

Регулярні вирази створюються за допомогою двох слэшей / /, які є литералом регулярного виразу. Усередині ви вказуєте шаблон пошуку. Намагайтеся зберігати регулярні вирази в змінних, не додавайте їх безпосередньо в код (inline), це дозволить значно поліпшити читаність.

// погано
if (/\d+/.test(text)) { // non-precompiled, but faster
console.log('so many numbers!');
}


// добре
var numeric = /\d+/; // precompiled
if (numeric.test(text)) {
console.log('so many numbers!');
}


Крім того, ви можете ознайомитися з тим, як складати регулярні вирази і дізнатися для чого вони потрібні. Ви можете також візуалізувати їх, подивившись у режимі онлайн.

До прочитання:
1. Регулярні вирази
2. Клас RegExp
3. Спосіб пошуку і заміни для рядків
4. Методи RegExp і String
5. JavaScript продуктивність precompiled регулярного виразу
6. Основні поняття і проблеми продуктивності
7. Завдання на роботу з регулярними вараженіямі

Консоль розробника

Для перевірки стану веб-додатки дуже зручно використовувати об'єкт console, який надалі можна прибрати при продакшені. Ця можливість не є стандартною і стандартизувати її поки ніхто не збирається. Також можуть бути присутніми великі несумісності між реалізаціями, і її поведінку у майбутньому може змінитися. Тому не використовуйте її на сайтах, викладаючи свій код в інтернет. При налагодженні свого веб-додатки намагайтеся не записувати все підряд в журнал виводу (console.log

До прочитання:
1. Налагодження JavaScript для початківців
2. Використання console
3. Стек виклику. Трасування
4. Вся правда про Chrome. Web Inspector
5. Chrome console API

Коментарі

Коментарі, що не призначені для пояснення того, що робить ваш код. Хороший код повинен говорити сам за себе. Якщо ви задумалися про написанні коментаря в тому місці, де хочете пояснити, що робить шматок коду, швидше за все, вам необхідно змінити сам код. Винятком з цього правила є пояснення складних ділянок коду, навантажених синтаксичними конструкціями за типом регулярних виразів. Хороший коментар повинен розповісти про те, чому шматок коду працює саме так, і в чому його конкретна мета.

// погано
// створюємо центрований контейнер
var p = $('<p/>');
p.center(div);
p.text('foo');

// добре
var container = $('<p/>');
var contents = 'foo';
container.center(parent);
container.text(contents);
megaphone.on('data', function (value) {
container.text(value); // megaphone періодично оновлює дані в контейнері
});

var numeric = /\d+/; // пошук одного або більше цифр у рядку
if (numeric.test(text)) {
console.log('so many numbers!');
}



Коментувати величезні блоки коду не слід, адже у вас є система контролю версій!

До прочитання:
1. 16 прийомів написання суперчитабельного коду
2. Кращі практики коментування коду
3. Коментування функцій
4. Генератори JavaScript документації

Назва

Змінні повинні мати осмислені назви. Адже ви не хочете вдаватися до читання коментарів або документації, щоб зрозуміти, що робить частину даного коду.

// погано
function a (x, y, z) {
return z * y / x;
}
a(4, 2, 6);
// <- 3


// добре
function ruleOfThree (had, got, have) {
return have * got / had;
}
ruleOfThree(4, 2, 6);
// <- 3


До прочитання:
1. JavaScript Style Guide and Coding Conventions

Polyfill-бібліотеки

Там, де неможливо використовувати нативні функції браузера, тобто вони просто не реалізовані за замовчуванням, включають polyfill-бібліотеки, які емулюють необхідне нам поведінка для підтримки цих функцій в старих браузерах. У підсумку, нам легше працювати з кодом, тепер не потрібно нагромаджувати його умовними та іншими конструкціями для підтримки стабільності.

Якщо вам не вистачає можливостей polyfill-бібліотеки, ви можете написати плагін для такої бібліотеки або просто самостійно розширити можливості стандартних функцій, об'єктів, обернувши їх у свій polyfill.

До прочитання:
1. Сучасний DOM: полифиллы
2. 10 кращих polyfill-бібліотек
3. Список готових polyfill-бібліотек

Щоденні хитрості

1. Використовуйте логічний оператор ||, щоб визначити значення за замовчуванням. Якщо значення зліва від оператора буде хибним, то буде використовуватися значення праворуч по відношенню до оператора (false || true). Прийміть до відома, з-за слабкого порівняння типу значення, таких як false, 0, null або ", які є помилковими, використовується значення за замовчуванням, яке ви вкажете справа по відношенню до логічного оператора. Для суворої перевірки типів використовувати таку конструкцію: if (value === void 0) { value = defaultValue }

function a (value) {
var defaultValue = 33;
var used = value || defaultValue;
}


2. часткового використання функції, можете скористатися методом .bind
function sum (a, b) {
return a + b;
}

var addSeven = sum.bind(null, 7);

addSeven(6);
// <- 13


3. Використовуйте Array.prototype.slice.call, якщо хочете отримати масив з об'єкта
var args = Array.prototype.slice.call(arguments);


4. Використовуйте слухачі подій на все що завгодно!
var emitter = contra.emitter(); // example, event emitters: jQuery, Angular, React ..

body.addEventListener. ('click', function () {
emitter.emit('click', e.target);
});

emitter.on('click', function (elem) {
console.log(elem);
});

// эмитация кліка
emitter.emit('click', document.body);


5. Якщо вам потрібна порожня функція (no-op), використовуйте Function()
function (cb) {
setTimeout(cb || Function(), 2000);
}


До прочитання:
1. browserhacks
2. 5 популярних JavaScript-хаків
3. Кілька JavaScript хаків для хіпстерів
4. Створення і виклик подій
5. Магія JavaScript: arguments

Керівництво по ES6

Мова JavaScript стандартизується організацією ECMA (зразок W3C), а сам стандарт носить назву ECMAScript. ECMAScript визначає наступне:

  • Синтаксис мови — правила парсинга, ключові слова, оператори, вирази та інше
  • Типи — числа, рядки, об'єкти та інше
  • Прототипи і спадкування
  • Стандартну бібліотеку вбудованих об'єктів і функцій — JSON, Math, методи масивів, методи об'єктів тощо


У 2009 році ES5 був офіційно закінчено (пізніше ES5.1 в 2011), і став широко поширеним стандартом для браузерів, таких як Firefox, Chrome, Opera, Safari і багатьох інших. В очікуванні наступної версії JS (в 2013, потім у 2014, а потім вже і в 2015) найбільш обговорюваною новою гілкою була — ES6.

ES6 — це радикальний стрибок вперед. Навіть якщо ви думаєте, що знаєте JS (ES5), то ES6 сповнений нових речей, про які вам ще тільки належить дізнатися. Так що будьте готові! ES6 досить сильно відрізняється. Це результат довгих років злагодженої роботи. І це скарб нових можливостей мови. Найбільш значне оновлення JS, яке було коли-небудь. Нові можливості варіюються від простих зручностей кшталт функцій-стрілок та інтерполяції рядків до мозговзрывающих концепцій на зразок проксі і генераторів, але вже зараз ви можете приступити до його вивчення (посилання в наведеній літературі нижче).

До прочитання:
1. Сучасний Style Guide по ES6
2. Шпаргалка по ES6
3. Все необхідне для вивчення ES6
4. ES6 в деталях: введення
5. ES6 і за його межами
6. Приклади ECMAScript 6
7. 350 аспектів ES6

Джерело: Хабрахабр
  • avatar
  • 0

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.