イベントや関数を使用したアクションの実行

本投稿の前提
Trailheadの英語版をChatGPT3.5(以下、ChatGPT)に投げて返ってきた内容を載せています。もともとはTrailheadのみで学習していたのですが、不自然な日本語や、JavaScript初学者に理解しづらい用語・概念を手っ取り早く調べるとためにChatGPTとやり取りしながらの学習に切り替えました。
ChatGPTに聞くとおおよそ同じような答えが返ってきますが、毎回聞くのも手間ですし、用語なども追加で聞いていることもあるので備忘録を兼ねて残します。
僕と同じようなJavaScript初学者の方に参考になれば幸いです。
対象のTrailhead↓
イベントや関数を使用したアクションの実行
https://trailhead.salesforce.com/content/learn/modules/javascript-essentials-salesforce-developers/take-action-events-functions
目的
このユニットを終了すると、以下のことができるようになります。
- 関数を定義することができる
- 関数宣言と関数式の違いを理解できる
- 関数を呼び出すことができる
- 関数を渡したり代入することができる
- Lightning Web Componentsにおける関数とイベントの使用方法を説明できる
JavaScriptにおいて何かを実行する場合、関数とイベントの使用が必要になってきます。このユニットでは、関数とイベントについて学び、JavaScriptで機能を実現する方法を理解することができます。関数を定義し、呼び出したり、渡したり、代入したりすることができるようになります。また、Lightning Web Componentsにおいて関数とイベントを使用する方法も学ぶことができます。
JavaScriptのランタイムのモデルを思い出してみましょう。これを少し拡張しましょう。
ブラウザ上でJavaScriptを使用する場合、イベントがたくさん発生します。DOMの一部は、そのDOMオブジェクトが行う内容に対応するイベントを発生させます。ボタンにはクリックイベントがあり、入力および選択コントロールには変更イベントがあり、表示されているDOMのほとんどすべての部分には、マウスカーソルがそれと相互作用したときのイベントがあります(通過したりするような)。ウィンドウオブジェクトには、デバイスイベントを処理するためのイベントハンドラーもあります(モバイルデバイスの動きを検出するためのものなど)。
Webページで何かを実現するには、これらのイベントに関数がイベントハンドラーとして割り当てられます。
JavaScriptのコア言語には含まれていない、DOMイベントやブラウザ環境に関連するその他のイベントは、ブラウザが提供するJavaScriptのAPI(DOM API、Web API、Canvas API、WebGL API、XMLHttpRequest APIなど)です。つまり、ブラウザがJavaScriptの機能を拡張して提供しているものです。
イベントが発生すると、エンジン内にメッセージが作成されます。これらのメッセージは、以前に説明したイベントキューに配置されます。
スタックが空になると、イベントハンドラーが呼び出されます。これにより、呼び出しスタックにフレームが作成されます。1つの関数が別の関数を呼び出すたびに、新しいフレームがスタックに追加され、完了するとスタックからポップされます。最終的にイベントハンドラーのフレームがポップされ、スタックが空になり、すべてが再び始まります。
関数の定義と代入
JavaScriptにおいて、関数は基本的に特殊なオブジェクトとして扱われます。関数は、JavaScriptの特権階級に属するオブジェクトのひとつであり、変数の値として代入したり、他の関数の引数として渡したり、関数から戻り値として返したりすることができます。
関数には、2つの重要なフェーズがあります。関数を定義するフェーズと関数を呼び出すフェーズです。
関数が宣言されると、その定義がメモリに読み込まれます。その後、変数名やパラメータ名、オブジェクトプロパティのいずれかの形式で関数にポインターが割り当てられます。この処理には、いくつかの異なる構文が使われることがあります。
関数宣言
関数宣言は、function
キーワードを使用して関数を作成するステートメントのことです。実際、オブジェクトコンストラクターの説明の中で、既に宣言を見てきました。コンストラクター自体も関数ですが、コンストラクター関数はやや特殊なため、その前に、シンプルな関数のしくみを見てみましょう。以下のコード例を見てください。
// 関数宣言
function calculateGearRatio(driverGear, drivenGear){
return (driverGear / drivenGear);
}
// 関数の呼び出し
let gearRatio = calculateGearRatio(42, 30);
console.log(gearRatio); // 1.4`
このコード例では、functionの後に関数名が続き、引数は括弧で囲まれています。
これでうまく動きますが、いくつかの暗黙的なことが行われています。まず、関数名は変数名になります。また、変数がそれを囲むコンテキストに暗黙的に代入されます。最後に、この関数を宣言する前にコールすることができます。上記の例では、calculateGearRatio関数は宣言の前の行で呼び出されています。
// 関数の呼び出し
let gearRatio = calculateGearRatio(42, 30);
// 宣言の前の行で関数が呼び出される
// 関数宣言ではこのような使い方ができます
function calculateGearRatio(driverGear, drivenGear){
return (driverGear / drivenGear);
}
console.log(gearRatio); // 1.4
関数式
関数式は、関数宣言と同じことをより明示的に行う方法です。
const calculateGearRatio = function(driverGear, drivenGear){
return (driverGear / drivenGear);
}
// 以降は同じ
let gearRatio = calculateGearRatio(42, 30);
console.log(gearRatio); // 1.4
この例では、明示的に変数に代入しています。ポインターに名前をつけたため、functionキーワードの後に関数名を省略できます。ただし、関数を呼び出す前に、関数を宣言する必要があります。
特筆すべきは、関数式はオブジェクトのメンバーとして関数を代入するためにも使用されます。Bike.prototypeにchangeGear関数を代入したときを思い出してください。
Bike.prototype.changeGear = function(direction, changeBy) {
if (direction === 'up') {
this.currentGear += changeBy;
} else {
this.currentGear -= changeBy;
}
}
関数を返す
JavaScriptにおいては、関数が値として扱えるファーストクラスオブジェクトであるため、関数が別の関数を返す場合には、新たな関数を宣言することができます。このような宣言方法を、ファクトリ関数と呼びます。
// この関数が呼び出されると、関数が代入される
function gearFactory(){
return function(driverGear, drivenGear){
return (driverGear / drivenGear);
}
}
// calculateGearRatioを関数として呼び出せるようになる
const calculateGearRatio = gearFactory();
// 以降は通常通り
上記の例は単純ですが、有効な方法です。ファクトリ関数は、一時的に再利用可能な関数として役立ちます。また、ファクトリ関数は変数参照をクロージャにキャプチャすることができるため、クロージャについて学ぶ際にも役立ちます。クロージャーについては後の単元で説明します。
「変数参照をクロージャにキャプチャする」とは
関数内で定義された変数を、その関数のスコープ外であるにもかかわらず、参照し続けることができる仕組みのことを指します。つまり、関数が終了しても、その内側で定義された変数にアクセス可能になります。
ファクトリ関数において、内側の関数が外側の関数内で定義された変数にアクセスすると、その変数はクロージャによってキャプチャされ、内側の関数が呼び出されるたびに同じ変数の値が使用されます。このようにして、ファクトリ関数は、変数の値を保持しながら、同じ処理を繰り返し行うことができます。
無名関数
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関数は単一のパラメータを取ります。これは、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つの異なる方法を考えてみましょう。
// 関数を呼び出してratioResultに値を代入する
let ratioResult = bike.calculateGearRatio();
// calculateGearRatio関数を新しいポインタに代入する
const ratioFunction = bike.calculateGearRatio;
最初の例では、calculateGearRatio関数が呼び出され、関数から返された結果がratioResult変数に(この場合、プリミティブ値として)代入されます。一方でratioFunctionは、calculateGearRatio関数が単に代入されるか、ポイントされます。次に、ratioFunction()として呼び出すことができます。
Array.map()関数のような別の関数のパラメーターとして、関数を別のポインタに割り当てる理由があるかもしれません。ただし、this参照を使用する関数は、thisが異なる時間に異なるものを指す可能性があるため、壊れる可能性があります。このことについては、後で説明します。
イベントハンドラーとしての関数
特定のイベントが発生した時に関数を実行するためには、その関数をイベントに接続する必要があります。そうすることで、その関数はイベントハンドラとなります。もし呼び出し元のイベントにアクセスする必要がある場合、関数定義には、イベントを参照するための単一の引数を含める必要があります。ただし、この引数はオプションです。
各イベントには、そのイベントに関する情報が含まれています。例えば、clickイベントでは、クリックに関するデータ(イベントの種類、クリックを発生させた要素、クリックの座標など)が検出できます。
console.log(event.type); // クリック
console.log(event.currentTarget); // クリックされた要素
console.log(event.screenX); // 画面のX座標
console.log(event.screenY); // 画面のY座標
}
DOM API を使用したイベントハンドラーの代入
簡単なウェブページでは、HTMLに直接イベントハンドラを割り当てることがあります。
<button onclick="handleClick(event)">
Click to Go
</button>
しかし、現代のウェブアプリケーションでは、HTMLでのイベントバインディングはほとんど使用されません。代わりに、DOM APIのJavaScript Element.addEventListener()関数が好まれます。
まず、HTML要素への参照が必要です。以下の例では、buttonにid属性を追加し、onclick属性を削除しました。
<button id=”clicker”>
次に、DOMにアクセスし、buttonの参照を取得し、handleClickイベントリスナーを値として渡すことで、イベントリスナーを割り当てます(注:括弧はありません)。
let button = document.getElementById("clicker");
button.addEventListener("click", handleClick);
DOM APIを使用することで、開発者はUIを高度にインタラクティブにし、ユーザーの操作に応答することができます。開発者は、機能をオフにする必要がある場合、イベントリスナーを削除することもできます。
button.removeEventListener("click", handleClick);
また、無名関数をイベントリスナーとして追加することもあります。
button.addEventListener("click", function(event){
//...無名関数の本体...
});
無名関数は、関数の参照がないため、removeEventListenerで削除することはできません。
Lightning Web コンポーネントのイベントと関数
Lightning web componentは、JavaScriptモジュール、HTMLテンプレート、CSSファイルの3つのキーコードアーティファクトから構成されます。このうち、必須なのはJavaScriptモジュールだけです(.xmlファイルも必要ですが、コードではなく、コンポーネントに関するメタデータです)。
(出典:イベントや関数を使用したアクションの実行)
Lightning web componentにおいて、JavaScriptモジュールからエクスポートされたクラスは、コンポーネントのロジックを定義するための重要な部分です。クラスのメンバーとして定義される関数は、コンポーネントのロジック内で使用され、イベントハンドラーなどの機能を提供します。つまり、クラスのメンバーとして定義された関数は、コンポーネント内で使用されるためにエクスポートされているということです。
HTMLテンプレートは、静的なHTMLバインディングに似た方法でハンドラ関数を参照しますが、実際には異なります。テンプレートはJavaScriptアーティファクトにコンパイルされるため、静的に見えるバインディングは、コンポーネントのライフサイクルのどこかでaddEventListenerを呼び出すためのフレームワークが使用する構文上の規約にすぎません。
テンプレート内のこのマークアップは、イベントハンドラのバインディングを示しています。
<lightning-input onchange={handleChange} label="Input Text" value={text}>
</lightning-input>
こちらがイベントハンドラです。
handleChange(event){
this.text = event.target.value;
}
Lightning Web Componentsのイベントには、より高度な機能がいくつかあります。これらの機能を探索するには、Lightning Web Components Basicsモジュールを完了するか、サンプルアプリのいずれかにアクセスしてください。