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 = {
step:2
}スコープチェーンとクロージャー
以下のコードを見てください。config を渡すことでティッカーのカスタマイズがすでに実装されていることに注意してください。
functionticker(config){
var n = config.nStart;
function tiny(){
}
}
console.log(ticker.n); // ->unknown
なぜティッカーがオブジェクトから関数に変わったのか疑問に思っているかもしれません。これは、JavaScript では関数のみがスコープを持ち、関数内の変数には関数本体の外からアクセスできないためです。 ticker() の外部でticker.n にアクセスすると undefine が発生しますが、ticker() 内で n にアクセスするのは問題ありません。 Nick() から Ticker()、そしてグローバルまで、これは JavaScript の「スコープ チェーン」です。
しかし、まだ問題があります。それは、tick() をどのように呼び出すかということです。 ticker() のスコープは、ticker() もマスクします。解決策は 2 つあります:
1)ticker() の戻り値として n をインクリメントするメソッドを使用するのと同じように、戻り値としてメソッドを呼び出す必要があります。
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; }
}
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 が同じ関数オブジェクトを指すことが期待されます。
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() がプロトタイプで定義されており、この関数はインスタンスの小さな関数を呼び出すことによってオブジェクト内のデータを操作します。