Совершенствуйте свои навыки 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.

Вадим Дворниковавтор-переводчик статьи «Improve Your JavaScript Knowledge By Reading Source Code»