ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScript のアドレッシング、クロージャ、オブジェクト モデル、および関連する問題の詳細_JavaScript スキル

JavaScript のアドレッシング、クロージャ、オブジェクト モデル、および関連する問題の詳細_JavaScript スキル

WBOY
WBOYオリジナル
2016-05-16 18:53:381005ブラウズ

JS は動的言語であるため、JS のアドレス指定は C のようにコンパイル後に決定されるのではなく、オンサイトのアドレス指定になります。さらに、JS では this ポインターが導入されましたが、これはパラメーターとして関数に「暗黙的に」渡されるため、非常に厄介なものです。まず、「スコープ チェーン」トピックの例を見てみましょう:
var testvar = 'window property';
var o1 = {testvar:'1', fun:function(){alert('o1: 'この .testvar);}};
var o2 = {testvar:'2', fun:function(){alert('o2: ' this.testvar);}}; // '1'
o2.fun(); // '2'
o1.fun.call(o2); // '2' 興味深いですね。それは?実際、すべての興味深く奇妙な概念は 1 つの問題に要約でき、それは対処することです。
単純な変数のアドレス指定
JS のスコープは静的ですか?それとも動的ですか?
残念なお知らせですが、JS は静的にスコープされています。つまり、変数のアドレス指定は、Perl のような動的スコープの言語よりもはるかに複雑です。次のコードは、プログラミング言語の原則の例です。 ');
04| 関数 f2(){var x = 2; f2() }; >f1 は eval を使用して動的に定義されますが、出力は 1 で、pascal や ada とまったく同じです。別の例もプログラミング言語の原則から来ています:
function big2(){
var x = 1;
function f2(){echo(x)}; //x の値を使用して出力を生成します。
関数 f3(){var x = 3;f4(f2)};
関数 f4(f){var x = 4;f()};
f3(); 🎜 >big2();//出力 1: 深いバインディング; 出力 4: 浅いバインディング; 出力 3: 特別なバインディング
出力は 1 のままで、JS が静的スコープであるだけでなく、深いバインディングであることを示します。今、何かが間違っています...
ARI の概念
関数 (特に Ada などの入れ子関数を許可する言語) の実行時の複雑なアドレス指定の問題を説明するために、次のように定義されています。書籍「プログラミング言語の原則」「ARI」:
関数アドレス
ローカル変数
リターン アドレス
ダイナミック リンク
スタティック リンク
を含む、スタック上のいくつかのレコードです。ここで、動的リンクは常に関数の呼び出し元を指します (たとえば、b が実行されるときに a が呼び出され、a の ARI では動的リンクが b を指します)。 a が定義されているため、関数はルート付きツリーとして構成されており、要約後はすべての静的リンクがホスト (ウィンドウなど) を指すことになります (コメント後の出力)。 = 'ホスト内の x';
関数 a(){echo(x)};
関数 b(){var x = 'b 内の x';echo(x)}; ){var x = 'c 内の x';a()};
function d() {
var x = 'd 内の x、クロージャで作成された関数';
return function(){ echo(x)}};
a();// ホスト内の x
b ();// b 内の x
c();// ホスト内の x
d()( );// d の中に x があり、クロージャで作成した関数が最初の文で呼び出されます。「スタック」には次の内容があることがわかります (スタックの最上部は左側にあります):
[ARI a] → [ホスト] A の静的リンクはホストに直接接続されます。これは、x が a で定義されていないためです。インタプリタは x を探します。b を呼び出すと、静的チェーンに沿ってホスト内で x が見つかります。 x は b のローカル変数に記録され、最後のエコーは x inside b: 'x inside b';
c を呼び出すときの状況はさらに興味深いものになります。 :
動的チェーン: [a]→[c]→[ホスト]
静的チェーン: [c]→[ホスト] [a]→[ホスト]
x のアドレス指定は後で実行されるためa を呼び出しても、静的リンクは依然としてホストを直接指します。当然のことながら、x は依然として「ホスト内の x」です。
d の状況はさらに興味深いものです。d は戻り値として関数を作成し、すぐに呼び出されます。d の戻り値は d のライフサイクル内で作成されるため、d の戻り値は静的です。 link は d を指しているため、呼び出されると、d の x が出力されます: 'x inside d、クロージャが作成した関数'。
静的リンクを作成するタイミング
Yueying と amingoo は、「クロージャ」は関数の「呼び出し時参照」であると述べましたが、「プログラミング言語の原則」では単に ARI と呼ばれていますが、違いは「プログラム」であるということです。 「設計言語の原則」の ARI はスタックに保存され、関数のライフサイクルが終了すると ARI は破棄されますが、JS クロージャーの場合はそうではなく、クロージャが存在しない場合に限り破棄されます。それとそのメンバーを指すポイント (というより、どのコードもそれを見つけることができません)。関数 ARI は、関数の「服」に包まれただけのオブジェクトとして単純に考えることができます。
「プログラミング言語の原理」で説明されている静的チェーンは呼び出されたときに作成されますが、静的チェーン間の関係はコードのコンパイル時に決定されます。たとえば、次のコード:
PROCEDURE a;
PROCEDURE b;
END
END
では、b と c の静的リンクがポイントされます。に。 b が呼び出され、b の変数が b のローカル変数に含まれていない場合、コンパイラはコードを生成し、変数または RTE が見つかるまで静的チェーンに沿ってスタックを検索します。
ada などのコンパイル言語とは異なり、JS は完全にインタープリタ型の言語であり、関数を動的に作成できるため、「静的なチェーンの保守」という問題が生じます。幸いなことに、JS 関数は erl のシンボルと同様に直接変更できません。したがって、静的リンクは定義されるたびに更新するだけで済みます。定義方法が function(){} であるか eval 代入であるかに関係なく、静的チェーンは関数の作成後に固定されます。
大きな例に戻りましょう。インタプリタが「function big(){...}」を実行すると、メモリ内に関数インスタンスが作成され、それがホストに静的に接続されます。ただし、最後の行で呼び出された場合、インタプリタはメモリ内の領域を ARI として描画します。私たちは ARI[big] になるのもいいかもしれません。実行ポインタが 2 行目に移動します。
実行が 3 行目に達すると、インタプリタは「f1」のインスタンスを作成し、それを ARI[big] に保存し、静的リンクを ARI[big] に接続します。次の行。インタプリタは「f2」のインスタンスを作成し、静的チェーンを接続します。次に、5 行目で f2 が呼び出されて ARI[f1] が作成され、f2 が f1 を呼び出して ARI[f1] が作成されます。f1 が x を出力したい場合は、x をアドレス指定する必要があります。
単純な変数のアドレス指定
続けて、x をアドレス指定する必要がありますが、x は f1 のローカル変数に現れないため、インタプリタはスタックを検索して出力から x を見つける必要があります。インタプリタは「スタック」に沿って層ごとに検索を行っていませんが、現時点では「スタック」が次のとおりであるため、ジャンプがあります。 big | x = 1
|HOST|
インタープリターがスタックに沿ってレイヤーごとに実際に検索する場合、出力は 2 になります。これは、Js 変数アドレス指定の本質、つまり静的チェーンに沿った検索に触れます。
上記の問題を続けると、実行ポインターは f1 の静的チェーンに沿って検索し、big が x=1 であることが判明するため、1 が出力され、すべて問題ありません。
では、静的リンクがループを形成し、アドレス指定の「無限ループ」を引き起こすのでしょうか? remember 関数は相互にネストされているため、心配する必要はありません。言い換えれば、関数はルート ツリーを形成し、すべての静的チェーン ポインターは最終的にホストに集約される必要があるため、「ポインター ループ」を心配するのはばかげています。 (逆に、動的スコープ言語のアドレス指定は簡単に無限ループを引き起こす可能性があります。)
ここで、単純な変数のアドレス指定の方法を要約します。インタプリタは現在の関数のローカル変数で変数名を検索します。 not found, it follow 静的チェーンは、変数が見つかるまで、またはホストまでトレースバックし、それでも変数が見つからないまでトレースバックします。
ARI の概要
次に、ARI がローカル変数 (パラメーターを含む)、このポインター、動的チェーン、そして最も重要なことに、関数実行時の関数インスタンスのアドレスを記録することを見てみましょう。 ARI は次の構造を持つと想像できます:
ARI :: {
variables :: *variableTable, //変数テーブル
dynamicLink :: *ARI, //ダイナミックリンク
instance :: * funtioninst //関数インスタンス
}
変数にはすべてのローカル変数、パラメーター、およびこのポインターが含まれます。dynamicLink は呼び出される ARI を指し、instance は関数インスタンスを指します。関数インスタンスには、
functioninst:: {
source:: *jsOperations, //関数命令
staticLink:: *ARI, //静的リンク
....
}
関数が呼び出されると、次の「正式なコード」が実際に実行されます:
*ARI p;
p = new ARI();
p->dynamicLink = thread. currentARI;
p->instance = 呼び出された関数
p->variables.insert (パラメータリスト、この参照)
thread.transfer(p->instance->operations[0])
Did見えますか? ARI を作成し、パラメータとこれを変数テーブルにプッシュし、スレッド ポインタを関数インスタンスの最初の命令に転送します。
関数が作成されたときはどうなるでしょうか?関数命令を割り当てた後、以下も必要になります。
newFunction->staticLink = thread.currentARI
これで問題は明らかになりました。関数の定義時に、現在の ARI を直接指す静的リンクを作成しました。スレッドの。これで、単純な変数アドレス指定の問題のほぼすべてが説明できます。たとえば、次のコード:
function test(){
for(i=0;i(function(t){ //この匿名関数は暫定的に f setTimeout(function(){echo('' t)},1000) //ここでの匿名関数は g
})(i)
} }
test()
これ このコードの効果は、1秒遅れて0 1 2 3 4の順に出力することです。 setTimeout が動作する関数に注目してみましょう。静的リンクが作成されると、(ARI の) f の変数テーブルには i が含まれます (パラメータはローカル変数とみなされます)。 setTimeout の期限が切れると、匿名関数 g は変数 t を検索します。変数 t は、匿名関数 f の ARI 内で見つかります。したがって、0 1 2 3 4 が作成順に 1 つずつ出力されます。
パブリック匿名関数 f の関数インスタンスには合計 5 つの ARI があります (関数が呼び出されるたびに ARI が 1 回作成されることを覚えていますか?)。これに対応して、g も 5 回「作成」されます。最初の setTimeout が期限切れになる前に、スタックには次のレコードと同等のレコードが存在します (g を 5 に分けて書きました)。 t =0 ←——————g0
の静的リンク; t=1 ←——————fの静的リンク
| t=2 —— ————g2
| f の aRI ←—————— g3
| t=4 の aRI ←—————— g4 リンクの静的
------
g0 が呼び出されるとき、「スタック」は次のようになります:
テストの ARI [ループの最後に i=5]
| f の ARI; t=0 ←—————— f の ARI; t=1 ←—————— f の ARI; t=2 ←—— ————g2
の静的リンク; t=3 ←——————fの静的リンク
| ————g4 静的リンク
------
g0 の ARI
| ここでは t をアドレス指定する必要があるため、... t=0
------
g0 の ARI は可能です。これは f シリーズの ARI にはなく、ホストに直接配置されていると見なすことができますが、アドレス指定に関係する静的リンクは依然として各 f の ARI に突き付けられています。 setTimeout は順番に待機キューにプッシュされるため、最終的な出力は 0 1 2 3 4 の順になります。
関数が再定義されると、静的リンクは変更されますか?
次の質問を見てみましょう: 関数が定義されると、静的リンクが確立されます。その後、関数が再定義されると、別の静的リンクが確立されますか?まず例を見てみましょう:
var x = "x in host";
f = function(){echo(x)};
function big(); 🎜>var x = 'x in big';
f();
f()
}
big(); 🎜>出力:
x in host
x in host
x in big
この例は、big が実行されると、ホスト内の f が再定義され、その静的リンクが理解しやすいかもしれません。 "new" f は big を指しているため、最後の行は 'x in big' を出力します。
しかし、次の例はもっと興味深いものです:
var x = "x in host";
f();関数 big(){
var x = 'x ';
var f1 = f; ()
}
big()
出力:
x in host
x in host
x in host
x in host
は、再定義が実行されることを意味しません。静的リンクを変更しますか?ただし、ここでの 2 つの代入は単なる代入であり、f1 と f のポインターのみが変更されます (JS 関数が参照型であることを思い出してください)。f の実際のインスタンスでは、静的リンクは変更されていません。 。したがって、ホストでは 4 つの出力は実際には x になります。
構造体 (オブジェクト) 内でのコンポーネント (属性) のアドレス指定の問題
キリスト教 (java) とモルモン教 (csh) の人々がこの奇妙な名前を使用することを許してください。ただし、JS オブジェクトはハッシュに非常に似ています。表で、このアドレス指定の問題を考えてみましょう。
a.b コンパイル済み言語は、a を見つけてから、それを後方に一定距離オフセットして b を見つけるコードを生成します。ただし、JS は完全に動的言語であり、オブジェクトのメンバーは次のことができます。プロトタイプの問題により、JS オブジェクトのメンバーのアドレス指定が非常に興味深いものになります。
オブジェクトはハッシュ テーブルです
いくつかの特別なメソッド (およびプロトタイプ メンバー) を除いて、オブジェクトはハッシュ テーブルとほぼ同じです。メソッドとプロパティは「ハッシュ テーブル」の「ラティス」に格納できるためです。で。 Yue バージョンは、彼の「JS Return of the King」に HashTable クラスを実装しました。
オブジェクト自体のプロパティのアドレス指定
「独自の」プロパティは、hasOwnProperty が true であるプロパティを指します。実装の観点から見ると、これはオブジェクト自体の「ハッシュ テーブル」のメンバーです。例:
function Point(x,y){
this.x = x;
this.y = y;
var a = new Point(1,2);
echo("a.x:" a.x)
Point コンストラクターは、「Point」オブジェクト a を作成し、x 属性と y 属性を設定します。そのため、a のメンバー テーブルには次のようになります。 - --> 1
| y | ---> 2
a.x を検索する場合、インタープリターは最初に a を見つけ、次に a のメンバー テーブルで x を検索し、1 を取得します。
コンストラクターからオブジェクトにメソッドを設定することは、同じ型の 2 つのオブジェクトが異なるメソッドを持つことになるため、良い戦略ではありません。
function Point(x,y){
this.x = x;
this.y = y;
this.abs = function(){return Math.sqrt(this.x*this.x this.y*this.y)}
}
var a = 新しいポイント (1,2);
var b = 新しいポイント (1,2)
echo("a.abs == b.abs ? " (a.abs==b.abs)) ;
echo("a.abs === b.abs ? " (a.abs===b.abs));
4 行目ではオブジェクトの abs メンバー ( Method ) は毎回作成されるため、a.abs と b.abs は実際には 2 つのまったく異なる関数インスタンスを指します。したがって、等しいように見える 2 つのメソッドは、実際には等しくありません。
プロトタイプのアドレス指定の問題に持ち込む
プロトタイプは、(クラスではなく) オブジェクトを指す関数 (クラス) の属性です。 「プロトタイプ」の考え方は、「猫から虎を描く」ことにたとえられます。「虎」と「猫」の間には、相手を継承する関係はなく、「虎」と「猫」の間には関係があるだけです。 」。プロトタイプは類似性に焦点を当てており、コードは次のように記述できます:
Tiger.prototype = new Cat() 関数のプロトタイプは単なる空のオブジェクトにすることもできます:
SomeClass.prototype = {} しましょう。のアドレス指定に戻って、 を使用して特定の属性を取得し、それがプロトタイプ内の属性だった場合はどうなるでしょうか。現象は、それは確かに得られたが、どのようにして得られたのかということです。オブジェクト自体のプロパティがプロトタイプのプロパティと同じ名前である場合はどうなるでしょうか?幸いなことに、オブジェクト自体のプロパティが優先されます。
Defining methods in prototypes is a good design strategy. If we change the above example:
function Point(x,y){
this.x = x;
this.y = y;
}
Point.prototype.abs = function(){return Math.sqrt(this.x*this.x this.y*this,y)}
var a = new Point(1,2);
var b = new Point(1, 2);
echo("a.abs == b.abs ? " (a.abs==b.abs));
echo("a.abs === b.abs ? " (a .abs===b.abs));
Now, the outputs are finally equal. The reason is that a.abs and b.abs point to the member abs of the Point class prototype, so the outputs are equal. However, we cannot directly access Point.prototype.abs, and an error will occur during testing. Correction: After re-testing, the "Point.prototype.abs cannot be accessed" problem is a problem with the JSConsole I used. The reply is correct, thank you for your correction!
The prototype chain can be very long, or even wound into a loop. Consider the following code:
A = function(x){this.x = x};
B = function(x){this.y = x};
A.prototype = new B(1 );
B.prototype = new A(1);
var a = new A(2);
echo(a.x ' , ' a.y);
var b = new B(2) ;
echo(b.x ' , ' b.y);
The relationship described is probably "I am like you, and you are like me." The prototype pointer causes the following output:
2, 1
1, 2
When searching for a.y, "a.prototype" was found along the prototype chain, and the output was 1; the same principle applies to b.x . Now, we want to output the unregistered attribute "a.z":
echo(tyoeof a.z) We are surprised that there is no infinite loop here. It seems that the interpreter has a mechanism to deal with the problem of the prototype chain becoming a loop. At the same time, the prototype will either form a tree or a single ring, and will not have a multi-ring structure. This is a very simple graph theory.
This: Hidden rules in functions
The most troublesome hidden rule in method (function) calling is this problem. Logically speaking, this is a pointer pointing to the caller (an object). But if this always pointed to the caller, the world would be wonderful. But this damn pointer will "kick your dog" every now and then. Possible modifications include call, apply, asynchronous call and "window.eval".
I prefer to treat this as a parameter, just like self in lua. Lua's self can be passed explicitly or called with a colon:
a:f(x,y,z) === a.f(a,x,y,z) The same is true for "prime" method calls in JS This looks like:
a.f(x,y,z) === a.f.call(a,x,y,z)f.call is the truly "clean" calling form, just like the clean calling in Lua . Many people say that Lua is a clear version of JS. Lua simplifies many things of JS and exposes many hidden rules of JS. This is true.
The principle of correcting "this"
"Use closure to correct this" mentioned above in "The Return of the King", first look at the code:
button1.onclick = (
function(e){return function (){button_click.apply(e,arguments)}}
)(button1) Don’t underestimate this line of code. In fact, it creates an ARI, binds button1 here, and then returns a function. The function forces e calls button_click for the caller (subject), so this passed to button_click is e, which is button1! After the event binding is completed, the environment will probably look like the following:
button1.onclick = _F_; //Set a name for the returned anonymous function
_F_.staticLink = _ARI_; //Anonymous function called after creation ARI
_ARI_[e] = button1 //The e in the anonymous ARI parameter table is also the e that _F_ is looking for
So, when we click the button, _F_ will be called, and _F_ initiates One caller is the button_click function of e. According to our previous analysis, e is equal to button1, so we get a safe "specified caller" method. Maybe we can continue to develop this idea and make a universal interface:
bindFunction = function(f,e){ //We are good people, we don’t change the prototype, we don’t change...
return function(){
f.apply(e,arguments)
}
}

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