JavaScript再入門 #3

変数について

  • let(ES6~)
    • 再宣言 : ✗
    • 再代入 : ○
    • スコープ範囲 : ブロック
    • 初期化 : ✗
  • const(ES6~)
    • 再宣言 : ✗
    • 再代入 : ✗
    • スコープ範囲 : ブロック
    • 初期化 : ✗
  • var(非推奨)
    • 再宣言 : ○
    • 再代入 : ○
    • スコープ範囲 : 関数
    • 初期化 : undefined

varの場合はホスティング(巻き上げ)が発生するため利用しないようにする

データ型

型 / 英名 - 利用例

  • 真偽値 / Boolean - true / false
  • 数値 / Number - 12
  • 文字列 / String - “Hello”
  • undefined / Undefined - undefined
  • null / Null - null
  • シンボル / Symbol - 一意の値
  • Biglnt / Bigint - 12n
  • オブジェクト / Object - {a: ‘value’}

暗黙的な型変換

変数が呼ばれた状況によって、変数の型が自動的に変換されること

  • JS自体は動的型付け言語のため、変数を仕様する状況によって変数の型が変更される。
  • また、TypeScriptやJavaの場合は静的型付け言語のため宣言時の型制限が維持される。
// 型の種類と値を表示する 
function printTypeAndValue(val) {
	console.log(typeof val, val);
}

let a = 0;

printTypeAndValue(a); // Number, 0

let b = '1' + a;
printTypeAndValue(b); // String, 10 => '1'のの文字列が優先され、0の数字が `1`+'0'となる

let c = 15 - b; 
printTypeAndValue(c); // number, 5 => -の演算子は数値にしか利用されないため、 15から 5が引かれる

let d = c - null;
printTypeAndValue(d); // number, 5 => nullが数値の0に暗黙的に変換される

let e = d - true;
printTypeAndValue(e); // number, 4 => trueが数値の1に暗黙的に変換される

// 明示的な型変換を行う場合
let f = parseInt('10') + 10;
printTypeAndValue(f); // number, 20 => parseIntで10の文字列を数字に型変換している

厳格な等価性と抽象的な等価性

厳格な等価性の場合は**「型の比較を行う」が、抽象的な等価性の場合は「型の比較は行わない」**

  • 基本的に抽象的な等価性はバグを生む原因となってしまうため、厳格な等価性を利用すること
let a = '1';
let b = 1;

console.log(a === b); // false - 数値と文字列のため型が異なる
console.log(a == b); // true - 数値と文字列だが、1で等しく判断される

let c = true;

console.log(b === c); // false - 数字の1とBoolean: trueの比較のため型が異なる

// MEMO: Boolean: 数値が変換された場合trueになり、0だとfalseで判定される
console.log(b == c); // true - Boolean(b):true === true で判断される

let e = ''; // 空文字のString
let f = 0; // 0のInt

console.log(e === f); // false - 空文字と数値の0の比較のため型が異なる

// MEMO: 空文字は数値の0と等価になるので Boolean('')とBoolean(0)で比較される
console.log(e == f); // true - Boolean(e):false === Boolean(f):false で判断される

let g = null;
let h; // undefined

console.log(g === h); // false - null型とundefined(未定義)型のため型が異なる

// MEMO: 抽象的な等価性の場合、nullとundefinedは等しく判断される
console.log(g == h); // true

FalsyとTruthy

  • Falsyな値とは Booleanで真偽値に変換した場合にfalseになる値のこと
// Falsyな値

- false
- null
- 0
- 0n
- undefined
- NaN
- ""

// truthyな値

-  上記以外

// falsyな値の場合に実行したい処理がある場合
let a = "";
if (!a) {
  // 注意点として0でもfalsyと判断されるため、0を含んでも問題がないかを検討すること
	console.log('Hello');
}

AND条件とOR条件

  • 条件文、ANDとORの動作について
// && - AND条件

const a = 1;
const b = 2;
const c = 3;
const d = 0;

// 左からtrutsyな値かどうかを確かめて、
// 1. falsyな値があった場合は、falsyな値を返却
// 2. trutsyな値の場合は、最後の値を返却する
console.log(a && b); // 2
console.log(a && b && c); // 3
console.log(a && d); // 0
// || - OR条件

const a = 1;
const b = 0;
const c = 3;
const d = null;

// 左からtrutsyな値かどうかを確かめて、
// 1. 全てfalsyな値の場合、最後の値を返却する 
// 2. trutsyな値があった場合、trutsy値を返却する
console.log(a || b); // 1
console.log(a || b || c); // 1
// AND条件とOR条件を混合したものを各場合はグループ化すること

const a = 1;
const b = 2;
const c = 0;
const d = 3;
const e = 4;

console.log((a && b) || (c || d) || e);

応用した利用方法として

function hello(name) {
	// 値が渡ってこなかった初期値を指定
	// if(!name) {
	//		name = 'Tom';
	// }
  // 上記のコードをOR条件を利用した場合の記載方法
	name = name || 'Tom';

	console.log('Hello'+ name); // "Hello Tom"
}

hello();

// ES6以降は default関数が存在しているため下記の形で記載可能
function hello(name = 'Tom'){
  // name = name || 'Tom'; // 記載不要
	console.log('Hello' + name); // "Hello Tom"
}

// 下記のように記載すると、
let name;
name && hello(name); // nameがtrutsyな値の場合、&&以降の処理を実行する

プリミティブ型とオブジェクト

プリミティブ型

  • 変数にが格納される
  • 一度作成すると、その値を変更することはできない - immutable(不変)
    • letなどの変数の値を変更した場合、メモリ空間上の参照が異なる値に変わっているだけ。

オブジェクト(型)

  • 変数には参照が格納される
  • 値を変更する事ができる - mutable(可変)
    • 参照を名前(プロパティー)付きで管理している

参照とコピー

  • プリミティブ型のコピーした場合は、参照先の値がコピーされる
  • オブジェクトのコピーした場合、オブジェクトへの参照がコピーされる
let a = 'hello';
let b = a; // aとbで別々の値が複製される
b = 'bye';
console.log(a, b); // hello bye

let c = {
	propr: 'hello'
}
let d = c; // dからの参照がcのpropに
d.prop = 'bye';
console.log(c,d); // {prop : 'bye'} {prop : 'bye'}

参照とconst

constを利用しているとき、オブジェクトのkey: valueは変更できる

// プリミティブ型のconstについて
const a = 'Hello'; // a -> 'Hello' の参照がロックされる
a = 'bye'; // constによってロックされているため、Errorとなる

// オブジェクトのconstについて
const b = { // b -> {} への参照がconstでロックされる
	prop: 'hello'; // メモリ空間上では、{} -> prop -> hello で参照される
}
b = {}; // constによってロックされているため、Errorとなる
b.prop = 'bye'; // {} -> prop -> 'hello' の参照部分はロックされていないため変更可
console.log(b); // {prop: 'bye'}

参照と引数

// プリミティブ型
let a = 0;

function fn1(arg1) {
	arg1 = 1;
	console.log(a, arg1); // 0, 1
} 

fn1(a);

// オブジェクト(型)
let b = {
	prop: 0;
}

function fn2(arg2) {
	arg.prop = 1;
	console.log(b, arg2); // {prop: 1}, {prop: 1}
}

fn2(b);
// オブジェクト(型)

let b = {
	prop: 0;
}

function fn3(arg2) {
	arg2 = {};
	console.log(b, arg2); // {prop: 0}, {}
}

fn3(b);

参照と分割代入

オブジェクトから特定のプロパティを抽出して宣言する - let {a, b} = object;

  • プロパティが持っている参照先がコピーされて、新しい変数が定義される
const a = {
	prop: 0
}

let { prop } = a;
// TIPS: let {prop: b} = a; とすれば名称を変更できる
// 

prop = 1;

console.log(a, prop); // {prop: 0}, 1

function fn(obj) {
	let { prop } = obj;
	prop = 1;
	console.log(obj, prop): // { prop: 0}, 1
}

// 分割代入で特定の変数のみ利用するパターンの場合は下記の省略した形でもOK
function fn({ prop }) {
	prop = 1;
	console.log(a, prop) // { prop: 0}, 1
}

const c = {
	prop1: {
		prop2: 0	
	}
}

let { prop1 } = c;
prop1.prop2 = 1;

console.log(c, prop1) // {prop1: { prop2: 1 } }, {prop2: 1} 

参照の比較と値の比較

プリミティブ型ではの比較で、オブジェクトは参照の比較 となる。

// 内部の値が同じオブジェクトを作成
const a = {
	prop: 0;
}
const b = {
	prop: 0;
}

console.log(a === b); // false(オブジェクト同士の比較になるため)
console.log(a.prop === b.prop); // true(オブジェクト内の数値が等しいため)

const c = a;
console.log(a === c); // true (cの参照先はaのため)

補足: 静的解析でコードを読む方法

  • 動的解析 ⇒ コードを実行して解析する
  • 静的解析 ⇒ コードを実行せずに読んで解析する
let obj =
{
  prop1: 10,
  prop2: {
      prop3: 1
  }
};

function fn({ prop2 }) {
    let prop = prop2 
    prop.prop3 = 2;
    prop = { prop3: 3 }; // ※3
    return { prop2: prop }; // ※2
}

obj = fn(obj);  // ※1

// 下記のコードでprop3に入る値を調べたい時は、
// console.log(obj.prop2.prop3);

// ※1 で fn(obj)でfnが実行されていることがわかるので…
// ※2 を見ると return で返却されるものが固定されている prop これを見る。
// ※3 prop は prop = {prop3 : 3}; で定義されているのでここの値が正しい