イベントや関数を使用したアクションの実行
学習の目的
この単元を完了すると、次のことができるようになります。
- 関数を定義する。
- 関数宣言と関数式を区別する。
- 関数を呼び出す。
- 関数を渡して代入する。
- Lightning Web コンポーネントでの関数やイベントの使用について説明する。
JavaScript で何かを行う場合、それを実行するのはイベントと関数です。
ランタイムのモデルを覚えていますか? もう少し詳しく説明しましょう。
ブラウザーの JavaScript には、あちこちにイベントが存在します。DOM の各部が、DOM オブジェクトが実行する内容に対応するイベントを生成します。ボタンにはクリックイベントがあり、入力コントロールや選択コントロールには変更イベントがあります。また、表示可能な DOM のほぼすべての部分に、マウスのカーソル操作で (カーソルを置いた場合など) 生じるイベントが存在します。window オブジェクトでさえ、デバイスイベント (モバイルデバイスの動きの検出など) を処理するイベントハンドラーがあります。
Web ページで何かが行われるのは、関数がこれらのイベントにイベントハンドラーとして代入されるからです。
繰り返しになりますが、DOM イベントやブラウザー環境に関連するその他のイベントは、実際には JavaScript のコア言語の一部ではなく、JavaScript のためにブラウザーに実装された API です。
イベントが生じると、エンジンでメッセージが作成されます。前述のイベントキューに入れられるのはこうしたメッセージです。
スタックが空になると、イベントハンドラーが呼び出されます。この動作によって、コールスタック上にフレームというものが作成されます。1 つの関数が別の関数を呼び出すたびに、新しいフレームがスタックに追加され、完了するとフレームがスタックから取り出され、最終的には実際のイベントハンドラーのフレームが取り出されます。スタックが空になると、また同じことが繰り返されます。
関数の定義と代入
JavaScript では、関数は基本的に特殊なオブジェクトです。オブジェクトである関数は、JavaScript の特権階級のメンバーです。関数は変数の値として代入されたり、パラメーターとして他の関数に渡されたり、関数から返されたりします。
関数のライフサイクルには、定義と呼び出しという 2 つの重要なフェーズがあります。
関数が宣言されると、その定義がメモリに読み込まれます。次に、ポインターが変数名、パラメーター名、オブジェクトプロパティのいずれかの形式でその関数に割り当てられます。このことを実行するために数種の構文があるのも驚くことではありません。
関数宣言
宣言とは、function
キーワードを使用して関数を作成するステートメントです。実際、オブジェクトコンストラクターの説明で、すでに宣言を見てきました。このコンストラクターが関数です。けれども、コンストラクター関数はやや特殊なため、旧来のシンプルな関数に戻って、そのしくみを見てみましょう。
// declare function
function calculateGearRatio(driverGear, drivenGear){
return (driverGear / drivenGear);
}
// call function
let gearRatio = calculateGearRatio(42, 30);
console.log(gearRatio); // 1.4
このサンプルコードでは、関数の後に関数名が続き、パラメーターが括弧で囲まれています。
これでもきちんと機能しますが、暗黙のうちにいくつかのことが行われます。まず、関数名が変数名になります。また、変数がそれを囲んでいるコンテキストに暗黙的に代入されます。最後に、この関数を、以下のように宣言される前にコールすることができます。ここでは、calculateGearRatio
が宣言の前の行で呼び出されています。
// call function
let gearRatio = calculateGearRatio(42, 30);
// function is declared after the line it is called
// this is allowed in function declaration
function calculateGearRatio(driverGear, drivenGear){
return (driverGear / drivenGear);
}
console.log(gearRatio); // 1.4
関数式
関数式は宣言と同じことを明示的に行います。
const calculateGearRatio = function(driverGear, drivenGear){
return (driverGear / drivenGear);
}
// the rest works the same
let gearRatio = calculateGearRatio(42, 30);
console.log(gearRatio); // 1.4
この例では、変数が明示的に代入されています。ポインターを指定しているため、function
キーワードの後の関数名を省略できます。この唯一の難点は、呼び出される前に関数を宣言する必要があることです。
関数式は特に、関数をオブジェクトのメンバーとして代入する場合にも使用されます。changeGear
関数を Bike.prototype
に代入したときのことを覚えていますか?
Bike.prototype.changeGear = function(direction, changeBy) {
if (direction === 'up') {
this.currentGear += changeBy;
} else {
this.currentGear -= changeBy;
}
}
関数を返す
関数は特権階級のオブジェクトであるため、関数を宣言するもう 1 つの方法は、関数が別の関数を返したときに行うことです。このパターンを通常はファクトリ関数といいます。
// when invoked, this function will assign a function
function gearFactory(){
return function(driverGear, drivenGear){
return (driverGear / drivenGear);
}
}
// calculateGearRatio can now be invoked as a function
const calculateGearRatio = gearFactory();
// and all the rest
上記の例はシンプルながら有効です。ファクトリ関数は、クロージャー内の変数参照を捕捉する場合をはじめ、単発の再利用可能な関数で役立ちます。クロージャーについては後の単元で説明します。
無名関数
JavaScript には、機能させるために関数を渡す必要のある API が多数あります。たとえば、配列があり、その配列の値から派生した新しい配列を作成したいとします。この場合は、おそらく Array.map
関数を使用するものと思われます。
let myArray = [1, 5, 11, 17];
let newArray = myArray.map( function(item){ return item / 2 } );
console.log(newArray); // [0.5, 2.5, 5.5, 8.5]
このスニペットでは、myArray.map
が 1 つのパラメーター、つまり、myArray
の項目ごとに一回ずつ実行される関数を取ります。
この関数は再利用できません。この関数に渡される引数 (名前はありません。だから「無名」です) として宣言され、map
関数の実装の内側で実行されます。無名関数 (一定の言語ではラムダともいいます) は JavaScript でごく普通に使用されています。
関数の呼び出し
関数を宣言したら、おそらく呼び出してみたいと思うでしょう。関数が呼び出されると、いくつかのことが行われます。
前述のとおり、最初に行われるのは、新しいフレームがスタックに入れられることです。次に、その変数と引数を含むオブジェクトがメモリ内に作成されます。続いて、this
ポインターがこのオブジェクトと他のいくつかの特殊オブジェクトにバインドされます。そして、引数に渡された値が代入され、最後にランタイムが関数の本体でステートメントを実行し始めます。
this
のバインドには、重要な例外が 1 つあります。この点については、非同期 JavaScript の単元で説明します。
呼び出しと代入
関数を操作する場合に、JavaScript の入門者が混乱する原因となると思われるのが、関数を代入する/渡すのか、それとも呼び出すのかについてです。突き詰めれば、()
を使用するかどうかの違いです。
bike オブジェクトには calculateGearRatio
関数があります。
let bike = {
...,
calculateGearRatio: function() {
let front = this.transmission.frontGearTeeth[this.frontGearIndex],
rear = this.transmission.rearGearTeeth[this.rearGearIndex];
return (front / rear);
},
...
}
ここで、calculateGearRatio
関数への 2 通りのアクセス方法について考えてみます。
// invoke function and assign value to ratioResult
let ratioResult = bike.calculateGearRatio();
// assign calculateGearRatio function to a new pointer
const ratioFunction = bike.calculateGearRatio;
1 つ目の例では、calculateGearRatio
が、ratioResult
変数に (この例ではプリミティブ値として) 代入されている関数から返された結果によって呼び出されています。他方、ratioFunction
は、calculateGearRatio
関数に単純に代入またはポイントされています。逆にして、ratioFunction()
として呼び出すことも可能です。
関数を別のポインターに代入するのには理由があります。特に Array.map()
関数などを使用する別の関数のパラメーターとして代入する場合です。けれども、this
参照を使用する関数はすべて破損のリスクがあります。this
が異なる時点で異なるものをポイントすることがあるためです。詳細は後で説明します。
イベントハンドラーとしての関数
関数をイベントの結果として起動したい場合は、関数をそのイベントに結び付ける必要があります。そうすれば、その関数がイベントハンドラーになります。呼び出し元のイベントのコンテキストにアクセスする必要がある場合、関数を起動するイベントへの参照として使用される 1 つの引数を関数定義に含める必要があります。この引数は省略可能です。
var handleClick = function(event) {
}
各イベントには、対処するためにそのイベントについて知っておくべき内容を説明するプロパティがあります。たとえば、click
の場合は、クリックに関するデータ (イベントの種別、どの要素によって起動されたのか、クリックの座標など) を確認できます。
var handleClick = function(event) {
console.log(event.type); // click
console.log(event.currentTarget); // the thing you clicked
console.log(event.screenX); // screen X coordinate
console.log(event.screenY); // screen Y coordinate
}
DOM API を使用したイベントハンドラーの代入
シンプルな Web ページでは、HTML に明示的に代入されたイベントハンドラーを確認できることがあります。
<button onclick="handleClick(event)">
Click to Go
</button>
けれども、昨今の Web アプリケーションの HTML でイベントバインドが使用されていることはほとんどありません。その代わり、DOM API、特に JavaScript の Element.addEventListener()
関数が好んで使われています。
この場合はまず、HTML 要素への参照が必要です。以下では、ボタンに id
属性を追加し、onclick 属性を削除しています。
いよいよ、DOM にアクセスして、ボタンへの参照を取得し、それを値として渡す方法で handleClick
というイベントリスナーを代入します (括弧はありません)。
let button = document.getElementById("clicker");
button.addEventListener("click", handleClick);
DOM API を使用すると、開発者が対話性やユーザーの動作に対する応答性の高い UI を柔軟に作成できるようになります。また、機能をオフにする必要がある場合に、開発者がイベントリスナーを削除できます。
button.removeEventListener("click", handleClick);
さらに、イベントリスナーとして追加された無名関数が表示されます。
button.addEventListener("click", function(event){
//...anonymous function body...
});
removeEventListener
を使用して無名関数を削除することはできません。関数を識別するために渡すポインターがないためです。
Lightning Web コンポーネントのイベントと関数
Lightning Web コンポーネントの主なコードアイテムは、JavaScript モジュール、HTML テンプレート、CSS ファイルです。このうち唯一必須であるのが JavaScript モジュールです (.xml
ファイルも必須ですが、これはコードではなく、コンポーネントに関するメタデータです)。
Lightning Web コンポーネントの関数は多くの場合、コンポーネントの JavaScript モジュールでエクスポートされたクラスのメンバーである、メソッドの形態になっています。これらの関数がイベントハンドラー、またはイベントハンドラーから下方に呼び出される他の関数である場合があります。
HTML テンプレートは、静的 HTML バインドと似た方法でハンドラー関数を参照しますが、実際には異なります。このテンプレートは JavaScript アーティファクトにコンパイルされるため、静的に見えるバインドも実際には、フレームワークがコンポーネントのライフサイクルのいずれかの時点で addEventListener
を呼び出すために使用する単なる構文規則です。
テンプレートのこのマークアップは、イベントハンドラーのバインドを示しています。
<lightning-input onchange={handleChange} label="Input Text" value={text}>
</lightning-input>
これはイベントハンドラーです。
handleChange(event){
this.text = event.target.value;
}
リソース
-
JavaScript の関数
-
イベントハンドラー
- Lightning Web Components 開発者ガイド: イベントを使用した通信