JS/ES – DOM – 要素の追加・変更・削除

概要

window.documentのメソッドによって、bodyや親要素に対して動的に要素を追加・変更・削除する方法。

なお要素のタグで囲まれたテキストの操作についてはこちらにまとめた。

要素の追加

要素の追加は以下の手順。

  1. document.createElement()で要素オブジェクトを生成
    • 引数で生成する要素名を指定
  2. 要素の内容やスタイルを設定
    • 要素のスタイルはCSSプロパティーで設定
    • 要素のテキストを設定
  3. 親要素のappendChild()メソッドの引数に追加する要素を指定して追加

bodyへの追加

bodyへの追加も他の親要素への追加と同じで、親要素としてdoccument.bodyを指定する。以下の例では、htmlに書かれたp要素に続けて動的にp要素を追加している。

appendChild()はbody要素が読み込まれた後でなければならない。イベントリスナーの登録タイミングと同じように、windows.onloadの中で実行するなど留意する必要あり(以下の例では要素の生成もonloadの中に書いているが、最低限appendChild()がここにあればよい)。

HTML

結果

親要素への追加

生成した要素を特定の親要素の子要素として登録する場合、親要素のappendChild()の引数に追加する要素を指定。

以下の例では、HTMLに書かれたdiv要素の中とその次にp要素を追加している。

HTML

結果

元から設置されたdiv要素

div要素の子要素としてp要素を追加

div要素の2番目の子要素としてp要素を追加

 

div要素の次にp要素を追加

要素の入れ替え

要素の入れ替えは、入れ替える要素の親要素のreplace()メソッドを実行する。1つ目の引数に入れ替え後の新しい要素を、2つ目の引数に入れ替えられる前の要素を指定する。

HTML

結果

置き換え用p要素の1つ目

元から設置されたp要素の2つ目

置き換え用p要素の2つ目

追加された要素の2つ目

要素の削除

要素を削除するには、その要素を含む親要素のremoveChild()メソッドに削除する要素を指定する。

以下の例では、HTMLに書かれた2つのp要素と動的に追加した2つのp要素から1つずつを削除している。

HTML

結果

元から設置されたp要素の2つ目です

追加された要素の2つ目です

 

JS/ES – interval

概要

WindowOrWorkerGlobalScopesetInterval()メソッドは一定間隔で関数を実行し、clearInterval()メソッドはこれを停止する。

書式

timer_id = setInterval(function, interval);

clearInterval(timer_id);

function

一定時間間隔で実行させる関数を指定する。
interval
functionを実行させる時間間隔をミリ秒単位で与える。

setInterval()は、戻り値として実行中のタイマーのidを整数で返す。clearInterval()の引数にこのタイマーidを指定して、実行中のタイマーを停止させる。

実行例

以下の例ではページに1つのp要素と2つのボタンを配置する。

startボタンを押すとカウントが始まり、1秒ごとに増加するカウンターの値がp要素に表示される。

stopボタンを押すとカウントアップが停止される。

 

 

JS/ES – イベント登録のタイミング

概要

オブジェクトにイベントリスナーを登録する場合、対象オブジェクトの生成とリスナー登録の前後関係に留意しなければならない。

失敗例

まず失敗例。以下のコードはボタンをクリックしても動作しない。

 

なぜなら、head要素でbodyが読み込まれる前にid=”button”の要素を読み込もうとするが、その段階でまだ対象となるbutton要素は読み込まれていないからだ。

その後bodyでbutton要素が配置されるが、そのときにはリスナーを登録しようとして失敗しているので、結局button要素にはリスナーは登録されていない。

事実検証画面で確認すると、”Uncaught TypeError: Cannot read property ‘addEventListener’ of null”となっていて、リスナーを登録しようとしている要素がnullだとなっている。

改善例1~後置

以下は、button要素が配置された後にスクリプトを実行するように、script要素の位置を後ろに異動したもの。この場合はエラーにはならず、ボタンを押せば無事ダイアログが表示される。

改善例2~onload

以下の例は、スクリプトをhead要素に置いたままで正常に実行する例。

リスナー登録の部分をwindow.onloadのリスナーとして定義することで、ページの読み込みが完了した後にwindow.onload()が実行され、その内容としてリスナーの登録が行われる。

この方法は、JSのコードを別ファイルとしてheadで読み込むような際にも有効。

改善例2’~windowリスナーの追加

もう一つの改善例は、windows.onload()メソッドをオーバーライドする代わりに、windowloadイベントに対するリスナーを追加するもの。

これは先のwindow.onload()のオーバーライドと同じ効果を持ち、ボタンには適切にリスナーが登録される。

 

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)は、関数名、引数、戻り値を指定して定義し、プログラム中で呼び出す。

関数定義

引数なし・戻り値なし

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

引数あり・戻り値なし

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

引数あり・戻り値あり

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

関数定義の巻き上げ

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