JS/ES – イベントハンドリングの基本

概要

JavaScriptのイベントハンドリングの基本は(他の言語と同様に)、以下の手順を踏む。

  1. イベントを発生させるオブジェクトを生成する
  2. イベントリスナー(イベントハンドラー)を定義する
    • その際、捕捉したいイベントを指定する
  3. イベントリスナーをオブジェクトに登録する

以下、JSにおけるイベントリスナーの登録手順を整理する。

基本形式

最も基本に忠実な手順を如何に例示する。

具体的には、ボタンを配置し、クリックに対するリスナーを定義し、そのリスナーをボタンに登録している。

無名関数

以下の例では、無名関数としてリスナーを定義し、それを直接addEventListenerの引数としている。

無名関数を使っているので、リスナーを保持する変数は必要ない(リスナーに名前を付ける必要がない)。リスナーの処理内容が少ないときに使える。

メソッドチェーン

以下の例では、ボタンオブジェクトの取得とリスナーの登録をメソッドチェーンで行い、さらにリスナーを無名関数で渡している。

リスナーの処理コード量が多いときは、無名関数ではなく通常の関数定義とする。

 

JS/ES – 配列~Array

配列の定義

リテラル

配列のリテラルは他の言語と似ていて、[]の中に要素を','で区切って並べる。

空の配列も定義可能。

Arrayコンストラクター

Arrayクラスのコンストラクターで空の配列を作成可能。

要素の参照・追加

配列の要素の参照は他の言語と同じ。もちろん値の更新もできる。

新たに要素を追加するには、インデックスと値を指定。もちろん値の更新も可能。

配列の要素は、すべて同じ型でなくてもよい。

要素番号を飛ばして値を追加すると、その間は'empty'になる。

配列の長さ

配列の長さ(要素数)はlengthプロパティーで得られる。

先頭・末尾要素の取出し・追加・削除

配列の先頭要素・末尾要素について、追加や削除・取出しのメソッドが準備されていて、配列をキューやスタックのように使うことができる。詳細は配列の先頭・末尾要素の追加・削除にまとめている。

  • push~末尾追加
  • pop~末尾取出し・削除
  • unshift~先頭追加
  • shift~先頭取出し・削除

要素の取出し・削除・置換

splice~インデックス指定で取出し・削除・置換

splice()メソッドは指定したインデックスから指定した数の要素を削除する。追加する要素を指定した場合は指定した位置に要素が追加される。

filter~指定した内容の要素の取出し・削除

条件に一致する要素のみ取り出す場合、filter()メソッドを使う。filter()メソッドは非破壊的で新たな配列を生成する。

要素の内容を指定して削除する場合もfilter()で。

map~配列の要素の加工

map()メソッドは配列の各要素やインデックスなどの処理結果を要素とする配列を返す。

値とインデックスの順次取り出し

for文で値を取り出す

最も基本的なfor文の使い方で、インデックスを0~配列長−1まで変化させて直接参照する方法。

for...ofで値を直接取り出す

ES6のfor...of構文は、配列から要素を順次取り出してくれる(Pythonのfor...inと同じ動作)。

for...inでインデックスを取り出す

ES6のfor...inは配列からインデックスを順次取り出してくれる。ただし戻り値はStringなので要注意

entries()でインデックスと値を同時に取り出す

Array.prototype.entries()はインデックスと値の配列をイテレーションで返してくれる。

map()に値とインデックスの2つの引数を指定する

Array.prototype.map()は引数で指定したコールバック関数を各要素に適用した結果を配列として返す。通常は引数を1つ指定して配列の要素を得て、それに対して処理する。

map()は2つ目の引数を指定することができて、その場合2つ目の引数にはインデックスの値が入る。しかも、for…inと違ってインデックスは数値として得られる。

reducer~配列要素に対するイテレーション

samやmax/minメソッドはない

JSには多言語のように配列要素の合計や最大値を求める汎用の関数やメソッドが準備されていない。

その実装は、プログラムごとにArray.reduce()メソッドで行われているようだ。たとえば要素の合計を求める例を以下に示す。

配列の複製

const array_clone = [].concat(array)

const array_clone = array.slice(0, array.length)

const array_clone = [...array]

const array_clone = Array.from(array)

const array_clone = array.map(x => x)

 

JS/ES – Array.reduce

概要

Array.prototype.reduce()メソッドは、配列の要素を一つずつ取り出しながら畳み込み処理し、一つの値を返すメソッド。

要素の合計や最大値の取得といった基本的な処理もこのメソッドで実装する。

書式

reduce()メソッドは引数にコールバック関数をとる。

[配列].reduce(callback[, initial_value]);

コールバック関数がとるべき引数は決まっている。

callback(accumulator, current_value[, index[, array]]) 

処理の概要

initial_valueが指定されない場合、以下のように処理される。

  1. 配列の最初の要素の値がaccumulatorにセットされる
    1. 要素数が1の場合には、そのまま終了
    2. 要素数が2の場合には、2番目の要素の値がcurrent_valueにセットされる
  2. 最後の要素に達するまで、定義された関数の処理を行い、curren_valueを次の要素の値とする

initial_valueが指定された場合は、accumulatorの初期値としてinitial_valueが使われ、最初のcurrent_valueには1つ目の要素の値がセットされる。通常、initial_valueを指定しておく方が安全。

畳み込みの途中結果はaccumulatorに保存されていくが、戻り値は処理内容に応じてユーザーが定義する。

基本的な例

要素の合計

以下の例では、配列の要素の合計値を求めている。

関数定義では、各要素をスキャンするごとにその値をaccumulatorに足しこんでいる。その結果、最後の要素に達した時にaccumulatorの値は要素の合計値となっている。

要素の最大値

以下の例では、配列の要素の最大値を求めている。

accumulatorの値は要素をスキャンするごとにより大きい値へと書き換えられ、実行終了時には要素のうち最大の値となっている。

処理過程の確認

reduce()のイテレーション各回でaccumulatorやcurrent_valueがどのように推移するか確認してみた。

コールバック関数の戻り値がないため、2回目以降のイテレーションでaccumulatorの内容がundefinedになっている。逆に言えば、reduceのイテレーションごとにコールバックの戻り値をaccumulaterに渡している。

イテレーションの最後でcurrent_valueの値がundefinednullemptyなどのいずれでもない理由は不明。

initial_valueの指定の有無によって、accumulator、current_valueの各回の値やイテレーション回数が異なっている点に注意。

 

JS/ES – アロー関数

概要

アロー関数/アロー関数式は、function定義の短縮構文。

基本形

function'=>'

'function(引数)''(引数) =>'の形にするのが基本形。

1行表記の簡略化

さらに以下のように簡略化できる。

  • {}で囲んで1行表記
  • 処理内容が1行の場合は{}が不要
  • 引数が1つの場合は引数の()も不要

ただし引数がないときは()が必要。

 

JS/ES – 関数式/無名関数/即時関数

関数式~関数の変数への代入

ユーザー定義関数はfunctionキーワードの後に関数名を指定して関数を定義し、その関数名で関数を呼び出して実行した。

これに対して、関数を変数に代入して、その変数名を用いて関数を実行することができる(関数式:function formula)。

無名関数

このような使い方で、関数名自体を指定せずに関数の実体を変数に代入することもできる。

このように名前を持たない関数を無名関数/匿名関数(anonymous function)と呼んでいる。

このような無名関数を変数に与える関数式は、メソッドや関数の引数に関数を与えることが想定されている場合に用いる。たとえばDOMに対するイベントリスナーの登録や、配列オブジェクトのreduce()メソッドに合計を計算するためのreducerを与える例など、その活用場面は多い。

即時関数

さらに、関数の変数への代入自体も省いて、無名関数のまま実行させてしまう書き方を即時関数(immediately-invoked function express: IIFE)と呼んでいる。

この記法は、varによる変数定義が関数スコープであることを利用して、変数の汚染を防ぐために使われる。

ES6以降導入されたブロックスコープより前にはグローバルスコープと関数スコープしかなかったため、必要なコード全体をIIFEの中に納めて、そのスコープ外の変数と独立させようという考え。

 

JS/ES – ユーザー定義関数

概要

ユーザー定義関数(user-defined function)は、関数名、引数、戻り値を指定して定義し、プログラム中で呼び出す。

関数定義

引数なし・戻り値なし

関数の中で必要な処理だけを行う。

引数あり・戻り値なし

与えられた引数に基づいて実行。

引数あり・戻り値あり

与えられた引数に基づいて実行し、その結果が戻り値として返される。

関数定義の巻き上げ

関数定義は実行の後に書いてもよく、実行時に定義のみがプログラム先頭に巻き上げられる。

 

JS/ES – 変数のスコープ

スコープの種類と宣言キーワード

スコープの種類

グローバルスコープ

ドキュメント内のどこからでも参照可能。

関数スコープ

宣言された関数内でのみ参照可能。関数内の入れ子の関数・ブロックからは参照可能。

ブロックスコープ

宣言されたブロック内でのみ参照可能。ブロック内の入れ子の関数・ブロックからは参照可能。

宣言キーワードとスコープ

キーワードなしの宣言

ドキュメント内のどこからでも参照可能なグローバルスコープ。

var

宣言された関数内の関数スコープ。

  • ドキュメント直下で宣言した場合、グローバルスコープ
  • 関数で宣言した場合、その関数内のスコープ
  • ブロック内で宣言した場合、そのすぐ外側の関数のスコープ(ブロックがドキュメント直下にあればグローバルスコープ)

let, const

宣言されたブロック内のブロックスコープ。

  • ドキュメント直下で宣言した場合、グローバルスコープ
  • 関数で宣言した場合、その関数内のスコープ

確認

グローバルスコープ

JSコード

実行結果

 

関数スコープ

  • var, let, constとも関数内で宣言された場合はその関数内のみのスコープ
  • 宣言された関数の中に入れ子で宣言された関数からは参照可能
  • 入れ子の内側の関数内で宣言されたvar, let, constは、外側の関数からは参照できない

JSコード

実行結果

ブロックスコープ

  • var宣言はブロック内外に関わらず参照可能
  • let, constは宣言されたブロック内のみのスコープ
  • 宣言されたブロックの中の入れ子のブロックからは参照可能
  • 入れ子の内側のブロック内で宣言されたvar, let, constは、外側のブロックからは参照できない

JSコード

実行結果

 

Tips – リストのマーカーを画像に

list-style-imageによる方法

直感的で簡明な方法。ただしマーカーの画像の位置やサイズの調整が細やかにできない難点がある。

HTML

CSS

背景画像による方法

手数が多くなるが、画像の位置やサイズの調整を細やかに調整できる。大まかな手順は以下の通り。

  • ulで
    • list-styleを消す
    • マーカーを含めた適切なインデント位置にするため、paddingを設定する
  • liで
    • backgroundやbackground-imageで画像ファイルを読み込む
    • no-repeat指定
    • positionは左からの距離と上からの距離が標準
    • マーカーとテキストが重ならないようにpaddingを設定

HTML

CSS

 

Tips – 間にだけ仕切り線を引く

概要

たとえば縦に並んだリストの要素間だけに横の境界線を引いたり、横に並んだ要素の左右以外の間にだけ縦の境界線を引きたい場合。

隣接セレクターを使う方法

以下の例は、li要素に対して隣接セレクター(隣接兄弟結合子:adjacent sibling combinator)を利用している。

HTML

CSS

隣接セレクターは、セレクターAとセレクターBを'+'結合子でつなぐ。そうすると、BのうちAの直後に置かれたもののみにスタイルが適用される。

上の例では、先頭のli要素にはその前のli要素がないのでスタイルが適用されない。2番目以降のli要素には直前のli要素があるので上線が引かれる。その結果、最初のli要素の上側と最後のli要素の下側には線が引かれず、li要素間にのみ線が引かれることになる。

なお上の例では、

  • adjacentクラスの中のli要素にのみ対象としている
  • ブロック要素であるliの中にインライン要素のspanを置き、そのspanに対して上線を引いている

疑似クラスを使う方法

疑似クラスを使って以下のようにしても実現できる。

  • 全ての要素に上線を引く
  • 最初の要素だけ境界なしとしてオーバーライド

HTML

CSS

この方法は、一旦全要素に線を引いてから1つ目を消すという2度手間になっている。

なお、上線とfirst-childの代わりに下線とlast-childでも同様のことが実現できる。

クラスを使う方法

ブラウザーによって隣接セレクターや疑似クラスが使えない場合は、最初か最後のli要素にクラスを指定して、疑似クラスと似たような方法で線を消す。