検索
ホームページウェブフロントエンドjsチュートリアルJavaScriptのスコープとクロージャの深い理解_基礎知識

範囲

スコープは変数と関数のスコープです。JavaScript の関数内で宣言されたすべての変数は常に関数本体内で参照できます。JavaScript にはグローバル スコープとローカル スコープはありません。変数はグローバル変数よりも優先されます。いくつかの例を使用して、JavaScript のスコープの「隠されたルール」を理解しましょう (これらはフロントエンドのインタビューでもよく聞かれる質問です)。

1. 変数を事前に宣言します
例 1:

var scope="global";
function scopeTest(){
  console.log(scope);
  var scope="local" 
}
scopeTest(); //undefined

ここでの出力は未定義であり、エラーは報告されません。これは、前述の関数内の宣言が常に関数本体で表示されるためです。

var scope="global";
function scopeTest(){
  var scope;
  console.log(scope);
  scope="local" 
}
scopeTest(); //local

var を忘れた場合、変数はグローバル変数として宣言されることに注意してください。

2. ブロックレベルのスコープなし

他の一般的に使用されている言語とは異なり、JavaScript にはブロックレベルのスコープがありません:

function scopeTest() {
  var scope = {};
  if (scope instanceof Object) {
    var j = 1;
    for (var i = 0; i < 10; i++) {
      //console.log(i);
    }
    console.log(i); //输出10
  }
  console.log(j);//输出1

}

JavaScript の変数のスコープは関数レベルです。つまり、関数内のすべての変数は関数全体で定義されます。これにより、注意を払わないと遭遇する可能性があるいくつかの「隠れたルール」も生じます。 >

var scope = "hello";
function scopeTest() {
  console.log(scope);//①
  var scope = "no";
  console.log(scope);//②
}
①で出力された値が未定義であることが判明しました。これは、すでにグローバル変数の値を定義済みです。ここはこんにちはではないでしょうか。実際、上記のコードは次と同等です:

var scope = "hello";
function scopeTest() {
  var scope;
  console.log(scope);//①
  scope = "no";
  console.log(scope);//②
}
事前に宣言しておき、グローバル変数はローカル変数よりも優先度が低いというこの 2 つのルールに従って、なぜ undefine が出力されるのかを理解するのは難しくありません。

スコープチェーン

JavaScript では、各関数に独自の実行コンテキストがあり、この環境でコードが実行されると、変数オブジェクトのスコープ チェーンが作成され、順序付けされたアクセスが保証されます。変数オブジェクトに。

スコープ チェーンのフロント エンドは、現在のコード実行環境の変数オブジェクトであり、多くの場合、「アクティブ オブジェクト」と呼ばれます。オブジェクトに変数属性が含まれている場合、変数の検索は最初のチェーンのオブジェクトから開始されます。そうでない場合、検索は停止します。グローバル オブジェクトが見つかるまで上位スコープ チェーンの検索が続けられます:

スコープ チェーンの段階的な検索もプログラムのパフォーマンスに影響します。これが、変数スコープ チェーンの使用を避ける主な理由の 1 つです。グローバル変数。

閉店

基本概念

スコープはクロージャを理解するための前提条件です。クロージャとは、外部スコープ内の変数が現在のスコープ内で常にアクセスできることを意味します。

function createClosure(){
  var name = "jack";
  return {
    setStr:function(){
      name = "rose";
    },
    getStr:function(){
      return name + ":hello";
    }
  }
}
var builder = new createClosure();
builder.setStr();
console.log(builder.getStr()); //rose:hello
上記の例では、関数内に 2 つのクロージャが返されます。どちらのクロージャも外部スコープへの参照を保持しているため、外部関数内の変数はどこで呼び出されても常にアクセスできます。関数内で定義された関数は、外部関数のアクティブなオブジェクトを独自のスコープ チェーンに追加します。そのため、上記の例では、内部関数を通じて外部関数のプロパティにアクセスできます。プライベート変数をシミュレートします。

注: クロージャには追加の関数スコープがあるため (内部匿名関数は外部関数のスコープを持ちます)、過度に使用するとメモリ使用量が増加する可能性があります。

クロージャ内の変数

クロージャを使用する場合、スコープチェーンメカ​​ニズムの影響により、クロージャは内部関数の最後の値しか取得できません。これによる副作用として、内部関数がループ内にある場合、その値が取得されます。変数は常に最後の値です。


  //该实例不太合理,有一定延迟因素,此处主要为了说明闭包循环中存在的问题
  function timeManage() {
    for (var i = 0; i < 5; i++) {
      setTimeout(function() {
        console.log(i);
      },1000)
    };
  }
上記のプログラムでは、期待したように 1 ~ 5 の数字が入力されず、5 回すべて 5 が出力されます。別の例を見てみましょう:

function createClosure(){
  var result = [];
  for (var i = 0; i < 5; i++) {
    result[i] = function(){
      return i;
    }
  }
  return result;
}
createClosure()[0]() の呼び出しの戻り値は 5 ですが、createClosure()[4]() の戻り値は 5 のままです。上記の 2 つの例を通して、ループで内部関数を使用するときにクロージャが抱える問題がわかります。各関数のスコープ チェーンには外部関数 (timeManage、createClosure) のアクティブなオブジェクトが格納されるため、それらはすべて同じ変数を参照します。 i. 外部関数がリターンすると、このときの i の値は 5 なので、各内部関数 i の値も 5 になります。

では、この問題をどうやって解決すればいいのでしょうか?期待される結果を匿名ラッパー (匿名の自己実行関数式) 経由で強制的に返すことができます:

function timeManage() {
  for (var i = 0; i < 5; i++) {
    (function(num) {
      setTimeout(function() {
        console.log(num);
      }, 1000);
    })(i);
  }
}
または、クロージャ匿名関数で匿名関数の代入を返します:

function timeManage() {
  for (var i = 0; i < 10; i++) {
    setTimeout((function(e) {
      return function() {
        console.log(e);
      }
    })(i), 1000)
  }
}
//timeManager();输出1,2,3,4,5
function createClosure() {
  var result = [];
  for (var i = 0; i < 5; i++) {
    result[i] = function(num) {
      return function() {
        console.log(num);
      }
    }(i);
  }
  return result;
}
//createClosure()[1]()输出1;createClosure()[2]()输出2

无论是匿名包裹器还是通过嵌套匿名函数的方式,原理上都是由于函数是按值传递,因此会将变量i的值复制给实参num,在匿名函数的内部又创建了一个用于返回num的匿名函数,这样每个函数都有了一个num的副本,互不影响了。

闭包中的this

在闭包中使用this时要特别注意,稍微不慎可能会引起问题。通常我们理解this对象是运行时基于函数绑定的,全局函数中this对象就是window对象,而当函数作为对象中的一个方法调用时,this等于这个对象(TODO 关于this做一次整理)。由于匿名函数的作用域是全局性的,因此闭包的this通常指向全局对象window:

var scope = "global";
var object = {
  scope:"local",
  getScope:function(){
    return function(){
      return this.scope;
    }
  }
}

调用object.getScope()()返回值为global而不是我们预期的local,前面我们说过闭包中内部匿名函数会携带外部函数的作用域,那为什么没有取得外部函数的this呢?每个函数在被调用时,都会自动创建this和arguments,内部匿名函数在查找时,搜索到活跃对象中存在我们想要的变量,因此停止向外部函数中的查找,也就永远不可能直接访问外部函数中的变量了。总之,在闭包中函数作为某个对象的方法调用时,要特别注意,该方法内部匿名函数的this指向的是全局变量。

幸运的是我们可以很简单的解决这个问题,只需要把外部函数作用域的this存放到一个闭包能访问的变量里面即可:

var scope = "global";
var object = {
  scope:"local",
  getScope:function(){
    var that = this;
    return function(){
      return that.scope;
    }
  }
}
object.getScope()()返回值为local。

内存与性能

由于闭包中包含与函数运行期上下文相同的作用域链引用,因此,会产生一定的负面作用,当函数中活跃对象和运行期上下文销毁时,由于必要仍存在对活跃对象的引用,导致活跃对象无法销毁,这意味着闭包比普通函数占用更多的内存空间,在IE浏览器下还可能会导致内存泄漏的问题,如下:

 function bindEvent(){
  var target = document.getElementById("elem");
  target.onclick = function(){
    console.log(target.name);
  }
 }

上面例子中匿名函数对外部对象target产生一个引用,只要是匿名函数存在,这个引用就不会消失,外部函数的target对象也不会被销毁,这就产生了一个循环引用。解决方案是通过创建target.name副本减少对外部变量的循环引用以及手动重置对象:

 function bindEvent(){
  var target = document.getElementById("elem");
  var name = target.name;
  target.onclick = function(){
    console.log(name);
  }
  target = null;
 }

闭包中如果存在对外部变量的访问,无疑增加了标识符的查找路径,在一定的情况下,这也会造成性能方面的损失。解决此类问题的办法我们前面也曾提到过:尽量将外部变量存入到局部变量中,减少作用域链的查找长度。

总结:闭包不是javascript独有的特性,但是在javascript中有其独特的表现形式,使用闭包我们可以在javascript中定义一些私有变量,甚至模仿出块级作用域,但闭包在使用过程中,存在的问题我们也需要了解,这样才能避免不必要问题的出现。

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

Web開発におけるJavaScriptの主な用途には、クライアントの相互作用、フォーム検証、非同期通信が含まれます。 1)DOM操作による動的なコンテンツの更新とユーザーインタラクション。 2)ユーザーエクスペリエンスを改善するためにデータを提出する前に、クライアントの検証が実行されます。 3)サーバーとのリフレッシュレス通信は、AJAXテクノロジーを通じて達成されます。

JavaScriptエンジンの理解:実装の詳細JavaScriptエンジンの理解:実装の詳細Apr 17, 2025 am 12:05 AM

JavaScriptエンジンが内部的にどのように機能するかを理解することは、開発者にとってより効率的なコードの作成とパフォーマンスのボトルネックと最適化戦略の理解に役立つためです。 1)エンジンのワークフローには、3つの段階が含まれます。解析、コンパイル、実行。 2)実行プロセス中、エンジンはインラインキャッシュや非表示クラスなどの動的最適化を実行します。 3)ベストプラクティスには、グローバル変数の避け、ループの最適化、constとletsの使用、閉鎖の過度の使用の回避が含まれます。

Python vs. JavaScript:学習曲線と使いやすさPython vs. JavaScript:学習曲線と使いやすさApr 16, 2025 am 12:12 AM

Pythonは、スムーズな学習曲線と簡潔な構文を備えた初心者により適しています。 JavaScriptは、急な学習曲線と柔軟な構文を備えたフロントエンド開発に適しています。 1。Python構文は直感的で、データサイエンスやバックエンド開発に適しています。 2。JavaScriptは柔軟で、フロントエンドおよびサーバー側のプログラミングで広く使用されています。

Python vs. JavaScript:コミュニティ、ライブラリ、リソースPython vs. JavaScript:コミュニティ、ライブラリ、リソースApr 15, 2025 am 12:16 AM

PythonとJavaScriptには、コミュニティ、ライブラリ、リソースの観点から、独自の利点と短所があります。 1)Pythonコミュニティはフレンドリーで初心者に適していますが、フロントエンドの開発リソースはJavaScriptほど豊富ではありません。 2)Pythonはデータサイエンスおよび機械学習ライブラリで強力ですが、JavaScriptはフロントエンド開発ライブラリとフレームワークで優れています。 3)どちらも豊富な学習リソースを持っていますが、Pythonは公式文書から始めるのに適していますが、JavaScriptはMDNWebDocsにより優れています。選択は、プロジェクトのニーズと個人的な関心に基づいている必要があります。

C/CからJavaScriptへ:すべてがどのように機能するかC/CからJavaScriptへ:すべてがどのように機能するかApr 14, 2025 am 12:05 AM

C/CからJavaScriptへのシフトには、動的なタイピング、ゴミ収集、非同期プログラミングへの適応が必要です。 1)C/Cは、手動メモリ管理を必要とする静的に型付けられた言語であり、JavaScriptは動的に型付けされ、ごみ収集が自動的に処理されます。 2)C/Cはマシンコードにコンパイルする必要がありますが、JavaScriptは解釈言語です。 3)JavaScriptは、閉鎖、プロトタイプチェーン、約束などの概念を導入します。これにより、柔軟性と非同期プログラミング機能が向上します。

JavaScriptエンジン:実装の比較JavaScriptエンジン:実装の比較Apr 13, 2025 am 12:05 AM

さまざまなJavaScriptエンジンは、各エンジンの実装原則と最適化戦略が異なるため、JavaScriptコードを解析および実行するときに異なる効果をもたらします。 1。語彙分析:ソースコードを語彙ユニットに変換します。 2。文法分析:抽象的な構文ツリーを生成します。 3。最適化とコンパイル:JITコンパイラを介してマシンコードを生成します。 4。実行:マシンコードを実行します。 V8エンジンはインスタントコンピレーションと非表示クラスを通じて最適化され、Spidermonkeyはタイプ推論システムを使用して、同じコードで異なるパフォーマンスパフォーマンスをもたらします。

ブラウザを超えて:現実世界のJavaScriptブラウザを超えて:現実世界のJavaScriptApr 12, 2025 am 12:06 AM

現実世界におけるJavaScriptのアプリケーションには、サーバー側のプログラミング、モバイルアプリケーション開発、モノのインターネット制御が含まれます。 2。モバイルアプリケーションの開発は、ReactNativeを通じて実行され、クロスプラットフォームの展開をサポートします。 3.ハードウェアの相互作用に適したJohnny-Fiveライブラリを介したIoTデバイス制御に使用されます。

next.jsを使用してマルチテナントSaaSアプリケーションを構築する(バックエンド統合)next.jsを使用してマルチテナントSaaSアプリケーションを構築する(バックエンド統合)Apr 11, 2025 am 08:23 AM

私はあなたの日常的な技術ツールを使用して機能的なマルチテナントSaaSアプリケーション(EDTECHアプリ)を作成しましたが、あなたは同じことをすることができます。 まず、マルチテナントSaaSアプリケーションとは何ですか? マルチテナントSaaSアプリケーションを使用すると、Singの複数の顧客にサービスを提供できます

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ヘンタイを無料で生成します。

ホットツール

WebStorm Mac版

WebStorm Mac版

便利なJavaScript開発ツール

SublimeText3 Linux 新バージョン

SublimeText3 Linux 新バージョン

SublimeText3 Linux 最新バージョン

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

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

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

SublimeText3 英語版

SublimeText3 英語版

推奨: Win バージョン、コードプロンプトをサポート!

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

Eclipse を SAP NetWeaver アプリケーション サーバーと統合します。