#JSの実行環境ってどうなっているの?
JSは主にブラウザで動作しています。
そのブラウザの中にJavaScript Engineが入っており、その中で動作するようになっています。
##JavaScript Engine
JSエンジンにはいくつか種類がありますが今シェアが多いのはV8というエンジンみたいです。
###そのJSエンジンの中には何があるのか?
ECMAScriptとWebAPIsが含まれています。
・ECMAScript
JSの言語仕様
・WebAPIs
DOMAPIやFetchAPIなどのJSの便利な機能が提供されている
このWebAPIsを通してブラウザを操作している
#実行前にJSが用意するもの
・コード
あなたが書いたもの
・グローバルオブジェクト(Windowオブジェクト)
この中にWebAPIが含まれる
・this
オブジェクトへの参照を保持しているもの(コンテキストによって変化する)
#JSの実行コンテキスト
コードを実行する際の文脈や状況のこと
要するにブラウザで実行する際のコード、グローバルオブジェクト(Windowオブジェクト)、thisを全部まとめたもの,
だそうです。
・グローバルコンテキスト(一番外側のコード)
実行中のコンテキスト内の変数と関数 + グローバルオブジェクト + this
・関数コンテキスト(関数内のコード)
実行中のコンテキスト内の変数と関数 + arguments + super + this + 外部変数(関数の外側の変数)
・モジュールコンテキスト
実行中のコンテキスト内の変数と関数 + グローバルオブジェクト
thisが使えないとこに注意
#コールスタック
処理がたどってきたコンテキストの積み重ねのこと
処理の進行する際にコンテキストが生成されるようになります。
function a(){
console.log('a');
b();
}
function b(){
console.log('b');
c();
}
function c(){
console.log('c');
}
a();
このような場合、a()⇨function a()⇨function b()⇨function c()の順番で実行されます。
そのため、こんなイメージです。
処理は最後から行われますので、一番上のfunction c()から処理され消滅していきます。
#ホイスティング
コンテキスト内で宣言した変数や関数の定義をコード実行前にメモリに配置することをいいます
これにより、呼び出し元より下に変数や関数を定義することができるようになります
a();
function a(){
console.log('a')
}
これは「a」が出力されます
varホイスティングの初期値には、undefinedになります。
console.log(b);
var b = 'b'
これは「undefined」が出力されます
let・const ホイスティングの初期値が初期化されません。
console.log(c);
console.log(d);
let c = 'c'
const d = 'd'
これは「エラー」が出る
変数の場合、変数の定義(宣言)はホイスティングされるが、値の代入はコード上で行なっています。
そのため、「undefined」や「エラー」が表示されることになるのです。
つまり、入れ物だけが先に用意されているイメージですね。
#スコープ
###グローバルスコープ
<script>
var a = 'a';
function b(){
}
</script>
scriptタグの直下(一番外側)のvar変数や関数は、グローバルスコープのwindowオブジェクトのプロパティとして保持されます。
###スクリプトスコープ
<script>
let a = 'a';
const b = 'b';
</script>
scriptタグの直下(一番外側)のlet変数やconstはスクリプトスコープとなります。
###関数スコープ
有効範囲:関数内
function a(){
let b = 'b';
cosole.log(b);
}
a();
これは、bが出力されます。
関数a()の中で変数bを定義し、出力しています。
function a(){
let b = 'b';
}
cosole.log(b);
これはエラーになります。
関数の外から関数内で定義された変数bを呼ぼうとした為です。
###ブロックスコープ
有効範囲:{}の中(if文など)
{
let a = 'a';
const b = 'b';
cosole.log(a);
cosole.log(b);
}
aとbが出力されます
{
let a = 'a';
const b = 'b';
}
cosole.log(a);
cosole.log(b);
これはエラーになります
ちなみに、var変数や関数はブロックスコープにならない為{}の外から呼び出しても使用できてしまう事には注意です。
関数にブロックスコープを適用するには関数式を活用しましょう
const a = function (){
let b = 'b';
console.log(b);
}
###モジュールスコープ
モジュールとはソースコードを機能ごとに分割し、メンテナンスしやすくする仕組みのことですね。
んでモジュールスコープとは、モジュールを使用した際のスクリプトスコープに当たる部分だそうです。
つまり、モジュール内で定義された変数や関数はそのモジュール内からしか参照できない。という事です。
###レキシカルスコープ
コードを書く場所によって参照できる変数が変わるスコープ
記述した時点で範囲が決定する為、静的スコープともいう。。。
こんな感じに自身のスコープより外側のスコープの変数は参照できます。(外部スコープとも言う)
しかし、内側はできません。
また、今回の例のようにスコープが複数階層で連なっている状態をスコープチェーンといいます。
スコープチェーンでは、内側のスコープから順に変数を探していくことになります。
#スコープと実行コンテキストの関係
- 実行コンテキスト:実行する環境
- スコープ:実行中のコードから見える範囲
グローバルコンテキストの中には
グローバルスコープ、グローバルオブジェクト、this が含まれます。
関数コンテキストの中に
実行中のコンテキスト内の変数・関数、arguments、super、this、外部変数(レキシカルスコープ) が含まれています。
#クロージャー
レキシカルスコープの変数を関数が使用している状態のことです。
このクロージャーの考え方を使い、プライベート変数や動的な関数の生成を行う事ができるようになります。
スコープの使い方応用編とでも言えばいいでしょうか
##①プライベート変数の定義
外部からはアクセスできない変数のことです。
まずは普通の変数から見ていきます。
let num = 0;
function increment(){
num = mum++;
console.log(num);
}
increment(); //1が出力される
increment(); //2が出力される
この場合、変数numはグローバル変数となるため、何処からでも変更が可能になります。
では、関数スコープの中で変数を生成してみましょう。
function increment(){
let num = 0;
num = mum++;
console.log(num);
}
increment(); //1が出力される
increment(); //1が出力される
関数の中に変数numを入れるだけでは、関数increment()を呼び出すたびに変数numを初期化してしまいます。
それでは、クロージャーの考え方を使用し、プライベート変数を生成します。
function incrementFactory(){
let num = 0;
function increment(){
num = mum++;
console.log(num);
}
return increment;
}
const increment = incrementFactory();
increment(); //1が出力される
increment(); //2が出力される
これで、変数numは外部からアクセスできず、関数increment()からでしか変更できなくなりました。
###どのように処理されるでしょうか?
先ず、const increment = incrementFactory();
この関数式で関数incrementFactory()を呼び出し、実行します。
関数式は定義したタイミングで一度だけ実行され、その返り値が、変数に格納されます。
つまり、**const increment = incrementFactory();**を実行すると、
関数incrementFactory()が処理され、変数numが生成されます。
さらに、関数increment()が返され、const incrementに格納されます。
レキシカルスコープの説明で、自身のスコープより外側のスコープの変数は参照できるが内側はできない、とあったように関数スコープ内で定義された変数numは外側から参照することはできません。
つまり、関数incrementFactory()内からでしか変更できないと言うことです。
そして、const incrementは関数incrementFactory()から返却された関数increment()が入っています。
よってconst incrementを実行する事で**関数incrementFactory()内の関数increment()**を実行することになるため、変数numを変更する事ができるわけです。
##②動的な関数の生成
関数を生成する関数に渡す値によって、生成される関数が変化する
function incrementFactory(num){
function increment(val){
return num + val;
console.log(num);
}
return increment;
}
const incrementA = incrementFactory(10);
incrementA(10); //20が出力される
const incrementB = incrementFactory(100);
increment(100); //200が出力される
関数incrementFactory()を呼び出す際に引数で渡す値によって定義されるプライベート変数を別々に管理できるようになります。
#即時関数
関数定義と同時に一度だけ実行される関数
(function (引数){
処理
})(実引数)
基本はこのように記述するようになります。
let AAA = (function (num){
return num * 2;
})(10)
関数式で書くとこうなります。
ちなみにどうなっているのかと言うと
function AAA(num){
return num * 2;
}
AAA(10);
この関数AAA()を呼び出している部分を()で囲んで(AAA)(10)としているようなものです。
こうすることで、()で囲われた部分は一つの塊として扱うようになります。
宣言 | 再宣言 | 再代入 | スコープ | 初期化 |
---|---|---|---|---|
let | ❌ | ⭕️ | ブロックスコープ | ❌ |
const | ❌ | ❌ | ブロックスコープ | ❌ |
var | ⭕️ | ⭕️ | 関数スコープ | undefined |