1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaScriptのメカニズム(スコープ)

Last updated at Posted at 2025-02-21

参考

【JS】ガチで学びたい人のためのJavaScriptメカニズム

あらすじ

現場でrailsを触ってlaravelを触って今はtypescriptとreact-routerで実装することが多くなってきたが、今までJavascriptをがっつり勉強してなかったので復習も兼ねて上記講座を受けてみようと思った。

とある先輩から「エンジニアじゃない人間が聞いてもわかる程度の説明にしろ」という言葉をいただいたので、できるだけ頑張る。

スコープについて

実行中のコードから値と式が参照できる範囲

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

グローバルスコープとスクリプトスコープについて

let a = 0;
var b =0;
function c(){}

こうするとchrome開発者ツールのsourceのScope欄に

  • Script a: 0
  • Global: window (b: 0....c: f c())
    のような形で入る。このことから
    console.log(b)console.log(window.b)と同義。
    グローバルオブジェクトはwindowを省略できる。
    window自体がグローバルスコープ
    一般的にはスクリプトスコープもグローバルスコープと呼ばれる

関数スコープとブロックスコープ

関数スコープ

function f(){
    //この部分が関数スコープ
}
function f(){
    const b = 0;
}
console.log(b);

// Uncaught ReferenceError

これは関数スコープ内でbが宣言されているため、外では使えない。

ブロックスコープとは

{
//ここのこと
}

ルールとしてはletまたはconstを使うこと
varを使うとブロックスコープが適用されない。
関数もブロックスコープが適用されないので注意。
↑厳格モード、モジュールで使用可能

{
    const b = 0;
    console.log(b);

    // 出力
    // 0

    const c = 0;
}
console.log(c);
// 出力
// ReferenceError

以下の場合はvarとfunctionはブロックスコープが適用されない。

{
    var a = 'hoge';
    function b(){
    console.log('d is called');
    }
}
console.log(a);
b();

// 出力
// hoge
// d is called

ブロックスコープの用途としてはifforと一緒に使う

スコープと実行コンテキストのまとめ

スコープ

実行中のコードから見える範囲

実行コンテキスト

コードが実行される状況

レキシカルスコープ

プログラムの文脈ではソースコードのどこに何を書いているかという意味。
コードを書く場所によって参照できる変数が変わるスコープのこと。
コードを記述した時点で決定するのでレキシカルスコープという。

let a = 0;
function fn1(){
    let b = 1;
    function fn2(){
        let c = 3;
    }
    fn2
}
fn1

上記は全て出力できるa, fn1>b,fn2,>cのような関係。

let a = 0;
function fn1(){
    let b = 1;
}
fn1

function fn2(){
    let c = 3;
}
fn2

上記のように外に出すとエラーになる。
これがレキシカルスコープ

let a = 0;  // グローバルスコープ

function fn1() {
    let b = 1;  // fn1のスコープ
    
    function fn2() {
        let c = 2;  // fn2のスコープ
        console.log(a); // グローバルスコープの変数にアクセス可能
        console.log(b); // 外側のfn1スコープの変数にアクセス可能
        console.log(c); // 自身のスコープの変数にアクセス可能
    }
    
    fn2();
    // console.log(c); // エラー:cにはアクセスできない
}

fn1();
// console.log(b); // エラー:bにはアクセスできない

関数コンテキストの外部変数=レキシカルスコープである。
レキシカルスコープとは、実行中のコードからみた外部スコープのことである。
上記の例だとcのレキシカルスコープは

  • 自身のスコープ(c)
  • fn2の関数スコープ
  • fn1の関数スコープ(b)
  • グローバルスコープ(a)
    と言える。

上記はスコープチェーンと言える。
※スコープチェーンは複数のスコープが連なっている状態

おまけ

let a = 2;
window.a = 4;
function fn1(){
    console.log(a);
}

この場合は2が出力される。スコープは内側から探すので、グローバルオブジェクトのwindowはletのスクリプトスコープより外側にあるのでletの方が入るという仕組み。

クロージャーについて

クロージャーとはレキシカルスコープの変数を関数が使用している状態

function fn1(){
    let b = 1;
    function fn2(){
        console.log(b);
    }
}
fn1

fn2のbの状態をクロージャーと呼ぶ。

クロージャー(プライベート変数)について

  let num = 0;
  function increment() {
    num += 1;
    console.log(num);
  }

上記の実装をするとnumを好きなタイミングですり替えられる

  let num = 0;
  function increment() {
    num += 1;
    console.log(num);
  }
  
  increment();
  increment();

  num = 0;

  increment();

  // 出力例
  // 1
  // 2
  // 1

上記を避けるためにプライベート関数を使う

function incrementFactory() {
  let num = 0;
  function increment() {
    num += 1;
    console.log(num);
  }
  return increment;
}

const increment = incrementFactory();
increment();
increment();
increment();

このようにすると、const宣言した際にincrementFactory()が実行され、
以下のincrement()はconst宣言したincrementを実行する流れになる。
こうすることで初回に0がセットされ、以降のincrement()は戻り値のincrementが実行されるので1,2,3,4と処理が出力される。
num = 0はこの実装だとスコープ外なのでnumも代入できないので強い。

クロージャー(動的な関数生成)について

function addNumberFactory(num) {
  function addNumber(value) {
    return num + value;
  }
  return addNumber;
}

const add5 = addNumberFactory(5);
const result = add5(10);
console.log(result);

上記はadd5宣言でaddNumberFactory(num)のnumに5が入り、
以降のadd5(10)ではaddNumberFactory(5)、addNumber(value)が返っている状態。
Factoryに渡す値によって生成される値が変わるので動的と呼ばれる。

即時関数

即時関数とは、関数定義と同時に一度だけ実行される関数

let result =
    (function(仮引数){
        return 戻り値;
    })(実引数)

即時関数の特徴は実行結果が呼び出し元に返却される

()をつける理由は、function hoge()と基本は命名しないといけないが、
↑(関数式の場合は中で利用しない限りは名前をつけなくてよい)
ご指摘ありがとうございます!
補足:

  • 関数宣言 例
function hello(name) {  
    return `Hello, ${name}!`;
}

// 名前なしはエラー、
function(name) {  // SyntaxError
    return `Hello, ${name}!`;
}
  • 関数式 例
// 名前なしOK
const goodbye = function(name) {
    return `Goodbye, ${name}!`;
};

即時関数は一度だけ実行する際に名前をつけるのが面倒な場合、
名前を省略して()を繋げることができる。

let c = (function() {

	let privateVal = 0;
	let publicVal = 10;

	function privateFn() {
		console.log('privateFn is called');
	}
	function publicFn() {
		console.log('publicFn is called: ' + privateVal++);
	}
	
	return {
		publicVal,
		publicFn
	};
})()

この場合、privateValprivateFn()は直接外からアクセスできない。
また、privateValpublicFn()とクロージャーの関係である。
呼び出された初回でprivateValが実行されるので、複数publicFn()を呼ぶと0,1,2,3と増えていく。

1
0
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?