💭

[JS]挙動を見る②実行コンテキストを少し深める!+scope、クロージャについて

2023/08/27に公開4

実行コンテキストとscope、クロージャについて

前回の記事では、
実行コンテキストと、コールスタック、ホイスティングについて主に扱いました。
たくさん見ていただき、いいねをいただき、とても嬉しい限りです。

今回も、この記事に入り、見ていただき、ありがとうございます。
今回の記事では,前回に続き以下をメインに書きたいと思います。

  1. scopeについて
     - 概念、種類
  2. scopeと実行コンテキストとの関係性(繋がり)
  3. レキシカルスコープ
  4. クロージャについて

まずはscopeから書きますが、基本的な内容のおさらいのような形なので、
scopeは、飛ばしてもらっても構いません〜

Scopeについてと種類

英語直訳
Scope: 〔行動・思考・知覚・調査などの〕範囲、領域

JavaScriptは、特性上である
シングルスレッド実行と、スコープという概念によって、
変数や関数がどこからアクセス可能かが制御されます。具体的に見ていきましょう。

補足: シングルスレッド、マルチスレッド

今回はここはメインではないので、
以下の記事に詳しく載せてますのでご興味がありましたらご覧ください(^^)/
https://zenn.dev/airiswim/articles/14d8a9e87503b6

■ scopeとは(概念、種類)

実行中のコードから値と式が参照できる範囲のこと。
変数や関数がどこからアクセス可能かを定義する重要な概念。
JSは、シングルスレットなので、1行1行実行されるが、
その実行行から見える値と式のことをScopeという。

"実行の現在のコンテキスト"とも言えるが、これは、のちの関係性の章で。

<種類は、5個>

  • グローバルスコープ
  • スクリプトスコープ
  • 関数スコープ
  • ブロックスコープ
  • モジュールスコープ

一旦これらについて解説書きますが、もういいよって方は、ここの章は飛ばしてくださいませ!!

■ グローバルスコープ, スクリプトスコープ

■ スクリプトスコープ

スクリプト全体を包括するスコープ。
=>つまり、スクリプト内で定義された変数や関数は、どこからでもアクセス可能です。
=グローバルスコープの部分で後述するが、
 グローバルスコープとほぼ同等なため、まとめてグローバルスコープと言われることが多い。

補足:コードと検証画面
let varA = 100;;
function fn1 () {}
debugger;

debuggerで止めて、developer toolのscope(範囲)で確認するとこのように見ることができる。

■ グローバルスコープ

すべてのスクリプトや関数の外側で定義された変数や関数が含まれる。
グローバルスコープはアプリケーション全体でアクセス可能です。
= windowオブジェクトそれ自体が、グローバルスコープ!!!!

// グローバルスコープ
const globalVar = 10;

function globalFunction() {
    console.log("This is a global function.");
}

console.log(globalVar);  // 10
globalFunction();        // This is a global function.
console.log(window.globalVar);  // 10 (ブラウザ環境)
  • スクリプトスコープも、グローバルスコープもつながっててほぼ変わらないため、
    まとめてグローバルスコープと言われることが多い。
補足:windowオブジェクト

Windouオブジェクトって??

ブラウザのウィンドウ全体を表すオブジェクトだ。
JavaScript実行前に、
グローバルオブジェクトと、thisというキーワードが、
JavaScriptエンジンによって準備されます。
このグローバルオブジェクトは、ブラウザでは window オブジェクトとなる。

詳しくはこちらに解説を載せてるのでご覧ください!

■ 関数スコープ

関数スコープは、関数内で定義された変数や関数が含まれるスコープ。
関数内で宣言された変数は、その関数内でのみアクセス可能

function a(){
  // 関数スコープ
  let var = 100; 
  console.log(var);
}
a();   // 100

この関数外で、関数内の変数呼び出しは...できない
最初に記述した通り、
実行コードからその変数が見えるのか、見えないのか、ということなので、
この場合は、変数varは取得不可だ。

関数外で、関数内の変数呼び出しできない、を確認しよう。

と問えば以下は上記したコードだが、この関数の中から、

function a(){
  let var = 100; 
}
console.log(var);

これだと読み込めなくて、こんなエラーが確認できます。

■ ブロックスコープ

JSでは{}がブロックを表す。
基本的には、if文for文とともに生成されることが多い.

ブロックスコープには条件があり、
変数の宣言でletまたはconstを使うことが条件だ。
varとかだとブロックスコープは、生成されない。
=> varを使用すると、ブロックスコープが無視されてしまう。
varはスコープを持たないため

また以下のような関数宣言もブロックスコープは無視されてしまうため、

{
 function fn(){
   console.log("fnが出現!");
 }

以下のように形式を変えることで可能になります。

}
  const b = function(){
    console.log("bが出現!");
  }
  b();
}
変数の種類と違い

■ 変数(variable)と、その種類について

変数とは、"名前付けされた格納場所" であり、
繰り返し使う値を格納しておくもの。

<変数の種類について>

変数 再代入 関数スコープを生成 ブロックスコープを生成
let (ES6~) 可能
const (ES6~) 不可能
var (非推奨) 可能 ×

ひとまず、varについては、現在のJavaScriptでは非推奨となっているため、
使わないようにしよう。

varのみ、ブロックスコープを生成しない。

スコープと実行コンテキストの関係性

実行コンテキスト: コードが実行される状況
scope: 実行中のコードから見える範囲

■ まず先に...実行コンテキストについておさらい

用語補足:
直訳 >Context:前後関係、脈絡、コンテキスト、状況、環境
ここでの実行コンテキスト
コードを実行する際の文脈・状況
= コードがどのような状況で実行されているのかということ

実行コンテキストには3種類がある。

実行コンテキストの種類 説明
グローバルコンテキスト スクリプト全体の実行コンテキストであり、
ブラウザでは window オブジェクトがグローバルオブジェクトとなる。
関数コンテキスト 関数が呼び出されるたびに生成され、
その関数内での変数やスコープ、this の値を管理。
evalコンテキスト eval 関数によって実行されるコードの実行コンテキスト。
一般的には避けるべき。
evalという変数はもうすでに非推奨になっている.
例も用いて...

例を用いてわかりやすくすると...

index.html
<!DOCTYPE html>
<html lang="en">
<head>
   :
</head>
<body>
   :
    <script src="main.js"></script>
</body>
</html>

htmlファイルから、<script src="main.js"></script>でmain.jsを読み込んでいる。
このmain.jsの直下に書かれたコードが、実行される環境のことをグローバルコンテキストと呼ぶ。
スクリプト全体の実行を管理する。
以下のように、グローバルコンテキスト(今回で言うと、main.js)内で変数や関数が使える、
ということもここで理解できる。

main.js
// グローバルコンテキスト
const val = "I'm Variable";
function fn(){
}
fn();

関数コンテキストというのは、"関数が実行される際に生み出されるコンテキスト"と言ったが、
上記の例だと、関数fnが実行されているが、このこの関数のナビブロック{}の中が、
関数コンテキスト
という。

そしてその中で使えるのは、
実行中のコンテキスト内の変数と関数argumentssuperthis外部変数

main.js
const val = "I'm Variable"; // 外部変数(関数の外で宣言された変数のこと。)
function fn(){
// 関数コンテキスト!
console.log(this,arguments,val)
}
fn();

Consoleで確認するとこのようにできている。

ほんで...Scopeに当てはめると...

実行コンテキスト = コードが実行される状況
scope      = 実行中のコードから見える範囲

レキシカルスコープ

英語直訳
レキシカル: 語彙的な

主に、JSとしての歴史カルスコープは、2つの意味合いがある。

1. プログラム内で変数がどこからアクセス可能かを決定する仕組みである。
どのようにてスコープを決定するかの仕様と言える。)

コードの静的な構造に基づいて、変数がどのスコープに属するかを判断する。
この仕組みは、変数のスコープをネストした関数やブロックによって形成されるものだ。

  1. 実行中のコードからみた外部変数のこと。 (静的スコープと同意。)

言葉で見てもわからないのでコードで見ていきます。

function outerFn() {
    const outerVar = "I'm outside!";
    
    function innerFn() {
        const innerVar = "I'm inside!";
        console.log(outerVar + " " + innerVar);
    }
    
    innerFn();
}
outerFn();  // 出力: "I'm outside! I'm inside!"

この例では、

  • outer関数内に,outerVar変数
  • inner関数内に,innerVar変数 が定義されている。

inner関数はouter関数の中にネストされていますが、
inner関数からはouterVarにアクセスできます

なぜなら、レキシカルスコープがコードの静的な構造に基づいて、
ネストされた関数が外側のスコープの変数にアクセスできることを可能にしているからです。

outerFunctionからは,グローバルスコープが外部スコープとなり、参照可能になる.
innerFunctionからは、関数スコープ(outerFn)とグローバルスコープが
外部変数であり,参照可能になる

これをもう少しイメージできるように、イラストにするとこんな感じだ。

そしてこのような仕組みは、
"変数のスコープをネストした関数やブロックによって形成されるもの"と記述したが、
これを、スコープチェーンという。

スコープチェーンがあるから上記のような現象が起こる。

あるスコープが、他のスコープを含んでいる状態のことを言う。
JavaScriptにおいて、変数の参照を解決する仕組み。

クロージャー (Closure)

クロージャーは、組み合わされた(囲まれた)関数と、
その周囲の状態(レキシカル環境)への参照の組み合わせです。
言い換えれば、クロージャーは内側の関数から外側の関数スコープへのアクセスを提供します。
JavaScript では、関数が作成されるたびにクロージャーが作成されます。

簡単にいうと、レキシカルスコープの変数を、関数が使用している状態のことを言います。

レキシカルスコープでのコードを見た時も、
内側に定義されている関数(今回のinnnerFn)から、
レキシカルスコープのouterVarの参照を保持している。この状態をクロージャーという。

これがあることで何...??例

■ クロージャーを用いたプライベート変数の定義

クロージャーを用いたプライベート変数の定義

以下のようなコードがあったとします。

function createCounter() {
    let count = 0;  // プライベートな変数
    return {
        increment: function() {
            count++;
        },
        decrement: function() {
            count--;
        },
        getCount: function() {
            return count;
        }
    };
}
const counter = createCounter();
console.log(counter.getCount());  // 0
counter.increment();
counter.increment();
console.log(counter.getCount());  // 2
// console.log(counter.count);    // undefined(プライベート変数に直接アクセス不可)

このコードでは、createCounterという関数を定義している。
この関数内で変数countを定義し、
それに対するインクリメント、デクリメント、値の取得を行う関数を返している。
これにより、外部から直接count変数にアクセスすることができませんが、
返されるオブジェクトのメソッドを通じて操作が可能
だ。

const counter = createCounter();

上記のコードで、counterオブジェクトが生成される。
このオブジェクトはクロージャー内で定義された関数によってプライベートな変数countにアクセスし、
変更することができる。
しかし、直接counter.countとするとcount変数へのアクセスはできません。


ここまで見ていただき、ありがとうございます。
もし間違いや補足点などありましたら、コメントいただけたら嬉しいです(^^)/

最後に...記事を書くって...

https://zenn.dev/eldorado215/articles/8acee7204c102d

今韓国で、日本でエンジニア就職を目指し頑張っている方の振り返り投稿ですが、
私はこの記事を読んで、いろいろ思い出したり、感じることがありました。笑

技術ブログを書くって、
解説も書きながら振り返って、わからないところも見つかるし説明するつもりで作ってるから
本当に勉強にはなるんだけど...正直、時間使う
(笑)

好きだしメリットの方が大きいと感じているから、就職までは毎日続けたし、
就職した今も、1週間に1つは、記事を作成しています。

優先順位って本当に今でも大変だけど、
仕事だけにならないためにもこういうのは、大事なのかなと思います。
(別に仕事だけでもいいんだけど、それより多く学んで、使えるようにしていきたい、
上を目指したいという個人の価値観です。)

"コードを書く"は当たり前に、そこは最低限に、プラスで学んで記事にしていく。
それが誰かに影響していく。つながっていく。そうなっていたら最高じゃない??!

と思うのです。私は!!!!!笑

記事を書くのがどんなペースでも、
学んで記事でもコードでもいろんな形でアウトプットを継続していくって、
明日以降の自分に、メリットしかないんじゃないかな??

今まで間違えて書いてしまったこともあるけど、
ここのzennにいるエンジニア先輩方に指摘してもらえて学んだことも多かったし、
これからも頑張りたいなと思います。

長々書いてしまいました、もし読んでくれたなら、ありがとうございます!!!笑

明日からも、また頑張っていきましょう❤️

Bye(^^)/~~

Discussion

Hyon🇰🇷Hyon🇰🇷

ええええ!今、読みました!!!
アドバイスありがとうございます!!!!

AirichanAirichan

あくまで私の感覚というか考えですが、アドバイスにもなっていたならよかったです!!😆笑

jonsoku2jonsoku2

クロージャはReactでも大事に使ってるのでどこになぜ使うかも調べたらいいかもですよ🫡