書籍で独習したが限界があり、手を動かして学べる実習サイトを利用することとした。学習サイト自体は初心者向けのものだったが、それなりにJavaScriptを身近に感じられたので、書籍に戻って整理することが可能となった。
・対象は、ES2015。但し、既存バージョンのES5も最小限対象とする。
・参考書:「JavaScript本格入門(山田祥寛著)」。
※下記記載内容は、この参考書から抜粋させていただいたものです。
・実習サイト: Progate。
・学習阻害要因は、自身Java開発者であり、似て非なる部分が用語も含めて混乱するところ。
JavaScriptは関数型言語
・関数を変数に代入できる。
・関数の引数や戻り値に関数を使用できる。
※関数はデータ型の一種である。
Java開発者→JavaScript習得の難しさ
Javaは関数型言語ではありません。関数を変数に代入したりできません。「関数を変数に代入できる」事にすごく違和感があります。
ただ、Java8からラムダ式(関数型インターフェース)とStreamAPIが導入され、関数型言語の機能が一部導入されています。ラムダ式とStreamAPIに習熟していればいるほど、Java開発者でもJavaScriptの「関数型言語」の習得が容易になると思われます。
ES2015で導入された仕様
Java開発者には、ES2015で導入された下記仕様の方が直感的に理解しやすいと思います。
・class命令の導入で、Java/C#ライクなクラス定義が可能に
・import / export命令によるコードのモジュール化をサポート
・関数構文の改善(アロー関数、引数のデフォルト値、可変長引数など)
・let / const命令によるブロックスコープの導入
・for...of命令による値の列挙
・イテレーター/ジェネレーターにより列挙可能なオブジェクトの操作が可能に
・組み込みオブジェクトの拡充(Promise, Map / Set, Proxyなど)
・String / Number / Arrayなど、既存の組み込みオブジェクトも機能を拡張
※「JavaScript本格入門(山田祥寛著)」より抜粋。
※現実的には、IE11等がES2015に対応しないため、トランスコンパイラー(Babel等)で変換する必要があります。
JavaScript独特の用語(ES5、ES2015含む)
JavaにもJava開発者以外からみれば独特の用語があるのかも知れませんが、JavaScript独特の用語は、なかなか頭に入らず困ってしまいます。そこで、私が覚えにくい用語を一覧にしておきます(一部、一般的な用語も含みますが...)。
・変数のスコープ(ES5): ES5では、Javaのようなブロックスコープは存在しない。
・グローバル変数(ES5): var命令を使用しない、または関数の外で宣言した変数。Javaではグローバル変数が存在しないのでわかりにくいかも(publicかつstaticな変数が類似)。
・リテラル: データ型に格納できる値そのもの、また、値の表現方法のこと。
・配列リテラル: 配列の具体的表現。['JavaScript', 'Ajax', 'ASP.NET']。
・オブジェクト・リテラル: Mapの具体的表現。{name:'山田', sex:'男'}。ハッシュ、連想配列とも。
・関数リテラル: 名前のない関数定義を変数に代入したもの。変数 = function(引数){本体}。var getTriangle = function(base, height){ return base * height / 2; };
・テンプレート文字列: 文字列の中で「${変数}」とすることで、文字列の中に定数や変数を含めることができる。この時、文字列全体をバッククォーテーションで囲む必要がある。Ex. console.log(`私の名前は${hoge}です`
);
・テンプレートリテラル: テンプレート文字列の別名。
・タグ付きテンプレート文字列: テンプレート文字列をアプリ仕様にカスタマイズする手法。「<」「>」などの文字を「<」「>」等のエスケープ文字に置き換えたい時に使用する。ややこしいので、ここでは省略。
・匿名関数: 関数リテラルの別名。
・無名関数: 関数リテラルの別名。
・function命令: Javaのメソッド定義に最も近い形の関数定義。function 関数名(引数){本体}。
・Functionコンストラクタ: 関数リテラルに類似。名前のない関数定義を変数に代入したもの。但し、引数、本体を文字列で定義する所がことなる。動的に関数を構成できる点が特徴。多用は禁止。
・クロージャー: 関数内のローカル変数を参照している関数内関数のこと。function命令の応用形。高階関数の一種。コンストラクタで指定された値をメンバー変数で保持。かつ、メンバー変数を利用する内部function自体をリターンする形式のもの。インスタンス毎に結果が異なることから、Javaのクラスに近いと言える。
・再帰関数: 関数本体でその関数自体を呼び出す関数を言う。EX.階乗を求める関数。
・高階関数: 関数を引数や戻り値として扱う関数を言う。
・コールバック関数: 高階関数の引数に指定される関数のように、呼び出し先の関数の中で呼び出される関数の事を言う。あとで呼び出される(=コールバックされる)べき処理という意味です。ArrayオブジェクトのforEachメソッドやmapメソッドが該当する。
・可変長引数の表記法(ES2015)。function sum(...nums) {}。numsは配列として定義される。
・Objectオブジェクト: 全てのオブジェクトの雛形。Stringオブジェクト、Dateオブジェクト...等のルートオブジェクト。現時点の習熟度では、使い道不明。
・組み込みオブジェクト: JavaScriptに標準で組み込まれたオブジェクト。特別な宣言や定義なしで使用することができる。
Object, Array, String, Boolean, Number, Function, Math, Date, RegExp, Error/XxxxxError。
[ES2015]->Map/WeakMap, Set/WeakSet, Symbol, Proxy, Promise。
・Symbolオブジェクト: 定数値を表現するのに便利。無意味な値を割り当てる必要がない。Javaにもあれば良いと思う。異なるSymbol命令で生成されたシンボルは、仮に同名であってもユニーク(一意)になる。
const MONDAY = Symbol(); / const TUESDAY = Symbol();
・プロトタイプ: 「あるオブジェクトの元となるオブジェクト」のこと。ES2015でクラスが導入されるまでは、JavaScriptでは、(クラスの代わりに)プロトタイプを利用して、新たなオブジェクトを生成する仕組みであった。
・フォーム要素(HTML5): text, textarea, number, password, range, checkbox, radio, select, select multiple, file, date, time, datetime-local, month, week, color
・data-xxxxx属性: イベントリスナーで利用するパラメーターを埋め込む為のHTMLの属性。xxxxx部分は自由に命名できる。
・イベントハンドラー: イベントに対応してその処理内容を定義する関数の事。次の2点のいずれか。<1>タグ内の属性として宣言する。<2>要素オブジェクトのプロパティとして宣言する。制限として、同一要素の同一イベントに対して、複数のイベントハンドラーは紐付けできない。
・イベントリスナー: イベントに対応してその処理内容を定義する関数の事。次の指定方式のもの。<1>addEventListenerメソッドを使って宣言する。利点は、、同一要素の同一イベントに対して、複数のイベントリスナーを紐付けできること。
即時関数 - 不要だがメモ
ES5時代、ブロックスコープが存在しなかったため、疑似的にブロックスコープを実現するために使用されていた手法。ES2015以降は、不要(let, constがあるので)。
(function(){
var i = 5;
console.log(i); // 結果:5
})();
console.log(i); //変数iはスコープ外なのでエラー
※即時関数補足
名前の通り、その場で即時に実行するために特殊な記法を用いている。
・関数全体を括る「( )」 ・・・ 関数全体を括っているのは、これがないとfunction命令と解釈されて、その場で実行されないため。尚、慣例的に「( )」で括っているが、+や-などの単項演算子でも実はよい(+や-などの場合は、先頭1文字だけでよい)。
・関数の最後の「( )」 ・・・ これがないと関数ではなく変数と認識されてしまう。
演算子に関する話題(ES5, ES2015)
1.小数点の有効桁数
・ごく当たり前の小数計算で正しい結果が得られない。
//単純に実施すると...
console.log(0.2 * 3); //結果:0.600000000000001
//正確に行うには、保持したい有効桁数まで整数にして、演算を実施、本来の桁数に戻す。
console.log(((0.2 * 10) * 3) / 10); //結果:0.6
2.分割代入(配列)(ES2015)
const data = [56, 40, 26];
const [x0, x1, x2] = data;
console.log(x0); //結果:56
console.log(x1); //結果:40
※関数の戻り値が配列であれば、上記の分割代入が使用可能。
3.分割代入(オブジェクト)(ES2015)
const book = { title: 'JavaScript詳解', publish: 'XX出版', price: 2000 };
const { price, title, memo = 'メモ'} = book;
console.log(title); //結果:JavaScript詳解
console.log(price); //結果:2000
console.log(memo); //結果:メモ
※関数の戻り値がオブジェクトであれば、上記の分割代入が使用可能。
関数に関する話題(ES5, ES2015)
1.関数とは
・function命令で定義する。※静的宣言。コンパイル時に登録される。
・Functionコンストラクター経由で定義する(引数、関数本体を文字列で定義する必要がある特殊なケースのみ)。※new演算子は省略可能。
・関数リテラル表現で定義する。
・アロー関数で定義する。(ES2015)
// function命令
function getTriangle(base, height){
return base * height / 2;
}
console.log('三角形の面積:' + getTriangle(5,2));
--------------------------------------------------
//Functionコンストラクター
var getTriangle = Function('base', 'height', 'return base * height / 2;');
console.log('三角形の面積:' + getTriangle(5,2));
--------------------------------------------------
//関数リテラル
var getTriangle = function(base, height){
return base * height / 2;
}
console.log('三角形の面積:' + getTriangle(5,2));
--------------------------------------------------
//アロー関数(ES2015)
let getTriangle = (base, height) => {
return base * height / 2;
}
console.log('三角形の面積:' + getTriangle(5,2));
2.引数の様々な記法
・JavaScriptは引数の数をチェックしない。
・引数情報を管理するargumentオブジェクトが存在する。関数本体でのみ使用できる特別なオブジェクト。
※注。ES2015では、argumentオブジェクトは使用せず、残余引数を一律使用する方が良い。これは、アロー関数ではargumentオブジェクトが使用できないからである。
Ex. 引数の数をチェックする。if (argument.length !==1){エラーをスロー};
Ex. 可変長引数の関数をargumentオブジェクトを使って定義する事ができる。
・引数のデフォルト値(ES2015)を宣言できる。「仮引数 = デフォルト値」。
・可変長引数の関数を定義する(ES2015)。仮引数の前に「...」(ピリオド3個)を付与。仮引数の配列が渡される。「残余引数」と呼ぶ。
Ex. function sum(...nums){ } //引数numsを配列として処理に使用。
3.クロージャー
関数内のローカル変数を参照している関数内関数のこと。function命令の応用形。高階関数の一種(戻り値が関数である点)。コンストラクタで指定された値をメンバー変数で保持。かつ、メンバー変数を利用する内部function自体をリターンする形式のもの。インスタンス毎に結果が異なることから、Javaのクラスに近いと言える。
function closure(init) {
var counter = init;
return function(){
return ++counter;
}
}
var myClosure = closure(1); // closure関数のreturnの関数が変数に格納される。
console.log(myClosure()); // 結果:2
console.log(myClosure()); // 結果:3
console.log(myClosure()); // 結果:4
オブジェクト指向構文(ES5)
「JavaScript本格入門(山田祥寛著)」”5.1 JavaScriptにおけるオブジェクト指向の特徴” ~ ”5.3 オブジェクトの継承”より抜粋。
(1)JavaScriptにおけるオブジェクト指向の特徴
ES5では、クラスベースではなく、プロトタイプベースのオブジェクト指向である。
var Member = function(){}; // コンストラクタ
var mem = new Member(); // インスタンス化
// ※クラスを定義する変数は慣例的に、先頭を大文字にする。
var Member = function(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.getName = function(){
return this.lastName + ' ' + this.firstName;
}
}
// ※thisキーワードは、コンストラクタにより生成されるインスタンスを指す。
var mem = new Member('山田', '太郎');
console.log(mem.getName()); // 結果: 山田 太郎
※ES5では(class導入前)、コンストラクタでメソッドを定義するとインスタンスにメソッド定義がコピーされ、メモリが無駄に消費される。
(2)メソッド定義はプロトタイプで宣言する - prototypeプロパティ -
メソッド定義をプロトタイプで宣言するとインスタンスはメソッド定義への参照のみを保持するようになります。これにより下記の効果が発生します。
・メモリの使用量を節約できる。
・メンバーの追加や変更をインスタンスがリアルタイムに認識できる。
var Member = function(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
Member.prototype.getName = function(){
return this.lastName + ' ' + this.firstName;
}
Member.prototype.toString = function(){
return this.lastName + this.firstName;
}
var mem = new Member('山田', '太郎');
console.log(mem.getName()); // 結果: 山田 太郎
console.log(mem.toString()); // 結果: 山田太郎
//推奨記法(オブジェクトリテラルで記述する)
Member.prototype = {
getName : function() {
return this.lastName + ' ' + this.firstName;
},
toString : function() {
return this.lastName + this.firstName;
}
}
(3)静的メンバーの定義
オブジェクト名.プロパティ名 = 値
オブジェクト名.メソッド名 = function(){/* メソッド定義*/}
var Area = function(){}; // コンストラクター
Area.version = '1.0'; // 静的プロパティの定義
Area.triangle = function(base, height) { // 静的メソッドの定義
return base * height / 2;
}
console.log('Areaクラスのバージョン:' + Area.version); // 結果:1.0
console.log('三角形の面積:' + Area.triangle(5, 3)); // 結果:7.5
var a = new Area();
console.log('三角形の面積:' + a.triangle(5, 3)); // 結果:エラー
本格的な開発に備えるために(ES5)
「JavaScript本格入門(山田祥寛著)」”5.4 本格的な開発に備えるために”より抜粋。
(1)Object.definePropertiesメソッドによるアクセッサーメソッドの実装
※Javaのgetter/setterに似てはいるが、setterの利用方法はやはり違和感があります(Javaは、AAAインスタンス.setYYY(設定値);)。
function Triangle(){
// プライベート変数を宣言
var _base;
var _height;
// プロパティの定義
Object.defineProperties(this, {
base: { // baseプロパティ定義
get: function(){ // ゲッター
return _base;
},
set: function(base){ // セッター
if (typeof base === 'number' && base > 0) {
_base = base;
}
}
},
height: { // heightプロパティ定義
get: function(){ // ゲッター
return _height;
},
set: function(height){ // セッター
if (typeof height === 'number' && height > 0){
_height = height;
}
}
}
})
};
// メソッド定義
Triangle.prototype.getArea = function(){
return this.base * this.height / 2;
};
// 結果確認
var t = new Triangle();
t.base = 10; // 'base'プロパティのセッター利用
t.height = 5; // 'height'プロパティのセッター利用
console.log('三角形の底辺:' + t.base); // 三角形の底辺:10
console.log('三角形の高さ:' + t.height); // 三角形の高さ:5
console.log('三角形の面積:' + t.getArea()); // 三角形の面積:25
オブジェクト指向構文(ES2015)
「JavaScript本格入門(山田祥寛著)」”5.5 ES2015のオブジェクト指向構文”より抜粋。
(1)class定義 - class命令 -
ES2015で導入された、Java/C#系統のオブジェクト指向構文である。
※public/protected/private等のアクセス修飾子がなく違和感あり(全てpublic)。
※コンストラクタで設定されている変数(下記ではthis.baseのbase, this.heightのheight)が一見すると定義されているように思えて紛らわしい。ゲッター/セッターを見ると、this._base, this._heightが定義されており、一瞬考えこんでしまう。
class Triangle {
// コンストラクター
constructor(base, height) {
console.log('constructor called' + ' base = ' + base + ', height = ' + height);
this.base = base; // セッターが使用されている模様
this.height = height; // セッターが使用されている模様
}
// baseプロパティの定義
get base() { // ゲッター
console.log('get base called')
return this._base;
}
set base(value) { // セッター
console.log('set base called')
this._base = value;
}
// heightプロパティ定義
get height() { // ゲッター
console.log('get height called')
return this._height;
}
set height(value) { // セッター
console.log('set height called')
this._height = value;
}
// メソッド定義(三角形の面積を求める
getArea() {
console.log('getArea() start');
return this.base * this.height / 2;
}
};
// 結果確認用ログ出力(コンストラクターによる設定)
console.log('new Triangle(8, 9) start');
var t = new Triangle(8, 9);
console.log('new Triangle(8, 9) end');
console.log('');
console.log('コンストラクターによる三角形の面積:' + t.getArea()); // 三角形の面積:36
// 結果確認用ログ出力(セッターによる再設定)
console.log('');
console.log('底辺の再設定 start base=10');
t.base = 10;
console.log('底辺の再設定 end');
console.log('高さの再設定 start height=5');
t.height = 5;
console.log('高さの再設定 end');
console.log('');
console.log('セッター再設定による三角形の面積:' + t.getArea()); // 三角形の面積:25
// コンストラクターによる設定での実行結果
// new Triangle(8, 9) start
// constructor called base = 8, height = 9
// set base called
// set height called
// new Triangle(8, 9) end
// getArea() start
// get base called
// get height called
// コンストラクターによる三角形の面積:36
// セッターによる再設定での実行結果
// 底辺の再設定 start base=10
// set base called
// 底辺の再設定 end
// 高さの再設定 start height=5
// set height called
// 高さの再設定 end
// getArea() start
// get base called
// get height called
// セッター再設定による三角形の面積:25