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);//② }
スコープチェーン
JavaScript では、各関数に独自の実行コンテキストがあり、この環境でコードが実行されると、変数オブジェクトのスコープ チェーンが作成され、順序付けされたアクセスが保証されます。変数オブジェクトに。
スコープ チェーンのフロント エンドは、現在のコード実行環境の変数オブジェクトであり、多くの場合、「アクティブ オブジェクト」と呼ばれます。オブジェクトに変数属性が含まれている場合、変数の検索は最初のチェーンのオブジェクトから開始されます。そうでない場合、検索は停止します。グローバル オブジェクトが見つかるまで上位スコープ チェーンの検索が続けられます:
閉店
基本概念
スコープはクロージャを理解するための前提条件です。クロージャとは、外部スコープ内の変数が現在のスコープ内で常にアクセスできることを意味します。
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
注: クロージャには追加の関数スコープがあるため (内部匿名関数は外部関数のスコープを持ちます)、過度に使用するとメモリ使用量が増加する可能性があります。
クロージャ内の変数
//该实例不太合理,有一定延迟因素,此处主要为了说明闭包循环中存在的问题 function timeManage() { for (var i = 0; i < 5; i++) { setTimeout(function() { console.log(i); },1000) }; }
function createClosure(){ var result = []; for (var i = 0; i < 5; i++) { result[i] = function(){ return i; } } return result; }
では、この問題をどうやって解決すればいいのでしょうか?期待される結果を匿名ラッパー (匿名の自己実行関数式) 経由で強制的に返すことができます:
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中定义一些私有变量,甚至模仿出块级作用域,但闭包在使用过程中,存在的问题我们也需要了解,这样才能避免不必要问题的出现。

ホットAIツール

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

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

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

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

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

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

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

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

ホットトピック









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

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

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

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

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

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

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

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