検索
ホームページウェブフロントエンドjsチュートリアルJavaScript クロージャ パート 2: クロージャの実装

理論的な部分について説明した後、ECMAScript でクロージャがどのように実装されるかを紹介します。ここでもう一度強調しておく価値があります。ECMAScript は静的 (字句) スコープのみを使用します (一方、Perl などの言語は変数宣言に静的スコープと動的スコープの両方を使用できます)。

var x = 10;
 
function foo() {
  alert(x);
}
 
(function (funArg) {
 
  var x = 20;
 
  // 变量"x"在(lexical)上下文中静态保存的,在该函数创建的时候就保存了
  funArg(); // 10, 而不是20
 
})(foo);

技術的に言えば、関数を作成した親コンテキストのデータは、関数の内部プロパティ [[Scope]] に格納されます。 [[Scope]] が何なのかわからない場合は、まず前の章を読むことをお勧めします。この章では、[[Scope]] について詳しく説明しています。 [[Scope]] とスコープ チェーンの知識を完全に理解していれば、クロージャも完全に理解できるようになります。

関数作成アルゴリズムによると、ECMAScript では、すべての関数がクロージャであることがわかります。これは、関数が作成されたときに上位コンテキストのスコープ チェーンを保存するためです (例外を除く) (この関数が後でアクティブ化されるかどうかに関係なく) - [[スコープ]] は関数の作成時に利用可能です):

var x = 10;
 
function foo() {
  alert(x);
}
 
// foo是闭包
foo: <FunctionObject> = {
  [[Call]]: <code block of foo>,
  [[Scope]]: [
    global: {
      x: 10
    }
  ],
  ... // 其它属性
};

前述したように、最適化の目的で、関数が自由変数を使用しない場合、実装は内部の副作用ドメイン チェーンに保存されない場合があります。ただし、ECMA-262-3 仕様には何も記載されていません。したがって、通常、すべてのパラメータは作成フェーズ中に [[Scope]] 属性に保存されます。

一部の実装では、クロージャ スコープへの直接アクセスが可能です。たとえば、Rhino では、関数の [[Scope]] 属性には、非標準の __parent__ 属性があります:

var global = this;
var x = 10;
 
var foo = (function () {
 
  var y = 20;
 
  return function () {
    alert(y);
  };
 
})();
 
foo(); // 20
alert(foo.__parent__.y); // 20
 
foo.__parent__.y = 30;
foo(); // 30
 
// 可以通过作用域链移动到顶部
alert(foo.__parent__.__parent__ === global); // true
alert(foo.__parent__.__parent__.x); // 10

すべてのオブジェクトは [[Scope]] を参照します

ここにも注意してください: ECMAScript では、クロージャが作成されます同じ親コンテキスト内で [[Scope]] 属性を共有します。言い換えれば、クロージャによる [[Scope]] 内の変数の変更は、他のクロージャによるその変数の読み取りに影響します:

var firstClosure;
var secondClosure;
 
function foo() {
 
  var x = 1;
 
  firstClosure = function () { return ++x; };
  secondClosure = function () { return --x; };
 
  x = 2; // 影响 AO["x"], 在2个闭包公有的[[Scope]]中
 
  alert(firstClosure()); // 3, 通过第一个闭包的[[Scope]]
}
 
foo();
 
alert(firstClosure()); // 4
alert(secondClosure()); // 3

これは、すべての内部関数が同じ親スコープを共有することを意味します

これについては非常によく誤解されています開発者は、ループ ステートメント内で関数を作成する場合 (内部的にカウントする)、期待どおりの結果が得られないことがよくあり、各関数が独自の値を持つことが期待されます。

var data = [];
 
for (var k = 0; k < 3; k++) {
  data[k] = function () {
    alert(k);
  };
}
 
data[0](); // 3, 而不是0
data[1](); // 3, 而不是1
data[2](); // 3, 而不是2

上記の例は、同じコンテキストで作成されたクロージャが [[Scope]] 属性を共有することを証明しています。したがって、上位コンテキストの変数「k」は簡単に変更できます。

activeContext.Scope = [
  ... // 其它变量对象
  {data: [...], k: 3} // 活动对象
];
 
data[0].[[Scope]] === Scope;
data[1].[[Scope]] === Scope;
data[2].[[Scope]] === Scope;

このように、関数を有効にすると、最後に使用したkは3になりました。以下に示すように、クロージャを作成すると、この問題を解決できます:

var data = [];
 
for (var k = 0; k < 3; k++) {
  data[k] = (function _helper(x) {
    return function () {
      alert(x);
    };
  })(k); // 传入"k"值
}
 
// 现在结果是正确的了
data[0](); // 0
data[1](); // 1
data[2](); // 2

上記のコードで何が起こるか見てみましょう?関数「_helper」を作成した後、パラメーター「k」を渡すことで関数がアクティブになります。その戻り値も関数であり、対応する配列要素に格納されます。この手法により次のような効果が得られます。関数がアクティブ化されるたびに、「_helper」はパラメーター「x」を含む新しい変数オブジェクトを作成します。「x」の値は渡された「k」の値になります。このようにして、返される関数の [[Scope]] は次のようになります:

data[0].[[Scope]] === [
  ... // 其它变量对象
  父级上下文中的活动对象AO: {data: [...], k: 3},
  _helper上下文中的活动对象AO: {x: 0}
];
 
data[1].[[Scope]] === [
  ... // 其它变量对象
  父级上下文中的活动对象AO: {data: [...], k: 3},
  _helper上下文中的活动对象AO: {x: 1}
];
 
data[2].[[Scope]] === [
  ... // 其它变量对象
  父级上下文中的活动对象AO: {data: [...], k: 3},
  _helper上下文中的活动对象AO: {x: 2}
];

これを実現するには、この時点で関数の [[Scope]] 属性が実際に必要な値を持っていることがわかります。この目的のために、[[Scope]] に追加の変数オブジェクトを作成する必要があります。返された関数で「k」の値を取得したい場合、値は 3 のままであることに注意してください。

ちなみに、JavaScript を紹介する記事の多くは、追加で作成した関数のみがクロージャであると信じていますが、これは間違いです。実際には、この方法が最も効果的ですが、理論的な観点から見ると、ECMAScript の関数はすべてクロージャです。

ただし、上記の方法だけではありません。 「k」の正しい値は、次のような他の方法でも取得できます:

var data = [];
 
for (var k = 0; k < 3; k++) {
  (data[k] = function () {
    alert(arguments.callee.x);
  }).x = k; // 将k作为函数的一个属性
}
 
// 结果也是对的
data[0](); // 0
data[1](); // 1
data[2](); // 2

Funarg と return

別の機能はクロージャから返されます。 ECMAScript では、クロージャ内の return ステートメントは、制御フローを呼び出しコンテキスト (呼び出し元) に返します。 Ruby などの他の言語では、多くの形式のクロージャがあり、対応するクロージャの戻り値も異なります。次の方法が可能です。呼び出し元に直接返されることもあれば、場合によってはコンテキストから直接終了することもあります。 。

ECMAScript の標準の終了動作は次のとおりです:

function getElement() {
 
  [1, 2, 3].forEach(function (element) {
 
    if (element % 2 == 0) {
      // 返回给函数"forEach"函数
      // 而不是返回给getElement函数
      alert(&#39;found: &#39; + element); // found: 2
      return element;
    }
 
  });
 
  return null;
}

ただし、ECMAScript では、try catch によって次の効果を実現できます:

var $break = {};
 
function getElement() {
 
  try {
 
    [1, 2, 3].forEach(function (element) {
 
      if (element % 2 == 0) {
        // // 从getElement中"返回"
        alert(&#39;found: &#39; + element); // found: 2
        $break.data = element;
        throw $break;
      }
 
    });
 
  } catch (e) {
    if (e == $break) {
      return $break.data;
    }
  }
 
  return null;
}
 
alert(getElement()); // 2

理論バージョン

ここで説明すると、開発者はしばしば誤ってクロージャを単純化し、親からクロージャを理解します。 context 内部関数を返すと、匿名関数のみがクロージャになり得ることも理解されます。

繰り返しますが、スコープ チェーンのため、すべての関数はクロージャです (関数の種類に関係なく、匿名関数、FE、NFE、FD はすべてクロージャです)。

関数の [[Scope]] にはグローバル オブジェクトのみが含まれるため、関数のタイプは 1 つだけです。それは、Function コンストラクターを通じて作成された関数です。この問題をより明確にするために、ECMAScript のクロージャーの 2 つの正しいバージョン定義を示します。

ECMAScript では、クロージャーは以下を参照します。

理論的な観点から: すべての関数。それらはすべて、作成時に上位コンテキストのデータを保存するためです。これは、関数内のグローバル変数へのアクセスは自由変数へのアクセスと同等であるため、単純なグローバル変数にも当てはまります。このとき、最も外側のスコープが使用されます。

実用的な観点から: 以下の関数はクロージャとみなされます:

それが作成されたコンテキストが破棄されたとしても、それはまだ存在します (たとえば、内部関数は親関数から戻ります)

自由変数はコード内で参照されています

以上です JavaScript クロージャ パート 2: クロージャ実装の内容 詳細については、PHP 中国語 Web サイト (www.php.cn) に注目してください。


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

JavaScriptの最新トレンドには、TypeScriptの台頭、最新のフレームワークとライブラリの人気、WebAssemblyの適用が含まれます。将来の見通しは、より強力なタイプシステム、サーバー側のJavaScriptの開発、人工知能と機械学習の拡大、およびIoTおよびEDGEコンピューティングの可能性をカバーしています。

javascriptの分解:それが何をするのか、なぜそれが重要なのかjavascriptの分解:それが何をするのか、なぜそれが重要なのかApr 09, 2025 am 12:07 AM

JavaScriptは現代のWeb開発の基礎であり、その主な機能には、イベント駆動型のプログラミング、動的コンテンツ生成、非同期プログラミングが含まれます。 1)イベント駆動型プログラミングにより、Webページはユーザー操作に応じて動的に変更できます。 2)動的コンテンツ生成により、条件に応じてページコンテンツを調整できます。 3)非同期プログラミングにより、ユーザーインターフェイスがブロックされないようにします。 JavaScriptは、Webインタラクション、シングルページアプリケーション、サーバー側の開発で広く使用されており、ユーザーエクスペリエンスとクロスプラットフォーム開発の柔軟性を大幅に改善しています。

pythonまたはjavascriptの方がいいですか?pythonまたはjavascriptの方がいいですか?Apr 06, 2025 am 12:14 AM

Pythonはデータサイエンスや機械学習により適していますが、JavaScriptはフロントエンドとフルスタックの開発により適しています。 1. Pythonは、簡潔な構文とリッチライブラリエコシステムで知られており、データ分析とWeb開発に適しています。 2。JavaScriptは、フロントエンド開発の中核です。 node.jsはサーバー側のプログラミングをサポートしており、フルスタック開発に適しています。

JavaScriptをインストールするにはどうすればよいですか?JavaScriptをインストールするにはどうすればよいですか?Apr 05, 2025 am 12:16 AM

JavaScriptは、最新のブラウザにすでに組み込まれているため、インストールを必要としません。開始するには、テキストエディターとブラウザのみが必要です。 1)ブラウザ環境では、タグを介してHTMLファイルを埋め込んで実行します。 2)node.js環境では、node.jsをダウンロードしてインストールした後、コマンドラインを介してJavaScriptファイルを実行します。

クォーツでタスクが開始される前に通知を送信する方法は?クォーツでタスクが開始される前に通知を送信する方法は?Apr 04, 2025 pm 09:24 PM

Quartzタイマーを使用してタスクをスケジュールする場合、Quartzでタスク通知を事前に送信する方法、タスクの実行時間はCron式によって設定されます。今...

JavaScriptでは、コンストラクターのプロトタイプチェーンで関数のパラメーターを取得する方法は?JavaScriptでは、コンストラクターのプロトタイプチェーンで関数のパラメーターを取得する方法は?Apr 04, 2025 pm 09:21 PM

JavaScriptプログラミング、プロトタイプチェーンの関数パラメーターの理解と操作のJavaScriptのプロトタイプチェーンの関数のパラメーターを取得する方法は、一般的で重要なタスクです...

WeChat MiniプログラムWebViewでVUE.JSダイナミックスタイルの変位が失敗した理由は何ですか?WeChat MiniプログラムWebViewでVUE.JSダイナミックスタイルの変位が失敗した理由は何ですか?Apr 04, 2025 pm 09:18 PM

WeChatアプレットWeb-ViewでVue.jsを使用する動的スタイルの変位障害がvue.jsを使用している理由の分析...

TamperMonkeyで複数のリンクの同時GETリクエストを実装し、順番に戻る結果を決定する方法は?TamperMonkeyで複数のリンクの同時GETリクエストを実装し、順番に戻る結果を決定する方法は?Apr 04, 2025 pm 09:15 PM

複数のリンクの同時ゲットリクエストを作成し、結果を返すために順番に判断する方法は? TamperMonkeyスクリプトでは、複数のチェーンを使用する必要があることがよくあります...

See all articles

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

SecLists

SecLists

SecLists は、セキュリティ テスターの究極の相棒です。これは、セキュリティ評価中に頻繁に使用されるさまざまな種類のリストを 1 か所にまとめたものです。 SecLists は、セキュリティ テスターが必要とする可能性のあるすべてのリストを便利に提供することで、セキュリティ テストをより効率的かつ生産的にするのに役立ちます。リストの種類には、ユーザー名、パスワード、URL、ファジング ペイロード、機密データ パターン、Web シェルなどが含まれます。テスターはこのリポジトリを新しいテスト マシンにプルするだけで、必要なあらゆる種類のリストにアクセスできるようになります。

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

AtomエディタMac版ダウンロード

AtomエディタMac版ダウンロード

最も人気のあるオープンソースエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい