April 3rd, 2021

Учебник по JavaScript: ч.1: var, глобальный объект, объект функции

Продолжаю читать шестой раздел («Продвинутая работа с функциями») первой части («Язык программирования JavaScript») учебника по JavaScript.

https://learn.javascript.ru

Часть 1. Язык программирования JavaScript (в т.ч. 93 подраздела)

Разделы:

6. Продвинутая работа с функциями (11 подразделов)

6.4 Устаревшее ключевое слово "var"
6.5 Глобальный объект
6.6 Объект функции, NFE

Ранее в учебнике уже упоминалось о существовании устаревшего ключевого слова var, служащего для объявления переменной (сейчас вместо него рекомендуют использовать ключевые слова let и const). В подразделе 6.4 это ключевое слово рассматривается более подробно: рассмотрены два отличия от ключевых слов let и const — для ключевого слова var не существует блочной области видимости и, кроме того, это ключевое слово обрабатывается в начале запуска функции независимо от того, где оно фактически прописано (такое поведение называют по-английски «hoisting», что по-русски означает «всплытие» или «поднятие»).

Глобальный объект (в стандарте языка это globalThis, в браузере — это window, а в других средах этот объект может называться по-другому) предоставляет переменные и функции, доступные в любом месте программы.

Ранее в учебнике уже пояснялось, что функция в языке JavaScript является объектом. В подразделе 6.6 учебника эта тема рассматривается более подробно. Например, функция имеет встроенные свойства (к примеру, name (содержит название функции) и length (содержит количество параметров функции)). Кроме этого, программист может добавить в функцию, как и в обычный объект, новые свойства и методы (в последнем случае получается функция функции, что в моем понимании звучит очень необычно).

Ранее в учебнике было рассказано, что функцию в языке JavaScript можно объявить несколькими способами. Например, есть способ, который по-английски называется «Function Declaration» (по-русски «объявление функции»), а есть способ, который по-английски называется «Function Expression» (по-русски «функциональное выражение», то есть объявление функции с помощью присвоения безымянной функции некой переменной). Как оказалось, в последнем случае переменной можно присвоить и функцию с именем, такой способ объявления функций по-английски называется «Named Function Expression» (или сокращенно «NFE»), что по-русски означает «функциональное выражение с именованной функцией».

1. Объявление функции («Function Declaration»):
function foo( /* параметры */ ) {
    // тело функции
}

2. Функциональное выражение («Function Expression»):
let foo = function( /* параметры */ ) {
    // тело функции
};

3. Функциональное выражение с именованной фукнцией («Named Function Expression» или «NFE»):
let foo = function func( /* параметры */ ) {
    // тело функции
};

Учебник по JavaScript: ч.1: работа с объектом функции

Начало тут: Учебник по JavaScript: ч.1: var, глобальный объект, объект функции.

Понравилась задача «Установка и уменьшение значения счётчика» к подразделу 6.6 «Объект функции, NFE» учебника.

В задаче предлагается менять функцию makeCounter (по-русски «создать счетчик»), но код этой функции не дается, потому что подразумевается, что ученик читал текущий и предыдущие подразделы учебника, в которых эта функция разбиралась.

Есть два варианта этой функции.

1. Вариант из подраздела 6.3 «Замыкание» (для хранения значения счетчика используется локальная переменная count):
function makeCounter() {
    let count = 0;

    return function() {
        return count++;
    };
}

2. Вариант из текущего подраздела 6.6 (для хранения значения счетчика используется свойство count, которое программист добавляет в функцию counter, возвращаемую функцией makeCounter):
function makeCounter() {
    function counter() {
        return counter.count++;
    };

    counter.count = 0;

    return counter;
}

Для тестирования этой функции в обоих вариантах можно использовать следующий код:
let counter = makeCounter(); // создаём счетчик, который используем ниже
alert( counter() ); // 0
alert( counter() ); // 1
alert( counter() ); // 2

Итак, в задаче требуется написать к функции counter, возвращаемой функцией makeCounter, два метода: counter.set(value), который должен устанавливать счетчик в значение value, и counter.decrease(), который должен уменьшать значение счетчика на 1.

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

Для тестирования обоих полученных решений можно использовать следующий код:
let counter = makeCounter(); // создаём счетчик, который используем ниже

alert( counter() ); // 0
alert( counter() ); // 1

counter.set(10);

alert( counter() ); // 10
alert( counter() ); // 11

counter.decrease();

alert( counter() ); // 11
alert( counter() ); // 12

1. Вариант решения с хранением значения счетчика в локальной переменной count:
function makeCounter() {
    let count = 0;                  // (*)

    function counter() {            // (*)
        return count++;
    }

    counter.set = function(value) { // (*)
        count = value;
    };

    counter.decrease = function() { // (*)
        count--;
    };

    return counter;
}
Что интересно, куски кода, помеченные в этом решении звездочкой (*), можно безболезненно менять местами в любом порядке внутри функции makeCounter, пока все они находятся до инструкции return counter; (впрочем, объявление функции counter можно сделать внутри функции makeCounter и после инструкции return counter;). Таковы свойства области видимости переменных и функций в языке JavaScript, эта тема уже разбиралась в учебнике ранее.

2. Вариант решения с хранением значения счетчика в свойстве count функции counter, возвращаемой функцией makeCounter:
function makeCounter() {
    counter.count = 0;              // (*)

    function counter() {            // (*)
        return counter.count++;
    }

    counter.set = function(value) { // (*)
        counter.count = value;
    };

    counter.decrease = function() { // (*)
        counter.count--;
    };

    return counter;
}
В этом решении куски кода, помеченные звездочкой (*), можно так же менять местами, как это было описано для предыдущего решения.

Кроме этого, в принципе, добавление и инициализацию свойства count к функции counter, а также добавление методов к этой функции можно вообще вынести из функции makeCounter и прописать после запуска функции makeCounter, но до использования этого свойства и этих методов (для первого варианта решения такое невозможно). Вот так:

3.
function makeCounter() {
    function counter() {            // (*)
        return counter.count++;
    }

    return counter;
}

let counter = makeCounter(); // создаём счетчик, который используем ниже

counter.count = 0;                  // (*)

counter.set = function(value) {     // (*)
    counter.count = value;
};

counter.decrease = function() {     // (*)
    counter.count--;
};

// дальнейшее тестирование

Тут можно видеть разницу между использованием локальных переменных (замыкание) и свойств функций, как объектов. Локальную переменную count из первого варианта решения нельзя поменять извне функции makeCounter, а свойства и методы функции counter можно менять хоть внутри функции makeCounter, хоть снаружи. По идее, в разных случаях могут понадобиться все перечисленные варианты решения обсуждаемой задачи.