ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScript のモジュール性: カプセル化、継承

JavaScript のモジュール性: カプセル化、継承

高洛峰
高洛峰オリジナル
2016-11-28 10:29:221065ブラウズ

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();
注目に値するのは、ticker() で、この .n - にアクセスすることです。これは、n がtickerのメンバーであるためではなく、tickerがticker()を呼び出すためです。実際、ここにticker.nを書く方が良いでしょう。tick()が呼び出された場合、それはティッカーではなく、次のような別のものになるからです。今回は、tick() を呼び出すのは実際には window であり、関数が実行されると window.n にアクセスしようとしてエラーが発生します。

実際、この「オープンドア」モジュラーアプローチは、プログラムではなく JSON スタイルのデータを整理するためによく使用されます。たとえば、次の JSON オブジェクトをティッカーの関数に渡して、ティッカーが 100 からカウントを開始し、毎回 2 ずつ進むことを決定できます。

var config = {

nStart:100,

step:2

}

スコープチェーンとクロージャー
以下のコードを見てください。config を渡すことでティッカーのカスタマイズがすでに実装されていることに注意してください。

functionticker(config){
var n = config.nStart;
function tiny(){

n += config.step;

}
}
console.log(ticker.n); // ->unknown
なぜティッカーがオブジェクトから関数に変わったのか疑問に思っているかもしれません。これは、JavaScript では関数のみがスコープを持ち、関数内の変数には関数本体の外からアクセスできないためです。 ticker() の外部でticker.n にアクセスすると undefine が発生しますが、ticker() 内で n にアクセスするのは問題ありません。 Nick() から Ticker()、そしてグローバルまで、これは JavaScript の「スコープ チェーン」です。

しかし、まだ問題があります。それは、tick() をどのように呼び出すかということです。 ticker() のスコープは、ticker() もマスクします。解決策は 2 つあります:

1)ticker() の戻り値として n をインクリメントするメソッドを使用するのと同じように、戻り値としてメソッドを呼び出す必要があります。

2) 外側のスコープの変数を設定します。 getN のticker() ) で行ったのと同じです。

var getN;

functionticker(config){

var n = config.nStart;
getN = function(){
return n;
};
return function(){
n += config.step;
}

varticker({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 = new 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=未定義)。呼び出されると、TICKER() はコンストラクターとして機能し、TICKER.prototype オブジェクトはクラスとして機能します。


声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。