Совершенствуйте свои навыки JavaScript, читая чужой код
В этой статье мы расскажем, как использовать чужой код для улучшения своих знаний и навыков программирования на JavaScript. На примере Redux продемонстрируем, как лучше всего подходить к изучению библиотек.
Преимущества чтения исходного кода
Когда я начал изучать исходный код фреймворка Mithril, у меня было смутное представление о том, что такое виртуальный DOM. Когда я закончил, то понял, что виртуальный DOM - это метод, включающий в себя создание дерева объектов, которые описывают, как должен выглядеть пользовательский интерфейс. Затем это дерево превращается в элементы DOM с помощью таких API-интерфейсов, как document.createElement.
Еще одним преимуществом чтения чужого кода является обучение созданию правильной архитектуры приложений. Например, структура React отражает его новую архитектуру. Авторы отделили модуль, отвечающий за обновление пользовательского интерфейса (react-reconciler), от модуля, отвечающего за отображение элементов DOM (react-dom). Благодаря этому подходу разработчикам стало проще писать средства визуализации, подключаясь к пакету react-reconciler.

Вскоре исходный код, который вы читаете, приведет вас к спецификации JavaScript.
Изучение чужого кода облегчило мне понимание официальной спецификации JavaScript. Когда я первый раз читал спецификацию, то исследовал разницу между throw Error и throw new Error . Я заметил, что фреймворк Mithril использовал throw Error в реализации функции m. И задался вопросом о пользе использования throw new Error.
С тех пор я узнал, что логические операторы && и || не обязательно возвращают логические значения. А также нашел правила, управляющие тем, как оператор равенства == приводит значения.
Методы чтения исходного кода
Изучение исходников лучше всего начать с определения метода из выбранной библиотеки и документирования того, что происходит при его вызове.
Таким образом я исследовал ReactDOM.render и многое узнал о React Fiber. Поскольку React является популярным фреймворком, я нашел множество статей, написанных на эту же тему, и это ускорило процесс изучения.
Это также познакомило меня с концепциями методом window.requestIdleCallback и реальным примером связанных списков (React обрабатывает обновления, помещая их в очередь, которая является связанным списком приоритетных обновлений). При этом желательно создать простое приложение с использованием изучаемой библиотеки.
Если я не провожу глубокое исследование, то открываю папку /node_modules (в проекте, над которым работаю) или перехожу в репозиторий GitHub. Это происходит, когда я сталкиваюсь с ошибкой или интересной функцией.
Менее сложный способ чтения исходного кода я называю метод «беглого взгляда». В самом начале изучения кода я установил фреймворк express.js, открыл его папку /node_modules и просмотрел все зависимости. Если из файла README я не получал понимания кода, то переходил к чтению исходника. Благодаря этому мне удалось сделать следующие выводы:
- js зависит от двух модулей, которые объединяют объекты, но делают это по-разному. merge-descriptors только добавляет свойства, найденные в исходном объекте, а также объединяет свойства. utils-merge только перебирает перечисляемые свойства объекта. А также свойства, найденные в его цепочке прототипов. merge-descriptors использует Object.getOwnPropertyNames() и Object.getOwnPropertyDescriptor(), тогда как utils-merge использует for..in.
- Модуль setprototypeof обеспечивает кроссплатформенные установки прототипа из созданного объекта.
- escape-html- это модуль, предназначенный для экранирования строки, чтобы ее можно было преобразовать в HTML-контент.
Для тестирования front-end кода используйте инструменты отладки, встроенные в браузер. Также они позволяют остановить программу в любое время, проверить ее состояние и пропустить выполнение функции.

Подойдите к отладке так же, как к любому приложению. Сформируйте гипотезу, а затем проверьте ее.
Пример: функция подключения Redux
React-Redux - это библиотека для управления состоянием React-приложений. Знакомство со столь популярными платформами я начинаю с поиска статей, которые рассказывают об их реализации.
connect является функцией React-Redux, которая связывает компоненты React с хранилищем Redux-приложения. Как? Согласно официальной документации, она делает следующее:
«... возвращает новый класс связанного компонента, который оборачивает переданный компонент».
Следующим шагом является создание простого примера приложения, которое использует connect. Но в данном случае я решил использовать новое React- приложение, которое мы создадим в Limejump. Так я хотел изучить возможности connect в создании приложений.
Компонент, с которым я работал:
class MarketContainer extends Component {
// здесь код пропущен для краткости
}
const mapDispatchToProps = dispatch => {
return {
updateSummary: (summary, start, today) => dispatch(updateSummary(summary, start, today))
}
}
export default connect(null, mapDispatchToProps)(MarketContainer);
Это компонент контейнера, который объединяет четыре связанных компонента. В файле, экспортирующем метод connect, указано, что он реализует шаблон проектирования типа фасад. В конце файла мы видим, что connect экспортирует вызов функции с именем createConnect. Ее параметры – это набор значений, используемых по умолчанию, которые были структурированы следующим образом:
export function createConnect({
connectHOC = connectAdvanced,
mapStateToPropsFactories = defaultMapStateToPropsFactories,
mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,
mergePropsFactories = defaultMergePropsFactories,
selectorFactory = defaultSelectorFactory
} = {})
Здесь мы сталкиваемся с другим обучающим моментом: экспорт вызванных функций и деструктурирование аргументов функций, используемых по умолчанию. Это обучающий момент, потому что код был написан следующим образом:
export function createConnect({
connectHOC = connectAdvanced,
mapStateToPropsFactories = defaultMapStateToPropsFactories,
mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,
mergePropsFactories = defaultMergePropsFactories,
selectorFactory = defaultSelectorFactory
})
Его выполнение вызвало бы ошибку «Uncaught TypeError: Cannot destructure property 'connectHOC' of 'undefined' or 'null'». Это связано с тем, что у функции нет аргумента, используемого по умолчанию, к которому можно обращаться.
createConnect сама по себе ничего не делает в теле функции. Она возвращает вызванную функцию connect:
export default connect(null, mapDispatchToProps)(MarketContainer)
Она принимает четыре обязательных аргумента. Первых три из них аргумента проходят через функцию match, которая помогает определить их поведение в соответствии с наличием аргументов и типом их значения.
Второй аргумент функции match является одной из трех функций, импортированных функцией connect, я должен решить, какому потоку следовать.
После функций сопоставления мы переходим к функции connectHOC, которая принимает компонент React и подключает его к Redux. Это еще один вызов функции, которая возвращает функцию wrapWithConnect. Она отвечает за обработку соединения компонента с хранилищем.
Заключение
Чтение исходного кода поначалу затруднительно. Но со временем становится легче.
Его цель не в том, чтобы понять все, а найти другую точку зрения. Например, функция isPlainObject использует if (typeof obj !== 'object' || obj === null) return false, чтобы проверить, что данный аргумент является простым объектом. Я удивился, почему она не использует Object.prototype.toString.call(opts) !== '[object Object]'. В этом случае используется меньше кода и происходит обработка входящих данных по типам объектов. Но дальнейшее чтение кода показало, что эта проблема решается проверкой Object.getPrototypeOf(obj) === null.
Еще одна особенность работы функции isPlainObject скрывалась в приведенном ниже коде:
while (Object.getPrototypeOf(baseProto) !== null) {
baseProto = Object.getPrototypeOf(baseProto)
}
Поиск в Google привел меня к обсуждению проблем Redux. В нем объясняется, как этот код обрабатывает проверку объектов, которые происходят из iFrame.