JavaScript の基本概念について
学習の目的
この単元を完了すると、次のことができるようになります。
- JavaScript ランタイム環境の特性を説明する。
- JavaScript エンジンと言語を区別する。
- JavaScript を学習する際の重大な落とし穴を回避する。
- JavaScript のいくつかの重要なベストプラクティスについて説明する。
JavaScript の概要、使用する理由、使用法
2000 年代初頭のソフトウェア業界は、インターネット上で完全にホストされて配信されるアプリケーションを構築できるのではないかというアイデアで沸き返っていました。今では Web アプリケーションなど当たり前のことになっていますが、当時は自転車に乗るロボットを見るのと同じくらいショッキングなことでした。
ブラウザーはシンプルな HTML レンダラーで、HTML や JavaScript の標準も断片的でした。そこそこ複雑な機能を備えた Web ページを作成する場合は、ユーザーに 1 つのブラウザー (通常は Internet Explorer) のみを使用させる必要がありました。今日でも、この時代に行われた設計上の決定のために、これらの制約を強制するアプリケーションがあります。
こうした束縛から逃れるために、サーバー側の UI フレームワークの開発が進み、ソフトウェア開発者が Web ページをサーバー上で動的に作成できるようになりました。複雑なロジックは、高いコンピューティング性能を備えた場所、つまりサーバー上で実行されました。そして、レンダリングされた HTML が性能の劣るブラウザーに送られました。こうした時代において、JavaScript は主としてページを多少なりとも対話型にする手段であり、サーバーとの往復なしで基本的なロジックを実行する手段でした。Salesforce のフレームワークである Visualforce をはじめ、サーバー側のフレームワークは高い人気を博していました。当時の人気の証しとして、今日でも Salesforce には何百万もの Visualforce ページが存在します。
そして現在では、世界がまったく異なる場所に変貌しています。ブラウザーは Web ページの表示を最適化する強力なアプリケーションです。JavaScript は ECMAScript 標準に沿って標準化が進み、大手のブラウザーメーカーが概して新しい機能を難なく採用しています。今日の Web アプリケーションは、ブラウザーで最新の JavaScript を実行する多彩なユーザーインターフェースを備えています。昨今の Web フレームワークは、サーバー側のアプリケーションではなく、クライアント側でレンダリングされる傾向にあります。Salesforce では、Lightning コンポーネントフレームワークでこの処理を行っています。けれども多くの開発者にとって、JavaScript を記述することは依然として目新しいことです。主に Visualforce ページや Apex コントローラーで作業してきた場合には、JavaScript がどのように機能するかを把握し、コンポーネントに対する理解を深めるためにサポートが必要なことがあります。
そろそろ JavaScript のスキルをレベルアップする時期です。
JavaScript ランタイム
JavaScript ランタイムは、JavaScript コードを解釈するエンジンです。ブラウザーの一部であることもあれば、サーバーなど他のランタイム環境である場合もあります。ここ最近の JavaScript エンジンは洗練され、実行を最適化する能力を備え、ECMAScript 標準にも適合するよう設計されています。
JavaScript エンジンを決定づける特徴は、以下のスタックで表されるシングルスレッドのランタイムです。スタックで行われている作業によってスレッドが所有され、その同期ロジックを完了したうえでスレッドの管理権が戻されます。
ランタイムは忙しい場所です。ユーザー (UI イベント) や Web API (地理位置やデバイスの動きなど) をはじめ、さまざまなところからいつ何時新しい作業が取り込まれるかわかりません。スレッドが 1 つしかないため、作業がスレッドを使用する順番を待つキューがあります。
スタックが空のときは、イベントループが実行待ちの作業をキューから取得して、スタックに移します。
もちろん、これは単純化した説明ですが、JavaScript エンジンがどのように作業をこなすかの基本的なモデルを示しています。JavaScript 言語は、こうしたアーキテクチャにより、実際にこのようなやり方で機能します。
JavaScript という言語
JavaScript は誤解されることの多い言語です。期間の長短を問わず、この言語をかじったことがある人ならば、「これはスクリプト言語なのか?」「Java とはどういう関係があるのか?」「本当にプログラミング言語なのか?」「なぜこんなことになるのか?」と疑問を抱いたことがあるはずです。
これらのいくつかの質問に答えるために、言語としての JavaScript にまつわる状況を確認していきましょう。
JavaScript は絶えず変化している
JavaScript は ECMAScript 標準に従って構築されているため、常に変化しています。新機能を説明する標準の更新情報が毎年公開され、JavaScript エンジンプロジェクト (ブラウザーやランタイムのメーカー) がその内容を導入しています。
JavaScript が成熟する中、更新情報にこの言語の最新機能が説明されていることがあります。あるいは、既存の機能により明確な構文を実装するための機能が追加されていることもあります (糖衣構文といいます)。
API は普遍的に採用されているわけではない
この一文に恐怖心を覚えるかもしれませんが、JavaScript API の大多数はごく一般的なブラウザープラットフォームで機能します。とはいえ、プラットフォームのメーカーが言語の各機能や API を一様に採用しているわけではありません。特定の機能をまったく採用していないところもあります (稀ですが...)。一般に、この言語の最新機能を使用する場合は、caniuse.com のようなリソースを参考にして、対象とするブラウザーでどの程度機能するか把握する必要があります。
新しい機能がネイティブで実装されていない場合は、差し当たってその欠落している機能の目的を果たすために記述された短いコードが存在します。この当座のコードをポリフィルといいます。実際、Lightning コンポーネントフレームワークでも、ブラウザーの互換性を自動的に向上させる他のコードを実行するまでは、選定されたポリフィルのリストを使用しています。
絶対に知っておくべきこと
ではいよいよコードを見てみましょう。まず、JavaScript 開発者の全員が自らの仕事を楽にするために知っておくべきことから始めます。
大文字と小文字を区別する点に注意
JavaScript は大文字と小文字を区別します。Apex と SOQL はどちらも大文字と小文字を区別しないため、これらの言語に慣れている多数の Salesforce 開発者にとってこの点は厄介です。Salesforce で JavaScript コードを記述するときは、常に大文字と小文字を区別する点に留意します。
変数の宣言
変数宣言は、var
、let
、const
の 3 つの演算子のいずれかを使用して行います。一般には let
と const
を使用します。下表に、これら 3 つのキーワードの機能上の違いがまとめられています。
キーワード |
通用範囲 |
代入の変更可能性 |
---|---|---|
var |
関数 |
可 |
let |
ブロック |
可 |
const |
ブロック |
不可 |
通用範囲は後で取り上げるトピックのため、ここでは変更可能性の意味を説明します。
変数はすべてポインターです。代入とは、その変数をメモリ内の何かにポイントする動作です。変更可能性は、変数を最初に何かに代入した後で、その変数を代入し直すことができるかどうかを表します。var
と let
を使用した場合は変更可能なポインターが作成されますが、const
は変更不能です。この点についても、理解する一番の方法は実例を見ることです。
//primitive assignments
var myBike = "Mountain Bike";
let currentGear = 5;
const numberOfGears = 12;
//reassignment
myBike = "Penny Farthing"; // this works
currentGear = 1; // so does this
numberOfGears = 1; // error
上記の myBike
と currentGear
は、値を再代入しても何も問題がありませんでした。他方、numberOfGears
に再代入しようとするとエラーが生じます。
オブジェクト (プリミティブではなく) を操作するときは、const
のみ変数を他のオブジェクトに再代入できないことを覚えておきます。オブジェクト自体 (そのプロパティや関数など) は、以下のとおり変更できます。
// call constructor, new object, assign it to bike
const bike = new Bike();
//Change internal state by calling a function
bike.changeGear("front", "Up");
// add a new member to bike that did not exist before
bike.type = "Penny Farthing";
// check for success
console.log(bike.calculateGearRatio()); // 4.0909...
console.log(bike.type); // "Penny Farthing"
// attempt to point bike to new instance of Bike
bike = new Bike(1,2); // error
このコードでは、Bike
コンストラクターからオブジェクトを作成して、bike
変数に代入します (大文字と小文字に注意します)。そうすると、状態を変更する関数の呼び出しや新しいメンバーの追加など、そのオブジェクトに関して必要なことをすべて変更できます。けれども、bike を何か別のものに再代入しようとした瞬間に (この場合はコンストラクターを再度呼び出すこと) エラーが生じます。
暗黙的な型の強制変換
ほとんどの JavaScript 演算子は無効な型に遭遇すると、値を有効な型に変換しようとします。暗黙のうちに型を変換するこのプロセスを暗黙的な型の強制変換といいます。次の例を考えてみます。
let num1 = 9 * "3";
console.log(num1); // 27 (a number)
let num2 = 9 + "3";
console.log(num2); // "93" (a string)
1 つ目の例では、*
演算子は算術演算にしか使用できないため、文字列 "3"
が数値に強制変換されます。この結果は 27 になります。2 つ目では、+
演算子は "3"
を文字列と認識するため、単項演算子 (連結) になります。したがって、9 を "9"
という文字列に強制変換するため、結果は "93"
という文字列になります。
一見便利なように思えるかもしれませんが、実際には混乱を来す可能性があります。
暗黙的な型の強制変換は使用しない
暗黙的な型の強制変換の用例の多くで混乱を招いています。たとえば、Boolean 比較です。C ファミリー言語に共通する ==
と !=
の比較演算子は、何もかもを Boolean 値に変換しようとします。決定論的なルールがあるものの、複雑すぎて実用的ではありません。以下に面白い例を示します。
false == ""; // true
false == "0"; // true
"" == "0"; // false
[0] == 0; // true
「なぜこんなことになるのか?」
と思うでしょう。
Boolean 比較のベストプラクティスは、===
と !==
を使用することです。これらの演算子を使用する場合、プリミティブ型が等価になるのは、型と値の両方が一致した場合のみです。また、オブジェクトの比較が true になるのは、それぞれのポインターが同じメモリアドレスをポイントしている場合のみです。上記と同じ比較を試してみます。
false === ""; // false
false === "0"; // false
"" === "0"; // false
[0] === 0; // false
true とみなされる値 (truthy) と false とみなされる値 (falsy)
たった今明示したルールが暗黙的な型の強制変換を警告するものであることはおわかりですよね? けれども例外があります。
Boolean 値が予想される式では、以下の値が常に false とみなされます。
-
false
(もちろんです)
-
0
(ゼロという数値)
-
""
または''
(空の文字列)
-
null
-
undefined
-
NaN
(算術演算に失敗した結果)
false
は false
です (もちろんです!)。残りの値は強制的に false
になります。これらをまとめて falsy といいます。
true
がそのまま true
になることに疑いの余地はありません。けれども、あらゆる型の他の値はすべて強制的に true
になります。これらの値を truthy といいます。
こうした強制変換には都合の良い副作用があります。たとえば、数種類の無効なデータをテストするために、変数を if 式に渡すとします。
const myRecord = response.getReturnValue();
if (myRecord) {
//now process the record
}
何らかの理由で上記の代入に失敗し、初期化されていない変数 (undefined
)、空の文字列、0
などが示された場合、myRecord
変数に条件チェックを実行すればすべてに対処できます。これは JavaScript で広く受け入れられているやり方です。
厄介な this
このモジュールでは、this
ポインターの使い方に 1 つのセクションを丸々割いています。このポインターには明確に定義されたルールがありますが、this
が何をポイントするのかは同じ関数内でも変化することがあります。たとえば、Apex クラスで次のようなコードを目にすることがあります。
public class MyApexClass {
private String subject;
public MyApexClass(String subject) {
this.subject = subject;
}
}
この Apex クラスの例では、this
は常に MyApexClass
の現在のインスタンスのみを参照します。JavaScript では、this
が何をポイントするかは、関数が定義された場所ではなく、その関数がコールされた場所によって決まります。this
については後ほど詳しく説明します。
関数は値
JavaScript では、すべてがオブジェクトです。関数もその例外ではありません。関数も、他のすべてのオブジェクトと同様に、変数に代入されたり、他の関数のパラメーターに渡されたり、他のすべてのオブジェクトと同じ方法で使用されたりします。
関数が特権階級である言語やラムダを使用する言語を扱ったことがない場合は、この点に多少なりとも衝撃を受けるかもしれません。関数については後ほど 1 つの単元を割いて説明します。
関数がオブジェクトであることや、他のさまざまな点についてもこの後解説します。
これで、JavaScript の概念の基本を抑えることができました。次は、ブラウザーで JavaScript がどのように機能するかを見ていきます。
リソース
-
TrailheaDX Session: JavaScript for the Salesforce Platform, A Beginner's Guide (TrailheaDX セッション: Salesforce Platform の JavaScript、入門ガイド)
-
Pluralsight Getting Started with JavaScript in Salesforce (Pluralsight の Salesforce での JavaScript の使用開始)
-
MDN JavaScript Basics (MDN の JavaScript の基本)
-
You Don't Know JavaScript (JavaScript のことをあなたは知らない)
-
JavaScript Type Conversion (JavaScript の型変換)
-
JS the Right Way (JS はまっとうな方法)