ホームページ ウェブフロントエンド jsチュートリアル JavaScriptによるスコープ、スコープチェーン、クロージャの詳細説明(画像とテキスト)

JavaScriptによるスコープ、スコープチェーン、クロージャの詳細説明(画像とテキスト)

Mar 21, 2017 pm 02:23 PM

この記事では主に図Javascript - スコープ、スコープチェーン、クロージャ、その他の知識を紹介します。非常に良い基準値を持っています。以下のエディターで見てみましょう

スコープとは何ですか?

スコープとは、コードのコンパイル段階で決定され、変数関数のアクセス可能な範囲を規定するルールです。グローバル変数にはグローバル スコープがあり、ローカル変数にはローカル スコープがあります。 js はブロックレベルのスコープを持たない言語です (if、for などのステートメントを含む中括弧のコード ブロック、または個々の中括弧のコード ブロックはローカル スコープを形成できません)。そのため、js のローカル スコープの形成は次のようになります。関数の中括弧内で定義されたコード ブロック、つまり関数スコープが形成されます。

スコープチェーンとは何ですか?

スコープ チェーンはスコープ ルールの実装です。スコープ チェーンの実装を通じて、そのスコープ内で変数にアクセスでき、そのスコープ内で関数を呼び出すことができます。

スコープチェーンは、一方向にのみアクセスできるリンクリストであり、このリンクリストの各ノードは、実行コンテキストの変数オブジェクトです(コードが実行されるときのアクティブなオブジェクト)。 -way リンク リスト (最初に訪問したノードからアクセス可能) は常に、現在呼び出され実行されている関数の変数オブジェクト (アクティブ オブジェクト) であり、末尾は常にグローバル アクティブ オブジェクトです。

スコープチェーンの形成?

コードの実行からスコープチェーンの形成プロセスを見てみましょう。

function fun01 () {
 console.log('i am fun01...');
 fun02();
}
function fun02 () {
 console.log('i am fun02...');
}
fun01();
ログイン後にコピー

JavaScriptによるスコープ、スコープチェーン、クロージャの詳細説明(画像とテキスト)

データアクセスプロセス

上の図に示すように、プログラムが変数にアクセスするとき、スコープチェーンの一方向アクセス機能に従って、最初にヘッドノードのAOを検索します。そうでない場合は、次のノードの AO に進み、最後のノード (グローバル AO) まで検索します。この処理では、見つかった場合は、未定義のエラーが報告されます。

スコープ チェーンを拡張する

上記のスコープ チェーンの形成から、関数が呼び出されて実行されると、チェーン上の各ノードが現在の関数の AO をチェーン ヘッドにシフト解除することがわかります。ノードを形成するもう 1 つの方法は、「スコープ チェーンを拡張する」ことです。これは、必要なオブジェクト スコープをスコープ チェーンの先頭に挿入することを意味します。スコープチェーンを拡張するには 2 つの方法があります:

1. with ステートメント

function fun01 () {
 with (document) {
  console.log('I am fun01 and I am in document scope...')
 }
}
fun01();
ログイン後にコピー

JavaScriptによるスコープ、スコープチェーン、クロージャの詳細説明(画像とテキスト)

2. try-catch ステートメントの catch ブロック

function fun01 () {
 try {
  console.log('Some exceptions will happen...')
 } catch (e) {
  console.log(e)
 }
}
fun01();
ログイン後にコピー

JavaScriptによるスコープ、スコープチェーン、クロージャの詳細説明(画像とテキスト)

ps:ステートメントの使用要件はそれほど多くはありませんが、try-catch の使用も要件に依存します。私は個人的にこれら 2 つをあまり使用しませんが、この部分を整理する過程で、スコープ チェーン レベルでの未熟な パフォーマンス最適化 のヒントをいくつか思いつきました。

スコープチェーンによるパフォーマンスの最適化に関する少し未熟な提案

1. 変数のスコープチェーンのアクセスノードを減らします

ここでは、変数の数を示す「検索距離」と呼ばれるランキングを定義します。未定義の変数にアクセスするときにプログラムが通過するスコープ チェーン内のノード。現在のノードで変数が見つからない場合は次のノードに飛んで検索するため、検索中の変数が次のノードに存在するかどうかも判断する必要があるためです。 「探索距離」が長くなると、より多くの「ジャンプ」動作や「判断」動作が必要となり、リソースのオーバーヘッドが大きくなり、パフォーマンスに影響を与えます。このパフォーマンスのギャップは、少数の変数検索操作ではそれほど大きなパフォーマンス上の問題を引き起こすことはありませんが、変数検索操作が複数回実行される場合、パフォーマンスの比較はより明確になります。


(function(){
 console.time()
 var find = 1      //这个find变量需要在4个作用域链节点进行查找
 function fun () {
  function funn () {
   var funnv = 1;
   var funnvv = 2;
   function funnn () {
    var i = 0
    while(i <= 100000000){
     if(find){
      i++
     }
    }
   }
   funnn()
  }
  funn()
 }
 fun()
 console.timeEnd()
})()
ログイン後にコピー

JavaScriptによるスコープ、スコープチェーン、クロージャの詳細説明(画像とテキスト)


(function(){
 console.time()
 function fun () {
  function funn () {
   var funnv = 1;
   var funnvv = 2;
   function funnn () {
    var i = 0
    var find = 1      //这个find变量只在当前节点进行查找
    while(i <= 100000000){
     if(find){
      i++
     }
    }
   }
   funnn()
  }
  funn()
 }
 fun()
 console.timeEnd()
})()
ログイン後にコピー

Mac ProのChromeブラウザで実験を行い、1億回の検索操作を実行します。

実験結果: 前者は5回実行すると平均85.599ミリ秒かかり、後者は5回実行すると平均63.127ミリ秒かかります。

2. スコープチェーン内のノード AO で変数定義が多すぎることを避ける

変数定義が多すぎるとパフォーマンスの問題が発生する主な理由は、変数を見つけるプロセスでの「判断」操作のオーバーヘッドが高いことです。 。パフォーマンスの比較には with を使用します。

(function(){
 console.time()
 function fun () {
  function funn () {
   var funnv = 1;
   var funnvv = 2;
   function funnn () {
    var i = 0
    var find = 10
    with (document) {
     while(i <= 1000000){
      if(find){
       i++
      }
     }
    }
   }
   funnn()
  }
  funn()
 }
 fun()
 console.timeEnd()
})()
ログイン後にコピー

JavaScriptによるスコープ、スコープチェーン、クロージャの詳細説明(画像とテキスト)

在mac pro的chrome浏览器下做实验,进行100万次查找运算,借助with使用document进行的延长作用域链,因为document下的变量属性比较多,可以测试在多变量作用域链节点下进行查找的性能差异。

实验结果:5次平均耗时558.802ms,而如果删掉with和document,5次平均耗时0.956ms。

当然,这两个实验是在我们假设的极端环境下进行的,结果仅供参考!

关于闭包

1.什么是闭包?

函数对象可以通过作用域链相互关联起来,函数体内的数据(变量和函数声明)都可以保存在函数作用域内,这种特性在计算机科学文献中被称为“闭包”。既函数体内的数据被隐藏于作用于链内,看起来像是函数将数据“包裹”了起来。从技术角度来说,js的函数都是闭包:函数都是对象,都关联到作用域链,函数内数据都被保存在函数作用域内。

2.闭包的几种实现方式

实现方式就是函数A在函数B的内部进行定义了,并且当函数A在执行时,访问了函数B内部的变量对象,那么B就是一个闭包。如下:

JavaScriptによるスコープ、スコープチェーン、クロージャの詳細説明(画像とテキスト)

JavaScriptによるスコープ、スコープチェーン、クロージャの詳細説明(画像とテキスト)

如上两图所示,是在chrome浏览器下查看闭包的方法。两种方式的共同点是都有一个外部函数outerFun(),都在外部函数内定义了内部函数innerFun(),内部函数都访问了外部函数的数据。不同的是,第一种方式的innerFun()是在outerFun()内被调用的,既声明和被调用均在同一个执行上下文内。而第二种方式的innerFun()则是在outerFun()外被调用的,既声明和被调用不在同一个执行上下文。第二种方式恰好是js使用闭包常用的特性所在:通过闭包的这种特性,可以在其他执行上下文内访问函数内部数据。

我们更常用的一种方式则是这样的:

//闭包实例
function outerFun () {
 var outerV1 = 10
 function outerF1 () {
  console.log(&#39;I am outerF1...&#39;)
 }
 function innerFun () {
  var innerV1 = outerV1
  outerF1()
 }
 return innerFun //return回innerFun()内部函数
}
var fn = outerFun()  //接到return回的innerFun()函数
fn()     //执行接到的内部函数innerFun()
ログイン後にコピー

此时它的作用域链是这样的:

JavaScriptによるスコープ、スコープチェーン、クロージャの詳細説明(画像とテキスト)

3.闭包的好处及使用场景

js的垃圾回收机制可以粗略的概括为:如果当前执行上下文执行完毕,且上下文内的数据没有其他引用,则执行上下文pop出call stack,其内数据等待被垃圾回收。而当我们在其他执行上下文通过闭包对执行完的上下文内数据仍然进行引用时,那么被引用的数据则不会被垃圾回收。就像上面代码中的outerV1,放我们在全局上下文通过调用innerFun()仍然访问引用outerV1时,那么outerFun执行完毕后,outerV1也不会被垃圾回收,而是保存在内存中。另外,outerV1看起来像不像一个outerFun的私有内部变量呢?除了innerFun()外,我们无法随意访问outerV1。所以,综上所述,这样闭包的使用情景可以总结为:

(1)进行变量持久化。

(2)使函数对象内有更好的封装性,内部数据私有化。

进行变量持久化方面举个栗子:

我们假设一个需求时写一个函数进行类似id自增或者计算函数被调用的功能,普通青年这样写:

 var count = 0
 function countFun () {
  return count++
 }
ログイン後にコピー

这样写固然实现了功能,但是count被暴露在外,可能被其他代码篡改。这个时候闭包青年就会这样写:

function countFun () {
 var count = 0
 return function(){
  return count++
 }
}
var a = countFun()
a()
ログイン後にコピー

这样count就不会被不小心篡改了,函数调用一次就count加一次1。而如果结合“函数每次被调用都会创建一个新的执行上下文”,这种count的安全性还有如下体现:

function countFun () {
 var count = 0
 return {
  count: function () {
   count++
  },
  reset: function () {
   count = 0
  },
  printCount: function () {
   console.log(count)
  }
 }
}
var a = countFun()
var b = countFun()
a.count()
a.count()
b.count()
b.reset()
a.printCount()  //打印:2 因为a.count()被调用了两次
b.printCount()  //打印出:0 因为调用了b.reset()
ログイン後にコピー

以上便是闭包提供的变量持久化和封装性的体现。

4.闭包的注意事项

クロージャ内の変数は他の通常の変数のようにガベージ コレクションされず、常にメモリ内に存在するため、クロージャを多用するとパフォーマンスの問題が発生する可能性があります。

以上がJavaScriptによるスコープ、スコープチェーン、クロージャの詳細説明(画像とテキスト)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、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