実行コンテキスト
1.実行コンテキストは何ですか?
(1)コンパイルと構文解析の段階、これは前処理の段階とも言えますね:
この段階で、var型の変数はすべてコンテキストに格納され、undefinedという値が代入されます。
さらに、宣言された関数をスタックに格納し、関数本体をスタックに代入します。
this "オブジェクトもあります。
(ii) 実施段階:
語彙環境の変数への値の割り当て
その変数が辞書環境に存在しない場合は、その変数を辞書環境に追加し、適切な値を代入します。
2.変数名と関数名の衝突
構文解析の段階では、2つの関数が同じ名前の場合、引数の数が異なっていても、後の関数が前の関数を上書きします。
test('second')
function test(){
console.log('first')
}
function test(val){
console.log(val)
}
//second
実施段階で
宣言的な方法で定義された関数は、実行段階では処理されません。
関数を宣言的に定義する方法は、定義された関数や属性を上書きしません。
後から割り当てられた属性は、最初に割り当てられた属性の内容を上書きします。
優先順位
関数宣言 > varで定義された変数関数が先に宣言され、変数が後から宣言された場合、名前は同じですが、変数は関数を上書きせず、無視します。
function test(){
console.log('関数宣言')
}
var test = function(){
console.log('変数の宣言')
}
test()
//関数宣言
3.ケース
console.log(a)
console.log(b)
console.log(fn1)
console.log(fn2)
var a = 'cat'
b = 'dog'
fn1()
fn2()
function fn1(){
console.log('pig')
}
var fn2 = ()=>{
console.log('sheep')
}
//undefined
//
//
//undefined
//pig
//
------ まず、コンパイルと解析の段階で、変数 a、関数 fn1、変数 fn2 が宣言されますが、変数 b は宣言されません。次に実行段階に入り、まず、代入されていないので未定義の変数 a と、宣言されていないのでエラーを報告している変数 b が出力されます。console.log('pig')}、この時点ではfn2には値が代入されていないため、これも未定義となり、実行が続行されます。変数aはcatに代入され、変数bは定義されていないため無視されます。関数fn1が宣言され、実行できるようになり、pigが正常に出力され、実行が続行されます。この時点ではfn2は未定義であるため、エラーがスローされます。変数 fn2 は関数メソッドに代入され、上記の環境のコードが実行されます。
function fun(a,b){
console.log(a)
console.log(b)
var a=20
function b(){
console.log("b関数が実行される");
}
}
fun(1,2)
//1
//
------ 宣言された関数は変数よりも優先順位が高いため、b( ){...}は変数bを上書きします。fun(1,2)行が実行されると、aの値は1に変わり、実行フェーズでは宣言的な方法で定義された関数はそれ以上処理されないため、bの出力は関数本体b( ){...}のままとなります。
II.スコープとスコープチェーン
1.スコープとは何ですか?
スコープは独立した領域であり、変数が漏れたり公開されたりすることはありません。言い換えれば、スコープの最大の用途は変数を分離することであり、異なるスコープで同じ名前の変数が衝突しないようにすることです。ES6の登場により、スコープはグローバルスコープ、関数スコープ、ブロックレベルスコープに分けられました。
グローバル・スコープ:
グローバル・スコープは1つしかなく、ローカル・スコープの変数にアクセスすることはできません。
スクリプトタグに直接書かれたJSコードは、グローバルにスコープされます。
または別のJSファイル
グローバルスコープは、ページが開かれたときに作成され、ページが閉じられたときに破棄されます。
グローバル・スコープにはグローバル・オブジェクト・ウィンドウがあり、直接
グローバルスコープでは、作成された変数はすべてウィンドウオブジェクトのプロパティとして保存され、作成された関数はすべてウィンドウオブジェクトのメソッドとして保存されます。
機能スコープ:
- ご覧のように、関数内部のスコープ
- 関数内のコードは、その関数内でのみ動作します。
- 関数スコープは関数が呼び出されたときに作成され、関数が実行されたときに破棄されます。
- 各関数呼び出しは、互いに独立した新しい関数スコープを作成します。
- グローバルスコープにある変数には、関数内からアクセスできます。
ブロックレベルのスコープ:
- ES6では新たにletコマンドでブロックレベルのスコープが追加され、外側のスコープから内側のスコープにアクセスすることができなくなりました。外側のスコープと内側のスコープが同じ変数名を使っていても、お互いに干渉することはありません。
<script>
var num = 10
var test = 99
function fn(){
var num = 20
test = 100
console.log(num)
console.log(test)
}
fn()
console.log(num)
console.log(test)
//20
//100
//10
//100
</script>
グローバルスコープは変数numを定義し、関数スコープも変数numを定義します。関数スコープは外部から隔離されているため、変数名は同じですが衝突しません。そのため、最初のプリントは20となり、テスト変数はグローバルスコープにあり、関数内部はフルスコープの変数にアクセスできるため、テストのグローバルスコープには100という値が代入され、次に100とプリントします。関数スコープ外部は関数内部の変数numにアクセスできないため、10となります。そのため、100と表示され、関数スコープの外部は関数内部の変数にアクセスできないので、次に表示されるのはグローバルスコープの変数numの10です。
2.グローバル変数とローカル変数
JavaScriptでは、変数のスコープによってグローバル変数とローカル変数の2種類があります:
グローバル変数:
- グローバル・スコープで宣言された変数をグローバル変数と呼びます。
- グローバル変数はグローバルに使用可能で、ローカルスコープの変数はグローバルスコープでアクセスできません。
- グローバル変数を作る最初の方法:グローバル・スコープでvarによって宣言された変数はグローバル変数です。
- グローバル変数を作成する2つ目の方法:varキーワードを使わずに、関数内で変数に直接値を代入する場合もグローバル変数です。
ローカル変数:
- ローカルスコープで宣言された変数はローカル変数と呼ばれます。
- ローカル変数は関数内でのみ使用でき、グローバル変数はローカルスコープでアクセスできます。
- 関数内でvarで宣言された変数はローカル変数です。
- 関数の正式なパラメータは、実際にはローカル変数です。
グローバル変数とローカル変数の違い:
- グローバル変数:どの場所でも使用可能、グローバル変数はブラウザを閉じたときにのみ破棄されます。
- ローカル変数: 関数内でのみ使用可能で、その変数が存在するコードブロックが実行されるときに初期化されます。
3.スコープチェイニング
コードである限りスコープがあり、関数の中に書かれたものはローカルスコープと呼ばれます;
関数の中に関数があれば、そのスコープの中に別のスコープが生まれます;
関数スコープで変数を操作する場合、まず自分のスコープを探し、そこにあればそのまま使用し、なければ上位のスコープを探します。グローバルスコープにもない場合は、エラーが報告されます。
内部関数が外部関数からアクセス可能な変数にアクセスできるというこのメカニズムに基づき、内部関数がアクセス可能なデータを決定するための連鎖検索は、関数スコープの連鎖と呼ばれます。
var a = 1;
function fn1(){
var a=2;
var b='22';
fn2();
function fn2(){
var a =3;
fn3();
function fn3(){
var a=4;
console.log(a);
console.log(b);
}
}
}
fn1();
//4
//22
クロージャ
1.クロージャーとは何ですか?
私たちが知っているように、jsのスコープは2種類に分かれて、グローバルとローカルは、スコープチェーンの身近な知識に基づいて、jsのスコープ環境内の変数にアクセスする権利は、内側から外側にあることを知って、内側のスコープは、変数の下に現在のスコープを取得することができ、現在の変数の下に外側のスコープの現在のスコープが含まれて取得することができ、その逆は、することはできません、つまり、外側のスコープでは、変数の下に内側のスコープを取得することはできません、また、異なる関数のスコープでも、お互いの変数にアクセスすることはできません。言い換えれば、外側のスコープで内側のスコープの変数にアクセスすることはできませんし、異なる関数のスコープで変数にアクセスすることもできません。クロージャの本質は、関数を別の関数の内部に作成することです。
2.クロージャの特性
ネストされた関数
(ii) 関数は、関数外のパラメータや変数を参照することができます。
パラメータと変数は、ゴミ収集メカニズムによってリサイクルされません。
3.ケーススタディ
function a(){
var name = 'liang'
return function(){
return name
}
}
var b = a()
console.log(b())
//liang
このコードでは、a()の戻り値はa()スコープ内にある無名関数なので、a()スコープ配下の変数名の値を取得し、この値を戻り値としてグローバルスコープ配下の変数bに代入することで、グローバル変数配下のローカル変数の値を実現しています。
function fn(){
var num = 3
return function(){
var n = 0
console.log(++n)
console.log(++num)
}
}
var fn1 = fn()
fn1() //1 4
fn1() //1 5
通常、関数 fn が実行されると、変数とともに破棄されるはずですが、この場合、fn1 の戻り値として無名関数が代入されており、fn1=function(){var n = 0 ...} と等価であり、無名関数は fn 内で変数 num を参照しているため、変数 num を破棄することはできません。 fn1=function(){var n = 0 ...}と等価であり、無名関数は fn 内で変数 num を内部参照するため、変数 num を破棄することはできず、変数 n は fn1 が呼び出されるたびに生成されるため、fn1 が実行を終了するたびに自分自身の変数も一緒に破棄され、num が残ることになります!
タイマーをクロージャと組み合わせるとどうなりますか?forループを書き、現在のループ・カウントを順番に出力させます。
for(var i = 0 ; i < 5 ; i++){
setTimeout(()=>{
console.log(i)
},1000)
}
//5 5 5 5 5
予想では1 2 3 4 5と出力されるはずなのに、結果は5と5回出力されています。それは、jsがシングルスレッドであるため、forループタイマsetTimeoutの実行では、実行を待ってタスクキューにキューに配置されていることが判明し、forループの処理を待っている間、実装では、forループが完了したときにsetTimeoutが実行できるようになるまで待って、iの値が5にプログラムされているので、5 5の出力は、その後、期待される結果を達成するために、どのようにこのコードを変更する必要があります?期待される結果は、このコードを変更する方法する必要がありますか?
for(var i = 0 ; i < 5 ; i++){
(function(i){
setTimeout(()=>{
console.log(i)
},i*1000)
})(i)
}
//0
//1
//2
//3
//4
4.閉鎖のメリットとデメリット
長所:
カプセル化を達成するために、関数の中で変数のセキュリティを保護し、競合という名前の他の環境に変数を防ぐために
キャッシングが可能な変数をメモリ上に保持します。
匿名自己実行関数によるメモリ消費量の削減
欠点:
参照されたプライベート変数は破棄できないため、メモリ消費量が増え、メモリリークを引き起こしやすくなります;
第二に、クロージャはクロススコープ・アクセスを伴うため、性能低下を招きますが、クロススコープ変数をローカル変数に格納し、ローカル変数に直接アクセスすることで実行速度への影響を軽減することができます。