Немного о JavaScript

Хотя кого я обманываю, получилась достаточно увесистая глава

В данный раздел я вынес ту информацию о JavaScript, которую необходимо знать, чтобы у вас не возникало «детских» проблем с использованием jQuery. Если у вас есть опыт работы с JavaScript, то листайте далее.

Изучать хотите JavaScript и jQuery? Так силу познайте инструмента истинного:

Список не полон, но console там есть, применять её надо уметь

О форматировании

Хотел бы сразу обратить внимание на форматирование JavaScript-кода. Мой опыт мне подсказывает — лучше всего спроецировать стандарты форматирования основного языка разработки на прикладной — JavaScript, а если вы хотите чего-нить из глобального и общепринятого, то я для вас уже погуглил:

В довесок поделюсь небольшим советом: все переменные, содержащие объект jQuery, лучше всего именовать, начиная с символа «$». Поверьте, такая небольшая хитрость экономит много времени.

Основы JavaScript

Переменные

Первое, с чем мы столкнемся — это объявление переменных:

var name = "Ivan";
var age = 32;

Всё просто, объявляем переменную, используя ключевое слово var.

Можно, конечно же, и без var, но делать я вам это настоятельно не рекомендую, т.к. могут возникнуть непредвиденные проблемы в коде, о чём чуть позже расскажу.

Да, да, JavaScript относится к языкам с динамической типизацией, и нам нет нужды указывать тип данных при объявлении переменных, вы даже можете устроить «holy war» по этому поводу, но делайте это локально, не выходя за рамки своей черепной коробки.

На имена переменных наложено два ограничения:

  • имя может состоять из букв, цифр, и символов «$» и «_»

  • первый символ не должен быть цифрой

Учтите, регистр букв имеет значение:

var company = "Facebook";
// совсем другая «компания»
var Company = "Google";

Хочу также вас познакомить с таким нововведением ES6 (он же ECMAScript-2015 или ES-2015), как объявление переменных с использованием конструкции let:

let company = "Facebook";

Внешне, от var не сильно отличается, зато какая разница в поведении:

  • область видимости переменной let ограничена блоком {...}, в отличие от var, которая видна везде внутри функции:

    вот так ведёт себя переменная объявленная с помощью var:

    var a = 0;
    if (true) {
        var a = 1000;
        alert(a); // 1000
    }
    alert(a);     // 1000

    а теперь сравните с поведением let:

    let a = 0;
    if (true) {
        let a = 1000;
        alert(a); // 1000
    }
    alert(a);     // 0
  • переменная let видна только после объявления:

    «var»:

    alert(a);  // undefined
    var a = 0;

    «let»:

    alert(b);  // error: "b" is not defined
    let b = 0;
  • переменную let нельзя объявить повторно:

    «var»:

    var a;
    var a; // ок

    «let»:

    let b;
    let b; // error: "b" has already been declared
  • внутри цикла переменная let будет объявлена новая для каждой итерации:

    «var»:

    for (var i = 0; i < 10; i++) { /* … */ }
    alert(i); // 10

    «let»:

    for (let j = 0; j < 10; j++) { /* … */ }
    alert(j); // error: "j" is not defined

Вот вам следующее задание - угадайте значение x и y в следующем примере:

var x;
let y;
for (let y = 0; y < 10; y++) {
    x = y;
}
y = y ? x : 5;
ответ
x = 9
y = 5

Константы

В JavaScript до ES6 не было констант, но поскольку необходимость в них всё же была и до того, то была негласная договоренность: переменные, набранные в верхнем регистре через подчёркивание, не изменять:

var USER_STATUS_ACTIVE = 1;
var USER_STATUS_BANNED = 2;

Такие константы необходимы, чтобы избежать появления «magic numbers».

Взгляните на следующий код if(status==2) — о чём тут речь, мало кому будет понятно, а вот код if(status==USER_STATUS_BANNED) уже более информативный.

Если же говорить об эре ES-2015, то тут уже используем const, а об остальном уже позаботится сам JavaScript:

const USER_STATUS_ACTIVE = 1;
USER_STATUS_ACTIVE = 2; // error: assignment to constant variable

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

Отдельно стоит заметить, константа не позволяет изменять саму переменную, но если мы присвоим константе объект или массив, то с ним вы сможете делать что душа пожелает:

const USER_STATUS_ACTIVE = 1;
const USER_STATUS_BANNED = 2;

const USER = {
    name: "mr.Smith",
    status: USER_STATUS_BANNED
};

USER.status = USER_STATUS_ACTIVE; // ok

USER = {name: "mr.Wesson"}; // error

Вот и получился «читаемый» код, мотайте на ус

Типы данных

В JavaScript не так уж и много типов данных:

  • number — целое или дробное число:

    let answer = 42
    let pi = 3.1415

    также существуют следующие специальные значения:

    • Infinity — за гранью 1.7976931348623157E+10308 (т.е. больше)

    • -Infinity — за гранью -1.7976931348623157E+10308 (т.е. меньше)

    • NaN (not-a-number) — результат числовой операции, которая завершилась ошибкой; для образца запустите следующий код в консоли:

      Math.sqrt(-5);

      но учтите – есть только один правильный способ проверки результата вычисления на NaN:

      isNaN(NaN);   // true

      и много неправильных:

      NaN == false; // false
      NaN == NaN;   // false
      if (NaN) {
      // данный блок никогда не выполнится
      }
  • string — строка, заключается в кавычки:

    let str = "Hello World!"

    В JavaScript нет разницы между двойными кавычками и одинарными, достаточно соблюдения их парности (привет PHP и Ruby).

  • boolean — булево значение, т.е. или «true» или «false»

    let result = true
  • object — это объекты; на них остановлюсь подробнее чуть позже…

  • symbol — тип данных из ES6, служит для создания уникальных идентификаторов (о нём рассказа не будет, не в ходу ещё)

  • null — специальное значение для определения «пустоты»

    let result = null
  • undefined — ещё одно специальное значение, для «неопределенности», используется как значение неопределённой или несуществующей переменной, например, если переменная объявлена, но значение ей ещё не присвоено:

    let a;
    // переменная есть, значения нет
    if (typeof a === "undefined") {
        alert("variable is undefined")
    }
    
    // переменной нет, совсем
    if (window["b"] === undefined) {
        alert("variable does not exist")
    }

    Во втором примере нас может ожидать сюрприз, если что-то определит переменную «undefined»; как обойти такую «неприятность», я ещё расскажу.

Хотел ещё акцентировать ваше внимание на приведение типов в JavaScript. Этот момент вызывает много вопросов у начинающих разработчиков, а ошибки связанные с ним будут преследовать вас первые полгода работы. Откройте страничку учебника об особенностях операторов, изучите её, а когда будете готовы переходите к выполнению следующего задания.

Посмотрите внимательно на код, и скажите - каков будет результат вычисления:

let x
x = 42 + "x" + 42
x = x/2 || x
ответ
x = "42x42"

Массивы

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

//             0       1       2
var users = ["Ivan", "Petr", "Serg"]

Нумерация массивов начинается с «0», так что для получения первого элемента вам потребуется следующий код:

alert(users[0]); // выведет Ivan

Размер массива хранится в свойстве length:

alert(users.length); // выведет 3

users[3] = "Danylo";

alert(users.length); // выведет 4

В действительности length возвращает индекс последнего элемента массива+1, так что не попадитесь на следующую «ошибку новичка»:

var a = [];

a[4] = 10;       // объявляем сразу пятый элемент

alert(a.length); // выведет 5;

Для перебора массива лучше всего использовать цикл for(;;):

for (let i = 0; i < users.length; i++) {
    alert(users[i]); // последовательно выведет Ivan, Petr и Serg
}

На предыдущем примере цикл покажет четыре значения undefined, что может вызвать проблемы, и только затем будет работать со значением 10. Иначе действует похожий, но не равнозначный цикл for (in) (он сразу возьмётся за значение 10). Пример цикла:

for (let i in users) {
    alert(users[i]);
}

Для работы с последним элементом массива следует использовать методы push() и pop():

users.push("Sidorov"); // добавляем элемент в конец массива

let sidorov = users.pop(); // удаляем и возращаем последний элемент

Для работы с первым элементом массива следует использовать методы unshift() и shift():

users.unshift("Sidorov"); // добавляем элемент в начало массива

let sidorov = users.shift(); // удаляем и возращаем первый элемент

Последние два метода работают медленно, т.к. перестраивают весь массив пошагово.

Упражнение для закрепления материала:

  • как мне получить пользователя по имени Andrey из массива users?

  • какова длина массива?

var users = new Array;
users.push("Anton");
users.push("Andrey");
users[100] = "Bogdan";
users.push("Boris");
ответ
// выведет Andrey
alert(users[1])
// вернёт 102
alert(users.length)

Функции

С функциями в JavaScript всё просто, вот вам элементарный пример:

function hello() {
    alert("Hello world");
}

Просто, пока не заговорить об анонимных функциях…

Анонимные функции

В JavaScript можно создать анонимную функцию (т.е. функцию без имени), для этого достаточно слегка изменить предыдущую конструкцию:

function() {
    alert("Hello world");
}

Так как функция — это вполне себе объект, то её можно присвоить переменной, и (или) передать в качестве параметра в другую функцию:

var myAlert = function(name) {
    alert("Hello, " + name);
}

function helloMike(myFunc) {  // тут функция передаётся как параметр
    myFunc("Mike");           // а тут мы её вызываем
}

helloMike(myAlert); // "Hello, Mike"

Анонимную функцию можно создать и тут же вызвать с необходимыми параметрами:

(function(name) {
    alert("Hello, " + name);
})("Mike");

Это несложно, скоро вы к ним привыкнете, и вам их будет недоставать в других языках.

Стрелочные функции

Ах, этот ES6 – он принёс ещё более краткую форму для создания функций, это так называемые «функции-стрелки»:

// была простая анонимная функция
var inc = function (x) {
    return x+1;
}

// стала запись в одну строчку
var inc = x => x+1;

// была функция с несколькими аргументами
var sum = function (a, b) {
    return a+b;
}

// стала запись в одну строчку
var sum = (a, b) => a+b;

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

Функции-стрелки обладают ещё некоторыми особенностями, но это всё же не учебник по JavaScript ;)

Решите простую FizzBuzz-задачку:

Необходимо написать функцию, которая будет в качестве аргумента принимать число, если число кратно 3, то функция должна выводить Fizz, если число кратно 5, то Buzz, если число кратно и 3 и 5, то функция должна вернуть FizzBuzz, иначе возвращать само число

ответ
/**
 * Ha-ha, classic! 🚬🐱
 */
function fizzBuzz(n) {
  let output = '';
  if (n % 3 === 0) output += 'Fizz';
  if (n % 5 === 0) output += 'Buzz';
  console.log(output || n);
}

for (let i = 1; i <= 42; i++) {
  fizzBuzz(i)
}

Объекты

На объекты в JavaScript возложено две роли:

  • хранилище данных

  • функционал объекта

Первое предназначение можно описать следующим кодом:

let user = {
    name: "Ivan",
    age: 32
};

alert(user.name) // Ivan
alert(user.age)  // 32

Это, фактически, реализация key-value хранилища, или хэша, или ассоциативного массива, или …. Ну, вы поняли, названий много. В JavaScript это объект, а запись выше – это JSON или JavaScript Object Notation (хоть и с небольшими оговорками).

Для перебора такого хранилища можно использовать цикл for(.. in ..):

for (let prop in user) {
  alert(prop + "=" + user[prop]); // выведет name=Ivan
                                  // затем age=32
}

Классы

С классами, конструкторами и т.д. в JavaScript посложнее будет, хотя для понимания не так уж много и надо. Запоминайте: любая функция, вызванная с использованием ключевого слова new, возвращает нам объект, а сама становится конструктором данного объекта:

const USER_STATUS_ACTIVE = 'active';

function User(name) {
    this.name = name;
    this.status = USER_STATUS_ACTIVE;
}

const me = new User("Anton");

Поведение функции User() при использовании new слегка изменится:

  1. Данная конструкция создаст новый, пустой объект

  2. Ключевое слово this получит ссылку на этот объект

  3. Функция выполнится и, возможно, изменит объект через this (как в примере выше)

  4. Функция вернёт this (по умолчанию)

Результатом выполнения кода будет следующий объект:

{
    "name": "Anton",
    "status": 1
}

В стандарте ES6(ES2015) появилась конструкция class, что по сути — синтаксический сахар для JavaScript — специально для тех, кто не любит работать с прототипами (ага, для таких как я).

Если переписать код выше на классы, то получим следующий результат:

const USER_STATUS_ACTIVE = 'active';

class User {
    constructor(name) {
        this.name = name;
        this.status = USER_STATUS_ACTIVE;
    }
}

const me = new User("Anton");

Опять отправлю читать учебник по JavaScript :)

Простая задачка для закрепления материала:

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

ответ
// если использовать синтаксис функций
function Cat(breed, color) {
    this.breed = breed;
    this.color = color;
}

// если использовать синтаксис классов
class Cat {
    constructor(breed, color) {
        this.breed = breed;
        this.color = color;
    }
}

// в любом случае, использовать его будем следующим образом
const cat = new Cat("British Shorthair", "silver");

// и результатом будет объект
{
    "breed": "British Shorthair",
    "color": "silver"
}

Область видимости и this

Для тех, кто только начинает своё знакомство с JavaScript я расскажу следующие нюансы:

  • когда вы объявляете переменную или функцию, то она становится частью «window»:

var a = 1234;

alert(window["a"]); // => 1234

function myLog(message) {
    alert(message); // => 1234
}

window["myLog"](a);
  • когда искомая переменная не найдена в текущей области видимости, её поиски будут продолжены в области видимости родительской функции:

var a = 1234;

(function(){
    var b = 4321;
    (function() {
        var c = 1111;
        alert((a+b)/c); // => 5
    })();
})();
  • чудо-переменная this всегда указывает на текущий объект, вызывающий функцию (поскольку по умолчанию все переменные и функции попадают в «window», то «this == window»):

var a = 1234;

function myLog() {
    alert(this);   // => window
    alert(this.a); // => 1234
}
  • контекст this можно изменить, используя функции bind(), call(), и apply()

Всё, что касается «window», относится лишь к браузерам, хотя поскольку книга о jQuery, то иное поведение я и не рассматриваю, но вот так прозрачно намекаю, что есть альтернативная реальность со своим «jQuery», и звать её там cheerio ;).

Замыкания

Изучив замыкания, можно понять много магии в JavaScript. Приведу пример кода с пояснениями:

var a = 1234;

var myFunc = function(){
    var b = 4321;
    var c = 1111;
    return function() {
        return ((a+b)/c);
    };
};

var anotherFunc = myFunc(); // myFunc возвращает анонимную функцию
                            // с «замкнутыми» значениями c и b

alert(anotherFunc()); // => 5

Что же тут происходит? Функция, объявленная внутри другой функции, имеет доступ к переменным родительской функции. Повтыкайте в код, пока вас не осенит, о чём я тут толкую.

Хорошая задачка, которая в полной мере даёт понимание сути проблемы: «Армия функций»

Рекомендуемые статьи по теме:

Вводная по JavaScript затянулась, лучше не поленитесь, и изучите весь учебник от Ильи Кантора.

Last updated