Завантажити курси книги - Чи потрібні фігурні дужки в однорядкових операціях JavaScript? Важливі моменти оформлення коду. Варіації на тему

Третя частина уроку, присвяченого коду JavaScript. Прості рекомендаціїдля створення проекту, що легко підтримується.

Уникайте передбачуваного перетворення типів

JavaScript має на увазі перетворення типів змінних при їх порівнянні. Тому такі порівняння як false == 0 або "" == 0 повертають true.

Щоб уникнути проблем, викликаних передбачуваним перетворенням типів, завжди використовуйте оператори === і!== для перевірки та значень та типів порівнюваних виразів:

Існує інша школа програмування, у межах якої прийнято вважати, що надмірно використовувати оператор === , коли достатньо оператора == . Наприклад, коли використовується тип, який повертає рядок, немає причин вимагати жорсткої відповідності. Але JSLint вимагає жорсткої відповідності. І, крім того, код буде виглядати ціліснішим і зменшить кількість роздумів під час читання (" Цей оператор== використовується навмисно чи помилково?").

Уникайте використання eval()

Функція eval() бере довільний рядок і виконує його як JavaScript. Якщо код відомий (а чи не визначається під час виконання процесу), взагалі немає причин використовувати eval() . Якщо код динамічно генерується під час виконання, то часто можна досягти мети найкращим методом, ніж використання eval() .Наприклад, використання запису з квадратними дужками для динамічного доступу до властивостей краще і простіше:

// погано var property = "name"; alert(eval("obj." + property)); // Переважно робити так var property = "name"; alert(obj);

Використання eval() також може вплинути на безпеку, тому що ви можете виконувати код (наприклад, отриманий з мережі), який завдає шкоди. Досить поширена хибна практика роботи з відповіддю JSON на запит AJAX. У даному випадкукраще використовувати вбудовані методи браузера для аналізу відповіді JSON, щоб вирішити задачу безпечним методомі вірно. Для браузерів, які не підтримують JSON.parse(), можна скористатися бібліотекою з JSON.org.

Також важливо пам'ятати, що передача рядків функцій setInterval() , setTimeout() і конструктору Function() у більшості випадків схоже на використання eval(). Отже, таких дій треба уникати. На задньому плані JavaScript оцінює та виконує рядки, які ви передаєте, як програмний код:

// погано setTimeout("myFunc()", 1000); setTimeout("myFunc(1, 2, 3)", 1000); // переважно setTimeout(myFunc, 1000); setTimeout(function () ( myFunc(1, 2, 3); ), 1000);

Використання нового конструктора new Function() схоже на eval(), тому до нього треба ставитись з обережністю. Це потужний інструментале часто використовується неправильно. Якщо вам абсолютно необхідно використовувати eval(), розгляньте замість нього використання new Function(). Є невелика потенційна перевага, оскільки код, що визначається в new Function(), буде запускатися в локальному просторі функції, тому змінні, визначені з директивою var в коді, що визначаються, не будуть ставати глобальними автоматично. Інший спосіб уникнути автоматичного визначенняглобальних змінних - обертати виклик eval() на функцію.

Розглянемо наступний приклад. Тут тільки un залишається глобальною змінною, що забруднює простір імен:

Console.log(typeof un); // "undefined" console.log(typeof deux); // "undefined" console.log(typeof trois); // "undefined" var jsstring = "var un = 1; console.log(un);"; eval(jsstring); // Записує "1" jsstring = "var deux = 2; console.log(deux);"; new Function(jsstring)(); // Записує "2" jsstring = "var trois = 3; console.log(trois);"; (function () ( eval(jsstring); )()); // Записує "3" console.log (type of un); // Число console.log(typeof deux); // undefined console.log(typeof trois); // undefined

Інша відмінність між eval() і конструктором new Function() полягає в тому, що eval() може перетинатися з ланцюжком простору імен, а виконання Function відбувається в пісочниці. Не важливо, де ви виконуєте Function, вона використовує тільки глобальний простірімен. Тому вона менше забруднює локальний простірімен. У наступному прикладі eval() може отримати доступ і модифікувати змінні у своєму зовнішньому просторі імен, а Function не може (використання Function і new Function ідентично):

(function () ( var local = 1; eval("local = 3; console.log(local)"); // Записує "3" console.log(local); // Записує "3" )()); (function () ( var local = 1; Function("console.log(typeof local);")(); // Записує "undefined" )());

Перетворення числа за допомогою parseInt()

Використовуючи parseInt(), можна отримати число з рядка. Функція приймає другий параметр - основу системи числення, яка часто опускається. А даремно. Проблема проявляється, коли треба розібрати рядок, що починається з 0: наприклад, частина дати, яку вводять у полі форми. Рядок, що починається на 0, обробляється як вісімкове число(основа 8), що було визначено в ECMAScript 3 (але змінено в ECMAScript 5). Для виключення несумісності та несподіваних результатів завжди слід використовувати параметр основи обчислення:

Var month = "06", year = "09"; month = parseInt(month, 10); year = parseInt(year, 10);

У даному прикладі, якщо ви опустите параметр основи системи числення (викликаєте функцію як parseInt(year)), то отримаєте значення 0 , оскільки “ 09 ” мається на увазі як вісімкове число (наче ви зробили виклик parseInt(year, 8)), а 09 - неправильне число на підставі 8 .

Альтернативні методиперетворення рядка в число:

+ "08" // Результат 8 Number ("08") // 8

Дані методи часто виконуються швидше за parseInt() , тому що parseInt() робить розбір рядка, а не просте конвертування. Але якщо ви припускаєте, що введення може бути у вигляді “08 hello”, то parseInt() поверне число, інші методи - зазнають невдачі з поверненням NaN .

Вимоги до коду

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

Серйозні сутички та розбирання виникають під час обговорення різних аспектіввимог до коду (наприклад, ніж виконувати відступи – пробілами чи табуляціями). Тому за введення вимог до коду потрібно серйозно готуватися до дискусій. Але вимоги до коду та неухильне дотримання їм дуже важливе для існування та успішного розвитку проекту.

Відступи

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

А де слід робити відступи? Правило просте - скрізь, де є фігурні дужки. Тобто в тілі функцій, циклах (do, while, for, for-in), операторах if і switch і властивостях object . Наступний код показує приклади використання відступів:

Function outer(a, b) ( var c = 1, d = 2, inner; if (a > b) ( inner = function () ( return ( r: c - d ); ); ) else ( inner = function ( ) ( return ( r: c + d ); ); ) return inner; )

Фігурні дужки

Фігурні дужки завжди слід використовувати навіть у випадках, коли вони є опціями. Технічно, коли ви маєте лише один вираз в операторі if або for, фігурні дужки не потрібні, але їх слід використовувати однаково. Вони роблять код більш цілісним та простим для розширення.

Припустимо, що у вас є цикл з одним виразом. Ви можете опустити дужки, що не буде синтаксичною помилкою:

// погано for (var i = 0; i< 10; i += 1) alert(i);

Але що якщо пізніше потрібно додати ще один рядок у тіло циклу?

// погано for (var i = 0; i< 10; i += 1) alert(i); alert(i + " is " + (i % 2 ? "odd" : "even"));

Друга функція alertзнаходиться поза циклом, а відступи можуть зіграти з вами поганий жарт. Найкраще з розрахунку на перспективу завжди використовувати дужки, навіть для однорядкового блоку:

// переважно for (var i = 0; i< 10; i += 1) { alert(i); }

Також і для умов:

// погано if (true) alert (1); else alert(2); // переважно if (true) ( ​​alert(1); ) else ( alert(2); )

Положення відкритої дужки

Часто виникає питання, де розташовувати відкриту дужку - на тому ж рядку чи наступному?

If (true) ( ​​alert("Повідомлення!"); )

If (true) ( ​​alert("Повідомлення"); )

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

// попередження: несподіваний return function func() ( return // далі слідує блок коду, який ніколи не виконається ( name: "Batman" ) )

Якщо ви очікували, що дана функціяповерне об'єкт з властивістю name, то буде неприємно здивований. Внаслідок точки зору з комою функція поверне undefined . Попередній код еквівалентний наступному блоку:

// попередження: несподіваний return function func() ( return undefined; // далі слідує блок коду, який ніколи не виконається ( name: "Batman" ) )

Function func() ( return ( name: "Batman" ); )

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

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

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

1. Пропуск фігурних дужок

Одна з помилок, яку часто припускаються новачки JavaScript - пропуск фігурних дужок після операторів типу if, else, while і for. Хоча це не забороняється, ви повинні бути дуже обережні, тому що це може стати причиною прихованої проблеми і пізніше призвести до помилки.

Дивіться приклад, наведений нижче:

JS

// Цей код не робить те, що має! if(name === undefined) console.log("Please enter a username!"); fail(); // До цього рядка виконання ніколи не дійде: success(name); ) function success(name)( console.log("Hello, " + name + "!"); ) function fail()( throw new Error("Name is missing. Can"t say hello!"); )

Хоча виклик fail() має відступ і, здається, ніби він належить оператору if це не так. Він викликається завжди. Так що це корисна практикаоточувати всі блоки коду фігурними дужками, навіть якщо в них є тільки один оператор.

2. Відсутність крапок із комою

Під час парсування JavaScript здійснюється процес, відомий як автоматичне розташування точок з комою. Як випливає з назви, аналізатор розставляє знаки, що бракують, замість вас.

Мета цієї функції - зробити JavaScript доступнішим і простішим у написанні для новачків. Тим не менш, ви повинні завжди самі додавати крапку з комою, тому що існує ризик її пропустити.

Ось приклад:

JS

//Результатом обробки цього коду стане висновок повідомлення про помилку. Додавання крапки з комою вирішило б проблему. console.log("Welcome the fellowship!") ["Frodo", "Gandalf", "Legolas", "Gimli"].forEach(function(name)( hello(name) )) function hello(name)( console. log("Hello, " + name + "!") )

Так як у рядку 3 відсутня точка з комою, аналізатор припускає, що дужка, що відкривається, в рядку 5 є спробою доступу до властивості, використовуючи синтаксис масиву аксесору (дивися помилку № 8), а не окремим масивом, який є не тим, що передбачалося.

Це призводить до помилки. Виправити це просто – завжди вставляйте крапку з комою.

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

3. Нерозуміння наведень типу

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

Це робить JavaScript набагато простішим, ніж, скажімо, C # або Java. Але це приховує потенційну небезпеку помилок, які в інших мовах виявляються на етапі компіляції.

Ось приклад:

JS

// Очікування події введення текстового поля var textBox = document.querySelector("input"); textBox.addEventListener("input", function()( // textBox.value містить рядок. Додавання 10 містить // рядок "10", але не виконує його додавання.) console.log(textBox.value + " + 10 = " + (textBox.value + 10));));

HTML

Проблема може бути легко виправлена ​​із застосуванням parseInt(textBox.value, 10) , щоб перевести рядок до перед додаванням до неї 10.

Залежно від того, як ви використовуєте змінну, середовище виконання може вирішити, що воно має бути перетворене на той чи інший тип. Це називається приведення типу.

Щоб не допустити перетворення типу при порівнянні змінних в операторі if, ви можете використовувати перевірку суворої рівності (=== ).

4. Забуті var

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

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

Ось приклад, який також ілюструє й іншу помилку — кому, що бракує, при оголошенні відразу кількох змінних:

JS

var a = 1, b = 2, c = 3; function alphabet(str)( var a = "A", b = "B" // Упс, тут пропущена ","! c = "C", d = "D"; return str + " " + a + b + c + "…"; ) console.log(alphabet("Let"s say the alphabet!"));// О, ні! Щось не так! c);

Коли аналізатор досягає рядка 4, він автоматично додасть точку з комою, а потім інтерпретує оголошення c і d у рядку 5 як глобальні.

Це призведе до зміни значення іншої змінної c. Більше про підводних камінцях JavaScript тут.

5. Арифметичні операції з плаваючою точкою

Ця помилка характерна практично для будь-якої мови програмування, у тому числі JavaScript. Через спосіб, яким числа з плаваючою точкою представляються у пам'яті, арифметичні операціївиконуються не зовсім так, як ви вважаєте.

Наприклад:

JS

var a = 0.1; b = 0.2; // Сюрприз! Це неправильно: console.log(a + b == 0.3); // Тому що 0.1 + 0.2 не дає в сумі число, що ви очікували: console.log("0.1 + 0.2 = ", a + b);

Щоб уникнути цієї проблеми, ви не повинні використовувати десяткові числаЯкщо вам потрібна абсолютна точність — використовуйте цілі числа, або якщо вам потрібно працювати з грошовими одиницями, використовуйте бібліотеку типу bignumber.js.

6. Використання конструкторів замість оригінальних позначень

Коли програмісти Javaі C # починають писати на JavaScript, вони часто вважають за краще створювати об'єкти з використанням конструкторів: new Array(), new Object(), new String().

JS

var elem4 = новий Array(1,2,3,4); console.log("Four element array: " + elem4.length); / / Створення масиву з одного елемента. Це не працює так, як ви вважаєте: var elem1 = new Array(23); console.log("One element array?" + elem1.length); /* Об'єкти рядка також мають свої особливості */ var str1 = new String("JavaScript"), str2 = "JavaScript"; // Сувора рівність не дотримується: console.log("Is str1 the same as str2?", str1 === str2);

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

7. Нерозуміння того, як розділяються діапазони

Одна з важких для розуміння новачками речей у JS, це правила розмежування та закриття діапазонів. І це справді не просто:

JS

for(var i = 0; i< 10; i++){ setTimeout(function(){ console.log(i+1); }, 100*i); } /* Чтобы исправить проблему, заключите код в выражение самовыполняющейся функции: for(var i = 0; i < 10; i++){ (function(i){ setTimeout(function(){ console.log(i+1); }, 100*i); })(i); } */

Функції зберігають зв'язок із змінними у межах батьківських діапазонів. Але оскільки ми відкладаємо виконання через setTimeout, коли настане час для запуску функцій, цикл буде вже фактично завершений і змінна i збільшується до 11.

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

8. Використання Eval

Eval є зло. Це вважається поганою практикою, і в більшості випадків, коли ви думаєте його застосувати, можна знайти найкращий і швидший спосіб.

JS

// Це погана практика. Будь ласка, не робіть так: console.log(eval("obj.name + "is a" + obj." + access)); // Натомість для доступу до властивостей динамічно використовуйте масив приміток: console.log(obj.name + "is a" + obj); /* Використання eval у setTimout */ // Це також невдала практика. Вона повільна і складна для перевірки та налагодження: setTimeout(" if(obj.age == 30) console.log("This is eval-ed code, " + obj + "!"); ", 100); // Так краще: setTimeout(function()( if(obj.age == 30)( console.log("This code is not eval-ed, " + obj + "!"); ) ), 100);

Код усередині eval – це рядок. Відлагоджувальні повідомлення, пов'язані з Eval-блоками незрозумілі, і вам доведеться поламати голову, щоб правильно розставити одинарні та подвійні лапки.

Не кажучи вже про те, що це буде працювати повільніше, ніж звичайний JavaScript. Не використовуйте Eval, якщо ви не знаєте точно, що ви робите.

9. Нерозуміння асинхронного коду

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

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

Ось приклад, в якому я використовую сервіс FreeGeoIP для визначення вашого розташування за IP-адресою:

JS

// Визначення даних розташування поточного користувача. load(); // Висновок розташування користувача. Упс, це не працює! Чому? console.log("Hello! Your IP address is " + userData.ip + " and your country is " + userData.country_name); // Завантажувана функція визначатиме ip поточного користувача та його місцезнаходження // через ajax, використовуючи сервіс freegeoip. Коли це зроблено вона помістить дані, що повертаються // в змінну userData. function load()( $.getJSON("http://freegeoip.net/json/?callback=?", function(response)( userData = response; // Виведіть з коментарів наступний рядокщоб побачити повертається // результат: // console.log(response); )); )

Незважаючи на те, що console.log знаходиться після виклику функції load() , Насправді він виконується перед визначенням даних.

10. Зловживання відслідковуванням подій

Припустімо, що ви хочете відстежувати клік кнопки, але тільки за умови встановленого чеккера.

Мене завжди дивував JavaScriptпередусім тим, що він, напевно, як жодна інша широко поширена мова підтримує одночасно обидві парадигми: нормальні та ненормальне програмування. І якщо про адекватні best-практики та шаблони прочитано майже все, то дивовижний світтого, як не треба писати код але можна, залишається лише злегка відкритим.


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


Попереднє завдання:

Формулювання

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

Лічильник викликів – це лише привід, адже є console.count(). Суть у тому, що наша функція акумулює деякі дані при виклику оберненої функції та надає певний інтерфейс доступу до них. Це може бути збереження всіх результатів виклику, і збір логів, і певна мемоізація. Просто лічильник – примітивний і зрозумілий усім.


Уся складність – у ненормальному обмеженні. Не можна використовувати фігурні дужки, а отже доведеться переглянути побутові практики та ординарний синтаксис.

Звичне рішення

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


class CountFunction ( constructor(f) ( this.calls = 0; this.f = f; ) invoke() ( this.calls += 1; return this.f(...arguments); ) ) const csum = new CountFunction ((x, y) => x + y); csum.invoke(3, 7); // 10 csum.invoke(9, 6); // 15 csum.calls; // 2

Це нам відразу не годиться, оскільки:

  1. У JavaScript таким чином не можна реалізувати приватну властивість: ми можемо як читати callsекземпляра (що нам і потрібно), так і записувати в нього значення ззовні (що нам не потрібно). Звичайно, ми можемо використовувати замикання в конструкторі, але тоді в чому сенс класу? А свіжі приватні поля я поки що боявся використовувати без babel 7 .
  2. Мова підтримує функціональну парадигму, та створення екземпляра через newздається, тут не найкращим рішенням. Найприємніше написати функцію, що повертає іншу функцію. Так!
  3. Зрештою, синтаксис ClassDeclarationі МетодDefinitionне дозволить нам за всього бажання позбутися всіх фігурних дужок.

Але у нас є чудовий , який реалізує приватність за допомогою замикання:


function count(f) ( let calls = 0; return ( invoke: function() ( calls += 1; return f(...arguments); ), getCalls: function() ( return calls; ) ); ) const csum = count((x, y) => x + y); csum.invoke(3, 7); // 10 csum.invoke(9, 6); // 15 csum.getCalls(); // 2

Із цим уже можна працювати.

Цікаве рішення

Навіщо взагалі тут використовуються фігурні дужки? Це 4 різні випадки:

  1. Визначення тіла функції count ( FunctionDeclaration)
  2. Ініціалізація об'єкта, що повертається.
  3. Визначення тіла функції invoke ( FunctionExpression) з двома виразами
  4. Визначення тіла функції getCalls ( FunctionExpression) з одним виразом

Почнемо зі другогопункту. Насправді нам нема чого повертати новий об'єкт, при цьому ускладнюючи виклик кінцевої функції через invoke. Ми можемо скористатися тим фактом, що функція JavaScript є об'єктом, а значить може містити свої власні поля і методи. Створимо нашу функцію, що повертається dfі додамо їй метод getCalls, який через замикання матиме доступ до callsяк і раніше:


function count(f) ( let calls = 0; function df() ( calls += 1; return f(...arguments); ) df.getCalls = function() ( return calls; ) return df; )

Із цим і працювати приємніше:


const csum = count((x, y) => x + y); csum(3, 7); // 10 csum(9, 6); // 15 csum.getCalls(); // 2

C четвертимпунктом все ясно: ми просто замінимо FunctionExpressionна ArrowFunction. Відсутність фігурних дужок нам забезпечить короткий записстрілочної функції у разі єдиного вираження у її тілі:


function count(f) ( let calls = 0; function df() ( calls += 1; return f(...arguments); ) df.getCalls = () => calls; return df; )

З третім- все складніше. Пам'ятаємо, що насамперед ми замінили FunctionExpressionфункції invokeна FunctionDeclaration df. Щоб переписати це на ArrowFunctionдоведеться вирішити дві проблеми: не втратити доступ до аргументів (зараз це псевдомасив) arguments) і уникнути тіла функції з двох виразів.


З першою проблемою нам допоможе впоратися явно вказаний для функції параметр argsзі spread operator. А щоб об'єднати два вирази в один, можна скористатися logical AND. На відміну від класичного логічного операторакон'юнкції, що повертає бульова, він обчислює операнди зліва направо до першого "хибного" і повертає його, а якщо всі "справжні" - то останнє значення. Перше ж збільшення лічильника дасть нам 1, а значить це під-вираз завжди буде приводиться до true. Приводимость до " істини " результату виклику функції у другому под-виражении нас цікавить: обчислювач у разі зупиниться у ньому. Тепер ми можемо використати ArrowFunction:


function count(f) ( let calls = 0; let df = (...args) => (calls += 1) && f(...args); df.getCalls = () => calls; return df; )

Можна трохи прикрасити запис, використовуючи префіксний інкремент:


function count(f) ( let calls = 0; let df = (...args) => ++calls && f(...args); df.getCalls = () => calls; return df; )

Рішення першогоі найскладнішого пункту розпочнемо із заміни FunctionDeclarationна ArrowFunction. Але в нас поки що залишиться тіло у фігурних дужках:


const count = f => ( let calls = 0; let df = (...args) => ++calls && f(...args); df.getCalls = () => calls; return df; );

Якщо ми хочемо позбутися фігурних дужок, що обрамляють тіло функції, нам доведеться уникнути оголошення та ініціалізації змінних через let. А змінних у нас цілих дві: callsі df.


Спочатку розберемося із лічильником. Ми можемо створити локальну змінну, визначивши її у списку параметрів функції, а початкове значення передати викликом за допомогою IIFE (Immediately Invoked Function Expression):


const count = f => (calls => ( let df = (...args) => ++calls && f(...args); df.getCalls = () => calls; return df; ))( 0);

Залишилося конкатенувати три вирази на одне. Так як у нас всі три вирази є функціями, що наводяться завжди до true, то ми можемо також використовувати logical AND:


const count = f => (calls => (df = (...args) => ++calls && f(...args)) && (df.getCalls = () => calls) && df)(0 );

Але є ще один варіант конкатенації виразів: за допомогою comma operator. Він краще, тому що не займається зайвими логічними перетвореннями і вимагає меншої кількості дужок. Операнди обчислюються зліва-направо, а результатом є значення останнього:


const count = f => (calls => (df = (...args) => ++calls && f(...args), df.getCalls = () => calls, df))(0);

Напевно, мені вдалося вас обдурити? Ми сміливо позбулися оголошення змінної dfі залишили лише присвоєння нашої стрілочної функції. І тут ця змінна буде оголошено глобально, що неприпустимо! Повторимо для dfініціалізацію локальної змінної в параметрах нашої функції IIFE, тільки не будемо передавати ніякого початкового значення:


const count = f => ((calls, df) => (df = (...args) => ++calls && f(...args), df.getCalls = () => calls, df)) (0);

Таким чином мети досягнуто.

Варіації на тему

Цікаво, що ми змогли уникнути створення та ініціалізації локальних змінних, кількох виразів у функціональних блоках, створення літералу об'єкта. При цьому зберегли чистоту вихідного рішення: відсутність глобальних змінних, приватність лічильника, доступ до аргументів функції, що обертається.


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


const bind = (f, ctx, ...a) => (...args) => f.apply(ctx, a.concat(args));

Однак, якщо аргумент fне є функцією, по-доброму ми маємо викинути виняток. А виняток throwможе бути викинуто у тих висловлювання. Можна почекати throw expressions (stage 2) та спробувати ще раз. Чи у когось уже зараз є думки?


Або розглянемо клас, який описує координати деякої точки:


class Point ( constructor(x, y) ( this.x = x; this.y = y; ) toString() ( return `($(this.x), $(this.y))`; ) )

Який може бути представлений функцією:


const point = (x, y) => (p => (p.x = x, p.y = y, p.toString = () => ["(", x, ", ", y, ")"].join) (""), p)) (new Object);

Тільки ми тут втратили прототипне успадкування: toStringє властивістю об'єкта-прототипу Point, а чи не окремо створеного об'єкта. Чи можна цього уникнути, якщо неабияк постаратися?


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

Висновок

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

Теги: Додати теги

Помилки у програмі досить складно знайти. І щоб спростити собі завдання, потрібно писати код правильно і красиво (читаємо). Хороший кодповинен бути: легкочитаним, несуперечливим, передбачуваним, правильно оформленим, а також він повинен мати документацію.

Важливі моменти оформлення коду

Відступи

Якщо ви не робите відступи, код неможливо читати. Зазвичай відступи оформляють символом табуляції, але є й ті, хто відступ робить за допомогою прогалин, в основному 3-4 пробіли. Далі приклад правильної та не правильної розміщеннявідступів:

/* Правильні відступи */

< max; i++) {
if (arr[i] % 2 === 0) (
answer = (
sum: sum += arr[i]
};
}
}
/* А тут все погано */
var answer, arr = , sum = 0;
for (var i = 0, max = arr.length; i< max; i++) {
if (arr[i] % 2 === 0) (
answer = (
sum: sum += arr[i]
};
}
}

Фігурні дужки

Завжди використовуйте фігурні дужки навіть якщо вони не потрібні. З технічної погляду, якщо тіло конструкції if, while чи for складається з однієї інструкції, то фігурні дужки можна писати. Завжди написані фігурні дужки спрощують наступні зміни. Далі приклад, як правильно, а як ні:

/* Правильний запис */
for (var i = 0; i< 5; i++) {
alert(i);
}
/* А така немає */
for (var i = 0; i< 5; i++)
alert(i);

Розташування дужки, що відкриває

Є два типи програмістів:

If (a > b) (
...
}
if (a > b)
{
...
}

Як писати фігурну дужку, що відкриває, вирішувати вам. Ми вже знаємо, JavaScript сам підставляє крапки з комою і тут виникає проблема у другому способі. Допустимо, ви напишіть ось такий шматок коду:

Return
{
id: 1
};

І що зробить інтерпретатор? Він підставить крапку з комою після return і вийде, що функція поверне undefined . Тобто інтепретатор бачить цей шматок коду так:

Return undefined;
{
id: 1
};

Прогалини

Прогалини потрібно використовувати правильно. Ви коли пишите на папері, залишаєте після коми порожній простір? Напевно так. І при перерахуванні чогось у JavaScript потрібно робити пробіл після коми. Де потрібно ставити пробіл, щоб код був зрозумілим?

1. Після точок з комою в інструкції for

For (var i = 0; i< 100; i++) {...}

2. При оголошенні кількох змінних в інструкції for

For (var i = 0, max = arr.length; i< max; i++) {...}

3. Після ком, між елементами масиву