ECMAScript(ES6)基礎

# ECMAScript(ES6)基礎
自分用メモ
## constとletとBlock Scope
**変数定義をおこなう**
###const
- データの上書きができない
- 必ず定数を入れる
- 空の配列やオブジェクトならデータがなくても宣言できる
 - 配列の要素やオブジェクトのバリューならconst宣言の後でも追加変更できる
 - const宣言で不変になるのは変数の直接的な参照のみ
 - 配列をオブジェクトに変更するようなことはできない

###let
- constと違い、letで宣言した変数には新たなデータを代入できる
- letは同じスコープ内で変数名で定義しなおすことができない
- ECMAScriptではvarを一切使わず、const以外はすべてletで宣言するのが望ましいとされている

###Block Scope
従来のjavascriptでは関数スコープのみ適用されていた。
（例えば同じ関数の中であればif文の中で宣言された変数をif文の外からでも参照可能であった）

```
if (true) {
　var x = 1;
}
console.log(x); // x = 1
```

letやconstを使うと、関数のみならず波カッコでスコープを区切ることができる。
この波カッコで囲まれた部分をBlock Scopeという。

```
if (true) {
　　let x = 1;
}
console.log(x); // ReferenceError: x is not defined
```

もう一つblock scopeの例

```
{
    let x = 1;
    {
        let x = 2;
    }
    console.log(x); // x = 1
}
```

上記の例を除けば、letはvarと同じように使える

```
let a;
let b = 10;
let c, d, e;
let f = g = h = 10;
```

## アロー関数

### アロー関数の基本
1. アロー関数は無名関数またはコールバックの機能の拡張であり、functionの記述に置き換わる
2. アロー関数の引数が一つならば丸括弧を省略可能
3. 引数が複数あるか一つもない場合、丸括弧は必須
4. 中身が一部だけならば、処理内容の波括弧とreturnを省略できる
5. 4のやり方でオブジェクトを戻り値にするときは、丸括弧で囲む必要がある

```
//従来のjavascriptのコールバック
const array = [1, 2, 3, 4];
const byTwo = array.map(function(number) {
    return number * 2
});

console.log(byTwo); // [ 2, 4, 6, 8 ]
```

このfunction(number) {...}をアロー関数で置き換えることができる

```
const array = [1, 2, 3, 4];
const byTwo = array.map((number) => {
    return number * 2
});

console.log(byTwo); // [ 2, 4, 6, 8 ]
```

引数がひとつしかない場合、（）は省略できる

```
const array = [1, 2, 3, 4];
const byTwo = array.map(number => {
    return number * 2
});

console.log(byTwo); // [ 2, 4, 6, 8 ]
```

引数が複数ある場合、または引数がない場合は（）が必要

```
const array = [1, 2, 3, 4];
const byTwo = array.map(() => {
    return 2
});

console.log(byTwo); // [ 2, 2, 2, 2 ]
```

処理内容が一文しかない場合、｛｝とreturnも省略できる

```
const array = [1, 2, 3, 4];
const byTwo = array.map((number) => number * 2);

console.log(byTwo); // [ 2, 4, 6, 8 ]
```

このやりかたでobjectを返す場合は、別のカッコが必要

```
const array = [1, 2, 3, 4];
const byTwo = array.map((number) => {number: number * 2});
console.log(byTwo); // [ undefined, undefined, undefined, undefined ]


const array = [1, 2, 3, 4];
const byTwo = array.map((number) => ({number: number * 2}));
console.log(byTwo); //[ { number: 2 }, { number: 4 }, { number: 6 }, { number: 8 } ]
```

### this
アロー関数のthisは、親スコープのthisを引き継ぐ

【アロー関数の注意点】
1. argumentsは使えない
2. コンストラクタには不向きである

アロー関数のthisの挙動は通常のcallbackとは異なる

```
//通常のthisの挙動
let obj = {
  value: 0,
  increment: function() {
    setTimeout(function() {
      this.value++;
      console.log(this.value);
    }, 1000);
  }
}
obj.increment(); //NaN
```

this.valueのthisはobjを参照するのではなく、setTimeoutのコールバックを参照するため、NaNが返る

これを解決するにはいくつか方法がある

1.自らを参照させる

```
let obj = {
  value: 0,
  increment: function() {
    setTimeout(function() {
      obj.value++;
      console.log(obj.value);
    }, 1000);
  }
}
obj.increment(); // 1
```


2.thisを新たな変数に代入させる

```
let obj = {
  value: 0,
  increment: function() {
    var that = this;
    setTimeout(function() {
      that.value++;
      console.log(that.value);
    }, 1000);
  }
}
obj.increment(); // 1
```

3.bind関数を使う

```
let obj = {
  value: 0,
  increment: function() {
    setTimeout(function() {
      this.value++;
      console.log(this.value);
    }.bind(this), 1000);
  }
}
obj.increment(); // 1
```

アロー関数ならこれらの手順は一切不要になる

```
let obj = {
  value: 0,
  increment: function() {
    setTimeout(() => {
      this.value++;
      console.log(this.value);
    }, 1000);
  }
}
obj.increment(); // 1
```

この場合のthisは親スコープを参照している。
つまり、アロー関数のthisは親スコープのthisを引き継ぐことになる

```
let obj = {
  value: 0,
  increment: () => {
    setTimeout(() => {
      this.value++;
      console.log(this.value);
    }, 1000);
  }
}
obj.increment(); // NaN
```

## 引数、スプレッド
### デフォルトの引数

```
//itemのデフォルト値がmilkに、amountのデフォルト値が1に設定される
function buy(item = "milk", amount = 1) {
  ...
}
```

### レスト演算子(...仮引数名)
不特定多数の引数を配列として受け取ることができる

argumentsにも似たような機能がある

```
//argumentsの例
function print() {
  var arr = Array.prototype.slice.call(arguments);
  console.log(arr);
}
print(1, "a", true, {}, [1, 2, 3]);　//[ 1, 'a', true, {}, [ 1, 2, 3 ] ]
```

```
//同じことをレスト演算子を使用してやってみる
function print(...arr) {
  console.log(arr);
}
print(1, "a", true, {}, [1, 2, 3]);　//[ 1, 'a', true, {}, [ 1, 2, 3 ] ]

//部分的な活用も可能
function print(ele1, ele2, ...arr) {
  console.log(ele1);
  console.log(ele2);
  console.log(arr);
}
print(1, "a", true, {}, [1, 2, 3]);
//1
//a
//[ true, {}, [ 1, 2, 3 ] ]
```

### スプレッド演算子(...配列名)
配列の各括弧[]を取り除いて展開するようなイメージ

```
var arr = ["Hello,", "world!"];
arr = ["<br>"].concat(arr).concat(["<br>"]);
console.log(arr.join("")); //<br>Hello,,world!<br>

//これをスプレッド演算子を使用すると
var arr = ["Hello,", "world!"];
arr = ["<br>", ...arr, "<br>"];
console.log(arr.join("")); // <br>Hello,world!<br>
```

以下、スプレッド演算子の使い方の例

```
①文字列を配列にする
const message = "Hello, world";
const chars = [...message];
console.log(chars);
//[ 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd' ]

②直接引数にする
function add(a, b){
  return a + b;
}
var arr = [1, 2]
console.log(add(...arr)); //3

③pushとunshift
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
arr1.push(...arr2);
console.log(arr1); //[ 1, 2, 3, 4, 5, 6 ]

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
arr1.unshift(...arr2);
console.log(arr1); //[ 4, 5, 6, 1, 2, 3 ]

④配列のコピー
let arr1 = [1, 2, 3];
let arr2 = [...arr1];

console.log(arr2); //[ 1, 2, 3 ]

⑤配列データをくっつける
const arr1 = [1, 2, 3];
const arr2 = [...arr1];
const arr3 = ["a", ...arr2, true, {}, ...arr2];
console.log(arr3); //[ 'a', 1, 2, 3, true, {}, 1, 2, 3 ]

⑥配列の分解
let [a, b, ...arr] = [1, 2, 3, 4, 5]
console.log(arr); //[ 3, 4, 5 ]
```

## テンプレートリテラル
### バッククオート
` - バッククオート、バックティック
　windowsはshift + @ で出す

```
let name = "太郎";
let age = 18;
let message = "私の名前は" + name + "で、" + age + "歳です。"
console.log(message); //私の名前は太郎で、18歳です。

//バックティックを使うと
let name = "太郎";
let age = 18;
let message = `私の名前は${name}で、${age}歳です。`
console.log(message);  //私の名前は太郎で、18歳です。
```
${}をエクスプレッション（expression）と呼ぶ

### 複数行とスペース
バッククオートの中では改行できる

```
let name = "太郎";
let age = 18;
let message = "私の名前は" + name + "で、\n" + age + "歳です。"
console.log(message); 

//結果
私の名前は太郎で、
18歳です。

let name = "太郎";
let age = 18;
let message = `私の名前は${name}で、
${age}歳です。`;
console.log(message);

//結果
私の名前は太郎で、
18歳です。

//スペースやtabもそのまま反映される
let name = "太郎";
let age = 18;
let message = `私の名前は     ${name}で、
${age}  歳です。`;
console.log(message);

私の名前は     太郎で、
18  歳です

//従来の\nを併用でき、またエクスプレッションをエスケープすることもできる
let name = "太郎";
let age = 18;
let message = `私の\n名前は\${name}で、
${age}  歳です。`;
console.log(message);

私の
名前は${name}で、
18  歳です。
```

### さらなる事例
バッククオートの中ではあらゆるjavascript処理が可能

```
let name = "太郎";
let age = 18;
let message = `私の名前は${"桃" + name}で、${age *2 }歳です。`;
console.log(message);

私の名前は桃太郎で、36歳です。

//関数も使える
let foods = ["卵", "納豆", "豆腐"];
let message = `私が好きな食べ物は、${foods.join("、")}です`
console.log(message);
私が好きな食べ物は、卵、納豆、豆腐です


//メソッドチェーンも使える
let foods = ["卵", "納豆", "豆腐"];
let message = `私が好きな食べ物は、${foods.map(function(foods){
  return `「${foods}」`
})}です`
console.log(message);
私が好きな食べ物は、「卵」,「納豆」,「豆腐」です
```

### タグ付きテンプレート(tagged template)リテラル
バッククオートで引数を渡す

```
function output() {
  console.log(arguments);
}
output("a", "b");
{ '0': 'a', '1': 'b' }

//バッククオートで引数を渡す
function output() {
  console.log(arguments);
}
output `"a", "b"`; //{ '0': [ '"a", "b"' ] }

一つ目の配列の中に結果がすべて収まっている


//二つ目以降の引数はエクスプレッションで渡す
function output(string, ...values) {
  console.log(string);
  console.log(values);
}
output `"a", "b"`;

↓
[ '"a", "b"' ]
[]


function output(string, ...values) {
  console.log(string);
  console.log(values);
}
let name = "太郎";
let age = 18
output `私の名前は${name}で${age}です。`;


//それぞれのexpressionの部分をbタグで囲む
function output(string, ...values) {
  let message = "";
  string.forEach((string, index) => {
    message += string;
    if (index < values.length) {
      message += `<b>${values[index]}<b>`
    }
  });
  return message;
}
let name = "太郎";
let age = 18
const message = output `私の名前は${name}で${age}歳です。`;
console.log(message);
```

### raw
タグ付きテンプレートリテラルの最初の引数にはrawというメンバーが同時に定義されている

```
function output(string, ...values) {
  console.log(string[0])
}
output`私の名前は太郎です。\n18歳です。`;

→私の名前は太郎です。
18歳です。

↑では改行されて表示されるが、\nを表示させたい場合は以下のようにする

function output(string, ...values) {
  console.log(string.raw[0])
}
output`私の名前は太郎です。\n18歳です。`;
→私の名前は太郎です。\n18歳です。


//String.raw`` でも似たようなことができる
console.log(String.raw`あいう\nえお\nかき`)
あいう\nえお\nかき
```
## プロパティ
### 単独記述
プロパティ名とバリュー名が同じオブジェクトはプロパティ名とバリュー名を両方書かなくてもOK

```
let firstname = "太郎"
let lastname = "山田"
let age = 20;
let person = {
  firstname: firstname,
  lastname: lastname,
  age:age
}
console.log(person); //{ firstname: '太郎', lastname: '山田', age: 20 }

//単独記述
let firstname = "太郎"
let lastname = "山田"
let age = 20;
let person = {
  firstname,
  lastname,
  age
}
console.log(person); //{ firstname: '太郎', lastname: '山田', age: 20 }

```

### computed property name(計算表示)
[]をプロパティ名に使えるようになった

```
let key = "lastname";
let person = {
  firstname: "太郎",
};
person[key] = "山田"
console.log(person);//{ firstname: '太郎', lastname: '山田' }

//計算表示
let key = "lastname";
let person = {
  firstname: "太郎",
　[key]:"山田"
};
console.log(person);//{ firstname: '太郎', lastname: '山田' }
```
[]の中は一種のexpressionで[]内で計算処理を行えるようになった

```
let key = "lastname";
function getKey() {
  return "place";
}
let person = {
  firstname: "太郎",
　[key]:"山田",
　[getKey()]: "東京"
};
console.log(person); //{ firstname: '太郎', lastname: '山田', place: '東京' }

let key = "lastname";
function getKey() {
  return "place";
}
let person = {
  firstname: "太郎",
　[key]: "山田",
　["birth" + getKey()]: "東京"
};
console.log(person); //{ firstname: '太郎', lastname: '山田', birthplace: '東京' }

```

### メソッド
メソッドの定義も簡略化できるようになった

```
let person = {
  firstname: "太郎",
  age: 20,
  getOlder: function() {
    this.age++;
  }
}
person.getOlder(); //{ firstname: '太郎', age: 21, getOlder: [Function: getOlder] }
console.log(person);


//【復習】アロー関数を使うと上のscopeからthisを引き継ぐのでpersonを参照せず、
// 結果は20のままになる
let person = {
  firstname: "太郎",
  age: 20,
  getOlder: () => {
    this.age++;
  }
}
person.getOlder();
console.log(person); //{ firstname: '太郎', age: 20, getOlder: [Function: getOlder] }

//ES6では:とfunctionが省略できるようになった
let person = {
  firstname: "太郎",
  age: 20,
  getOlder() {
    this.age++;
  }
}
person.getOlder();
console.log(person); //{ firstname: '太郎', age: 21, getOlder: [Function: getOlder] }

//[]によるプロパティの動的表示はメソッドにも可能
function getName() {
  return "getOlder";
}
let person = {
  firstname: "太郎",
  age: 20,
  [getName()]() {
    this.age++;
  }
}
person.getOlder();
console.log(person); //{ firstname: '太郎', age: 21, getOlder: [Function: getOlder] }
```

## モジュール
### 外部ファイル化

モジュール化したいfunctionの前にmodule.exports=をつける

```
//add.jsファイル
module.exports = function add(a, b) {
  return a + b;
}
```
呼び出す側のファイルはrequire(パス＋ファイル名)を使う

```
//require(パス＋ファイル名)　./を忘れないようにする
const add = require('./add');
let value = add(3, 2);
console.log(value); //5
```
以下のような形式で複数のモジュールを呼び出すことができる

```
//util.js
function sub (a, b) {
  return a - b;
}
module.exports = {
  add(a, b) {
    return a + b;
  },
  mul(a, b) {
    return a * b;
  },
  sub,
  human: {
    height:170,
    weight:65
  }
}
```
呼び出す側は以下

```
const util = require('./util');
let value = util.sub(3, 2);
console.log(value); //1
console.log(util.human['weight']); //65
```

## クラス
### クラスの宣言

```
//従来のjavaScriptのクラス宣言
var Person = function(name, age) {
  this.name = name;
  this.age = age;
}
//従来のjavaScriptのメソッドの宣言
Person.prototype.jump = function() {
  console.log("ジャンプ");
}
var person = new Person("太郎", 30);
console.log(person); //Person { name: '太郎', age: 30 }
person.jump(); //ジャンプ
```

```
//ES6でのクラス宣言
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  jump() {
    console.log("ジャンプ");
  }
}

let person = new Person("太郎", 30);
console.log(person); //Person { name: '太郎', age: 30 }
person.jump(); //ジャンプ
```

### クラスの継承
```
class Employee extends Person{
  constructor(name, age, years) {
    //super関数は直近の親クラスのコンストラクタである子クラスで呼び出す形になる
    super(name, age);
    this.years = years;
  }
  //メソッドのoverride
  jump() {
    console.log("大ジャンプ")
  }
  //新たなメソッドを追加
  quit() {
    console.log("やめます");
    this.years = 0; 
  }
}
let employee = new Employee("太郎", 20, 15);
console.log(employee); //Employee { name: '太郎', age: 20, years: 15 }
employee.jump(); //大ジャンプ
employee.quit(); //やめます
console.log(employee); //Employee { name: '太郎', age: 20, years: 0 }
```

### 親クラスのメソッド
```
//super.メソッド名で親クラスのメソッドを使用できる。
//親クラスと子クラスでメソッド名が異なっていても使用できる。
class Employee extends Person{
  constructor(name, age, years) {
    super(name, age);
    this.years = years;
  }
  jump() {
    super.jump();
  }
}
let employee = new Employee("太郎", 20, 15);
employee.jump(); //ジャンプ
```

### 静的メソッド
```
//静的な宣言
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  //staticを付けて宣言。
  static setName(person, name) {
    person.name = name;
  }
}

class Employee extends Person{
  constructor(name, age, years) {
    super(name, age);
    this.years = years;
  }
}
let employee = new Employee("太郎", 20, 15);
//静的メソッドはClass名.メソッド名で使用する。prsonクラスをnewしなくても使える
Person.setName(employee, "次郎");
console.log(employee); //Employee { name: '次郎', age: 20, years: 15 }
```

### ゲッターとセッター
```
class Person {
  constructor(name, age) {
    //privateなフィールドという意味で、慣習として変数名の前にアンダースコアをつける
    this._name = name;
    this._age = age;
  }
  get name() {
    //getするデータを加工するのも自由
    return "「" + this._name + "」";
  }
  set name(name) {
    this._name = name;
  }
}

let person = new Person("太郎", 15);
//メソッドのように定義したが、使うときはpropertyのように使う。()は不要
console.log(person.name); //「太郎」

//セッターを使うときもゲッターと同じくプロパティとして使う
person.name = "次郎"
console.log(person.name); //「次郎」
```

## 分割代入
### 配列１

```
var numbers = [1 ,2 ,3, 4];
//従来の代入
var a = number[0];
var b = number[1];
var c = number[2];
var d = number[3];

//ES6での代入方法
let [a, b, c, d] = numbers;
//一部の要素だけ取得したい場合
let [,,c, d] = numbers;
//先頭からカウントされるので、以下のc,dには2と3が入る
let [,c, d] = numbers;
//この特性から、データの入れ替えもできる
[c, d] = [a, b];
```

### 配列2
```
//配列の分割代入の活用法
//例１
function getConfig() {
  return [
    true,
    10,
    1,
    2,
    3
  ]
}
//dataはスプレッド演算子
const [isOn, amount, ...data] = getConfig();
console.log(isOn); //true
console.log(amount); //10
console.log(data); //[ 1, 2, 3 ]



//例2
let _isOn = false;
let _amount = 10;

function setConfig([isOn, amount]) {
  _isOn = isOn;
  _amount = amount;
}

setConfig([
  true,
  20
]);
console.log(_isOn); //true
console.log(_amount); //20
```

### オブジェクトの分割代入

```
function getConfig() {
  return {
    hello: "Hello World",
    isOn: true,
    amount:10
  }
}

//オブジェクトの場合、代入する変数名はプロパティ名と同じにする必要がある
//getConfigの戻り値の順番を無視し、プロパティ名と同じ値を取得している
var {isOn, amount} = getConfig();
console.log(isOn); //true
console.log(amount); //10
```

### オブジェクトの分割代入の例
```
var config = {}
function setConfig({isOn, amount}){
  config = {
    isOn, amount
  }
}
setConfig({
  isOn:false,
  amount: 20
});
console.log(config); //{ isOn: false, amount: 20 }
```

### 任意の変数名
```
function getConfig() {
  return {
    isOn: true,
    amount:10,
    servers: {
      a:"abcd",
      b:"efgh"
    }
  }
}
//以下のようにすれば任意の変数名でオブジェクトのプロパティを受け取れる
let {
  isOn:onOffinfo,
  amount:dataAmount,
  servers:{
    a:serverA
  }
} = getConfig();
console.log(onOffinfo); //true
console.log(serverA); //abcd
```

### デフォルト
分割代入の際にもデフォルト値を設定できる

```
//配列の場合
const arr = [1];
let [a, b=2000] = arr;
console.log(a); //1
console.log(b); //2000

//オブジェクトの場合
const obj = {
  isOn: true
};
var {isOn: serverOn, amount = 1000} = obj;
console.log(serverOn); //true
console.log(amount); //1000
```