ホームページ > 記事 > ウェブフロントエンド > 高度なフロントエンドの基礎 (6): Chrome デベロッパー ツールで関数呼び出しスタック、スコープ チェーン、クロージャを観察する
フロントエンド開発には、ブレークポイント デバッグと呼ばれる非常に重要なスキルがあります。
Chrome の開発者ツールでは、ブレークポイント デバッグを通じて、JavaScript の実行プロセスを段階的に観察し、関数呼び出しスタック、スコープ チェーン、変数オブジェクト、クロージャ、this などの重要な情報を直感的に認識できます。したがって、ブレークポイント デバッグは、コード エラーを迅速に特定し、コードの実行プロセスを迅速に理解する上で非常に重要な役割を果たします。これは、フロントエンド開発者にとって不可欠な高度なスキルでもあります。
もちろん、JavaScript の基本概念 (実行コンテキスト、変数オブジェクト、クロージャ、this など) について十分に理解していないと、ブレークポイントのデバッグを完全にマスターするのは難しいかもしれません。しかし幸いなことに、以前の記事でこれらの概念について詳しく概要を説明したので、誰でもこのスキルを習得するのは比較的簡単であるはずです。
これとクロージャについて誰もがより深く理解できるように、また、前の記事のクロージャの定義には少し偏りがあるため、この記事ではブレークポイントのデバッグにクロージャ関連の例を使用して、誰もが理解できるように学習します。時間内に修正を加えます。ここで自分の間違いを認め、皆を誤解させました。
1. 基本概念の確認
関数が実行のために呼び出されるとき、現在の関数の実行コンテキストが作成されます。実行コンテキストの作成フェーズ中に、変数オブジェクト、スコープ チェーン、クロージャ、およびこのポインタがそれぞれ決定されます。一般に、JavaScript プログラムには複数の関数があり、JavaScript エンジンは関数呼び出しスタックを使用してこれらの関数の呼び出し順序を管理します。関数呼び出しスタックの呼び出しシーケンスは、スタック データ構造と一致しています。
2. ブレークポイントデバッグツールについて知る
Chrome ブラウザの最新バージョン (使用している古いバージョンが私のものと同じかどうかはわかりません) で、 Chrome ブラウザの開発者ツールを起動します。
ブラウザの右上隅にある 3 つの縦のドット -> その他のツール -> 開発者ツール -> ソース
インターフェイスは次のとおりです。
私のデモでは、コードを app.js に配置し、index.html に導入しました。ここでは、スクリーンショットの赤い矢印だけに注目する必要があります。左端の上にはアイコンが並んでいます。関数を使用することで、関数の実行順序を制御できます。左から右へ:
● スクリプト実行の再開/一時停止
スクリプト実行の再開/一時停止
● 次の関数呼び出しをステップオーバーします
実際のパフォーマンスは、関数が見つからない場合、次のステップが実行されます。関数に遭遇すると、関数に入らずに次のステップが直接実行されます。
● 次の関数呼び出しにステップインします
ステップインの実際のパフォーマンスは、関数が見つからない場合に次のステップが実行されます。関数が見つかると、関数実行コンテキストに入ります。
● 現在の関数からステップアウトします
現在の関数からジャンプします
● ブレークポイントを非アクティブ化します
ブレークポイントを非アクティブ化します
● 例外で一時停止しないでください
例外キャプチャを一時停止しないでください
その中には、ステップオーバー、ステップイン、およびstep out は 3 つの操作の中で最もよく使用する操作です。
上の図の左側にある 2 番目の赤い矢印は、関数呼び出しスタック (call Stack) を指しており、コードの実行中の呼び出しスタックの変化がここに表示されます。
左側の 3 番目の赤い矢印はスコープ チェーン (Scope) を指しており、現在の関数のスコープ チェーンが表示されます。ここで、Local は現在のローカル変数オブジェクトを表し、Closure は現在のスコープ チェーン内のクロージャを表します。ここでスコープ チェーン表示を利用すると、例の中で誰がクロージャであるかを直感的に判断でき、クロージャを深く理解するのに非常に役立ちます。
3. ブレークポイントの設定
コード行数が表示されている箇所をクリックしてブレークポイントを設定します。ブレークポイント設定には次の特徴があります:
別の変数が宣言されている行 (値が割り当てられていない場合) または関数が宣言されている行にはブレークポイントを設定できません。
ブレークポイントを設定してページを更新すると、ブレークポイントの位置で一時停止されるまで JavaScript コードが実行され、上で紹介した操作を使用してデバッグを開始できます。
複数のブレークポイントを設定すると、Chrome ツールは最も古いブレークポイントから実行を開始するように自動的に決定するため、通常は 1 つのブレークポイントを設定するだけです。
IV. 例
次に、いくつかの例を使用して、ブレークポイント デバッグ ツールを使用して、デモ関数が実行中にどのように動作するかを確認します。
// demo01 var fn; function foo() { var a = 2; function baz() { console.log( a ); } fn = baz; } function bar() { fn(); } foo(); bar(); // 2
さらに読み進める前に、立ち止まって考えてみましょう。この例のクロージャは誰でしょうか?
这是来自《你不知道的js》中的一个例子。由于在使用断点调试过程中,发现chrome浏览器理解的闭包与该例子中所理解的闭包不太一致,因此专门挑出来,供大家参考。我个人更加倾向于chrome中的理解。
● 第一步:设置断点,然后刷新页面。
● 第二步:点击上图红色箭头指向的按钮(step into),该按钮的作用会根据代码执行顺序,一步一步向下执行。在点击的过程中,我们要注意观察下方call stack 与 scope的变化,以及函数执行位置的变化。
一步一步执行,当函数执行到上例子中
我们可以看到,在chrome工具的理解中,由于在foo内部声明的baz函数在调用时访问了它的变量a,因此foo成为了闭包。这好像和我们学习到的知识不太一样。我们来看看在《你不知道的js》这本书中的例子中的理解。
书中的注释可以明显的看出,作者认为fn为闭包。即baz,这和chrome工具中明显是不一样的。
而在备受大家推崇的《JavaScript高级编程》一书中,是这样定义闭包。
这里chrome中理解的闭包,与我所阅读的这几本书中的理解的闭包不一样。具体这里我先不下结论,但是我心中更加偏向于相信chrome浏览器。
我们修改一下demo01中的例子,来看看一个非常有意思的变化。
// demo02 var fn; var m = 20; function foo() { var a = 2; function baz(a) { console.log(a); } fn = baz; } function bar() { fn(m); } foo(); bar(); // 20
这个例子在demo01的基础上,我在baz函数中传入一个参数,并打印出来。在调用时,我将全局的变量m传入。输出结果变为20。在使用断点调试看看作用域链。
是不是结果有点意外,闭包没了,作用域链中没有包含foo了。我靠,跟我们理解的好像又有点不一样。所以通过这个对比,我们可以确定闭包的形成需要两个条件。
● 在函数内部创建新的函数;
● 新的函数在执行时,访问了函数的变量对象;
还有更有意思的。
我们继续来看看一个例子。
// demo03 function foo() { var a = 2; return function bar() { var b = 9; return function fn() { console.log(a); } } } var bar = foo(); var fn = bar(); fn();
在这个例子中,fn只访问了foo中的a变量,因此它的闭包只有foo。
修改一下demo03,我们在fn中也访问bar中b变量试试看。
// demo04 function foo() { var a = 2; return function bar() { var b = 9; return function fn() { console.log(a, b); } } } var bar = foo(); var fn = bar(); fn();
这个时候,闭包变成了两个。分别是bar,foo。
我们知道,闭包在模块中的应用非常重要。因此,我们来一个模块的例子,也用断点工具来观察一下。
// demo05 (function() { var a = 10; var b = 20; var test = { m: 20, add: function(x) { return a + x; }, sum: function() { return a + b + this.m; }, mark: function(k, j) { return k + j; } } window.test = test; })(); test.add(100); test.sum(); test.mark(); var _mark = test.mark(); _mark();
注意:这里的this指向显示为Object或者Window,大写开头,他们表示的是实例的构造函数,实际上this是指向的具体实例
上面的所有调用,最少都访问了自执行函数中的test变量,因此都能形成闭包。即使mark方法没有访问私有变量a,b。
我们还可以结合点断调试的方式,来理解那些困扰我们很久的this指向。随时观察this的指向,在实际开发调试中非常有用。
// demo06 var a = 10; var obj = { a: 20 } function fn () { console.log(this.a); } fn.call(obj); // 20
更多的例子,大家可以自行尝试,总之,学会了使用断点调试之后,我们就能够很轻松的了解一段代码的执行过程了。这对快速定位错误,快速了解他人的代码都有非常巨大的帮助。大家一定要动手实践,把它给学会。
最後に、上記の探索状況に基づいて、クロージャをもう一度まとめてみましょう:
クロージャは、関数が呼び出されて実行されたときにのみ作成されることが確認されます。
クロージャの形成は、スコープ チェーンのアクセス順序に直接関係します。
クロージャは、内部関数が上位スコープ チェーンの変数オブジェクトにアクセスする場合にのみ形成されるため、クロージャを使用して関数内の変数にアクセスできます。
Chrome で理解されるクロージャは、「あなたの知らない JS」や「JavaScript 高度なプログラミング」で理解されるクロージャとは大きく異なります。個人的には、Chrome を信じる傾向があります。ここで結論を急ぐつもりはありません。私のアイデアに基づいて検討した後、ご自身で確認してください。以前の記事で、本から学んだことを基に定義を作成しましたが、間違っていたと思います。皆さん、申し訳ありません。