ホームページ >ウェブフロントエンド >jsチュートリアル >JavaScriptを深く理解するシリーズ(19):評価戦略を詳しく解説_基礎知識

JavaScriptを深く理解するシリーズ(19):評価戦略を詳しく解説_基礎知識

WBOY
WBOYオリジナル
2016-05-16 16:11:09965ブラウズ

はじめに

この章では、ECMAScript で関数にパラメーターを渡す戦略について説明します。

コンピュータサイエンスでは、この戦略を一般に「評価戦略」と呼んでいます(おじさん注:評価戦略と訳す人もいるし、課題戦略と訳す人もいます。以下の内容を見るとそう呼ばれていると思います)課題 戦略のほうが適切ですが、タイトルには、プログラミング言語での式の評価や計算のルールを設定するなど、誰にでもわかりやすい評価戦略を記載する必要があります。関数に引数を渡す戦略は特殊なケースです。

http://dmitrysoshnikov.com/ecmascript/chapter-8-evaluation-strategy/
この記事を書いた理由は、フォーラムの誰かがいくつかのパラメーター受け渡し戦略についての正確な説明を求めたためであり、皆さんの役に立つことを願って、対応する定義をここに示しました。

多くのプログラマーは、JavaScript (および他の言語でも) ではオブジェクトは参照によって渡され、プリミティブ値型は値によって渡されると信じています。また、多くの記事でこの「事実」について言及していますが、多くの人は本当にそう思いますか。この記事では、この用語を一つずつ説明していきます。

一般理論

割り当て理論には一般に 2 つの割り当て戦略があることに注意してください。厳密な意味では、パラメーターはプログラムに入る前に計算されます。非厳密な意味では、パラメーターは計算要件に従って計算されます。つまり、遅延計算と同等です)。

次に、ここでは、ECMAScript の開始点から見て非常に重要な、基本的な関数パラメーターの受け渡し戦略を検討します。最初に注意すべきことは、ECMAScript (および C、JAVA、Python、Ruby などの他の言語でも) は厳密なパラメーター受け渡し戦略を使用しているということです。

さらに、渡されるパラメータの計算順序も非常に重要です。ECMAScript では左から右ですが、他の言語で実装されている反映順序 (右から) も使用できます。

厳密なパラメータ渡し戦略もいくつかのサブ戦略に分割されており、そのうちの最も重要なものについてはこの章で詳しく説明します。

以下で説明するすべての戦略が ECMAScript で使用されるわけではないため、これらの戦略の特定の動作について説明するときは、擬似コードを使用してそれらを示します。

値渡し

多くの開発者がよく知っているように、パラメータの値は、関数内でパラメータの値を変更しても、外部オブジェクト (.パラメータは外部値にあります)、一般的に言えば、新しいメモリは再割り当てされます (割り当てられたメモリがどのように実装されるかには注意を払いません - これはスタックまたは動的メモリ割り当てでもあります)。新しいメモリ ブロックの値は次のコピーです。外部オブジェクトであり、その値は関数内で使用されます。

コードをコピー コードは次のとおりです:

バー = 10

プロシージャ foo(barArg):
barArg = 20;
終了

foo(バー)

// foo 内の値を変更しても、内部 bar
の値には影響しません。 print(bar) // 10

ただし、関数のパラメータがプリミティブ値ではなく、複雑な構造体オブジェクトである場合、構造体が値として関数に渡されると、C ではこの問題が発生します。これは完全なコピーです。

一般的な例を示し、次の代入戦略を使用して、2 つのパラメーターを受け取り、2 番目のパラメーターが渡されたかどうかをマークする関数について考えてみましょう。オブジェクトが完全に変更される (オブジェクトに値が再割り当てされる) か、オブジェクトの一部のプロパティのみが変更されます。

コードをコピー コードは次のとおりです:

// 注: 以下は疑似コードであり、JS 実装ではありません
バー = {
x: 10、
y: 20
}

プロシージャ foo(barArg, isFullChange):

isFullChange の場合:
barArg = {z: 1, q: 2}
終了
終了

barArg.x = 100
barArg.y = 200

終了

foo(バー)

// 値渡し、外部オブジェクトは変更されません
print(bar) // {x: 10, y: 20}

// オブジェクトを完全に変更します (新しい値を割り当てます)
foo(bar, true)

//どちらも変更なし
print(bar) // {z: 1, q: 2} の代わりに {x: 10, y: 20}

参照渡し

もう 1 つのよく知られた参照渡しメソッドは、値のコピーではなく、オブジェクトの直接の外部参照アドレスなど、オブジェクトへの暗黙的な参照を受け取ります。関数内のパラメータを変更すると、関数の外側のオブジェクトの値に影響します。これは、両方とも同じオブジェクトを参照するためです。つまり、この時点では、パラメータは外部オブジェクトのエイリアスと同等です。

疑似コード:

コードをコピー コードは次のとおりです:

プロシージャ foo(barArg, isFullChange):

isFullChange の場合:
barArg = {z: 1, q: 2}
終了
終了

barArg.x = 100
barArg.y = 200

終了

//上記と同じオブジェクトを使用します
バー = {
x: 10、
y: 20
}

// 参照による呼び出しの結果は次のようになります:
foo(バー)

// オブジェクトの属性値が変更されました
print(bar) // {x: 100, y: 200}

// 新しい値を再割り当てすると、オブジェクトにも影響します
foo(bar, true)

// オブジェクトは新しいオブジェクトになりました
print(bar) // {z: 1, q: 2}

この戦略により、プロパティの大きなバッチを持つ大きな構造オブジェクトなど、複雑なオブジェクトをより効率的に配信できます。

共有して通話
上記の 2 つの戦略は誰もが知っていますが、ここで私が話したい戦略は誰もがよく理解していないかもしれません (実際には学術的な戦略です)。ただし、これがまさに ECMAScript のパラメーター受け渡し戦略で重要な役割を果たす戦略であることがすぐにわかります。

この戦略には、「オブジェクトによるパス」または「オブジェクト共有によるパス」という同義語もいくつかあります。

この戦略は、1974 年に Barbara Liskov によって CLU プログラミング言語用に提案されました。

この戦略の重要な点は、関数がオブジェクトのコピー (コピー) を受け取り、参照コピーが仮パラメーターとその値に関連付けられることです。

関数によって受け取られるパラメータは直接のオブジェクトのエイリアスではなく、参照アドレスのコピーであるため、ここに表示される参照を「参照渡し」と呼ぶことはできません。

最も重要な違いは、関数内のパラメーターに新しい値を再割り当てしても (上記の例の参照渡しの場合と同様)、外部オブジェクトには影響しませんが、パラメーターはアドレスのコピーであるため、外部と内部で同じオブジェクトにアクセスすることはできません (たとえば、外部オブジェクトは値渡しのような完全なコピーではありません)。パラメーター オブジェクトの属性値を変更すると、外部オブジェクトに影響します。

コードをコピー コードは次のとおりです:

プロシージャ foo(barArg, isFullChange):

isFullChange の場合:
barArg = {z: 1, q: 2}
終了
終了

barArg.x = 100
barArg.y = 200

終了

//このオブジェクト構造を引き続き使用します
バー = {
x: 10、
y: 20
}

// コントリビューションを渡すとオブジェクトに影響します
foo(バー)

// オブジェクトのプロパティが変更されました
print(bar) // {x: 100, y: 200}

// 再割り当ては効果がありません
foo(bar, true)

// 上記の値のまま
print(bar) // {x: 100, y: 200}


この処理は、オブジェクトがプリミティブ値ではなく、ほとんどの言語で使用されることを前提としています。

共有による受け渡しは値による受け渡しの特殊なケースです

パスバイシェア戦略は、Java、ECMAScript、Python、Ruby、Visual Basic などの多くの言語で使用されています。さらに、Python コミュニティではこの用語が採用されており、他の名前は混乱を引き起こす傾向があるため、他の言語でもこの用語が使用される場合があります。 Java、ECMAScript、Visual Basic などのほとんどの場合、この戦略は値渡しとも呼ばれます。これは、特別な値参照コピーを意味します。

一方で、これは次のようなものです - 関数に渡されるパラメータはバインドされた値 (参照アドレス) の名前にすぎず、外部オブジェクトには影響しません。

一方で、多くのフォーラムでオブジェクトを JavaScript 関数に渡す方法について議論されているため、これらの用語は深く掘り下げるまでは実際には間違っていると考えられています)。

一般理論では、値は値によって渡されると言われていますが、現時点では値はいわゆるアドレスのコピー (コピー) であるため、ルールには違反しません。

Ruby では、この戦略は参照渡しと呼ばれます。繰り返しますが、これは大きな構造のコピーとして渡されません (つまり、値によって渡されません)。一方、元のオブジェクトへの参照を扱っていないため、このクロスは変更できません。 -用語の概念はさらなる混乱を引き起こす可能性があります。

理論上、値渡しの特殊なケースのような参照渡しの特殊なケースはありません。

しかし、上記のテクノロジー (Java、ECMAScript、Python、Ruby、その他) では、実際に使用される戦略が pass-by-share であることを理解する必要があります。

共有とポインタを押します

С/С の場合、この戦略はイデオロギー的には値によるポインタ渡しと同じですが、重要な違いが 1 つあります。この戦略はオブジェクトを完全に変更するだけでなく、ポインタを逆参照することもできます。ただし、一般に、値 (アドレス) ポインタは新しいメモリ ブロックに割り当てられます (つまり、以前に参照されたメモリ ブロックは変更されません)。ポインタを介してオブジェクト属性を変更すると、Adon 外部オブジェクトに影響します。

したがって、ポインター カテゴリでは、これがアドレス値によって渡されることが明らかにわかります。 この場合、 pass-by-share は、ポインター割り当てのように動作する (ただし逆参照はできない) か、参照のようにプロパティを変更する (逆参照操作は必要ない) 単なる「糖衣構文」です。場合によっては、「安全なポインター」という名前を付けることもできます。 」。

ただし、明示的なポインタ逆参照を行わずにオブジェクトのプロパティを参照する場合、С/С には特別な構文シュガーもあります。

コードをコピー コードは次のとおりです:

(*obj).x
の代わりに obj->x

このイデオロギーは C と最も密接に関連しており、たとえば boost::shared_ptr の「スマート ポインター」の実装で見ることができます。これは代入演算子とコピー コンストラクターをオーバーロードし、オブジェクトの参照カウンターも使用し、オブジェクトを削除します。 GC。 このデータ型には、shared_ptr という似た名前もあります。

ECMAScript 実装

これで、ECMAScript でオブジェクトをパラメーターとして渡す戦略がわかりました。共有による受け渡しです。パラメーターのプロパティの変更は外部に影響しますが、再割り当ては外部オブジェクトに影響しません。ただし、上で述べたように、ECMAScript 開発者の間では通常、値が参照アドレスのコピーである点を除いて、値渡しと呼ばれています。

JavaScript の発明者である Brendan Ash も次のように書いています: 渡されるのは参照のコピー (アドレスのコピー) です。したがって、フォーラムの参加者全員がかつて値渡しについて言ったことも、この説明では正しいです。

より正確には、この動作は単純な代入として理解でき、その内部にはまったく異なるオブジェクトがありますが、同じ値、つまりアドレスのコピーを参照していることがわかります。

ECMAScript コード:

コードをコピー コードは次のとおりです:

var foo = {x: 10, y: 20};
var bar = foo;

アラート(bar === foo) // true

bar.x = 100;
bar.y = 200;

alert([foo.x, foo.y]); // [100, 200]

つまり、2 つの識別子 (名前バインディング) がメモリ内の同じオブジェクトにバインドされ、このオブジェクトを共有します:

foo 値: addr(0xFF) => {x: 100, y: 200} (アドレス 0xFF) 再割り当てでは、以前にバインドされたオブジェクトに影響を与えることなく、新しいオブジェクト識別子 (新しいアドレス) にバインドされます:

コードをコピー コードは次のとおりです:

bar = {z: 1, q: 2};

alert([foo.x, foo.y]); // [100, 200] – 変更なし
alert([bar.z, bar.q]); // [1, 2] – ただし、新しいオブジェクトを参照しています

つまり、foo と bar は異なる値と異なるアドレスを持ちます:
コードをコピー コードは次のとおりです:

foo 値: addr(0xFF) => {x: 100, y: 200} (アドレス 0xFF)
バー値: addr(0xFA) => {z: 1, q: 2} (アドレス 0xFA)

もう一度強調しますが、ここで言及されているオブジェクトの値はアドレス (アドレス) であり、変数を別の変数に代入することは、代入された値への参照です。したがって、両方の変数は同じメモリ アドレスを参照します。次の割り当ては新しいアドレスです。これは古いオブジェクトにバインドされているアドレスを解決し、それを新しいオブジェクトのアドレスにバインドします。これが参照渡しとの最も重要な違いです。

さらに、ECMA-262 標準によって提供される抽象化レベルのみを考慮すると、アルゴリズムに表示されるのは「値」の概念と、実装によって渡される「値」だけです (プリミティブ値の場合もあります)。またはオブジェクト)、ただし、上記の定義によれば、参照アドレスも値であるため、「値による受け渡し」とも呼ばれます。

ただし、誤解 (外部オブジェクトのプロパティが関数内で変更できる理由) を避けるために、ここで考慮する必要がある実装レベルの詳細がまだあります。これは、共有やその他の方法で受け渡されると考えられているものです。言葉 - 安全なポインタを渡します。安全なポインタがオブジェクトを逆参照して変更することは不可能ですが、オブジェクトの属性値を変更することはできます。

期間版

ECMAScript でこの戦略のバージョンという用語を定義しましょう。

これは「値による受け渡し」と呼ばれます。ここで言及されている値は特殊なケースです。つまり、値はアドレスのコピーです。このレベルから次のことが言えます。 ECMAScript の例外を除くすべてのオブジェクトは値によって渡されます。これは実際には ECMAScript の抽象レベルです。

この場合、特に「共有による受け渡し」と呼ばれます。これにより、従来の値による受け渡しと参照による受け渡しの違いがわかります。 1 : 元の値。 2: オブジェクトは共有によって渡されます。

「オブジェクトを参照型で関数に変換する」という文は ECMAScript とは関係がなく、間違っています。

結論

この記事が全体像と ECMAScript での実装の詳細を理解するのに役立つことを願っています。いつものように、ご質問がございましたら、お気軽にご相談ください。

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