Das Studium des Quellcodes kann zweifellos die Richtung Ihrer Entwicklerkarriere verändern. Selbst wenn Sie nur eine Ebene unter die Oberfläche schauen, können Sie sich von den meisten durchschnittlichen Entwicklern abheben.
Es ist der erste Schritt zur Meisterschaft!
Hier ist eine persönliche Geschichte: Bei meinem aktuellen Job bei einem KI/ML-Startup konnte das Team nicht herausfinden, wie es Neo4j-Daten vom Server zum Frontend für eine Visualisierung bekommt, und sie hatten innerhalb von 12 Stunden eine Präsentation fertig. Ich wurde als Freiberufler eingestellt, und man konnte die Panik deutlich sehen. Das Problem war, dass die von Neo4j zurückgegebenen Daten nicht im richtigen Format lagen, das das Visualisierungstool neo4jd3 erwartete.
Stellen Sie sich Folgendes vor: Neo4jd3 erwartet ein Dreieck und Neo4j gibt ein Quadrat zurück. Das ist eine inkompatible Nichtübereinstimmung!
Vielleicht machen wir bald Graph Data Science mit JavaScript und Neo4j in Mastered ! Dieses Bild ist nostalgisch.
Es gab nur zwei Möglichkeiten: das gesamte Neo4j-Backend neu zu erstellen oder den Quellcode von Neo4jd3 zu studieren, das erwartete Format herauszufinden und dann einen Adapter zu erstellen, um das Quadrat in ein Dreieck umzuwandeln.
neo4jd3 <- adapter <- Neo4j
Mein Gehirn begann standardmäßig, den Quellcode zu lesen, und ich erstellte einen Adapter: neo4jd3-ts .
import createNeoChart, { NeoDatatoChartData } from "neo4jd3-ts";
Der Adapter ist NeoDatatoChartData
und alles andere ist Geschichte. Ich habe mir diese Lektion zu Herzen genommen und gehe bei jeder Gelegenheit in jedem Tool, das ich verwende, eine Ebene tiefer. Es ist so weit verbreitet, dass ich manchmal nicht einmal die Dokumentation lese.
Dieser Ansatz hat meine Karriere enorm verändert. Alles, was ich tue, wirkt wie Magie. Innerhalb weniger Monate leitete ich kritische Servermigrationen und -projekte, und das alles, weil ich einen Schritt in Richtung der Quelle gemacht hatte.
Darum geht es in dieser Serie: sich nicht mit der API zufrieden zu geben, sondern darüber hinauszugehen und zu lernen, diese Tools nachzubilden. In dieser Welt des KI-Hypes aus dem Durchschnitt auszubrechen, macht einen Entwickler überdurchschnittlich wertvoll!
Mein Plan für diese Reihe ist, beliebte JavaScript-Bibliotheken und -Tools zu studieren und gemeinsam herauszufinden, wie sie funktionieren und welche Muster wir von ihnen lernen können, ein Tool nach dem anderen.
Da ich hauptsächlich als Backend-Ingenieur arbeite (ja, Full Stack, aber ich beschäftige mich zu 90 % mit dem Backend), gibt es für den Einstieg kein besseres Tool als Express.js.
Ich gehe davon aus, dass du Programmiererfahrung und ein gutes Verständnis der Grundlagen des Programmierens hast! Du wirst wahrscheinlich als fortgeschrittener Anfänger eingestuft.
Es wird wirklich schwierig und mühsam sein, Quellcode zu lernen/lehren und gleichzeitig Grundlagen zu vermitteln. Sie können an der Serie teilnehmen, aber rechnen Sie damit, dass es schwierig wird. Ich kann nicht alles abdecken, aber ich werde versuchen, so viel wie möglich zu tun.
Dieser Artikel erschien aus einem bestimmten Grund vor Express: Ich habe mich entschieden, eine sehr kleine Bibliothek abzudecken, von der Express abhängt: „ merge-descriptors“. Während ich dies schreibe, wurde sie 27.181.495 Mal heruntergeladen und besteht aus lediglich 26 Codezeilen.
Dies gibt uns die Möglichkeit, eine Struktur zu erstellen, und ich kann in die Objektgrundlagen einführen, die für die Erstellung von JavaScript-Modulen von entscheidender Bedeutung sind.
Bevor wir fortfahren, stellen Sie sicher, dass Sie den Express- Quellcode und die Merge-Deskriptoren auf Ihrem System haben. Auf diese Weise können Sie ihn in einer IDE öffnen und ich kann Ihnen mit Zeilennummern zeigen, wo wir suchen.
Express ist eine umfangreiche Bibliothek. Wir werden in mehreren Artikeln so viel wie möglich abdecken, bevor wir zu einem anderen Tool übergehen.
Öffnen Sie Ihre Express-Quelle in Ihrer IDE, vorzugsweise mit Zeilennummern, navigieren Sie zum lib
Ordner und öffnen Sie die Datei express.js
, die Einstiegsdatei.
In Zeile 17 ist hier unsere erste Bibliothek:
var mixin = require('merge-descriptors');
Die Verwendung erfolgt in den Zeilen 42 und 43:
mixin(app, EventEmitter.prototype, false); mixin(app, proto, false);
Bevor wir uns damit befassen, was hier passiert, müssen wir einen Schritt zurückgehen und über Objekte in JavaScript jenseits der Datenstruktur sprechen. Wir werden Komposition, Vererbung, Prototypen und Mixins besprechen – der Titel dieses Artikels.
Schließen Sie den Express-Quellcode und erstellen Sie irgendwo einen neuen Ordner, um mitzuverfolgen, wie wir diese wichtigen Objektgrundlagen erlernen.
Ein Objekt ist eine Kapselung von Daten und Verhalten und bildet den Kern der objektorientierten Programmierung (OOP) . Interessante Tatsache: Fast alles in JavaScript ist ein Objekt.
const person = { // data name: "Jane", age: 0, // behavior grow(){ this.age += 1; } };
Alles zwischen der öffnenden und schließenden Klammer im person
wird als Objekteigenschaften bezeichnet. Dies ist wichtig.
Eigene Eigenschaften sind solche, die sich direkt auf dem Objekt befinden. name
, age
und grow
sind eigene Eigenschaften der person
.
Dies ist wichtig, da jedes JavaScript-Objekt eine prototype
hat. Lassen Sie uns das obige Objekt in einen Funktionsentwurf kodieren, damit wir person
dynamisch erstellen können.
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);
Der Prototyp ist die Art und Weise, wie JavaScript-Objekte Eigenschaften und Methoden von anderen Objekten erben. Der Unterschied zwischen Own Properties
und Prototype
besteht im Zugriff auf eine Eigenschaft eines Objekts:
john.name; // access
JavaScript sucht zuerst in Own Properties
, da diese eine hohe Priorität haben. Wenn die Eigenschaft nicht gefunden wird, sucht es rekursiv im eigenen prototype
des Objekts, bis es null findet und einen Fehler ausgibt.
Ein Prototypobjekt kann über seinen eigenen Prototyp von einem anderen Objekt erben. Dies wird als Prototypkette bezeichnet.
console.log(john.hasOwnProperty('name')); // true console.log(john.hasOwnProperty('print')); // false, it's in the prototype
Bei john
funktioniert print
jedoch:
john.print(); // "John is 32"
Aus diesem Grund wird JavaScript als eine prototypbasierte Sprache definiert. Mit Prototypen können wir mehr tun, als nur Eigenschaften und Methoden hinzuzufügen, wie zum Beispiel Vererbung.
Das „Hallo Welt“-Symbol der Vererbung ist das Säugetierobjekt. Lassen Sie es uns mit JavaScript nachbilden.
// our Mammal blueprint function Mammal(name) { this.name = name; } Mammal.prototype.breathe = function() { console.log(`${this.name} is breathing.`); };
In JavaScript gibt es innerhalb des Object
Objekts eine statische Funktion:
Object.create();
Es erstellt ein Objekt ähnlich wie {}
und new functionBlueprint
, aber der Unterschied besteht darin, dass create
einen Prototyp als Parameter annehmen kann, von dem geerbt wird.
// 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;
Jetzt verfügt Cat
über die breathe
von Mammal
. Wichtig zu wissen ist jedoch, dass Cat
auf Mammal
als seinen Prototyp verweist.
Säugetier-Blaupause : Wir definieren zuerst die Mammal
und fügen ihrem Prototyp eine breathe
hinzu.
Cat-Vererbung : Wir erstellen die Cat
Funktion und setzen Cat.prototype
auf Object.create(Mammal.prototype)
. Dadurch erbt der Cat
Prototyp von Mammal
, aber der constructor
wird auf Mammal
geändert.
Konstruktor korrigieren : Wir korrigieren den Cat.prototype.constructor
, sodass er zurück auf Cat
zeigt, und stellen so sicher, dass das Cat
Objekt seine Identität behält, während es Methoden von Mammal
erbt. Schließlich fügen wir Cat
eine meow
Methode hinzu.
Dieser Ansatz ermöglicht es dem Cat
Objekt, sowohl auf Methoden von Mammal
(wie breathe
) als auch auf Methoden seines eigenen Prototyps (wie meow
) zuzugreifen.
Das müssen wir korrigieren. Lassen Sie uns das vollständige Beispiel erstellen:
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.`); };
Um Cat.prototype.constructor = Cat
zu verstehen, müssen Sie etwas über Zeiger wissen. Wenn wir mit Object.create
von Mammal
erben, ändert sich der Zeiger unseres Cat
Prototyps in Mammal
, was falsch ist. Wir möchten immer noch, dass unsere Cat
ein eigenständiges Individuum ist, obwohl sie den übergeordneten Wert Mammal
hat.
Deshalb müssen wir es korrigieren.
In diesem Beispiel erbt Cat
von Mammal
über die Prototypenkette. Das Cat
Objekt kann sowohl auf breathe
als auch meow
-Methode zugreifen.
const myCat = new Cat("Misty", "Ragdoll"); myCat.breathe(); // Misty is breathing. myCat.meow(); // Misty is meowing.
Wir können einen Hund erschaffen, der auch vom Säugetier erbt:
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.
Wir haben grundlegende klassische Vererbung geschaffen, aber warum ist das wichtig? Ich dachte, wir behandeln Quellcode!
Ja, das stimmt, aber Prototypen sind der Kern der Erstellung effizienter und flexibler Module, über die Vererbung hinaus. Selbst einfache, gut geschriebene Module sind voller Prototypobjekte. Wir legen gerade erst die Grundlagen.
Die Alternative zur Vererbung ist die Objektkomposition. Dabei werden zwei oder mehr Objekte grob genommen und zu einem „Super“-Objekt zusammengeführt.
Mixins ermöglichen es Objekten, Methoden von anderen Objekten zu übernehmen, ohne Vererbung zu verwenden. Sie sind praktisch, um Verhalten zwischen nicht verwandten Objekten zu teilen.
Und genau das tut unsere erste Erkundung: die Bibliothek merge-descriptors
die wir versprochen haben, zuerst abzudecken.
Wir haben bereits gesehen, wo und wie es in Express verwendet wird. Jetzt kennen wir es für die Objektkomposition.
Hier ist in Zeile 17 unsere erste Bibliothek:
var mixin = require('merge-descriptors');
Die Verwendung erfolgt in den Zeilen 42 und 43:
mixin(app, EventEmitter.prototype, false); mixin(app, proto, false);
Aus unserem Wissen können wir bereits schließen, dass mixin
EventEmitter.prototype
und proto
zu einem Objekt namens app
zusammensetzt.
Wir kommen zur app
, wenn wir über Express sprechen.
Dies ist der vollständige Quellcode für 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;
Achten Sie von Anfang an immer darauf, wie die Funktion verwendet wird und welche Parameter sie benötigt:
// definition mergeDescriptors(destination, source, overwrite = true) // usage var mixin = require('merge-descriptors'); mixin(app, EventEmitter.prototype, false); mixin(app, proto, false);
App
ist unser Ziel. Wir wissen, dass mixin
Objektkomposition bedeutet. Dieses Paket komponiert grob das Quellobjekt in das Zielobjekt, mit der Option zum Überschreiben.
Überschreiben erfolgt unter der Annahme, dass app
(das Ziel) die exakt gleiche Eigenschaft wie die Quelle hat. Bei true
Überschreiben bleibt die Eigenschaft unverändert und der Vorgang wird übersprungen.
Wir wissen, dass Objekte nicht zweimal dieselbe Eigenschaft haben können. In Schlüssel-Wert-Paaren (Objekten) müssen die Schlüssel eindeutig sein.
Bei Express ist das Überschreiben false
.
Folgendes ist eine grundlegende Vorgehensweise. Behandeln Sie immer erwartete Fehler:
if (!destination) { throw new TypeError('The `destination` argument is required.'); } if (!source) { throw new TypeError('The `source` argument is required.'); }
Hier wird es interessant: Zeile 12.
for (const name of Object.getOwnPropertyNames(source)) {
Aus dem Obigen wissen wir, was OwnProperty
bedeutet, also bedeutet getOwnPropertyNames
eindeutig, die Schlüssel der eigenen Eigenschaften abzurufen.
const person = { // data name: "Jane", age: 0, // behavior grow() { this.age += 1; } }; Object.getOwnPropertyNames(person); // [ 'name', 'age', 'grow' ]
Es gibt die Schlüssel als Array zurück und wir führen in der folgenden Instanz eine Schleife über diese Schlüssel aus:
for (const name of Object.getOwnPropertyNames(source)) {
Im Folgenden wird geprüft, ob Ziel und Quelle denselben Schlüssel haben, den wir gerade durchlaufen:
if (!overwrite && Object.hasOwn(destination, name)) { // Skip descriptor continue; }
Wenn overwrite falsch ist, überspringen Sie diese Eigenschaft; überschreiben Sie nicht. Genau das macht continue
– es treibt die Schleife zur nächsten Iteration und führt den darunterliegenden Code nicht aus, der folgende ist:
// Copy descriptor const descriptor = Object.getOwnPropertyDescriptor(source, name); Object.defineProperty(destination, name, descriptor);
Wir wissen bereits, was getOwnProperty
bedeutet. Das neue Wort ist descriptor
. Lassen Sie uns diese Funktion an unserem eigenen person
testen:
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 // }
Es gibt unsere grow
Funktion als Wert zurück und die nächste Zeile ist selbsterklärend:
Object.defineProperty(destination, name, descriptor);
Es nimmt unseren Deskriptor aus der Quelle und schreibt ihn in das Ziel. Es kopiert die Eigenschaften der Quelle als seine eigenen Eigenschaften in unser Zielobjekt.
Lassen Sie uns ein Beispiel in unserem person
durchführen:
const val = { value: function isAlien() { return false; }, enumerable: true, writable: true, configurable: true, }; Object.defineProperty(person, "isAlien", val);
Jetzt sollte für person
die Eigenschaft isAlien
definiert sein.
Zusammenfassend lässt sich sagen, dass dieses häufig heruntergeladene Modul eigene Eigenschaften von einem Quellobjekt in ein Ziel mit der Option zum Überschreiben kopiert.
Wir haben unser erstes Modul in dieser Quellcodeebene erfolgreich abgedeckt und es werden noch weitere spannende Dinge folgen.
Dies war eine Einführung. Wir begannen mit der Behandlung der Grundlagen, die zum Verständnis des Moduls erforderlich sind, und als Nebenprodukt mit dem Verständnis der Muster in den meisten Modulen, also Objektzusammensetzung und Vererbung. Zuletzt navigierten wir durch das Modul merge-descriptors
.
Dieses Muster wird in den meisten Artikeln vorherrschen. Wenn ich der Meinung bin, dass es notwendige Grundlagen gibt, die behandelt werden müssen, werden wir diese im ersten Abschnitt durchgehen und dann den Quellcode behandeln.
Glücklicherweise werden merge-descriptors
in Express verwendet, auf das wir uns bei unserer ersten Quellcodestudie konzentrieren. Erwarten Sie also weitere Express.js-Quellcodeartikel, bis wir das Gefühl haben, dass Express gut genug läuft, und wechseln Sie dann zu einem anderen Modul oder Tool wie Node.js.
Was Sie in der Zwischenzeit als Herausforderung tun können, ist, in den Merge-Deskriptoren zur Testdatei zu navigieren und die gesamte Datei zu lesen. Es ist wichtig, den Quellcode selbst zu lesen. Versuchen Sie herauszufinden, was er tut und was er testet, und brechen Sie ihn dann ab, ja, und reparieren Sie ihn erneut oder fügen Sie weitere Tests hinzu!
Wenn Sie an exklusiveren praktischen und längeren Inhalten zur Verbesserung Ihrer Programmierkenntnisse interessiert sind, finden Sie mehr auf Ko-fi .