ソース コードを勉強すれば、間違いなく開発者としてのキャリアの軌道を変えることができます。表面を 1 レベルだけ見るだけでも、平均的な開発者の多くと一線を画すことができます。
それは習得への第一歩です!
個人的な話ですが、現在私が勤めている AI/ML スタートアップでは、チームが Neo4j データをサーバーからフロントエンドに取得して視覚化する方法を見つけられず、12 時間以内にプレゼンテーションを行う必要がありました。私はフリーランサーとして雇われましたが、パニックに陥っている様子がはっきりと見て取れました。問題は、Neo4j から返されるデータが、視覚化ツールneo4jd3が想定する正しい形式ではなかったことです。
想像してみてください。Neo4jd3 は三角形を期待しますが、Neo4j は四角形を返します。これは互換性のない不一致です。
近いうちにMasteredで JavaScript と Neo4j を使ったグラフ データ サイエンスをやるかもしれません。この画像は懐かしいですね。
選択肢は 2 つしかありませんでした。Neo4j バックエンド全体をやり直すか、Neo4jd3 のソース コードを調べて予想される形式を把握し、正方形を三角形に変換するアダプターを作成するかです。
neo4jd3 <- adapter <- Neo4j
私の脳はデフォルトでソースコードを読み、アダプタneo4jd3-tsを作成しました。
import createNeoChart, { NeoDatatoChartData } from "neo4jd3-ts";
アダプターはNeoDatatoChartData
で、他のすべては過去のものです。私はこの教訓を心に留め、機会があれば、使用するすべてのツールで 1 レベル下げるようにしています。この方法があまりにも普及したため、ドキュメントを読まないこともあります。
このアプローチは私のキャリアを大きく変えました。私がすることすべてが魔法のように見えます。数か月後には、重要なサーバーの移行とプロジェクトを主導するようになりましたが、それはすべて私がソースに向かって一歩踏み出したからです。
このシリーズのテーマは、API に満足するのではなく、その先へ進み、これらのツールを再作成する方法を学ぶことです。AI が大流行しているこの世界で平均的な存在から脱却することが、開発者を平均以上の価値あるものにするのです。
このシリーズでの私の計画は、人気のある JavaScript ライブラリとツールを研究し、それらがどのように機能し、どのようなパターンをそこから学べるかをツールごとに一緒に理解することです。
私は主にバックエンド エンジニア (フル スタックですが、90% の時間をバックエンドの処理に費やしています) なので、Express.js よりも優れたツールはありません。
あなたはプログラミングの経験があり、プログラミングの基礎をよく理解しているものと想定しています。上級初心者に分類されるかもしれません。
基礎を教えながらソースコードを学習/教えるのは本当に大変で退屈なことです。シリーズに参加できますが、大変になることは覚悟してください。すべてを網羅することはできませんが、できる限り努力します。
この記事が Express 以前の記事であるのには理由があります。Express が依存する非常に小さなライブラリ、 merge-descriptorsについて説明することにしたのです。この記事を書いている時点で、このライブラリのダウンロード数は 27,181,495 回で、コードはわずか 26 行です。
これにより、構造を確立する機会が得られ、JavaScript モジュールの構築に不可欠なオブジェクトの基礎を紹介できるようになります。
先に進む前に、Expressソース コードとマージ記述子がシステムにあることを確認してください。こうすることで、IDE で開くことができ、行番号でどこを見ているのかを案内することができます。
Express は強力なライブラリです。他のツールに移る前に、数回の記事でできるだけ詳しく説明します。
IDE で Express ソースを開き (できれば行番号付きで)、 lib
フォルダーに移動して、エントリ ファイルであるexpress.js
ファイルを開きます。
17 行目に、最初のライブラリがあります。
var mixin = require('merge-descriptors');
使用方法は42行目と43行目にあります。
mixin(app, EventEmitter.prototype, false); mixin(app, proto, false);
ここで何が起こっているのかを探る前に、少し立ち止まって、データ構造を超えて JavaScript のオブジェクトについて話す必要があります。この記事のタイトルである、構成、継承、プロトタイプ、ミックスインについて説明します。
Express ソース コードを閉じて、重要なオブジェクトの基礎を学習するためにどこかに新しいフォルダーを作成します。
オブジェクトは、オブジェクト指向プログラミング (OOP)の中核となるデータと動作のカプセル化です。興味深い事実: 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
を検索します。プロパティが見つからない場合は、null が見つかるまでオブジェクト自身のprototype
オブジェクトを再帰的に検索し、エラーをスローします。
プロトタイプ オブジェクトは、独自のプロトタイプを介して別のオブジェクトから継承できます。これをプロトタイプ チェーンと呼びます。
console.log(john.hasOwnProperty('name')); // true console.log(john.hasOwnProperty('print')); // false, it's in the prototype
ただし、 john
ではprint
機能します。
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
Mammal
にあるbreathe
法を持つことになりますが、重要なのは、 Cat
Mammal
をプロトタイプとして指しているということです。
Mammal ブループリント: まずMammal
関数を定義し、そのプロトタイプにbreathe
メソッドを追加します。
Cat 継承: Cat
関数を作成し、 Cat.prototype
をObject.create(Mammal.prototype)
に設定します。これにより、 Cat
プロトタイプはMammal
から継承されますが、 constructor
ポインターはMammal
に変更されます。
コンストラクタの修正: Cat.prototype.constructor
を修正してCat
を指すようにし、 Mammal
からメソッドを継承しながらCat
オブジェクトがその ID を保持するようにします。最後に、 Cat
にmeow
メソッドを追加します。
このアプローチにより、 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
を理解するには、ポインターについて知っておく必要があります。 Object.create
を使用してMammal
から継承すると、 Cat
プロトタイプのポインターがMammal
に変更されますが、これは誤りです。親がMammal
であるにもかかわらず、 Cat
独自の個体であることが必要です。
だからこそ、それを修正する必要があるのです。
この例では、 Cat
プロトタイプ チェーンを使用してMammal
から継承します。Cat オブジェクト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.
基本的な古典的な継承を作成しましたが、なぜこれが重要なのでしょうか? ソース コードについて説明していると思っていました。
確かにそうですが、プロトタイプは継承を超えて、効率的で柔軟なモジュールを構築する上で中核となるものです。シンプルでよく書かれたモジュールでさえ、プロトタイプ オブジェクトが満載です。私たちは基礎を築いているだけです。
継承の代替手段はオブジェクト合成です。これは、2 つ以上のオブジェクトを緩く取得し、それらを結合して「スーパー」オブジェクトを形成します。
ミックスインを使用すると、オブジェクトは継承を使用せずに他のオブジェクトからメソッドを借用できます。関連のないオブジェクト間で動作を共有するのに便利です。
これが私たちの最初の調査で行うこと、つまり最初に取り上げると約束した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
というオブジェクトに合成していることはすでに推測できます。
Express について話すときに、 app
についても説明します。
これは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 mixin
オブジェクトの構成を意味することはご存じのとおりです。大まかに言うと、このパッケージは、上書きオプションを使用して、ソース オブジェクトを宛先オブジェクトに構成します。
上書きは、想定では、 app
(宛先) にソースとまったく同じプロパティがある場合に実行されます。 true
上書きされる場合は、そのプロパティは変更されず、スキップされます。
オブジェクトは同じプロパティを 2 回持つことはできないことはわかっています。キーと値のペア (オブジェクト) では、キーは一意である必要があります。
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 が false の場合、そのプロパティをスキップします。つまり、上書きしません。これが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 は、ソース コード研究の開始点として私たちが注目している部分です。そのため、Express を十分に使いこなしたと感じられるまで、Express.js のソース コードに関する記事がさらに掲載され、その後、Node.js などの別のモジュールやツールに切り替えられることを期待してください。
その間、課題としてできることは、マージ記述子のテスト ファイルに移動して、ファイル全体を読むことです。自分でソースを読むことは重要です。それが何を実行し、何をテストしているのかを理解し、それを壊し、はい、そして再び修正するか、さらにテストを追加してください。
プログラミング スキルを向上させるための、より実践的で長めの独占コンテンツにご興味がある場合は、 Ko-fiで詳細をご覧ください。