ScalikeJDBCでページネーションに必要なクエリを発行する

ScalikeJDBCを使ってページネーションするにはどうすればよいか調べました。 DBはMySQLを使用します。

MySQLで発行するクエリ

SELECT SQL_CALC_FOUND_ROWS * -- LIMIT を付けなかった場合に返されるはずの行数を知るためのオプション
FROM users
LIMIT 10 -- 取得する行数を制限する
OFFSET 5; -- 開始位置(行数)

SELECT FOUND_ROWS(); -- 直前に実行したクエリの行数を取得する

ポイント:FOUND_ROWS()

直前に実行したクエリの行数を取得する 直前に実行したクエリにSQL_CALC_FOUND_ROWSが指定されていた場合、「LIMIT を付けなかった場合に返されるはずの行数」を取得できる

ScalikeJDBCで書く

DSLを使って書くとこんな感じ

withSQL {
  select(sqls"sql_calc_found_rows *")
    .from(User as u)
    .limit(10)
    .offset(5)
}.map(User(u)).single.apply()

SQL("select found_rows() as total").map(_.long("total")).single.apply()

ハマったところ

メソッド内で上のコードを実行すると、SQL操作ごとに新しいセッションを開始するためか、select found_rows()のクエリで直前のクエリを認識できない(totalCountが1件になる)

def totalCount(implicit session: DBSession = AutoSession): Option[Long] = {
  withSQL...
}

res: Option[Long] = Some(1) // 1件になってしまう

解決策:DB.readOnly で囲えば正しい結果を取得できた

def totalCount: Option[Long] = {
  DB readOnly { implicit s =>
    withSQL...
  }
}

res: Option[Long] = Some(3) // 正しい結果がとれる

--- 12/25 追記 ---

Call back function after list.apply · Issue #63 · scalikejdbc/scalikejdbc · GitHub

AutoSession starts new session for each SQL operation

ScalikeJDBCの AutoSessionSQL操作ごとに新しいセッションを開始する仕様とのことなので、 ReadOnlySession を使ってクエリ実行後に session.close する方法なら、DB.readOnly で囲わなくても良さそうです。

Scala学習【エラー処理に使う型】

前回に引き続きScalaの学習。今回はエラー処理で使うであろう戻り値をラップする型について。

Option型

  • 値をひとつだけ入れることができる
  • 2種類の形を持っている
    • Some(x) 値があるときの形
    • None 値がないときの形
  • 値があったりなかったりする値を表現するときに使う
  • エラー内容を表現できないので、エラー処理では使わない
// Some: 値があるとき
scala> val some: Option[String] = Option("hoge")
some: Option[String] = Some(hoge)

scala> some.get
res7: String = hoge

// None: 値がないとき
scala> val none: Option[String] = Option(null)
none: Option[String] = None

scala> none.get
java.util.NoSuchElementException: None.get
  at scala.None$.get(Option.scala:349)
  at scala.None$.get(Option.scala:347)
  ... 29 elided

Noneget したときの例外は getOrElse メソッドを使えば回避できる

scala> option.getOrElse("値がありません")
res3: String = 値がありません

パターンマッチを使って処理することもできる

scala> val some: Option[String] = Option("hoge")
some: Option[String] = Some(hoge)

scala> some match {
     |   case Some(str) => println(str) // Someの中身をstrという変数に束縛している
     |   case None => print("None")
     | }
hoge

Optionはコレクションの性質を持っているので、例えばmapを使える

// Some の場合
scala> Some(3).map(_ * 3)
res10: Option[Int] = Some(9)


// None の場合は関数を適用しても None のまま
scala> val none: Option[Int] = None
none: Option[Int] = None

scala> none.map(_ * 3)
res11: Option[Int] = None

// foldメソッドを使えばデフォルト値を指定できる
scala> none.fold(0)(_ * 3)
res12: Int = 0

Either型

  • Option型と違い、エラー時に任意のエラーの種類まで取得できる
  • RightとLeftの2つの値を持つ
    • Right: 正常な値
    • Left: エラー値
scala> val right: Either[String, Int] = Right(123)
right: Either[String,Int] = Right(123)

scala> val left: Either[String, Int] = Left("abc")
left: Either[String,Int] = Left(abc)

// パターンマッチもできる
scala> right match {
     |    case Right(i) => println(i)
     |    case Left(s)  => println(s)
     | }
123

Option型と同様にmapメソッドを持っている Either型の左右が平等に扱われる場合、mapメソッドはどちらに適用されるのだろう。となるが、暗黙的にrightを優先される(Scala2.12から) Scala2.12より前は平等に扱われるため、RightProjection型に変換する必要があった

// サジェストされるメソッドを確認
scala> right.
canEqual   filterOrElse   forall      isLeft     joinRight   merge            productIterator   swap       toTry
contains   flatMap        foreach     isRight    left        productArity     productPrefix     toOption
exists     fold           getOrElse   joinLeft   map         productElement   right             toSeq

// Scala2.12
scala> right.map(_ * 3)
res0: scala.util.Either[String,Int] = Right(369)

// Scala2.12より前
scala> right.right
res1: scala.util.Either.RightProjection[String,Int] = RightProjection(Right(123))

scala> right.right.map(_ * 3)
res0: scala.util.Either[String,Int] = Right(369)

Try型

  • 2つの値をとる
    • Success: 正常な値
    • Failure: エラー値(ただしThrowableというExceptionスーパークラスしか入れられない)
  • applyで生成する際に例外をキャッチし、Failureにする
  • NonFatal(致命的でない)という種類の例外だけキャッチする
scala> import scala.util.Try
import scala.util.Try

scala> val failure: Try[Int] = Try(throw new RuntimeException("to be caught"))
failure: scala.util.Try[Int] = Failure(java.lang.RuntimeException: to be caught)

scala> val success = Try(3)
success: scala.util.Try[Int] = Success(3)

参考

Scalaスケーラブルプログラミング第3版 - インプレスブックス

N予備校 プログラミングコース

Scala学習【コレクション】

最近Scalaをはじめたので、学習した内容をブログに書いていけば、知識が補強されて理解が深まるのでは?と思い書いていきます。 今回はScalaの豊富なコレクションライブラリーについて簡単に特徴をまとめました。おそらく基本的なものしか網羅できてません。

シーケンス

Seq型はただのトレイトであり、実際の実装は複数存在している

List型

  • 先頭の値(head)とその後ろのリスト(tail)により構成される
  • 不変
  • 先頭の値の取得や削除は高速だが、任意の添字を持つ要素へのアクセスは、リストを線形に辿っていくため、高速ではない
  • 先頭の値の取得や削除は高速なので、パターンマッチに適している
scala> List(1, 2, 3)
res0: List[Int] = List(1, 2, 3)

scala> 3::res0
res1: List[Int] = List(3, 1, 2, 3)

Array型

  • Javaの配列と互換性を持った型
  • 可変
  • Javaの配列は、宣言時にメモリの確保を行う。
  • そして、添字で数値を指定してそのメモリ上の値を取得するため、添字を使った特定の要素の参照は、一度のアクセスで行うことができ高速
  • 一方挿入削除を行う際は、再度配列のメモリの確保を行うのでメモリ効率は悪くなる
scala> Array(1, 2, 3)
res3: Array[Int] = Array(1, 2, 3)

scala> res3(1)
res4: Int = 2

ListBuffer

  • 可変
  • 要素を先頭に挿入するときも末尾に追加するときも一定時間で処理できるリスト
scala> import scala.collection.mutable.ListBuffer
import scala.collection.mutable.ListBuffer

scala> val buf = new ListBuffer[Int]
buf: scala.collection.mutable.ListBuffer[Int] = ListBuffer()

scala> buf += 1
res6: buf.type = ListBuffer(1)

scala> buf += 2
res7: buf.type = ListBuffer(1, 2)

scala> 3 +=: buf
res8: buf.type = ListBuffer(3, 1, 2)

ArrayBuffer

  • 可変
  • 配列の先頭と末尾で要素を追加・削除できる
scala> import scala.collection.mutable.ArrayBuffer
import scala.collection.mutable.ArrayBuffer

scala> val buf = new ArrayBuffer[Int]()
buf: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer()

scala> buf += 12
res9: buf.type = ArrayBuffer(12)

scala> buf += 15
res10: buf.type = ArrayBuffer(12, 15)

scala> 3 +=: buf
res11: buf.type = ArrayBuffer(3, 12, 15)

Range

  • 範囲を表すオブジェクト
scala> 1 to 3
res12: scala.collection.immutable.Range.Inclusive = Range 1 to 3

scala> 1 to 17 by 4
res14: scala.collection.immutable.Range = Range 1 to 17 by 4

scala> 1 until 3

文字列(StringOps)

  • Predef(Scalaソースファイルに暗黙のうちにimportされているオブジェクト)がStringからStringOpsへ暗黙の型変換を提供しているため、すべての文字列はシーケンスのように扱うことが出来る。
scala> val s = "hoge"
s: String = hoge

scala> s(1)
res5: Char = o

集合

Set

  • 要素の集合を表す型
  • 要素は重複しない
  • 可変と不変があり、デフォルトは不変
  • 順番を維持しない(順序が必要な際はTreeSetを用いる)
scala> val s = Set(1, 1, 2, 2, 3, 3)
s: scala.collection.immutable.Set[Int] = Set(1, 2, 3)

連想配列

Map

  • キーと値を持つ連想配列
  • 可変と不変があり、デフォルトは不変
  • キーと値のセットは2要素のタプルで表現されている
  • 添字を使って中身の値を取得することができるが、nullが発生する可能性があるため、基本はgetメソッドを利用してOptionの値を取得する
  • 順番を維持しない(順序が必要な際はTreeMapを用いる)
scala> val m = Map("a" -> 1, "b" -> 2, "c" -> 3)
m: scala.collection.immutable.Map[String,Int] = Map(a -> 1, b -> 2, c -> 3)

コレクション

Stack

  • 後入れ先出しのコレクション
  • 可変と不変がある
  • pushメソッドで要素を入れ、pop2メソッドで先頭の値と残った要素を持つStackをタプルとして取得できる
scala> scala.collection.immutable.Stack()
res0: scala.collection.immutable.Stack[Nothing] = Stack()

scala> res0.push(1)
res1: scala.collection.immutable.Stack[Int] = Stack(1)

scala> res1.push(2)
res2: scala.collection.immutable.Stack[Int] = Stack(2, 1)

scala> res2.pop2
res3: (Int, scala.collection.immutable.Stack[Int]) = (2,Stack(1))

Queue

  • 先入れ先出しのコレクション
  • 可変と不変がある
  • enqueueメソッドで要素を入れ、dequeueメソッドで先頭の値と残った要素を持つQueueをタプルとして取得できる
scala> scala.collection.immutable.Queue()
res4: scala.collection.immutable.Queue[Nothing] = Queue()

scala> res4.enqueue(1)
res5: scala.collection.immutable.Queue[Int] = Queue(1)

scala> res5.enqueue(2)
res6: scala.collection.immutable.Queue[Int] = Queue(1, 2)

scala> res6.dequeue
res7: (Int, scala.collection.immutable.Queue[Int]) = (1,Queue(2))

PriorityQueue

  • enqueueした順によらず、自動的に中身をソートして並べてくれるQueue(優先度つきキュー)
  • 可変のみ
  • 挿入削除時に並べ替えるコストが低く、先頭の値取得も高速に行うことができる
  • newBuilderメソッドの引数にOrdering型のオブジェクトを渡すことでソートする順番を変更できる
  • ソートにコストがかかり、先頭の値だけを取得すれば良いような処理を、非常に高速に処理することができるコレクション

タプル

  • 異なる型のオブジェクトを結合して、1単位として渡せるようにしたもの
scala> (1, "hello", Console)
res21: (Int, String, Console.type) = (1,hello,scala.Console$@2de22e7)

参考

Scalaスケーラブルプログラミング第3版 - インプレスブックス

N予備校 プログラミングコース

【React】親コンポーネント更新による子コンポーネントの更新処理を減らす

Reactでは親コンポーネントが更新されると、その子コンポーネント全てに更新処理が走るのですが、値に変更がない子コンポーネントは更新処理を行わないようにできないものかと調べたら、良さそうな方法が見つかったので備忘録として残します。

参考:

ReactでshouldComponentUpdateを使ったチューニングの効果と注意どころ - Qiita Reactの再レンダリングをなるべく減らす - Aqutras Members' Blog

Reactコンポーネントの更新処理をキャンセルするためには、shouldComponentUpdate内でfalseを返してあげれば良い。(デフォルトではtrue) なので、子コンポーネントpropsstateに変更がなければ、shouldComponentUpdate内でfalseを返す処理を実装すれば、データの更新がない子コンポーネントの更新を行わないようにできる。

propsstateが全て値型か、参照型を含むかで異なる実装を行う。

全て値型の場合

react-addons-pure-render-mixinを使うと簡単に実装できて良い。

import PureRenderMixin from 'react-addons-pure-render-mixin';

class HogeComponent extends React.Component {
  constructor(props) {
    super(props);
    this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
  }

  render() {
    return (
      ...
    )
  }
}

値型だけでなく参照型を含む場合

lodashの _.isEqual 関数を用いてオブジェクトを比較する。 もちろん値型も見てくれるので、react-addons-pure-render-mixinを使わずに、この方法に統一しても良さそう。

import isEqual from 'lodash/isEqual';

class HogeComponent extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    const propsDiff = isEqual(nextProps, this.props);
    const stateDiff = isEqual(nextState, this.state);
    return !(propsDiff && stateDiff);
  }

  render() {
    return (
      ...
    )
  }
}

とりあえず今の自分の環境では↑の2通りの方法を使って実装しています。

GulpとBabelでES2015をES5に変換する環境をつくる

前回の記事でES2015の文法について覚えたので、今回はES2015をES5に変換するための環境をつくります。

参考:

Node.js

Node.jsとは

サーバーサイドのJavaScript、非同期の処理が得意。

インストール

方法は色々とありますが、今回はnodebrewというバージョン管理ツールを利用してインストールします。 ターミナルを開いて以下のコマンドを実行。 nodebrewのインストールが開始されます。

$ curl -L git.io/nodebrew | perl - setup
========================================
Export a path to nodebrew:

export PATH=$HOME/.nodebrew/current/bin:$PATH
========================================

インストール後、環境設定ファイルにnodebrewのパスを通してねと言われるので、.bash_profileをviエディタで開き、コードを追記します。

$ vi ~/.bash_profile
export PATH=$HOME/.nodebrew/current/bin:$PATH

sourceコマンドで.bash_profileをリロードします。

$ source ~/.bash_profile

nodebrewがインストールされているか確認。

$ nodebrew help
nodebrew 0.9.5

Usage:
    nodebrew help                         Show this message
...

nodebrewをインストールし終わったら、Node.jsの使用するバージョンを指定します。今回は安定版を指定しました。 -vコマンドでバージョンを確認してNode.jsのインストール完了です。

$ nodebrew use stable
use v5.10.1
$ node -v
v5.10.1

npmをインストール

Node.jsで書かれたツールを管理するnpmというパッケージ管理ツールをインストールします。Rubyでいうとgemのようなやつです。 プロジェクト用のディレクトリを作成し、ターミナルでその中へ移動します。今回はpractice/es2015devというディレクトリを作成しました。

$ cd practice/es2015dev

移動したら以下のコマンドでpackage.jsonを作成します。 対話形式で任意の項目を入力します。今回は全てEnterキーを押しました。

$ npm init

...
name: (es2015dev) 
version: (1.0.0) 
description: 
entry point: (index.js) 
test command: 
git repository: 
keywords: 
author: 
license: (ISC) 
About to write to /Users/XXXX/practice/es2015dev/package.json:

{
  "name": "es2015dev",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


Is this ok? (yes) yes

入力を終えると、ディレクトリ内にpackage.jsonが作成され、中には上の入力内容が記載されています。

Gulp

Gulpとは

様々な作業を自動化できる「タスクランナー」と呼ばれるツールです。Web制作においては、css,js,画像などの圧縮、Sassのコンパイル、ベンダープレフィックスの付与、LiveReloadなどを全て高速に行ってくれます。

インストール

以下のコマンドでGulpをインストールします。-gはグローバル領域にインストールするコマンドです。

$ npm install gulp -g

--save-devコマンドでローカルにもインストールします。

$ npm install gulp --save-dev

するとpackage.jsonにGulpが記載されます。

  "devDependencies": {
    "gulp": "^3.9.1"
  }

Gulpがグローバルとローカル両方に入っているか確認します。

$ gulp -v
[22:01:40] CLI version 3.9.1
[22:01:40] Local version 3.9.1

Babelをインストール

ES2016をES5記法に変換するBabelをローカルにインストールします。

$ npm install babel babel-preset-es2015 gulp-babel --save-dev

.babelrcファイルを作成し、利用するpresetsを記述します。

{
  "presets": ["es2015"]
}

Gulpの設定ファイルをpackage.jsonと同じディレクトリに作成します。 Gulpはバージョン3.9.0からBabelを標準サポートするようになりました。設定ファイルの名前をgulp.jsではなくgulp.babel.jsにすることで、ES2015で設定ファイルを記述できるようになります。

'use strict';

const gulp = require('gulp');
const babel = require('gulp-babel');

// タスクの登録
gulp.task('js', () =>
  gulp.src('src/js/**/*.js') // 読み込むファイル
    .pipe(babel()) // babelを実行
    .pipe(gulp.dest('dist/js')) // 出力先
);

src/jsディレクトリを作成し、その中にES2015で記述したjsファイルを作成します。

const obj = {
  add: (a,b) => a + b
};

準備ができたので、gulpを実行します。

$ gulp js
[22:18:57] Requiring external module babel-register
[22:18:57] Using gulpfile ~/practice/es2015dev/gulpfile.babel.js
[22:18:57] Starting 'js'...
[22:18:57] Finished 'js' after 107 ms

/dist/jsディレクトリにES5変換後のjsファイルが生成されました。

"use strict";

var obj = {
  add: function add(a, b) {
    return a + b;
  }
};

ES2015(ECMAScript2015)の文法を覚える

JavaScriptについての知識は初心者から抜け出せない程度のレベルですが、フロントエンド界隈では現在キャッチアップする価値がある技術の1つとしてES2015が挙げられていたので、勉強したときのメモです。 ES2015の仕様全ては理解しきれていないので、今回は使用頻度の高そうな文法に絞って書いています。

参考:

ES2015とは

ES2015とは、JavaScriptが採用している標準化された言語仕様、ECMAScriptの第6版です。ES2015は2015年6月に発行され、現在(2016年4月)最新バージョンのブラウザでも全ての機能を実装できていないこともあり、Babel等のトランスパイラを用いてES5に変換して使用する方法が一般的です。変換方法については次回の記事で書きます。


letとconst

varのように変数宣言に使用します。上書きリスクが減ることから、今後はletとconstを主に使いそうです。 それぞれの違いは、

  再宣言 再代入 スコープ 巻き上げ
var できる できる 関数スコープ あり
let できない できる ブロックスコープ なし
const できない できない ブロックスコープ なし
var a = 0; // var宣言
let b = 1; // let宣言
const c = 2; // const宣言
// const d; // constは宣言のみはできない

var a = 3; // varは再宣言できる
// let b = 4; // letは再宣言できない
// const c = 5; // constは再宣言できない

a = 6; // varは再代入できる
b = 7; // letは再代入できる
// c = 8; // constは再代入できない

{
  var a = 9; // {}外で宣言したaを上書きする
  let b = 10; // {}内でのみ有効なbを宣言する
  const c = 11; // {}内でのみ有効なcを宣言する

  console.log(a); // 9
  console.log(b); // 10
  console.log(c); // 11
}

console.log(a); // 9
console.log(b); // 7
console.log(c); // 2


function以外の中括弧(ここではif)でもスコープが切れる。

// 今まで
function foo() {
  var bar = 1;
  if (true) {
    var bar = 2;
  }
  console.log(bar);
}
foo(); // 2 ifでスコープが切られないので上書きされる

// ES2015
function foo2() {
  let bar = 1;
  if (true) {
    let bar = 2;
  }
  console.log(bar);
}
foo2(); // 1 ifでスコープが切られるので上書きされない


巻き上げ(宣言よりも前で変数を参照すること)が発生しない。

// 今まで
console.log(e); // undefined 宣言前で変数を参照できてしまう
var e = 'hoge';

// ES2015
// console.log(f); // エラー
// let f = 'hoge';


for内でsetTimeoutが含まれる場合でも正しく動作する。

// 今まで
for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // 3が3回表示されてしまう
  }, 1000);
}

// ES2015
for (let i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // 0,1,2 と表示される
  }, 1000);
}


アロー関数

function宣言を() =>という記述に省略できます。

// 今まで
var sum = function(a, b) {
  return a + b;
};

// ES2015
const sum = (a, b) => {
  return a + b;
};

// 関数本体が式だけで表せる場合、{}とreturnを省略できる
const sum = (a, b) => a + b;

// 引数が1つの場合、()を省略できる
const func = a => a * a;


さらに、アロー関数を使うとthisは自動的に補足されるため、setTimeout関数の引数に指定された無名関数の内部でthisがグローバルオブジェクトを指してしまう問題を回避することができます。

// 今まで
var takashi = {
  name: 'Takashi',
  sayHello: function() {
    var self = this; // thisをselfに保存しておく
    setTimeout(function() { // thisはグローバルオブジェクトを指す
      console.log("Hello, I'm " + self.name);
    }, 1000);
  }
}
takashi.sayHello(); // Hello, I'm Takashi

// ES2015
const takashi = {
  name: 'Takashi',
  sayHello() { // functionを省略できる(メソッド定義記法)
    setTimeout(() => { // アロー関数を使うとthisはtakashiを指す
      console.log("Hello, I'm " + this.name);
    }, 1000);
  }
}
takashi.sayHello(); // Hello, I'm Takashi


クラス

これまでJavaScriptにはクラスという概念がなく、prototypeを継承するなどして冗長な記述でクラスを定義していましたが、ES2015でクラスは正式な文法となりました。

// クラスの定義
class Person {
  constructor(name) { // コンストラクタ
   this.name = name; // プロパティ
  }

  sayHello() { // インスタンスメソッド
    console.log("Hello, I'm " + this.name);
  }

  static create(name) { // スタティックメソッド
    return new Person(name);
  }
}

// インスタンスの生成
const saki = new Person('Saki');
saki.sayHello(); // Hello, I'm Saki

// スタティックメソッドの呼び出し
const kota = Person.create('Kota');


クラスの継承はextendsを使い、継承元のコンストラクタメソッドを呼び出すためにはsuperを使います。

// クラスの継承
class Teacher extends Person {
  constructor(name, age) {
    super(name); // 継承元のコンストラクタを呼び出す
    this.age = age;
  }

  sayHello() {
    super.sayHello(); // 継承元のメソッドを呼び出す
    console.log(this.age + ' years old');
  }

  static create(name, age) { // スタティックメソッドの上書き
    return new Person(name, age);
  }
}

const ellen = new Teacher('Ellen', '28');
ellen.sayHello();


引数の拡張

関数に指定する引数のデフォルト値を設定することができるようになりました。

const sum = (a = 1, b = 2) => {
  return a + b;
};
console.log(sum()); // 3
console.log(sum(2)); // 4
console.log(sum(2,3)) // 5


さらに可変長の引数を取ることができるようになりました。(レストパラメータ)

const foo = (a,b,...rest) => {
  console.log('a:', a);
  console.log('b:', b);
  console.log('rest:', rest);
};
foo(1,2,3,4,5);
// a: 1
// b: 2
// rest: [3, 4, 5]


オブジェクト記法の拡張

様々な省略記法が可能になりました。 - オブジェクトのプロパティのキー名と値の変数名が同じ場合の省略記法

var foo = 1;

// 今まで
var obj = {'foo': foo};

// ES2015
const obj = {foo};
  • プロパティのキー名が変数に入った文字列である場合の省略記法
var key = 'foo';

// 今まで
var obj = {};
obj[key] = 1;
console.log(obj.foo); // 1

// ES2015
const obj = {
  [key]: 1
};
console.log(obj.foo); // 1
// 今まで
var obj = {
  add: function(a,b) {
    return a + b;
  }
};
console.log(obj.add(1,2)); // 3

// ES2015
const obj = {
  add(a,b) {
   return a + b;
  }
};
console.log(obj.add(1,2)); // 3

// ES2015 アロー関数を使う場合
const obj = {
  add: (a,b) => a + b
};
console.log(obj.add(1,2)); // 3


展開演算子(スプレッドオペレータ)

可変長の引数を配列として受け取るレストパラメータに対して、配列を複数の引数に展開する機能がスプレッドオペレータです。

var arr = [1, 3, 2];

// 今まで
var max = Math.max.apply(null, arr);
console.log(max); // 3

// ES2015
const max2 = Math.max(...arr);
console.log(max2); // 3


分割代入

分割代入は、配列やオブジェクトからデータを抽出できる機能です。

// 配列の分割代入
const week = ['月', '火', '水', '木', '金', '土', '日',];

const [a, b, c, d, e, f, g] = week;
console.log(a, b, c, d, e, f, g); // 月 火 水 木 金 土 日

// 2番目と5番目の値だけを代入
const [,b,,,e] = week;
console.log(b, e); // 火 金

// レストパラメータの活用
const [a, b,...cdefg] = week;
console.log(a, b, cdefg); // 月 火 ["水", "木", "金", "土", "日"]

// 値の交換
let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b); // 2 1

// オブジェクトの分割代入
const obj = {
  name: 'たかし',
  age: 15
};

const {name: a, age: b} = obj;
console.log(a, b); // たかし 15

// オブジェクト代入のプロパティ省略記法
const {name, age} = obj;
console.log(name, age); // たかし 15

// デフォルトの値の指定
const {name, age = 15} = {name: 'たかし'};
console.log(name, age); // たかし 15


イテレータ

イテレータとは、集合から要素を繰り返し取り出すためのオブジェクトです。現在の位置を記録しつつ、中の要素へ1つずつアクセスするnextメソッドを持っています。

const arr = [1, 2, 3];
const iter = arr[Symbol.iterator]();

console.log(iter.next()); // {value: 1, done: false}
console.log(iter.next()); // {value: 2, done: false}
console.log(iter.next()); // {value: 3, done: false}
console.log(iter.next()); // {value: undefined, done: true}


for of文

反復処理可能なオブジェクトの値を列挙することができます。for inはプロパティ名を取得するのに対して、for ofはプロパティの値を取得します。

const arr = [3, 4, 5];
arr.foo = 'hoge';
console.log(arr);

for (let a in arr) {
  console.log(a); // 0 1 2 foo
}

for (let b of arr) {
  console.log(b); // 3 4 5
}