paint-brush
ソースコードの学習方法: Express.js のオブジェクト プロトタイプとミックスイン@luminix
198 測定値

ソースコードの学習方法: Express.js のオブジェクト プロトタイプとミックスイン

Lumin-ix11m2024/08/19
Read on Terminal Reader

長すぎる; 読むには

ソースコードを勉強すれば、間違いなく開発者としてのキャリアの軌道を変えることができます。表面を 1 レベルでも見れば、平均的な開発者のほとんどと一線を画すことができます。 これがこのシリーズのテーマです。API に甘んじるのではなく、その先へ進み、これらのツールを再現する方法を学びます。AI が大流行しているこの世界で平均的な存在から脱却することが、開発者を平均以上の価値あるものにするのです。
featured image - ソースコードの学習方法: Express.js のオブジェクト プロトタイプとミックスイン
Lumin-ix HackerNoon profile picture
0-item
1-item

ソース コードを勉強すれば、間違いなく開発者としてのキャリアの軌道を変えることができます。表面を 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 のオブジェクトについて話す必要があります。この記事のタイトルである、構成、継承、プロトタイプ、ミックスインについて説明します。

JavaScript オブジェクト

Express ソース コードを閉じて、重要なオブジェクトの基礎を学習するためにどこかに新しいフォルダーを作成します。


オブジェクトは、オブジェクト指向プログラミング (OOP)の中核となるデータと動作のカプセル化です。興味深い事実: JavaScript のほぼすべてがオブジェクトです。


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

personオブジェクト内の開き括弧と閉じ括弧の間にあるすべてのものは、オブジェクト固有のプロパティと呼ばれます。これは重要です。


独自のプロパティは、オブジェクトに直接存在するプロパティです。 nameagegrow 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 PropertiesPrototypeの違いは、オブジェクトのプロパティにアクセスするときです。


 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をプロトタイプとして指しているということです。

説明:

  1. Mammal ブループリント: まずMammal関数を定義し、そのプロトタイプにbreatheメソッドを追加します。


  2. Cat 継承: Cat関数を作成し、 Cat.prototypeObject.create(Mammal.prototype)に設定します。これにより、 CatプロトタイプはMammalから継承されますが、 constructorポインターはMammalに変更されます。


  3. コンストラクタの修正: Cat.prototype.constructorを修正してCatを指すようにし、 Mammalからメソッドを継承しながらCatオブジェクトがその ID を保持するようにします。最後に、 Catmeowメソッドを追加します。


このアプローチにより、 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 オブジェクトCatbreathemeowメソッドの両方にアクセスできます。


 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.prototypeproto 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);


これで、 personisAlienプロパティが定義されるはずです。


要約すると、このダウンロード数の多いモジュールは、上書きオプションを使用して、ソース オブジェクトから宛先に独自のプロパティをコピーします。


このソース コード層で最初のモジュールを無事カバーできました。今後さらにエキサイティングな内容が予定されています。


これは入門編です。まず、モジュールを理解するために必要な基礎を取り上げ、副産物として、ほとんどのモジュールのパターンであるオブジェクトの構成と継承について理解しました。最後に、 merge-descriptorsモジュールについて説明しました。


このパターンはほとんどの記事でよく見られます。カバーする必要がある基礎事項があると感じた場合は、最初のセクションでそれを説明してから、ソース コードについて説明します。


幸いなことに、 merge-descriptors Express で使用されています。Express は、ソース コード研究の開始点として私たちが注目している部分です。そのため、Express を十分に使いこなしたと感じられるまで、Express.js のソース コードに関する記事がさらに掲載され、その後、Node.js などの別のモジュールやツールに切り替えられることを期待してください。


その間、課題としてできることは、マージ記述子のテスト ファイルに移動して、ファイル全体を読むことです。自分でソースを読むことは重要です。それが何を実行し、何をテストしているのかを理解し、それを壊し、はい、そして再び修正するか、さらにテストを追加してください。


プログラミング スキルを向上させるための、より実践的で長めの独占コンテンツにご興味がある場合は、 Ko-fiで詳細をご覧ください。