paint-brush
Как изучать исходный код: прототипы объектов и миксины в Express.jsк@luminix
200 чтения

Как изучать исходный код: прототипы объектов и миксины в Express.js

к Lumin-ix11m2024/08/19
Read on Terminal Reader

Слишком долго; Читать

Изучение исходного кода, несомненно, может изменить траекторию вашей карьеры разработчика. Даже заглянув под поверхность всего на один уровень, вы сможете выделиться среди большинства среднестатистических разработчиков. Вот о чем эта серия: не довольствоваться API, а выходить за его пределы, учиться воссоздавать эти инструменты. Вырваться из посредственности в этом мире ажиотажа вокруг ИИ — вот что делает разработчика ценным сверхсредним!
featured image - Как изучать исходный код: прототипы объектов и миксины в Express.js
Lumin-ix HackerNoon profile picture
0-item
1-item

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


Это первый шаг к мастерству!


Вот личная история: в моей текущей работе в стартапе AI/ML команда не могла понять, как перенести данные Neo4j с сервера на фронтенд для визуализации, и они сделали презентацию через 12 часов. Меня пригласили в качестве фрилансера, и вы могли ясно видеть панику. Проблема была в том, что данные, возвращаемые Neo4j, не были в правильном формате, ожидаемом инструментом визуализации neo4jd3 .


Представьте себе: Neo4jd3 ожидает треугольник, а Neo4j возвращает квадрат. Это несовместимое несоответствие прямо здесь!


neo4jd3


Скоро мы, возможно, займемся графовыми данными с JavaScript и Neo4j в Mastered ! Это изображение вызывает ностальгию.


Было только два варианта: переделать весь бэкэнд Neo4j или изучить исходный код Neo4jd3, выяснить ожидаемый формат, а затем создать адаптер для преобразования квадрата в треугольник.


 neo4jd3 <- adapter <- Neo4j 

адаптер, а именно



Мой мозг по умолчанию переключился на чтение исходного кода, и я создал адаптер: neo4jd3-ts .


 import createNeoChart, { NeoDatatoChartData } from "neo4jd3-ts";


Адаптер — NeoDatatoChartData , а все остальное — история. Я принял этот урок близко к сердцу, и при каждой возможности я спускаюсь на уровень ниже в каждом инструменте, который я использую. Это стало настолько распространенным, что иногда я даже не читаю документацию.


Этот подход кардинально изменил мою карьеру. Все, что я делаю, похоже на магию. Через несколько месяцев я руководил критически важными миграциями серверов и проектами, и все потому, что я сделал шаг к источнику.


Вот о чем эта серия: не довольствоваться API, а выходить за рамки, учиться воссоздавать эти инструменты. Вырваться из колеи в этом мире хайпа вокруг ИИ — вот что делает разработчика ценным за пределами среднего!


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


Поскольку я в основном работаю бэкенд-разработчиком (да, полного цикла, но 90% времени занимаюсь бэкендом), лучшего инструмента для начала, чем Express.js, не найти.


Я предполагаю, что у вас есть опыт программирования и хорошее понимание основ программирования! Вас можно отнести к продвинутому новичку.


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


Эта статья написана до Express по следующей причине: я решил рассмотреть очень маленькую библиотеку, от которой зависит Express, merge-descriptors , которая на момент написания статьи имела 27 181 495 загрузок и содержала всего 26 строк кода.


Это даст нам возможность создать структуру и позволит мне познакомить вас с основами объектов, которые имеют решающее значение при создании модулей JavaScript.

Настраивать

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


Express — это мощная библиотека. Мы рассмотрим все, что сможем, в нескольких статьях, прежде чем перейдем к другому инструменту.


Откройте исходный код Express в IDE, желательно с указанием номеров строк, перейдите в папку lib и откройте файл express.js , файл входа.


В строке 17 представлена наша первая библиотека:


 var mixin = require('merge-descriptors');


Использование в строках 42 и 43:


 mixin(app, EventEmitter.prototype, false); mixin(app, proto, false);


Прежде чем мы даже рассмотрим, что здесь происходит, нам нужно сделать шаг назад и поговорить об объектах в JavaScript, за пределами структуры данных. Мы обсудим композицию, наследование, прототипы и миксины — название этой статьи.

Объект JavaScript

Закройте исходный код Express и создайте где-нибудь новую папку, чтобы продолжить изучение этих важнейших основ объектов.


Объект — это инкапсуляция данных и поведения, лежащая в основе объектно-ориентированного программирования (ООП) . Интересный факт: почти все в JavaScript является объектом.


 const person = { // data name: "Jane", age: 0, // behavior grow(){ this.age += 1; } };

Все, что находится между открывающимися и закрывающимися фигурными скобками в объекте person , называется собственными свойствами объекта . Это важно.


Собственные свойства — это те, которые относятся непосредственно к объекту. name , age и grow — это собственные свойства person .


Это важно, поскольку каждый объект JavaScript имеет свойство prototype . Давайте закодируем указанный выше объект в функциональный план, чтобы иметь возможность динамически создавать объекты person .


 function createNewPerson(name, age){ this.name = name; this.age = age; } createNewPerson.prototype.print = function(){ console.log(`${this.name} is ${this.age}`); }; const john = new createNewPerson("John", 32);

Прототип — это то, как объекты JavaScript наследуют свойства и методы от других объектов. Разница между Own Properties и Prototype заключается в доступе к свойству объекта:


 john.name; // access


JavaScript сначала будет искать в Own Properties , так как они имеют высокий приоритет. Если свойство не найдено, он рекурсивно ищет в собственном prototype объекта, пока не найдет null и не выдаст ошибку.


Объект-прототип может наследовать от другого объекта через свой собственный прототип. Это называется цепочкой прототипов.


 console.log(john.hasOwnProperty('name')); // true console.log(john.hasOwnProperty('print')); // false, it's in the prototype


Однако print работает на john :


 john.print(); // "John is 32"


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


"Hello world" наследования — это объект млекопитающего. Давайте воссоздадим его с помощью JavaScript.


 // our Mammal blueprint function Mammal(name) { this.name = name; } Mammal.prototype.breathe = function() { console.log(`${this.name} is breathing.`); };


В JavaScript внутри объекта Object есть статическая функция:


 Object.create();


Он создает объект аналогично {} и new functionBlueprint , но разница в том, что create может принимать прототип в качестве параметра для наследования.


 // we use a cat blueprint function here (implemented below) Cat.prototype = Object.create(Mammal.prototype); // correction after we inherited all the properties Cat.prototype.constructor = Cat;


Теперь Cat будет метод breathe , найденный в Mammal , но важно знать, что Cat указывает на Mammal как на свой прототип.

Объяснение:

  1. Mammal Blueprint : Сначала мы определяем функцию Mammal и добавляем метод breathe к ее прототипу.


  2. Наследование Cat : Мы создаем функцию Cat и устанавливаем Cat.prototype в Object.create(Mammal.prototype) . Это делает прототип Cat наследуемым от Mammal , но изменяет указатель constructor на Mammal .


  3. Исправление конструктора : Мы исправляем Cat.prototype.constructor , чтобы он указывал обратно на Cat , гарантируя, что объект Cat сохраняет свою идентичность, наследуя методы от Mammal . Наконец, мы добавляем метод meow к Cat .


Такой подход позволяет объекту Cat получать доступ как к методам Mammal (например, breathe ), так и к методам его собственного прототипа (например, meow ).


Нам нужно это исправить. Давайте создадим полный пример:


 function Cat(name, breed) { this.name = name; this.breed = breed; } Cat.prototype = Object.create(Mammal.prototype); // cat prototype pointing to mammal // correction after we inherited all the properties Cat.prototype.constructor = Cat; // we are re-pointing a pointer, the inherited properties are still there Cat.prototype.meow = function() { console.log(`${this.name} is meowing.`); };


Чтобы понять Cat.prototype.constructor = Cat , вам нужно знать об указателях. Когда мы наследуем от Mammal с помощью Object.create , он меняет указатель нашего прототипа Cat на Mammal , что неправильно. Мы по-прежнему хотим, чтобы наш Cat был отдельным индивидуумом, несмотря на наличие родителя Mammal .


Вот почему нам нужно это исправить.


В этом примере Cat наследуется от Mammal с помощью цепочки прототипов. Объект Cat может получить доступ к методам breathe и meow .


 const myCat = new Cat("Misty", "Ragdoll"); myCat.breathe(); // Misty is breathing. myCat.meow(); // Misty is meowing.


Мы можем создать собаку, также унаследовав ее от млекопитающего:


 function Dog(name, breed) { this.name = name; this.breed = breed; } Dog.prototype = Object.create(Mammal.prototype); Dog.prototype.constructor = Dog; Dog.prototype.bark = function() { console.log(`${this.name} is barking.`); }; const myDog = new Dog('Buddy', 'Golden Retriever'); myDog.breathe(); // Buddy is breathing. myDog.bark(); // Buddy is barking.


Мы создали базовое классическое наследование, но почему это важно? Я думал, мы рассмотрим исходный код!


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


Альтернативой наследованию является композиция объектов, которая грубо берет два или более объектов и объединяет их вместе, образуя «супер»-объект.


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


Именно это и делает наше первое исследование: библиотека merge-descriptors мы обещали рассмотреть в первую очередь.

Модуль Merge-Descriptors

Мы уже видели, где и как это используется в Express. Теперь мы знаем, что это для композиции объектов.


В строке 17 представлена наша первая библиотека:


 var mixin = require('merge-descriptors');


Использование в строках 42 и 43:


 mixin(app, EventEmitter.prototype, false); mixin(app, proto, false);


Используя имеющиеся у нас знания, мы уже можем сделать вывод, что mixin объединяет EventEmitter.prototype и proto в объект с именем app .


Мы вернемся к app , когда начнем говорить об Express.


Это весь исходный код merge-descriptors :


 'use strict'; function mergeDescriptors(destination, source, overwrite = true) { if (!destination) { throw new TypeError('The `destination` argument is required.'); } if (!source) { throw new TypeError('The `source` argument is required.'); } for (const name of Object.getOwnPropertyNames(source)) { if (!overwrite && Object.hasOwn(destination, name)) { // Skip descriptor continue; } // Copy descriptor const descriptor = Object.getOwnPropertyDescriptor(source, name); Object.defineProperty(destination, name, descriptor); } return destination; } module.exports = mergeDescriptors;


С самого начала всегда обращайте внимание на то, как используется функция и какие параметры она принимает:


 // definition mergeDescriptors(destination, source, overwrite = true) // usage var mixin = require('merge-descriptors'); mixin(app, EventEmitter.prototype, false); mixin(app, proto, false);


App — это наш пункт назначения. Мы знаем, что mixin означает композицию объектов. Грубо говоря, этот пакет компонует исходный объект в целевой объект с возможностью перезаписи.


Перезапись, по предположению, происходит, если app (адресат) имеет точно такое же свойство, как и источник, при true перезаписи, в противном случае оставить это свойство нетронутым и пропустить.


Мы знаем, что объекты не могут иметь одно и то же свойство дважды. В парах ключ-значение (объектах) ключи должны быть уникальными.

В Express перезапись выполняется false .


Ниже приведены основные правила обслуживания, всегда обрабатывайте ожидаемые ошибки:


 if (!destination) { throw new TypeError('The `destination` argument is required.'); } if (!source) { throw new TypeError('The `source` argument is required.'); }


Вот тут начинается самое интересное: строка 12.


 for (const name of Object.getOwnPropertyNames(source)) {


Из вышесказанного мы знаем, что означает OwnProperty , поэтому getOwnPropertyNames явно означает получение ключей собственных свойств.


 const person = { // data name: "Jane", age: 0, // behavior grow() { this.age += 1; } }; Object.getOwnPropertyNames(person); // [ 'name', 'age', 'grow' ]


Он возвращает ключи в виде массива, и мы перебираем эти ключи в следующем примере:


 for (const name of Object.getOwnPropertyNames(source)) {


Далее проверяется, имеют ли пункт назначения и источник тот же ключ, по которому мы в данный момент просматриваем цикл:


 if (!overwrite && Object.hasOwn(destination, name)) { // Skip descriptor continue; }


Если overwrite ложно, пропустите это свойство; не перезаписывайте. Именно это и делает continue — он переводит цикл на следующую итерацию и не запускает код ниже, который является следующим кодом:


 // Copy descriptor const descriptor = Object.getOwnPropertyDescriptor(source, name); Object.defineProperty(destination, name, descriptor);


Мы уже знаем, что означает getOwnProperty . Новое слово — descriptor . Давайте проверим эту функцию на нашем собственном объекте person :


 const person = { // data name: "Jane", age: 0, // behavior grow() { this.age += 1; } }; Object.getOwnPropertyDescriptor(person, "grow"); // { // value: [Function: grow], // writable: true, // enumerable: true, // configurable: true // }


Он возвращает нашу функцию grow в качестве значения, а следующая строка не требует пояснений:


 Object.defineProperty(destination, name, descriptor);


Он берет наш дескриптор из источника и записывает его в место назначения. Он копирует собственные свойства источника в наш объект назначения как свои собственные свойства.


Давайте рассмотрим пример на примере нашего объекта person :


 const val = { value: function isAlien() { return false; }, enumerable: true, writable: true, configurable: true, }; Object.defineProperty(person, "isAlien", val);


Теперь person должно быть определено свойство isAlien .


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


Мы успешно завершили работу над нашим первым модулем на этом уровне исходного кода , и нас ждет еще более интересное.


Это было введение. Мы начали с изучения основ, необходимых для понимания модуля, и, как побочный продукт, понимания шаблонов в большинстве модулей, то есть композиции объектов и наследования. Наконец, мы прошлись по модулю merge-descriptors .


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


К счастью, merge-descriptors используется в Express, который является нашим фокусом в качестве стартового исследования исходного кода. Так что ждите больше статей об исходном коде Express.js, пока мы не почувствуем, что у нас достаточно хорошо получилось использовать Express, а затем переключимся на другой модуль или инструмент, например Node.js.


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


Если вас интересует более эксклюзивный практический и объемный контент для совершенствования навыков программирования, вы можете найти больше информации на Ko-fi .