ホームページ  >  記事  >  ウェブフロントエンド  >  経験豊富なドライバーが、JS クロージャーのさまざまな落とし穴を徹底的に理解するのに役立ちます。

経験豊富なドライバーが、JS クロージャーのさまざまな落とし穴を徹底的に理解するのに役立ちます。

angryTom
angryTom転載
2019-11-25 17:00:433251ブラウズ

経験豊富なドライバーが、JS クロージャーのさまざまな落とし穴を徹底的に理解するのに役立ちます。

#古いドライバーは、JS クロージャのさまざまな落とし穴を徹底的に理解するのに役立ちます

クロージャは JS です一般的な開発手法、クロージャとは何ですか?
#クロージャは、別の関数のスコープ内の変数にアクセスできる関数を指します。明確に言うと、クロージャは他の関数のスコープ内の変数にアクセスできる関数です。例:

function outer() {
     var  a = '变量1'
     var  inner = function () {
            console.info(a)
     }
    return inner    // inner 就是一个闭包函数,因为他能够访问到outer函数的作用域
}

多くの人は、匿名関数とクロージャの関係を理解できないでしょう。実際、クロージャはスコープの観点から定義されます。これは、inner が外側のスコープ内の変数にアクセスするため、inner はクロージャ関数です。定義は非常にシンプルですが、このポインタや変数のスコープなど落とし穴が多く、ちょっとした不注意でメモリリークを引き起こす可能性があります。問題を横に置いて、次の質問について考えてみましょう。クロージャー関数が他の関数のスコープにアクセスできるのはなぜですか?

スタックの観点から js 関数を見る

基本変数 の値は通常スタック メモリに格納されますが、オブジェクト型変数の値はヒープ メモリに格納され、スタック メモリには対応する空間アドレスが格納されます。基本データ型: 数値、ブール値、未定義、文字列、Null。

var  a = 1   //a是一个基本类型
var  b = {m: 20 }   //b是一个对象

対応するメモリ ストレージ:

経験豊富なドライバーが、JS クロージャーのさまざまな落とし穴を徹底的に理解するのに役立ちます。b={m:30} を実行すると、ヒープ内に新しいオブジェクト {m:30} が存在します。メモリ 、スタック メモリ内の b は新しい空間アドレス ({m: 30} を指す) を指し、ヒープ メモリ内の元の {m: 20} はプログラム エンジンによってガベージ コレクションされ、メモリ領域が節約されます。 js 関数もオブジェクトであり、ヒープ メモリとスタック メモリにも格納されることがわかっています。変換を見てみましょう:

var a = 1;
function fn(){
    var b = 2;
    function fn1(){
        console.log(b);
    }
    fn1();
}
fn();

経験豊富なドライバーが、JS クロージャーのさまざまな落とし穴を徹底的に理解するのに役立ちます。**

##スタックは先入れ後出しのデータ構造です:

1 fn を実行する前は、グローバル実行環境 (ブラウザーがウィンドウ スコープ) にあり、変数 a が存在します。グローバル スコープ;

2 「fn」と入力します。このとき、スタック メモリは fn の実行環境をプッシュします。この環境には変数 b と関数オブジェクト fn1 が含まれます。ここで、独自の実行で定義された変数にアクセスできます。環境とグローバル実行環境

3 fn1 と入力します。この時点で、スタック メモリは fn1 の実行環境をプッシュします。そこには他の変数は定義されていませんが、fn の変数とその実行環境にアクセスできます。プログラムが変数にアクセスすると、変数は一番下のスタックに移動するため、グローバル実行環境に対応する変数がないことが判明すると、プログラムはアンダーファイン エラーをスローします。

4 fn1() が実行されると、fn1 の実行環境が cup によって破壊され、その後 fn() が実行されると、fn の実行環境も破壊され、グローバル実行環境だけが残ります。現在、b 変数と fn1 関数オブジェクトはなく、a と fn (関数宣言の範囲はウィンドウの下にあります) のみです。

**

関数内の変数へのアクセスは、関数スコープ チェーン 変数が存在するかどうか。関数スコープ チェーンは、関数が配置されている実行環境スタックに従ってプログラムによって初期化されるため、上記の例では、変数 b を fn1 に出力し、対応する fn 実行環境を見つけます。 fn1 変数のスコープチェーンに応じて b.したがって、プログラムが関数を呼び出すと、次の作業が行われます。実行環境、初期関数スコープ チェーン、および引数パラメーター オブジェクトを準備します

元の例のアウターとインナー

function outer() {
     var  a = '变量1'
     var  inner = function () {
            console.info(a)
     }
    return inner    // inner 就是一个闭包函数,因为他能够访问到outer函数的作用域
}
var  inner = outer()   // 获得inner闭包函数
inner()   //"变量1"

プログラムが var inner = inner() の実行を終了しても、実際には、その内部の変数 a はまだ inner の関数スコープ チェーンによって参照されているため、outer の実行環境は破壊されません。 、これは、内部と外部の実行環境が破棄され、調整される場合にのみ、書籍「JavaScript Advanced Programming」では次のように推奨されています: クロージャは、クロージャを含む関数のスコープを保持するため、他の関数よりも多くのコンテンツを占有するため、過度の使用は避けてください。クロージャの数が多いと、過剰なメモリ使用量が発生します。

これで、クロージャ、対応するスコープとスコープ チェーンが理解できました。トピックに戻ります。

落とし穴 1: 参照される変数は変更される可能性があります

function outer() {
      var result = [];
      for (var i = 0; i<10; i++){
        result.[i] = function () {
            console.info(i)
        }
     }
     return result
}
結果内の各クロージャー関数は、対応する数値 1、2、3、4、...、10 を出力するように見えますが、各クロージャー関数は外部の実行環境で変数 i にアクセスするため、これは実際には当てはまりません。 i、ループが終了すると i は 10 になったので、各クロージャ関数が実行され、結果は 10, 10, ..., 10

この問題を解決するにはどうすればよいですか?

function outer() {
      var result = [];
      for (var i = 0; i<10; i++){
        result.[i] = function (num) {
             return function() {
                   console.info(num);    // 此时访问的num,是上层函数执行环境的num,数组有10个函数对象,每个对象的执行环境下的number都不一样
             }
        }(i)
     }
     return result
}

落とし穴 2: これは問題を示しています

var object = {
     name: &#39;&#39;object",
     getName: function() {
        return function() {
             console.info(this.name)
        }
    }
}
object.getName()()    // underfined
// 因为里面的闭包函数是在window作用域下执行的,也就是说,this指向window
落とし穴 3: メモリ リークの問題

function  showId() {
    var el = document.getElementById("app")
    el.onclick = function(){
      aler(el.id)   // 这样会导致闭包引用外层的el,当执行完showId后,el无法释放
    }
}
// 改成下面
function  showId() {
    var el = document.getElementById("app")
    var id  = el.id
    el.onclick = function(){
      aler(id)   // 这样会导致闭包引用外层的el,当执行完showId后,el无法释放
    }
    el = null    // 主动释放el
}
ヒント 1: クロージャを使用して再帰呼び出しの問題を解決する

function  factorial(num) {
   if(num<= 1) {
       return 1;
   } else {
      return num * factorial(num-1)
   }
}
var anotherFactorial = factorial
factorial = null
anotherFactorial(4)   // 报错 ,因为最好是return num* arguments.callee(num-1),arguments.callee指向当前执行函数,但是在严格模式下不能使用该属性也会报错,所以借助闭包来实现
// 使用闭包实现递归
function newFactorial = (function f(num){
    if(num<1) {return 1}
    else {
       return num* f(num-1)
    }
}) //这样就没有问题了,实际上起作用的是闭包函数f,而不是外面的函数newFactorial
** ヒント 2: クロージャを使用してブロックレベルのスコープを模倣する**

es6以前は、var を使用して変数を定義するときに変数の昇格の問題がありました。例:

for(var i=0; i<10; i++){
    console.info(i)
}
alert(i)  // 变量提升,弹出10

//为了避免i的提升可以这样做
(function () {
    for(var i=0; i<10; i++){
         console.info(i)
    }
})()
alert(i)   // underfined   因为i随着闭包函数的退出,执行环境销毁,变量回收

もちろん、現在はほとんどの変数は es6 の let と const を使用して定義されています。

この記事はここで終了しました。さらに興味深いコンテンツについては、PHP 中国語 Web サイトの
JavaScript ビデオ チュートリアル

列に注目してください。

以上が経験豊富なドライバーが、JS クロージャーのさまざまな落とし穴を徹底的に理解するのに役立ちます。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はjianshu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。