Сейчас JavaScript довольно быстро развивается, чтобы соответствовать условиям современных фреймворков и сред разработки. Понимание этих изменений может очень сильно облегчить вашу работу и улучшить написанный вами код.
Знание того, как работают современные JavaScript переменные может помочь вам сделать выбор, когда использовать современный синтаксис, а когда логичнее придерживаться традиционных методов.
За что можно зацепиться?
Я не знаю никого, кто бы не запутался в современном JavaScript, и ваш опыт здесь не играет совершенно никакой роли. Множество фреймворков, просто изменений в языке и многое другое. Вообще удивительно, что кто-то может выполнять работу, учитывая тот объём новой информации, который приходится осваивать чуть ли не каждый месяц.
Мне кажется, что секретом успеха во владении любого языка программирования, независимо от сложности приложения, является возвращение к основам. Например, если вы хотите разобраться с Rails, то для начала освойте Ruby, особенно если вы хотите использовать однонаправленные потоки данных в изоморфном режиме с помощью вебпак, то лучше всего начать с основ JavaScript.
Понимание того, как работает язык – это более полезное умение, нежели знакомство с самыми новыми технологиями, ведь они меняются слишком быстро. С JS абсолютно такая же история.
Проблема заключается в том, что некоторые новые особенности перечёркивают некоторые возможности ранних версий. Но иногда современный синтаксис может наоборот заменить громоздкую конструкцию более простой. Тем не менее, хотя новый подход и может зачастую показаться чем-то более простым, необходимо знать множество тонкостей старых и проверенных методов.
А сейчас немного о синтаксисе
Многие изменения в JS в последние годы были описаны, как добавочные к основному синтаксису. Иногда подобные добавления помогают новичкам быстрее разобраться в некоторых нюансах, а для остальных все они явились лишь более чистым и простым способом воссоздать то, что уже известно. Другие же изменения привнесли куда более обширные возможности.
Если же вы хотите попытаться реализовать давно решённую задачу при помощи нового синтаксиса, не понимая принципов его работы, то будьте готовы столкнуться со следующими трудностями:
- необходимость отладки кода, который раньше работал;
- тонкости, которые приводят к ошибкам во время работы программы;
- баги в самых неожиданных местах.
На самом деле некоторые нововведения, призванные заменить старый код по факту работают по совершенно иным принципам, нежели их предшествующие решения. Именно поэтому иногда следует всё же пользоваться чем-то старым, но проверенным. Из всего этого можно вынести, что знание старого и нового, принципов работы и умение делать выбор являются тем, что имеет решающее значение для написания современного эффективного кода.
Переменные и константы (var, let и const)
В современном JS появились два новых ключевых слова, let
и const
, которые в большинстве случаев могут заменить var
. Тем не менее они ведут себя совершенно иным образом, нежели старый оператор объявления переменной.
По традиции JavaScript переменные объявлялись при помощи ключевого слова var
. Невозможность сделать это означало, что переменная являлась глобальной, это в свою очередь вытекало в использование её разными скриптами на разных страницах.
Самой лучшей заменой var
в современном скрипте является оператор let
. Переменные, объявленные с помощью var
всегда имели наивысший приоритет, неважно как «глубоко» они находились. Это означало, что любая переменная могла быть использована в любой части скрипта вне зависимости от её местоположения. Подобного нельзя себе позволить, если применять let
или const
.
console.log(usingVar); // не определена
var usingVar = "defined";
console.log(usingVar); // определена
console.log(usingLet); // ошибка
let usingLet = "defined"; // никогда не будет выполнена
console.log(usingLet); // никогда не будет выполнена
Когда вы объявляете переменные с помощью let
или const
, то они будут доступны только в одном блоке, в котором и находятся. Блок в JS обозначается фигурными скобками {}
.
Подобные приёмы довольно удобны если вам надо выполнить цикл с итерацией или просто цикл. Раньше в JavaScript тип переменной, подобный старому варианту мог вызвать некоторые затруднения, так как имя такой переменной могло быть использовано где-нибудь вне цикла, что определённо вызывало бы ошибку. Тем не менее let
может подловить вас на том, что вы будете ожидать от данной переменной возможности использования где-либо вне её блока.
for (var count = 0; count < 5; count++) {
console.log(count);
} // выводит цифры 0 - 4 в консоль
console.log(count); // 5
for (let otherCount = 0; otherCount < 5; otherCount++) {
console.log(otherCount);
} // выводит цифры 0 - 4 в консоль
console.log(otherCount); // ошибка, otherCount неопределённо
Другой альтернативой может являться const, которая представляет собой константу, хотя и не совсем. const не может быть объявлена без точного значения в отличие от var или let:
var x; // правильно
let y; // правильно
const z; // ошибка
const также выдаст ошибку если вы попытаетесь изменить её значение после того, как она объявлена:
const z = 3; // правильно
z = 4; // ошибка
Если вы надеетесь, что контекст, описанный как константа будет всегда неизменным, то вас очень удивят объекты или массивы, объявленные через const
.
const z = []; // правильно
z.push(1); // правильно, z присваивается значение [1]
z = [2] // ошибка
Именно поэтому я отношусь скептически к описыванию переменных, как констант даже если вы не собираетесь их потом изменять.
Хотя это довольно неплохой ход при описании переменных, которые в дальнейшем должны сохранять своё значение, но const всё же теряет большую часть своих свойств при работе, например, с массивами, так что новички могут войти в заблуждение, ожидая от данного приёма большей «защиты», нежели он может представить.
Обычно я использую const для записи простых чисел, либо некоторых строк, которые я хочу инициализировать один раз и потом закрыть. let мне чаще всего нужен для ограничения пространства, в котором переменная может быть использована, хотя чаще всего именно её я и использую, не видя нужды пользоваться классическим приёмом для обозначения переменной, так как он зачастую просто ломает всю иерархию.
Ограничение поля применения функции
Зачастую функции, описанные с помощью ключевого слова function, могут вызываться для выполнения ряда операторов, определённых в блоке по любым параметрам, необязательно возвращая какое-либо значение:
function doSomething(param) {
return(`Did it: ${param}`);
}
console.log(doSomething("Hello")); // "выведет Hello"
Они также могут использоваться с ключевым словом для создания объектов с прототипным наследованием, и это определение может быть размещено в любом месте, где они могут быть вызваны. Функция также может быть вызвана с помощью new для создания объектов с прототипным наследованием:
function Animal(name) {
this.name = name;
}
let cat = new Animal("Fluffy");
console.log(`My cat's name is ${cat.name}.`); // выведет "My cat's name is Fluffy."
Функции, используемые обоими способами, могут быть определены как до, так и после вызова.
console.log(doSomething("Hello")); // выведет "Hello"
let cat = new Animal("Fluffy");
console.log(`My cat's name is ${cat.name}.`); // выведет "My cat's name is Fluffy."
function doSomething(param) {
return(`Did it: ${param}`);
}
function Animal(name) {
this.name = name;
}
Обычная функция также создаёт некий контекст, определяя значение переменных, существующих только в теле самой функции. Но некоторые из этих переменных или подфункций всё же можно изменять при вызове функции.
Это открывает большие просторы для разработчика, а возможности, которые представляют разные типы функций зачастую уходят за пределы знаний и нужд обычного программиста.
А теперь перейдём к классам
Одна часть традиционной функции была заменена на класс. Это в свою очередь позволяет автору кода сделать свой выбор – пойти по пути функционала и использовать простые операторы для вызова функции, либо выбрать более объектно-ориентированный подход с прототипным наследованием функций и т.д.
Так же, как и в других объектно-ориентированных языках программирования в JavaScript тип переменной, известный больше как классы очень похож на свои аналоги в том же C++, что не может не радовать разработчиков, решивших перейти на серверную разработку.
Но есть и отличия между функциями и классами. Например, при выполнении ООП в JS классы требуют прямого объявления, т.е. класс должен быть объявлен до того, как он будет связан с новым ключевым словом. С классическими функциями всё происходит ровно наоборот благодаря прототипному наследованию, ведь объявление функции автоматически поднимает её вверх, что разрешает использовать ключевое слово уже после.
// Использование функции для объявления и создания объекта (поднятого)
let aProto = new Proto("Myra");
aProto.greet(); // выведет "Hi Myra"
function Proto(name) {
this.name = name;
this.greet = function() {
console.log(`Hi ${this.name}`);
};
};
// Использование функции для объявления и создания объекта (не поднятого)
class Classy {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hi ${this.name}`);
}
};
let aClassy = new Classy("Sonja");
aClassy.greet(); // выведет "Hi Sonja"
Различия между стрелочными и обычными функциями
Ещё один аспект традиционных функций может быть теперь достигнут с помощью функций стрелок и нового синтаксиса, который позволяет писать вызывающую функцию более кратко, аккуратнее входя в обратный вызов. По сути самой простой реализацией задуманного приёма является единственная строка, которая полностью закрыта фигурными скобками и автоматически возвращает результат выполняемой инструкции:
const traditional = function(data) {
return (`${data} from a traditional function`);
}
const arrow = data => `${data} from an arrow function`;
console.log(traditional("Hi")); // трад. функция "Hi from a traditional function"
console.log(arrow("Hi")); // стрелк. "Hi from an arrow function"
Функции стрелки заключают в себе несколько особенностей, которые делают их в каком-то смысле более удобными, упрощая вызов функции, не изменяя при этом привычное всем ключевое слово.
Например, функция стрелки наследует как this так и arguments из контекстов. Это хорошо подходит для тех ситуаций, когда программисту необходимо выполнить в коде обработку событий setTimeout и т.д. Обычные методы в такой ситуации зачастую вынуждают писать свёрнутый код, связывающий несколько функций с помощью .bind(this)
. Всё это уходит на задний план вместе с функциями стрелок.
class GreeterTraditional {
constructor() {
this.name = "Joe";
}
greet() {
setTimeout(function () {
console.log(`Hello ${this.name}`);
}, 1000); // внутренняя функция имеет свой собственный оператор this без имени
}
}
let greeterTraditional = new GreeterTraditional();
greeterTraditional.greet(); // выводит "Hello"
class GreeterBound {
constructor() {
this.name = "Steven";
}
greet() {
setTimeout(function () {
console.log(`Hello ${this.name}`);
}.bind(this), 1000); // передаёт это из внешнего контекста
}
}
let greeterBound = new GreeterBound(); // выведет "Hello Steven"
greeterBound.greet();
class GreeterArrow {
constructor() {
this.name = "Ravi";
}
greet() {
setTimeout(() => {
console.log(`Hello ${this.name}`);
}, 1000); // функция стрелка наследует this по умолчанию
}
}
let greeterArrow = new GreeterArrow();
greeterArrow.greet(); // выведет "Hello Ravi"
Что в итоге мы получаем
Множество новых изменений в JavaScript были представлены из-за растущих нужд разработчиков, а не только ради удобства. Но это совсем не означает, что старые приёмы канули в лето. Обычно грамотное комбинирование старых и новых техник сделают ваш код лаконичным.
Обязательно ознакомьтесь с этим туториалом. Помните, что у каждого разработчика свои методы, точно так же, как и у автора, это не является чем-то плохим, а наоборот показывает разнообразие решений разных задач. Однако, нам ещё многое предстоит освоить, в первую очередь это конечно традиционный JavaScript.
Лучшей практикой этого языка является понимание того, какую роль он пытается выполнить. В зависимости от ваших привычек это может быть не всегда легко понять. Чтобы решить, какой подход является лучшим для вас сначала подумайте о поставленной задаче и о том, кто в дальнейшем после вас будут ей заниматься.
Удачи!