четверг, 19 ноября 2020 г.

A programmer: JavaScript #22. Closures (Замыкания)

Короткая и простая тема. Думал я поначалу. Есть, как минимум, два объяснения, что это такое.

Во-первых, замыкания - это, грубо говоря, способ создать приватное свойство. То есть есть свойство объявляется внутри функции, а сама функция его возвращает с помощию внутренней функции. Это означает, что свойство остается неизменным, кто бы ни вызывал функцию, потому что область его видимости остается внутри. Например, функция:

function private() {

    let a = 0;

    return function() {

        return a;

    };

};

В ней никто не имеет доступа к переменной а извне, и потому не может её изменить.

Во-вторых, замыкания - это функция внутри функции. Основная польза от этого в том, что её можно присвоить какой-то переменной, а вызвать потом через эту переменную. Например, результатом присвоения let variable = private(); будет variable, которая при вызове variable() будет возвращать а. Одним из самых распространенных применеий замыканий является счетчик: 

function private() {

    let a = 0;

    return function() {

        return a++;

    }

}

let counter = private(); 

тогда при каждом вызове counter() будет возвращаться значение, на единицу большее, чем при предыдущем вызове функции.

Вот тут-то мне и стало непонятно: если функция отработала, то она должна была бы уничтожиться? Тогда в следующий раз заново бы создавалась и инициализировалась новая let a = 0;? Тогда о каком счетчике может быть речь?

Ответ пришлось поискать, и вот он: объект существует пока существует ссылка на него. И здесь мы имеем, что да, функция private() отработала, но внутренняя по отношению к ней функция продолжает существовать, поскольку в момент вызова private() её присвоили другому объекту (где она может ожидать своего вызова). А значит, и private() не уничтожена, а переменная изменила своё значение (в данном случае а = а+1)!

Если детализировать, то:

1. При вызове функции создается лексическое окружение (этой функции) - LexicalEnvironment, в котором, например, будут искаться переменные, если они не были найдены локально внутри. Грубо говоря, это внешняя область видимости по отношению к данной функции.

2. Также создании любая функции получает скрытое свойство [[Environment]], которое ссылается на её окружение (и тем самым как бы "запоминает" его). Для внутренней функции окружением будет внешняя функция с теми значениями переменных, которые на данный момент в ней есть.

То есть в момент вызова counter() значение переменной а будет находится во внешней по отношению к ней private(), и равняться это значение будет тому, которое существует на момент очередного вызова counter(). Другими словами, каждый новый counter() будет находить новую переменную а в своем окружении (имеющую то значение, которое осталось в результате предыдущего вызова counter()). Вот вам и счетчик.

Подробнее - здесь.

четверг, 12 ноября 2020 г.

A programmer: JavaScript #21. Event loop + Promises

JavaScript - однопоточный язык, то есть у него один стек, следовательно, только одна комманда может быть выполнена в единицу времени. Но если у тебя только один поток, как у JavaScript, это может привести к серьёзным задержкам. Проблема с однопоточностью решается "асинхронностью". То есть JavaScript использует дополнительные ресурсы браузера или NodeJS или какого-либо другого окружения, кроме собственно основного движка, для того, чтобы иметь возможность что-то делегировать этим ресурсам и не ждать окончания одной операции, чтобы приступить к следующей.

Это реализуется через "цикл событий" ("event loop"):

  1. Когда функция вызывается, она загружается в стек.
  2. Но потом она начинает выполняться в "дополнительных ресурсах" пока не выполнится до конца.
  3. Пока она выполняется "в стороне", другие функции из стека могут быть в него загружены.
  4. Когда функция выполнена, она возвращается сначала в "очередь на возвращение" ("task queue") и обратно в стек.

Event loop - потому что JavaScript циклом мечется между стеком, и "очередью", проверяя, 1) пустой ли стек, и если стек пустой, то 2) есть ли в очереди то, что нужно возвращать. И если ещё нету, бежит дальше по кругу, если есть - помещает это (то, что первое в очереди) в стек.

И здесь есть большая проблема: часто мы не можем знать, в какой очередности будут выполняться функции. То есть что выполнится первым - то, что идет следующим в коде или то, что вернется из очереди?

Так вот, чтобы избежать синхронности придумали колбеки ("callback"). Колбек - это функция, которая передается в качестве параметра в другую функцию, если нужно, чтобы она была выполнена после той, в которую передана (или в нужном месте внутри). Например:

function callback() {

    console.log('I am callback!');

};

let a = 'I am a parameter of anotherFunction!';

function anotherFunction(a, callback) {

    console.log(a);

    callback();

}

Тогда, если нам важна очередность выполнения, мы должны вкладывать колбек в колбек в колбек и т.д. В этом коде легко запутаться, особенно потому, что переданные функции могут быть описаны совершенно в другом месте, а здесь только вызваны - это называется "ад колбеков".

Чтобы его избежать, придумали (например) промисы ("promises").

Промис - это объект, который "обещает" выполнить то, что внутри. Он принимает функцию, которая имеет параметры resolve и reject, которые, соответственно, вызываются в случае успешного или неуспешного выполнения (вызываются с необходимыми параметрами). То есть, промис не только имеет возвращаемое значение той функции, которая выполнялась внутри него, но и состояние, успешно или нет прошло выполнение. И это состояние можно использовать - в случае неуспешного выполнения - ловить ошибку, добавив к промису через точку catch(...), а в случае успешного - вызывать следующую функцию, добавив к промису через точку then(...). Ну и соответственно, можно промисы цеплять в цепочку один за одним, если принципиально последовательное выполнение определенных функций.

И поскольку я вчера тупил, и не смог написать промис на собеседовании, то вот пример:

let promise = new Promise(function(resolve, request) {

    let data = 5 + 3;

    resolve(data);

}).then(function(data) {

     console log(data + 125);

});

Как-то так.

понедельник, 9 ноября 2020 г.

A programmer: JavaScript #20. Pattern "Observer"

В общих чертах петтерн "observer" (обозреватель, наблюдатель) предполагает, что есть некий объект, который меняет своё состояние и оповещает об этом тех, кто подписан на уведомления (subscribers). Применительно к React'у это более, чем актуально, поскольку при изменении состояния (данных), компоненты должны перерисовываться, а значит, должны следить за изменениями сообветствующих данных.

При этом компоненты, которые себя перерисовывают, получают данные, зависят от них, но state, который хранит состояние, не должен зависеть от них - то есть он не должен сам вызывать метод перерисовки, находящийся в компонентах. Тогда как компоненты узнают, что state изменился?

А вот как: 

С одной стороны, файл, где происходит отрисовка компонентов (допустим, index.js) вызывает rerender() и передает туда state, который предварительно импортирует:

rerender(state);

То есть index.js зависит от state'а, как и положено.

Но index.js также вызывает метод subscribe и передает туда вызов свой rerender():

subscribe(rerender);

Теперь rerender() может быть вызван прямо в state, при этом state от него не зависит, а только использует реализацию.

С другой стороны, при изменении state вызывает callSubscriber(), но его реализацию он получает в распоряжение из метода subscribe():

// некоторые изменения данных

callSubscriber();

В самом state он описывается приблизительно так:

export const subscribe = (observer) => {
     rerender = observer;
}

То есть теперь callSubscriber(), вызываемый в state, - это тот самый rerender(), который реализован в index.js.

Следовательно, теперь при любом изменениии state, он вызывает метод перерисовки, который он получил через subscribе. Это означает, что state по-преженему не зависит от UI, потому что ему всё равно, как реализована перерисовка - он её только вызывает, но не импортирует.

P.S.: Обычно паттерн "observer" объясняют на примере Java. В некотором смысле оно понятнее, потому что там есть интерфейсы как отдельные сущности. Но вот мне помогло объяснение в исполнении Димыча.

воскресенье, 8 ноября 2020 г.

A programmer: JavaScript #19 The logic of React. Part 1

Снова про React. На этот раз вдохновился довольно объёмным курсом от Димыча (IT-KAMASUTRA, "Курс "React JS - путь самурая 1.0", уроки, практика") - он объясняет всё очень подробно и понятно, действительно большой молодец, рекомендую.

React – библиотека для фронтенда, то есть для удобной отрисовки страницы, которая должна быть видна в браузере. Собственно, пишу только об основных принципах и ключевых особенностях.

1. React используется для Single Page Application. «Классический вариант» работы с сайтом предполагает, что когда нужно показать что-то с данного сайта, браузер посылает запрос на сервер, и сервер присылает новую страницу. В свою очередь логика Single Page Application предполагает, что практически всё необходимое загружается в браузер сразу, и при запросах на новый контент уже сам браузер отрисовывает его, не обращаясь к серверу.

2. Для этого используется модульность и виртуальный DOM. Если DOM (Document Object Model) – это модель документа, то виртуальный DOM – это копия этой модели в памяти React’а. В соответствии с виртуальным DOM’ом React «собирает» страницу из компонентов. Если нужно что-то перерисовать, он вносит необходимые изменения а нужный компонент, реагирует на изменения – поэтому и «React». В каждом компоненте есть метод render(), который, собственно, и отрисовывает его. Поэтому не затрагиваются все остальные элементы страницы, что обеспечивает быструю работу.

3. Сложность состоит в том, что слои отображения (User Interface (UI) – то, чем и являются компоненты) и бизнес-логика должны быть разделены. Изменения view предполагают операции с данными, но данные не должны храниться в самих компонентах – компоненты должны использовать только те данные, которые пришли из слоя бизнес-логики. Следовательно, их надо как-то передавать из уровня бизнес-логики в UI и обратно.

4. Из уровня бизнес-логики в UI данные передаются через props’ы – атрибуты компонентов. Они могут передаваться от родительских компонентов в наследников, в которых они становятся элементами state – состояния данного компонента.

5. Нюанс состоит в том, что если нужно передать props’ы на несколько уровней, то и прописывать их нужно на каждом уровне, что неудобно. Чтобы этого не делать, используют Context – некоторое хранилище данных. Компонент, в котором он создаётся, помещается в обёртку Context.Provider, который и передаёт необходимые props’ы, а остальные помещаются в обёртку Context.Consumer. Что означает, что они имеют доступ ко всем props’ам, которые предоставляет Context.Provider.

6. Для того, чтобы передать некоторые данные из UI на уровень бизнес-логики, в тех же props’ах компонентам передаются и необходимые методы. Сами методы прописаны на уровне бизнес-логики, но передаётся возможность их вызова.

7. На уровне бизнес-логики создают store – хранилище данных (состояний компонентов). С помощью переданных им методов компоненты передают туда данные. С помощью переданных компонентам props’ов компоненты перерисовывают себя – это постоянно работающий цикл. Такое «архитектурное решение» называется Flux. Для его реализации существуют «системы управления состоянием» («state management system»), наиболее известная из них – Redux.

8. В React возможет функциональный и объектно-ориентированный (ООП) подходы. В первом случае оперируют функциями, во втором – классами. Классы могут содержать и собственный state – состояние, и собственные функции. Если объект вызывает собственный метод, используется this. Но если мы передаем метод куда-либо, то контекст выполнения меняется, тогда this будет указывать на другой объект. Чтобы не происходило путаницы, метод привязывают к его контексту с помощью метода bind().

9. В Store кроме данных содержатся методы для работы с данными. Обычно их выносят в общий метод, называемый dispatch(), и передают ему (кроме необходимых данных) объект action, который имеет определенный тип. В зависимости от типа dispatch() выполняет нужный функционал. Это сокращает запись в компонентах (вместо того, чтобы прописывать разные отдельные методы).

 Продолжение следует.

пятница, 21 августа 2020 г.

A programmer: PHP #2 Странный WordPress

Вопрос был вот в чем. Есть сайт на WordPress, точнее - интернет-магазин. Страница выводит товары с помощью цикла и функции get_posts(), и делает этот вывод для каждой категории товаров, чтобы можно было дальше взаимодействовать с ними уже на странице.




Но в админке WordPress'а категории товаров указаны нормальными человеческими словами, а тут, как мы видим, ID. Возникает вопрос, как соотнести названия категорий и их ID?

Ответ оказался весьма интересным:

"Как посмотреть ID категории?- Зайдите в Консоль-> Записи Рубрики, кликните на заданную рубрику и в адрресной строке после ID= будет номер категории"

Огромное спасибо автору, а полный текст статьи здесь.



понедельник, 3 августа 2020 г.

A programmer: JavaScript #18 Heroku

Пару слов об опыте решения проблемы, которая, в общем, оказалась проблемой невнимательности (как часто бывает). Дело в том, что запушенное мной на Heroku приложение, написанное на React'e, не запускалось. Смотрел логи, гуглил ошибки и прочее - непонятно. В итоге оказалось, что не подумал о двух вещах. Во-первых, для запуска вебприложения нужен вебсервер! Вот ведь новость какая :) Написал, добавил в проект. Во-вторых (и это было неочевидно), его нужно было явно прописать в Procfile'e. Значит, надо его создать в нем написать (в моем случае):

web: node server.js

Где web - тип контейнера, server.js - название файла с сервером. И еще: чтобы не заморачиваться с отдельным гитом для Heroku, можно подключить собственный гитха, с которого он будет брать код, а потом либо вручную либо автоматически деплоить его. В документации это всё есть.


четверг, 9 июля 2020 г.

A programmer: JavaScript #17 React + Heroku

Хочется похвастаться. Я искал бесплатный или недорогой сервер, на котором можно было бы запускать Node.js и на нем всё остальное. Давно слышал про Heroku, но только сегодня руки дошли разобраться - создал один тестовый проект прямо там и второй просто туда задеплоил, оба на React'е. Собственно, пересказывать, как это делать, смысла не вижу, поскольку есть очень понятный мануал: https://devcenter.heroku.com/articles/getting-started-with-nodejs. А ещё - отличное видео: