Javascript閉パッケージを優雅な言葉で表現してください
8046 ワード
厳密には、閉パケットは、自己定義ではなく、自分が存在する役割ドメイン内の変数を使用し、これらの変数を役割ドメインの制限を突破する関数内部の関数として表現されることが多い.
関数内部の関数:プライベート関数
まず、私たちはこの内部関数から話します.これは形式的なので、最初に役割ドメインを話したら、少しわざとです.閉パッケージは形式的に関数の内部の関数です.たとえば、次のようになります.
jqueryを使うなら、このコードはよく使うでしょう.よく見ると、最初のfunctionはパラメータとしてjQuery()という関数に渡され、function内にはmessage()関数があることがわかります.すべてのjQueryコードは最初のfunctionで処理される.2番目の関数は関数体内部の関数であり,この関数は関数体内で宣言されており,外層関数の実行が完了するとこの関数は機能しなくなり,jQuery()外ではmessage()は使用できないため,message()は最初の関数内部のプライベート関数である.
変数の役割ドメイン
関数内部の変数は2種類あり、1つは局所変数であり、1つはグローバル変数である.局所変数は現在の関数体内でのみ有効であり,関数体が出ると,上位レベルの範囲で局所変数は無効である.
上のコードでは、bとc関数を見てみましょう.b関数のageはグローバル変数age(10)を直接参照し、c関数でローカル変数ageを再宣言するため、グローバル変数ageはc関数では無効である.
しかし、cでは、関数内部に関数add()があり、その関数内のageは、グローバル変数ageではなく、cで宣言された局所変数を指す.この例から,変数の役割ドメインを反映し,関数内の関数体に局所変数を宣言しなければ,その親級乃至祖先級関数の変数,およびグローバル変数を認める.
クローズドパッケージ
どうすれば閉鎖的になりますか?次のコードを見てみましょう.
前述した役割ドメインによれば,上記のコードではageがa()の局所変数であり,道理で関数が出てくるとアクセスできない.しかし,a()は私有関数を返し,この関数はageを返し,これによりa()外部で本来局所変数であるageにアクセスできるようになり,このとき,この内部関数を閉パケットと呼ぶ.その原理は,関数内部の関数であり,親関数の局所変数にアクセスできることである.
以上の説明を総合して、私たちはこのように閉包を理解しなければなりません.閉パケットは、自分以外の変数を使用する関数です. 閉パケットは、1つの役割ドメインである. 閉パッケージは、「関数とそれに関連する参照環境とを組み合わせたエンティティ」です. 厳密には、閉パケットは、自己定義ではなく、自分が存在する役割ドメイン内の変数を使用し、これらの変数を役割ドメインの制限を突破する関数内部の関数として表現されることが多い.
典型的な閉パッケージ:関数内の関数 この内部関数は親関数の局所変数 を参照するという内部関数は、参照された変数を役割ドメイン制限var a=1を突破した.function fun() {return a + 1;} alert(fun());
これは閉パケットとしても計算でき、a()はそれ以外に定義された変数を参照する.しかし、これは厳密な閉パケットではありません.それは役割ドメインを突破するこの点で表現されていないからです.
これは非常に典型的な閉パッケージです.また,なぜalert(c())の2回の値が異なるのか,以下で説明する.
あなたが開発したケースとより明確に結びつけるために、私たちがやったことを見てみましょう.
これは私たちがrequire.jsの1つの書き方で、それを私たちがよく知っている閉パッケージモードに復元します.
メモリから閉パッケージを見る
では、上記のalert()の2回の結果が異なる理由を説明します.
まず、一般的な関数宣言と使用中のメモリの変化を見てみましょう.
上は私たちが閉パッケージに遭遇していない場合で、メモリは私たちがこのように描きます(注意して、私はここで抽象的にメモリの変化を描きますだけで、本当のjavascriptメモリのメカニズムではありません.)
【画像が欠落している場合は、文末の原文リンクを参照してください.原文の画像は正常です.】
fun()の実行が完了するたびにfun()関数の実行環境が解放される(回収メカニズム).
次に、閉パッケージを見てみましょう.
上に閉包が現れました.注意してください.ここにadd変数が現れました.
【画像が欠落している場合は、文末の原文リンクを参照してください.原文の画像は正常です.】
次の2つのステップでは、実際にfun(2)部分には何の変化もなく、内部関数に対応するメモリ領域に変化があります.注意深いあなたは、add(2)を実行した後、fun対応のメモリが解放されず、その内部関数、すなわちfunction(2)が解放され、add(4)を実行したときに内部関数が再実行されただけであることに気づくかもしれません.fun()に対応するメモリが変化したらどうしますか?次の例を見てみましょう.
これは非常に典型的な閉パケットの例であり、局所変数bがあり、メモリマップを見てみましょう.
【画像が欠落している場合は、文末の原文リンクを参照してください.原文の画像は正常です.】
2、3、4ステップ目のメモリの変化に注意してください.ステップ2ではfun()を変数cに割り当てるだけで,このとき内部関数function(a)は実行されないので++bも実行されず,bの値は2である.しかし、ステップ3から++bが先に実行され、++bの意味は、まず自己加算してから演算することであり、b++とは異なり、b++であれば、c(1)の最終結果は4であるが、c(1)の実行開始時には、bは2であり、実行が完了してから3であるべきである.
奇妙なことに、内部関数では++bが局所変数値を変化させ、bが2から3に変化し、メモリが解放されず、fun()の実行環境が破壊されず、bがメモリに保存される.4ステップ目になるとbの初期値は3であり,++bを経た後,4となる.
このメモリ分析は非常にイメージ的な閉パケット概念の中で、「役割ドメインの制限を突破する」という点についてよく説明されています.もともと役割ドメインの制限に従って、関数の局所変数は外部環境にアクセスできず、修正できませんが、閉パケットは外部環境に局所変数の内容を読み取るだけでなく、修正することもできます.さらに、閉パッケージは、閉パッケージ関数に関連する非自己定義の変数をメモリに保存し続け、親関数を含む関連環境は破棄されません.
かばんを閉じて何の役に立つの?
こんなにたくさん言ったのに、閉包はいったい何の役に立つのか、私たちはどうして閉包を使うのか.上記の説明から、閉パッケージの唯一の役割は、役割ドメインの制限を突破することであることを知っているはずです.では、この役割をどのようにしてプログラムサービスに利用しますか?
常駐メモリは、読み取り速度が速いことを意味します(もちろん、メモリのコストも大きく、メモリのオーバーフローを招きます).常駐メモリとは、プログラムを再実行することなく、初期化後、同じメモリ内のオブジェクトを繰り返し使用できることを意味します.この点は、多くのプラグイン、モジュールの設計思想です.
一番いい例は前に私が挙げたdefine()の例です.後で私たちが今日知っている形式で実践すると、functionを他の言語のclassとして扱うことができます.cat.getAge(), cat.grow()のような操作は、プログラミングでの使用習慣に合っているのではないでしょうか.1つが生成されると、このcatは常にメモリに存在し、いつでも取り出すことができ、インスタンス化されたオブジェクトです.
よりイメージ化するために、簡単なコードブロックを作成します.
なぜこの例を挙げるのか、私はあなたにこのようなシーンを想像させたいので、バッグを閉じていなければどうしますか?
閉じていない状態は、関数が自分の親(祖先、グローバル)オブジェクトの変数にアクセスできない状態です.例:
この状況はどうしますか.パラメータとして関数に入力する必要があります.
そうであれば、面倒ですが、各関数に変数を入力する必要があります.さらに厄介なことに、役割ドメインの突破はありません.例えば、
この場合、私たちはどうしてもこの方法を使って私たちの目的を実現することはできません.唯一実現できるのは、次のような方法です.
私たちはthisキーワードを賢く使っていますが、age、weightなどの属性が直接外部に露出し、cat 1を実行するだけです.age = 12; すぐにcatを12歳に成長させることができますが、体重は何の変化もありません.
要約すると、閉パッケージは、次のようないくつかの面での応用上の利点をもたらすことができます.常駐メモリ、運転速度を速める パッケージ クローズドパッケージ使用中の注意点
上記のメモリオーバーヘッドの問題に加えて、閉パケットで参照される外部変数は、特定の場合、所望の値とは異なる誤差があることに注意する必要があります.
上記のコードでは、各pタグにclickイベントをバインドするループを使用したいと考えていますが、残念なことに、forループで使用される閉包関数は、あなたの望み通りにはならず、閉包関数では、iはpAryとして認識されています.length、すなわち最後のiの最終値にループする.どうしてこんなことになったの?元は、変数を直接使用するのではなく、変数を閉パッケージで参照し、「参照」は変数の内容にポインタを向けることを意味します.このため、i=0の場合、閉パケット内のiは確かに0であるが、iの値が大きくなるにつれて、閉パケット内のiは現在の値を保存せず、iの内容にポインタを向け続け、あるpラベルをクリックすると、iの内容は実際にはforから最後のiの値になる.
同様に、この問題はsettimeout、setInterval、$.ajaxなどの操作では、操作をバインドしたり、操作を実行したりするときに、対応する変数が変化しているかどうかを覚えておけばOKです.
テキストリンク:http://www.tangshuang.net/2368.html
関数内部の関数:プライベート関数
まず、私たちはこの内部関数から話します.これは形式的なので、最初に役割ドメインを話したら、少しわざとです.閉パッケージは形式的に関数の内部の関数です.たとえば、次のようになります.
jQuery(function($){
function message(msg) {
alert(msg);
}
if($(window).width() > 1000) message('window 1000');
});
jqueryを使うなら、このコードはよく使うでしょう.よく見ると、最初のfunctionはパラメータとしてjQuery()という関数に渡され、function内にはmessage()関数があることがわかります.すべてのjQueryコードは最初のfunctionで処理される.2番目の関数は関数体内部の関数であり,この関数は関数体内で宣言されており,外層関数の実行が完了するとこの関数は機能しなくなり,jQuery()外ではmessage()は使用できないため,message()は最初の関数内部のプライベート関数である.
変数の役割ドメイン
関数内部の変数は2種類あり、1つは局所変数であり、1つはグローバル変数である.局所変数は現在の関数体内でのみ有効であり,関数体が出ると,上位レベルの範囲で局所変数は無効である.
var age = 10;
function a(age) {
return age + 1;
}
function b(_age) {
return age + _age;
}
function c(_age) {
var age = 11;
function add() {
return age + _age;
}
return add();
}
alert(a(9)); // 10 : 9 + 1
alert(b(2)); // 12 : 10 + 2
alert(c(5)); // 16 : 11 + 5
上のコードでは、bとc関数を見てみましょう.b関数のageはグローバル変数age(10)を直接参照し、c関数でローカル変数ageを再宣言するため、グローバル変数ageはc関数では無効である.
しかし、cでは、関数内部に関数add()があり、その関数内のageは、グローバル変数ageではなく、cで宣言された局所変数を指す.この例から,変数の役割ドメインを反映し,関数内の関数体に局所変数を宣言しなければ,その親級乃至祖先級関数の変数,およびグローバル変数を認める.
クローズドパッケージ
どうすれば閉鎖的になりますか?次のコードを見てみましょう.
function a() {
var age = 10;
return function(){
return age;
}
}
var age = a();
alert(age()); // 10
前述した役割ドメインによれば,上記のコードではageがa()の局所変数であり,道理で関数が出てくるとアクセスできない.しかし,a()は私有関数を返し,この関数はageを返し,これによりa()外部で本来局所変数であるageにアクセスできるようになり,このとき,この内部関数を閉パケットと呼ぶ.その原理は,関数内部の関数であり,親関数の局所変数にアクセスできることである.
以上の説明を総合して、私たちはこのように閉包を理解しなければなりません.
典型的な閉パッケージ:
これは閉パケットとしても計算でき、a()はそれ以外に定義された変数を参照する.しかし、これは厳密な閉パケットではありません.それは役割ドメインを突破するこの点で表現されていないからです.
var a = 1;
function fun() {
var b = 2;
return function(){
return a + ++b;
};
}
var c = fun();
alert(c()); // 4
alert(c()); // 5
これは非常に典型的な閉パッケージです.また,なぜalert(c())の2回の値が異なるのか,以下で説明する.
あなたが開発したケースとより明確に結びつけるために、私たちがやったことを見てみましょう.
define(function(){
var age = 10;
function getAge() {
return age;
}
function grow() {
age ++;
}
return {
age : getAge,
grow : grow
};
});
これは私たちがrequire.jsの1つの書き方で、それを私たちがよく知っている閉パッケージモードに復元します.
function Cat(){
var age = 10;
function getAge() {
return age;
}
function grow() {
age ++;
}
return {
ageAge : getAge,
grow : grow
};
};
var cat = Cat();
var age = cat.getAge();
alert(age); // 10
cat.grow();
age = cat.getAge();
alert(age); // 11
メモリから閉パッケージを見る
では、上記のalert()の2回の結果が異なる理由を説明します.
まず、一般的な関数宣言と使用中のメモリの変化を見てみましょう.
function fun(a,b) {
return a+b;
}
alert(fun(1,2));
alert(fun(3,4));
上は私たちが閉パッケージに遭遇していない場合で、メモリは私たちがこのように描きます(注意して、私はここで抽象的にメモリの変化を描きますだけで、本当のjavascriptメモリのメカニズムではありません.)
【画像が欠落している場合は、文末の原文リンクを参照してください.原文の画像は正常です.】
fun()の実行が完了するたびにfun()関数の実行環境が解放される(回収メカニズム).
次に、閉パッケージを見てみましょう.
function fun(a) {
return function(b){return a + b;}
}
var add = fun(2);
alert(add(2));
alert(add(4));
上に閉包が現れました.注意してください.ここにadd変数が現れました.
【画像が欠落している場合は、文末の原文リンクを参照してください.原文の画像は正常です.】
次の2つのステップでは、実際にfun(2)部分には何の変化もなく、内部関数に対応するメモリ領域に変化があります.注意深いあなたは、add(2)を実行した後、fun対応のメモリが解放されず、その内部関数、すなわちfunction(2)が解放され、add(4)を実行したときに内部関数が再実行されただけであることに気づくかもしれません.fun()に対応するメモリが変化したらどうしますか?次の例を見てみましょう.
function fun() {
var b = 2;
return function(a){
return a + ++b;
};
}
var c = fun();
alert(c(1)); // 4
alert(c(1)); // 5
これは非常に典型的な閉パケットの例であり、局所変数bがあり、メモリマップを見てみましょう.
【画像が欠落している場合は、文末の原文リンクを参照してください.原文の画像は正常です.】
2、3、4ステップ目のメモリの変化に注意してください.ステップ2ではfun()を変数cに割り当てるだけで,このとき内部関数function(a)は実行されないので++bも実行されず,bの値は2である.しかし、ステップ3から++bが先に実行され、++bの意味は、まず自己加算してから演算することであり、b++とは異なり、b++であれば、c(1)の最終結果は4であるが、c(1)の実行開始時には、bは2であり、実行が完了してから3であるべきである.
奇妙なことに、内部関数では++bが局所変数値を変化させ、bが2から3に変化し、メモリが解放されず、fun()の実行環境が破壊されず、bがメモリに保存される.4ステップ目になるとbの初期値は3であり,++bを経た後,4となる.
このメモリ分析は非常にイメージ的な閉パケット概念の中で、「役割ドメインの制限を突破する」という点についてよく説明されています.もともと役割ドメインの制限に従って、関数の局所変数は外部環境にアクセスできず、修正できませんが、閉パケットは外部環境に局所変数の内容を読み取るだけでなく、修正することもできます.さらに、閉パッケージは、閉パッケージ関数に関連する非自己定義の変数をメモリに保存し続け、親関数を含む関連環境は破棄されません.
かばんを閉じて何の役に立つの?
こんなにたくさん言ったのに、閉包はいったい何の役に立つのか、私たちはどうして閉包を使うのか.上記の説明から、閉パッケージの唯一の役割は、役割ドメインの制限を突破することであることを知っているはずです.では、この役割をどのようにしてプログラムサービスに利用しますか?
常駐メモリは、読み取り速度が速いことを意味します(もちろん、メモリのコストも大きく、メモリのオーバーフローを招きます).常駐メモリとは、プログラムを再実行することなく、初期化後、同じメモリ内のオブジェクトを繰り返し使用できることを意味します.この点は、多くのプラグイン、モジュールの設計思想です.
一番いい例は前に私が挙げたdefine()の例です.後で私たちが今日知っている形式で実践すると、functionを他の言語のclassとして扱うことができます.cat.getAge(), cat.grow()のような操作は、プログラミングでの使用習慣に合っているのではないでしょうか.1つが生成されると、このcatは常にメモリに存在し、いつでも取り出すことができ、インスタンス化されたオブジェクトです.
よりイメージ化するために、簡単なコードブロックを作成します.
function Animal() {
this.age = 1;
this.weight = 10;
return {
getAge : function(){
return this.age;
},
getWeight : function(){
return this.weight;
},
grow : function(){
this.age ++;
this.weight = this.age * 10 * 0.8;
}
};
}
function Cat() {
var cat = new Animal(); //
cat.grow = function(){
cat.age ++;
cat.weight = cat.age * 10 * 0.6;
}
return cat;
}
var cat1 = new Cat();
alert(cat1.getAge());
cat1.grow();
alert(cat1.getAge());
なぜこの例を挙げるのか、私はあなたにこのようなシーンを想像させたいので、バッグを閉じていなければどうしますか?
閉じていない状態は、関数が自分の親(祖先、グローバル)オブジェクトの変数にアクセスできない状態です.例:
var a = 1;
function add() {
return ++a; // , undefined
}
この状況はどうしますか.パラメータとして関数に入力する必要があります.
var a = 1;
function add(a) {
return ++a;
}
alert(add(a)); // 2
そうであれば、面倒ですが、各関数に変数を入力する必要があります.さらに厄介なことに、役割ドメインの突破はありません.例えば、
function Cat() {
age = 1;
function getAge(age) {
return age;
}
function grow(age) {
age ++;
}
return {
getAge : getAge,
grow : grow
}
}
var cat = new Cat();
cat.grow();
alert(cat.getAge()); // 1,
この場合、私たちはどうしてもこの方法を使って私たちの目的を実現することはできません.唯一実現できるのは、次のような方法です.
var cat = {
age : 1,
weight : 10,
grow : function(){
this.age ++;
this.weight += 3;
}
};
var cat1 = cat;
alert(cat1.age);
cat1.grow();
alert(cat1.age);
私たちはthisキーワードを賢く使っていますが、age、weightなどの属性が直接外部に露出し、cat 1を実行するだけです.age = 12; すぐにcatを12歳に成長させることができますが、体重は何の変化もありません.
要約すると、閉パッケージは、次のようないくつかの面での応用上の利点をもたらすことができます.
上記のメモリオーバーヘッドの問題に加えて、閉パケットで参照される外部変数は、特定の場合、所望の値とは異なる誤差があることに注意する必要があります.
function init() {
var pAry = document.getElementsByTagName("p");
for( var i=0; i
上記のコードでは、各pタグにclickイベントをバインドするループを使用したいと考えていますが、残念なことに、forループで使用される閉包関数は、あなたの望み通りにはならず、閉包関数では、iはpAryとして認識されています.length、すなわち最後のiの最終値にループする.どうしてこんなことになったの?元は、変数を直接使用するのではなく、変数を閉パッケージで参照し、「参照」は変数の内容にポインタを向けることを意味します.このため、i=0の場合、閉パケット内のiは確かに0であるが、iの値が大きくなるにつれて、閉パケット内のiは現在の値を保存せず、iの内容にポインタを向け続け、あるpラベルをクリックすると、iの内容は実際にはforから最後のiの値になる.
同様に、この問題はsettimeout、setInterval、$.ajaxなどの操作では、操作をバインドしたり、操作を実行したりするときに、対応する変数が変化しているかどうかを覚えておけばOKです.
テキストリンク:http://www.tangshuang.net/2368.html