JavaScriptのモジュール化:カプセル化(クロージャ)、継承(プロトタイプ)入門_JavaScriptスキル
JavaScript は本質的にカジュアルなものですが、ブラウザーができることが増えるにつれて、この言語はますます本格的になってきています。複雑なロジックでは、JavaScript をモジュール化する必要があり、モジュールをカプセル化して、外部呼び出し用のインターフェイスのみを残す必要があります。クロージャはJavaScriptにおけるモジュールのカプセル化の鍵であり、多くの初心者が理解するのが難しいポイントでもあります。最初は戸惑いました。今では、この概念をより深く理解できるようになったと確信しています。理解を容易にするために、この記事では比較的単純なオブジェクトをカプセル化することを試みます。
ページ上で値 n を維持するカウンタ オブジェクト ティッカーを維持しようとしています。ユーザーが操作すると、カウントを増やす (値 n に 1 を加える) ことはできますが、n を減らしたり、n を直接変更したりすることはできません。さらに、この値を時々クエリする必要があります。
オープンドア JSON スタイルのモジュール性
ドアを開ける方法は次のとおりです:
varticker = {
n:0,
tiny:function(){
this.n ;
},
};
この書き方は自然で効果的です。カウントを増やす必要がある場合は、ticker.tick() メソッドを呼び出し、ticker.n 変数にアクセスします。しかし、その欠点も明らかです。モジュールのユーザーは、ticker.n-- やticker.n=-1 を呼び出すなど、n を自由に変更できます。ティッカーはカプセル化されていません。n とticker() はティッカーの「メンバー」であるように見えますが、それらのアクセシビリティはティッカーと同じであり、グローバルです (ティッカーがグローバル変数の場合)。カプセル化という点では、このモジュール方式のアプローチは、次のアプローチよりも少しだけばかばかしいだけです (ただし、一部の単純なアプリケーションでは、この少しのアプローチで十分です)。
varticker = {};
vartickerN = 0;
vartickerTick = function(){
tickerN ;
}
tickerTick();
tick() で this.n にアクセスすることに注意してください。これは、n がticker のメンバーであるためではなく、ticker がticker() を呼び出すためです。実際、ここにticker.nを書く方がよいでしょう。なぜなら、tick()が呼び出された場合、それはティッカーではなく、次のような別の何かであるからです:
var func =ticker.tick;
func( );
この時、実際にtick()を呼び出しているのはwindowであり、関数を実行するとwindow.nにアクセスしようとしてエラーが発生します。
実際、この「オープンドア」モジュラーアプローチは、プログラムではなく JSON スタイルのデータを整理するためによく使用されます。たとえば、次の JSON オブジェクトをティッカーの関数に渡して、ティッカーが 100 からカウントを開始し、毎回 2 ずつ進むことを決定できます。
var config = {
nStart: 100、
ステップ:2
}
スコープ チェーンとクロージャ
次のコードを見てください。設定を渡すことでティッカーのカスタマイズが実装されていることに注意してください。
functionticker(config){
var n = config.nStart;
function tiny(){
n = config.step;
}
}
console.log(ticker.n); // ->未定義
なぜティッカーがオブジェクトから関数に変わったのか疑問に思われるかもしれません。これは、JavaScript では関数のみがスコープを持ち、関数内の変数には関数本体の外からアクセスできないためです。 ticker() の外部でticker.n にアクセスすると undefine が発生しますが、ticker() 内で n にアクセスすると問題ありません。 Nick() から Ticker()、そしてグローバルまで、これは JavaScript の「スコープ チェーン」です。
しかし、まだ問題があります。それは、tick() をどのように呼び出すかということです。 ticker() のスコープには、ticker() も含まれます。解決策は 2 つあります:
•1)ticker() の戻り値として n をインクリメントするメソッドを使用するのと同じように、戻り値としてメソッドを呼び出す必要があります。
•2) 外側のスコープの変数を設定するだけです。 ticker() で行ったように getN を に設定します。
var getN;
functionticker( config){
var n = config.nStart;
getN = function(){
return n;
};
return function(){
n = config.step;
};
}
vartic =ticker({nStart:100,step:2});
tick();
console.log(getN()); // ->102
ご覧のとおり、現時点では変数 n は「クロージャ」内にあり、ticker() の外部から直接アクセスすることはできませんが、2 つのメソッドを通じて監視または操作できます。
このセクションの最初のコードでは、ticker() メソッドが実行された後、次回関数が呼び出されるまで n とticker() が破棄されますが、2 番目のコードでは、ticker() メソッドの実行後に破棄されます。実行後、n は破棄されません。これは、tick() と getN() がそれにアクセスしたり変更したりする可能性があり、ブラウザーが n を維持する責任を負うことになります。 「クロージャ」についての私の理解は、関数のスコープ内の変数である n が関数の実行後に維持される必要があり、他のメソッドを通じてアクセスできることを保証するために使用されるメカニズムです。
でも、まだ何か違う気がするんですが?同じ機能を持つ 2 つのオブジェクトticker1とticker2を維持する必要がある場合はどうすればよいでしょうか? ticker() は 1 つしかないので、再度書くことはできませんね。
new 演算子とコンストラクター
new 演算子を介して関数を呼び出すと、新しいオブジェクトが作成され、そのオブジェクトを使用して関数が呼び出されます。私の理解では、次のコードの t1 と t2 の構築プロセスは同じです。
function myClass(){}
var t1 = 新しい myClass();
var t2 = {};
t2.func = myClass;
t2.func();
t2.func = 未定義;
T1 と t2 は両方とも新しく構築されたオブジェクトであり、myClass() がコンストラクターです。同様に、ticker() も書き換えることができます。
function TICKER(config){
var n = config.nStart;
this.getN = function(){
return n;
};
this.tick = function(){
n = config.step;
}
}
varticker1 = new TICKER({nStart:100,step:2});
ticker1.tick();
console.log(ticker1.getN()); // ->102
varticker2 = new TICKER({nStart:20,step:3});
ticker2.tick();
ticker2.tick();
console.log(ticker2.getN()) ; // ->26
慣例により、コンストラクターは大文字で表記されます。 TICKER() は依然として関数であり、純粋なオブジェクトではないことに注意してください (「純粋」と言う理由は、関数が実際にはオブジェクトであり、TICKER() が関数オブジェクトであるためです。クロージャはまだ有効であり、ticker1 にアクセスできません)。 .n .
プロトタイプのプロトタイプと継承
上記の TICKER() にはまだ欠陥があります。つまり、ticker1.tick() とticker2.tick() は互いに独立しています。 new 演算子を使用して TICKER() を呼び出すたびに、新しいオブジェクトが生成され、この新しいオブジェクトにバインドする新しい関数が生成されます。新しいオブジェクトが構築されるたびに、ブラウザーが開きます。 Nick() 自体と変数を Tick() 内に保存することは、私たちが期待しているものではありません。 ticker1.tick とticker2.tick が同じ関数オブジェクトを指すことが期待されます。
これにはプロトタイプの導入が必要です。
JavaScript では、Object オブジェクトを除き、他のオブジェクトには別のオブジェクトを指すプロトタイプ プロパティがあります。この「別のオブジェクト」にはまだそのプロトタイプ オブジェクトがあり、最終的に Object オブジェクトを指すプロトタイプ チェーンを形成します。オブジェクトのメソッドを呼び出すときに、そのオブジェクトに指定されたメソッドがないことが判明した場合は、Object オブジェクトが見つかるまでプロトタイプ チェーン上でこのメソッドを検索します。
関数はオブジェクトでもあるため、関数にはプロトタイプ オブジェクトもあります。関数が宣言されると (つまり、関数オブジェクトが定義されると)、新しいオブジェクトが生成され、このオブジェクトのプロトタイプ プロパティは Object オブジェクトを指し、このオブジェクトのコンストラクター プロパティは関数オブジェクトを指します。
コンストラクターを通じて構築された新しいオブジェクトのプロトタイプは、コンストラクターのプロトタイプ オブジェクトを指します。したがって、コンストラクターのプロトタイプ オブジェクトに関数を追加できます。これらの関数は、ticker1 やticker2 ではなく、TICKER に依存します。
次のようにするとよいでしょう:
function TICKER(config){
var n = config.nStart;
}
TICKER.prototype.getN = function{
// 注意: 無効な実装
return n;
};
TICKER.prototype.tick = function{
// 注意: 無効な実装
n = config.step;
};
これは無効な実装であることに注意してください。プロトタイプ オブジェクトのメソッドはクロージャの内容、つまり変数 n にアクセスできないためです。 TICK() メソッドが実行されると、n にはアクセスできなくなり、ブラウザーは n を破棄します。クロージャの内容にアクセスするには、オブジェクトには、クロージャの内容にアクセスするためのいくつかの簡潔なインスタンス依存メソッドが必要であり、そのプロトタイプで複雑なパブリック メソッドを定義してロジックを実装する必要があります。実際、例の Nick() メソッドは十分に簡潔なので、それを TICKER に戻しましょう。次に、より複雑なメソッドticTimes()を実装します。これにより、呼び出し元はtick()の呼び出し回数を指定できるようになります。
function TICKER(config){
var n = config.nStart;
this.getN = function(){
return n;
};
this.tick = function(){
n = config.step;
} ;
}
TICKER.prototype.tickTimes = function(n){
while(n>0){
this.tick();
n--;
}
};
varticker1 = new TICKER({nStart:100,step:2});
ticker1.tick();
console.log(ticker1.getN()); // - >102
varticker2 = new TICKER({nStart:20,step:3});
ticker2.tickTimes(2);
console.log(ticker2.getN()); // - >26
このティッカーは素晴らしいです。 n はカプセル化されており、オブジェクトの外部から直接変更することはできません。また、複雑な関数 checkTimes() がプロトタイプで定義されており、この関数はインスタンスの小さな関数を呼び出すことによってオブジェクト内のデータを操作します。
そこで、オブジェクトのカプセル化を維持するために、私の提案は、データの操作を可能な限り最小の単位関数に分離し、コンストラクターでインスタンス依存 (" "プライベート" とも呼ばれる) として定義し、プロトタイプ (つまり「パブリック」) に複雑なロジックを実装します。
最後に、継承について話しましょう。実際、プロトタイプで関数を定義するとき、すでに継承を使用しています。 JavaScript の継承は、C よりも...まあ...単純、または粗雑です。 C では、動物を表すために Animal クラスを定義し、鳥を表すために Animal クラスを継承する Bird クラスを定義できますが、私が議論したいのはそのような継承ではありません (ただし、このような継承は JavaScript でも実装できます) C での継承の説明は、動物クラスを定義してから myAnimal オブジェクトをインスタンス化することです。はい、これは C ではインスタンス化ですが、JavaScript では継承として扱われます。
JavaScript はクラスをサポートしていません。ブラウザは現在利用可能なオブジェクトのみを考慮し、これらのオブジェクトがどのようなクラスであるか、どのような構造を持つべきかについては気にしません。この例では、TICKER() は関数オブジェクトであり、値を割り当てたり (TICKER=1)、削除したりすることができます (TICKER=unknown)。呼び出されると、TICKER() はコンストラクターとして機能し、TICKER.prototype オブジェクトはクラスとして機能します。
上記は私が知っているJavaScriptのモジュール化方法です。初心者の方も参考になれば幸いです。何か間違っているところがあれば、ご指摘ください。
著者: Yiyezhai マスター
出典: www.cnblogs.com/yiyezhai

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

Video Face Swap
完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック











関数の継承では、「基底クラス ポインター」と「派生クラス ポインター」を使用して継承メカニズムを理解します。基底クラス ポインターが派生クラス オブジェクトを指す場合、上方変換が実行され、基底クラスのメンバーのみにアクセスされます。派生クラス ポインターが基本クラス オブジェクトを指す場合、下向きキャストが実行される (安全ではない) ため、注意して使用する必要があります。

C++ では、クロージャは外部変数にアクセスできるラムダ式です。クロージャを作成するには、ラムダ式の外部変数をキャプチャします。クロージャには、再利用性、情報の隠蔽、評価の遅延などの利点があります。これらは、イベント ハンドラーなど、外部変数が破棄されてもクロージャが外部変数にアクセスできる現実の状況で役立ちます。

4月17日の当サイトのニュースによると、TrendForceは最近、Nvidiaの新しいBlackwellプラットフォーム製品に対する需要は強気で、2024年にはTSMCのCoWoSパッケージング総生産能力が150%以上増加すると予想されるレポートを発表した。 NVIDIA Blackwell の新しいプラットフォーム製品には、B シリーズ GPU と、NVIDIA 独自の GraceArm CPU を統合する GB200 アクセラレータ カードが含まれます。 TrendForce は、サプライチェーンが現在 GB200 について非常に楽観的であることを確認しており、2025 年の出荷台数は 100 万台を超え、Nvidia のハイエンド GPU の 40 ~ 50% を占めると予想されています。 Nvidiaは今年下半期にGB200やB100などの製品を提供する予定だが、上流のウェーハパッケージングではさらに複雑な製品を採用する必要がある。

クロージャは、外部関数のスコープ内の変数にアクセスできる入れ子関数です。その利点には、データのカプセル化、状態の保持、および柔軟性が含まれます。デメリットとしては、メモリ消費量、パフォーマンスへの影響、デバッグの複雑さなどが挙げられます。さらに、クロージャは匿名関数を作成し、それをコールバックまたは引数として他の関数に渡すことができます。

C++ ラムダ式は、関数スコープ変数を保存し、関数からアクセスできるようにするクロージャーをサポートしています。構文は [キャプチャリスト](パラメータ)->戻り値の型{関数本体} です。 Capture-list は、キャプチャする変数を定義します。[=] を使用してすべてのローカル変数を値によってキャプチャするか、[&] を使用してすべてのローカル変数を参照によってキャプチャするか、[variable1, variable2,...] を使用して特定の変数をキャプチャできます。ラムダ式はキャプチャされた変数にのみアクセスできますが、元の値を変更することはできません。

継承エラーのデバッグのヒント: 正しい継承関係を確認します。デバッガーを使用してコードをステップ実行し、変数値を調べます。仮想修飾子を正しく使用してください。隠れた相続によって引き起こされる相続ダイアモンド問題を調べてください。抽象クラスに実装されていない純粋仮想関数がないか確認します。

このWebサイトは7月9日、AMD Zen5アーキテクチャの「Strix」シリーズプロセッサには2つのパッケージングソリューションがあり、小型のStrixPointはFP8パッケージを使用し、StrixHaloはFP11パッケージを使用すると報じた。出典: videocardz 出典 @Olrak29_ 最新の事実は、StrixHalo の FP11 パッケージ サイズが 37.5mm*45mm (1687 平方ミリメートル) であり、これは Intel の AlderLake および RaptorLake CPU の LGA-1700 パッケージ サイズと同じであるということです。 AMD の最新の Phoenix APU は、サイズ 25*40mm の FP8 パッケージング ソリューションを使用しています。これは、StrixHalo の F

コードをカプセル化することにより、C++ 関数は GUI 開発効率を向上させることができます。 コードのカプセル化: 関数はコードを独立した単位にグループ化し、コードの理解と保守を容易にします。再利用性: 関数はアプリケーション間で再利用できる共通の機能を作成し、重複やエラーを削減します。簡潔なコード: カプセル化されたコードにより、メイン ロジックが簡潔になり、読みやすく、デバッグしやすくなります。
