見出し画像

#97 ES6 基本から

まずはモダンJS ES2015(ES6)


let : 変数の有効範囲が{}(ブロックスコープ)に限定する

if (true) {
  let x = 13;
}
console.log(x);

結果は、ReferenceError: x is not defined と参照できないエラーになる
{}を外れた時点で、変数xは破棄される。

let : 同名の変数は定義できない

let msg = 'Hello';
let msg = 'Good Bye';
console.log(msg);

SyntaxError: Identifier 'msg' has already been declared と、その識別子msgはすでに宣言されているから構文エラーです。とエラーになる。

const : 定数を宣言するための宣言

const author = 'YAMADA.Yoshihiro';
author = 'WINGS Project';
console.log(author);

TypeError: Assignment to constant variable. と、型エラー:定数の指定とでる。constant variableが変数という意味。

const list = ['React', 'Vue', 'Angular'];
list[0] = 'Riot';
console.log(list[0]);

const : 再代入はできないけど、変更はできるので、これはエラーにならない。

const list = ['React', 'Vue', 'Angular'];
list =  ['Java', 'C#', 'Python'];
console.log(list[0]);

これはlistに対して、再代入しているので、TypeError: Assignment to constant variable. と、型エラー:定数の指定とでる。

letかconstか。一般的にはconstを使う。アプリの場合は、元の値を上書きしなければならない状況はさほどない。あとから値を変更したくなったら、その時点でletに置き換えても遅くない。

リテラル : 値の方。文字列リテラル、数値リテラル、関数リテラル、オブジェクトリテラル

テンプレート文字列

const fullname = 'Taro Suzuki';
const msg = `Hello, ${fullname}
How are you today?`;
console.log(msg);

答えは、
Hello, Taro Suzuki
How are you today?
バッククォートで囲むと改行も変数も文字列と一緒に書ける。変数は文字列と区別するために${}で囲む。

昔は改行を\nにして、変数は+で追加しないといけなかった。

タグ付きテンプレート文字列 css`…`など

アロー関数

const circle = (radius)=>{
    return (radius ** 2) * Math.PI;
}
console.log(circle(10));

引数と関数を=>(アロー)でつなぐから、アロー関数と呼ばれる
例は、**はES2016で策定されたべき乗演算子。累乗ができる。
(radius ** 2)で、radiusの2乗を表す。これは半径*半径*Math.PI(3.14)で、円の面積を求めている。

昔の書き方だと、こんな感じ。

function circle(radius){
   return (radius ** 2) * Math.PI;
}

アロー関数 : 一文だと{}を省略できる 値がそのまま関数の戻り血なのでreturn文も削除

const circle = (radius)=> (radius ** 2) * Math.PI;
console.log(circle(10));

アロー関数:引数が1個だと()も削除。引数がない時は()は省略できない

const circle = radius=> (radius ** 2) * Math.PI;
console.log(circle(10));
const show = () => console.log('Hello');

オブジェクトリテラルの簡易構文 変数と同名の簡易構文

プロパティと値を格納した変数の名前が同じなら、値の指定を省略できる
省略していないバージョンはコメント部分

const title = '速習React';
const price = 500;

const book = { title, price };
// const book = { title: title, price: price };

console.log(book);
console.log(book.title);
console.log(book.price);

コンソールには、オブジェクトはオブジェクト、値は格納された値が表示される。

オブジェクトリテラルの簡易構文 メソッド

ES5までは、メソッドもプロパティ名:関数と表していたけど、ES6からはメソッド(){…}のように表示できるようになった。プロパティとの違いが明示的に表せるので積極的に使うといい

// const member = {
//   name: 'ほげほげ',
//   greet: function() {
//     console.log(`こんにちは、${this.name}さん!`);
//   }
// }

const member = {
  name: 'ほげほげ',
  greet() {
    console.log(`こんにちは、${this.name}さん!`);
  }
}

member.greet();

コンソールには、こんにちは、ほげほげさんと表示される

算出プロパティ名 プロパティ名を動的に生成する

プロパティ名をブラケットで括ることで、式の値からプロパティ名を生成できる

let i = 0;
const member = {
  [`attr${++i}`]: 'ほげほげ',
  [`attr${++i}`]: '人間',
  [`attr${++i}`]: '100歳'
};
console.log(member);
console.log(member.attr1);
console.log(member.attr2);
console.log(member.attr3);

コンソールには ほげほげ 人間 100歳と表示される

分割代入 配列の場合

配列から特定の値を抜き出して、個別の変数に割り当てる構文。

const list = [10, 20, 30];
const [x, y, z] = list;
console.log(x, y, z);

const [a, b] = list;
console.log(a, b);

const [l, m, n, o] = list;
console.log(l, m, n, o);

const [p, , r] = list;
console.log(p, r);

配列 [10, 20, 30]を変数 x, y, zに割り当てたり
配列の前から2個分 a, b に割り当てたり
配列の値の数より、変数の方が多く指定されていたら、変数に格納できないのでundefinedが返ってくる。
カンマで区切ることで、配列の値を飛ばし飛ばし変数に格納することもできる

もしも、配列を何個の変数に割り当てていいかわからない時は、残りの値をまとめて、スプレッド演算子(…演算子)を使える

const list = [10, 20, 30];
const [one, ...rest] = list;
console.log(one, rest);
console.log(list);
console.log(...list);

…restとした時は、10 [20, 30]とまとめて部分配列に入る
listだと[10,20,30] 配列のまま表示される
…listだと 配列の意味が消えてただの中身が表示される
スプレッド演算子はES2018にリリースされた

参考:

分割代入 オブジェクトの場合

基本的には、配列の分割代入と同じ書き方だけど、大括弧じゃなくて中括弧で囲む

const member = {
  fullname: 'ほげほげ',
  sex: 'man',
  age: 100
};
const { fullname, sex, memo = '---' } = member;

// let fullname, sex, memo;
// ({ fullname, sex, memo = '---' } = member);
console.log(sex, fullname, memo);

オブジェクトにはキーがあるから、配列と違って、番号を気にせずに書ける(キーと変数が同名なら)。割り当ててないプロパティがあっても大丈夫。この場合はage。
また目的のプロパティがない場合に備えて、「変数名=デフォルト値」の形式で既定値を設定することも可能。

キーと変数名を違う名前にするなら、下記のように代入する

const member = {
  fullname: 'ほげほげ',
  sex: 'man',
  age: 100
};

const { gender = sex } = member;
console.log(gender);

配列と同様に残りのプロパティを取得するなら、残りの値をまとめて、スプレッド演算子(…演算子)を使って代入する

const member = {
  fullname: 'ほげほげ',
  sex: 'man',
  age: 100
};

const { age, ...rest } = member;
console.log(age);
console.log(rest);

コンソールには
100
{ fullname: 'ほげほげ', sex: 'man' }
と表示される

入れ子のオブジェクトの分解
入れ子のオブジェクトを展開するならば、代入先の変数も{…}を入れ子にします。

const member = {
  fullname: 'ほげほげ',
  address: {
    prefecture: '東京都',
    city: '西区'
  }
};
const { address, address: { city } } = member;
console.log(address);
console.log(city);

分割代入側も、{}で入れ子にして指定する
コンソールログには、console.log(address);では、addressプロパティの値そのもの(オブジェクト)がそのままとりだされるだけ。
もし、変数に代入していない場合は、入れ子の場合は、
console.log(member.address.city); となるので冗長になりやすい。わかりやすいけど。

配列でも同様に、入れ子の値を分割代入できる

const list = [200, [300, 301, 302]];
const [x, [y1, y2, y3]] = list;
console.log(y1, y2, y3);

コンソールには300 301 302と表示される

宣言文と代入文を切り離した場合は注意が必要

let fullname, sex, memo;
({ fullname, sex, memo = '---' } = member);
console.log(fullname);

代入文の外側を()で括らないと、左辺の{}がブロックと見なされてしまう。。

同様の理由で、オブジェクトリテラルを返すだけの関数を作る場合は、右辺のブラケットの外側を()で囲わないとundefinedとエラーとなる

const func () => ({title:'hoge'});

分割代入によるオブジェクト引数の分解

function greet({ name, age }) { //分割代入による引数
  console.log(`こんにちは、私は${name}${age}歳です。`);
}

const my = { name: '佐藤理央', sex: 'female', age: 18 }; 
greet(my);  //メソッドは実行

オブジェクトを引数にすると、テンプレート文字列の中で、簡単に呼び出せる。Reactアプリのpropsの受け取りでよく使う。

const greet2 = (obj) => {
  console.log(`こんにちは、私は${obj.name}${obj.age}歳です。`);
}
const my = { name: '佐藤理央', sex: 'female', age: 18 }; 
greet2(my);

オブジェクトをつけないと、テンプレート文字列の中で、仮引数をキーとして付ける必要があるので少し冗長になる。

仮引数にデフォルト値を設定できる

function getTrapezoidArea(upper = 1, lower = 1, height = 1) {
  return (upper + lower) * height / 2;
}

console.log(getTrapezoidArea(10, 5, 3));
console.log(getTrapezoidArea(10, 5))
console.log(getTrapezoidArea(10));
console.log(getTrapezoidArea(1, 1, 1));
console.log(getTrapezoidArea());

台形の面積を求める関数。3つの仮引数にデフォルト値が入っているので、引数を指定しないで実行すると
console.log(getTrapezoidArea()); (1+1)*1/2=で 1が返る
console.log(getTrapezoidArea(10)); だと1つ目の仮引数に10が入り、後の2つは1なので、(10+1)*1/2=5.5 が返る

既定値には式を渡すことも可能

function getTrapezoidArea(upper = 1, lower = upper, height = upper) {...}

可変長引数の関数

任意個数の実引数でも、仮引数に…をつけることでまとめて配列として取得できる

function sum(...nums) {
  let result = 0;
  for (const num of nums) {
    result += num;
  }
  return result;
}

console.log(sum(10, 25, 2));
console.log(sum(7, 13, 25, 6, 100));

任意個数の実引数分を…演算子付きの仮引数で配列として受け取って、for of 文で回して全部足し終わったら、結果を返す処理

for of文もES6から登場。
配列ならfor of 、オブジェクトならfor inで回す
インデックスも取りたいならArrayのメソッドのforEach()を使う
とりあえず、配列なら for of と。

仮引数が複数あって、可変長な仮引数も含む場合は、引数リストの末尾におく

function hoge(title, ...args){ ... }

実引数に…をつけると、まとめずにバラバラにするスプレッド構文になる

function sum(...nums) {
  let result = 0;
  for (const num of nums) {
    result += num;
  }
  return result;
}

console.log(sum([10, 25, 2]));
console.log(sum(...[10, 25, 2]));

console.log(sum([10, 25, 2])); だと配列そのままが配列として…numsに入るので、正しい結果が得られない
console.log(sum(...[10, 25, 2])); のようにスプレッド演算子をつけると、実引数がバラバラになり、…numsで可変長の配列としてまとめられて、正しい結果が得られる

Optional Chaining演算子(?.)

たとえば、文字列を切り出すsubstringメソッドの場合

const str = 'Mozilla';
console.log(str.substring(1));

コンソールには ozilla が出る。
ただ、str(メソッドを呼び出すレシーバー)に、この時はMozillaが入っているけど、代入を忘れている時は、nullやundefinedの時がある。
ちなみにnullはドイツ語でゼロ。何もないという意味

たとえば、下記のコードはコンソールに、
TypeError: Cannot read properties of null (reading 'substring') と出る

const str = null;
console.log(str.substring(1));

このエラーを回避するには下記のようにする

const str = null;
if(str !== null && str !== undefined){
 console.log(str.substring(1));
}

この記述はよくやるけど、冗長なので2020年から使われるようになったOptional Chaining演算子を利用することで ?.演算子を使って下記のように書き換えが可能

const str = null;
console.log(str?.substring(1));

こうするとコンソールにエラーは出ずに、undefinedと出る
?.が値を判定して、値が空だった場合は無条件でundefinedを返してくれる
?.演算子は、下記のような複数階層に渡るアクセスでより効果を発揮する

obj?.title?.length

Null合体演算子

似たようなnullに関わる演算子 Null合体演算子

let value = null;
console.log(value ?? '既定値');

既定値を伴う式を表現したいときによく使われる。値が入っていないと既定値が返され、値が入っていたら値が入る

モジュール

モジュールは、アプリを機能単位に分割するための仕組み
変数やメソッドの競合リスクが減らせる。
非モジュールの時は、1つのJSファイルが肥大化していたけど
モジュールにすると、1つのファイルは1モジュールでコンパクトで見やすい

モジュールファイルの例 App.jsとする
exportキーワードをつけたものだけが外部からアクセスできる
この場合、APP_TITLE定数は外部からいじれない。

const APP_TITLE = 'Reactアプリ';

export function getTriangle(base, height) {
  return base * height / 2;
}

export class Article {
  getAppTitle() {
    return APP_TITLE;
  }
}

モジュールファイルを実行ファイルでimport命令で受け取って使う
App.jsモジュールのgetTriangle関数とArticleクラスをimportで受け取っている
Dart Sassのimportと似ているなぁ
モジュールは現在のjsファイルから相対パスで表す。
EJSに似ているなぁ。
また、モジュール側でexportしていても、import側で明示していない場合は、アクセスできない。

例 実行ファイル module_basic.js

import { Article, getTriangle } from './App.js';

console.log(getTriangle(10, 5));

const a = new Article();
console.log(a.getAppTitle());

html側では実行ファイルを読み込む
その際、type="module"属性にしてモジュール型のコードであることを宣言しないといけない。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>React実践入門</title>
</head>
<body>
<script type="module" src="module_basic.js"></script>
</body>
</html>

HTMLはローカルで開くだけだとクロスオリジンエラーが出るので、VSCodeのプラグインのLive Serverを有効にして、右クリック Open with Live Serverで確認ができる

importしたモジュールのメンバーは as句でエイリアスをつけることもできる

変更前

import { getTriangle } from './App.js';
console.log(getTriangle(10, 5));

As句 で関数名をtriと別名にして簡略化できる

import { getTriangle as tri } from './App.js';
console.log(tri(10, 5));

アスタリスクで、モジュール配下のメンバーをまとめてインポートできる
メンバとはクラス、関数、変数、定数などのこと

変更前

import { Article, getTriangle } from './App.js';

アスタリスクを使った場合

import * as app from './App.js';
console.log(app(10, 2));

デフォルトのエクスポート
モジュール1つにつき、1つだけ既定のエクスポートを設定できる

export default class Util {
  static getCircleArea(radius) {
    return (radius ** 2) * Math.PI;
  }
}

ちなみにstaticフィールドは2022年から作られた

既定エクスポートのインポートは下記のように呼び出す

import Util from './Util.js';
console.log(Util.getCircleArea(10));

既定メンバをその他のメンバーとまとめてインポートもできる

import Util { getTriangle } from './Util.js';

これはすべて静的インポートで、初期読み込みのタイミングですべてのモジュールをまとめてインポートする。

初期起動に不要なモジュールは動的インポートで、実行時に条件などに応じてモジュールをインポートできる。

難しい。thenメソッドを使った書き方をすると非同期でインポートできる

import('./App.js').then(app => {
  console.log(app.getTriangle(10, 5));

  const a = new app.Article();
  console.log(a.getAppTitle());
});


この記事が気に入ったらサポートをしてみませんか?