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

JavaScriptのスコープとクロージャの深い理解_基礎知識

May 16, 2016 pm 04:35 PM
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 までご連絡ください。

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

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

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

SublimeText3 中国語版

SublimeText3 中国語版

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

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

C言語でのtypedef構造体の使い方 C言語でのtypedef構造体の使い方 May 09, 2024 am 10:15 AM

typedef struct は、構造体の使用を簡素化するために構造体型のエイリアスを作成するために C 言語で使用されます。構造体の別名を指定することで、新しいデータ型を既存の構造体に別名付けします。利点としては、可読性の向上、コードの再利用、型チェックなどが挙げられます。注: エイリアスを使用する前に構造体を定義する必要があります。エイリアスはプログラム内で一意であり、宣言されているスコープ内でのみ有効である必要があります。

Javaで期待される変数を解決する方法 Javaで期待される変数を解決する方法 May 07, 2024 am 02:48 AM

Java における変数の期待値の例外は、変数の初期化、null 値の使用、およびローカル変数のスコープの認識によって解決できます。

JSのクロージャーの長所と短所 JSのクロージャーの長所と短所 May 10, 2024 am 04:39 AM

JavaScript クロージャーの利点には、変数スコープの維持、モジュール化コードの有効化、遅延実行、およびイベント処理が含まれますが、欠点としては、メモリ リーク、複雑さの増加、パフォーマンスのオーバーヘッド、およびスコープ チェーンの影響が挙げられます。

C++ で include は何を意味しますか C++ で include は何を意味しますか May 09, 2024 am 01:45 AM

C++ の #include プリプロセッサ ディレクティブは、外部ソース ファイルの内容を現在のソース ファイルに挿入し、その内容を現在のソース ファイル内の対応する場所にコピーします。主に、コード内で必要な宣言を含むヘッダー ファイルをインクルードするために使用されます。たとえば、標準入出力関数を組み込むための #include <iostream> などです。

C++ラムダ式でクロージャを実装するにはどうすればよいですか? C++ラムダ式でクロージャを実装するにはどうすればよいですか? Jun 01, 2024 pm 05:50 PM

C++ ラムダ式は、関数スコープ変数を保存し、関数からアクセスできるようにするクロージャーをサポートしています。構文は [キャプチャリスト](パラメータ)->戻り値の型{関数本体} です。 Capture-list は、キャプチャする変数を定義します。[=] を使用してすべてのローカル変数を値によってキャプチャするか、[&] を使用してすべてのローカル変数を参照によってキャプチャするか、[variable1, variable2,...] を使用して特定の変数をキャプチャできます。ラムダ式はキャプチャされた変数にのみアクセスできますが、元の値を変更することはできません。

C++ スマート ポインター: ライフサイクルの包括的な分析 C++ スマート ポインター: ライフサイクルの包括的な分析 May 09, 2024 am 11:06 AM

C++ スマート ポインターのライフ サイクル: 作成: スマート ポインターは、メモリが割り当てられるときに作成されます。所有権の譲渡: 移動操作を通じて所有権を譲渡します。リリース: スマート ポインターがスコープ外に出るか、明示的に解放されると、メモリが解放されます。オブジェクトの破壊: ポイントされたオブジェクトが破壊されると、スマート ポインターは無効なポインターになります。

C++ での関数の定義と呼び出しはネストできますか? C++ での関数の定義と呼び出しはネストできますか? May 06, 2024 pm 06:36 PM

できる。 C++ では、ネストされた関数の定義と呼び出しが可能です。外部関数は組み込み関数を定義でき、内部関数はスコープ内で直接呼び出すことができます。ネストされた関数により、カプセル化、再利用性、スコープ制御が強化されます。ただし、内部関数は外部関数のローカル変数に直接アクセスすることはできず、戻り値の型は外部関数の宣言と一致している必要があります。内部関数は自己再帰的ではありません。

js の this が指す状況がいくつかあります。 js の this が指す状況がいくつかあります。 May 06, 2024 pm 02:03 PM

JavaScript では、this のポインティング タイプには、1. グローバル オブジェクト、2. 関数呼び出し、4. イベント ハンドラー、5. アロー関数 (this の外側の継承) が含まれます。さらに、bind()、call()、および apply() メソッドを使用して、これが何を指すかを明示的に設定できます。

See all articles