はじめに
JavaScriptを基礎からしっかり学ぶべく、山田祥寛さん著「Javascript本格入門」を購入しました。その内容を参考に、特に覚えておくべき事項や忘れやすい事項をこのページに纏めていきます。
改訂新版JavaScript本格入門 ~モダンスタイルによる基礎から現場での応用まで
この記事の対象となる人
- JavaScriptの基本的な構文を知っている人
- 何となくならJavaScriptのコードを書けるが、"オブジェクトとは何か?""変数のスコープは?""オブジェクト指向構文の書き方は?"といったやや突っ込んだ内容までは分からない人
3章 組み込みオブジェクト
3.1 オブジェクトとは
- オブジェクトとは、プロパティとメソッドから構成される。
- 組み込みオブジェクト…JavaScriptに標準で組み込まれたオブジェクトのこと
- このうちNumber, Boolean, String…などはインスタンス化する必要がない。リテラルをそのまま対応する組み込みオブジェクトとして利用できる
- Symbolオブジェクトについて
- 以下のように値そのものに意味が無く、名前にだけ意味があるような定数は問題がある
- (例えば以下では曜日を数値で比較するのは望ましくなく、同じ値の定数が同居する可能性もある)
- 以下のように値そのものに意味が無く、名前にだけ意味があるような定数は問題がある
const MONDAY = 0;
const TUESDAY = 1;
const WEDNESDAY = 2;
...
そのため定数の値としてSymbolを利用する。異なるSymbol命令で生成されたシンボルは、同名であっても一意になる
const MONDAY = Symbol();
const TUESDAY = Symbol();
const WEDNESDAY = Symbol();
...
4章 関数
4.3 スコープ
スコープには以下の3種類が存在する。
- グローバルスコープ…スクリプト全体から参照可能
- ブロックスコープ…
- ローカルスコープ…定義された関数の中でのみ参照可能
スコープについて気を付けるべき代表的な点を以下に示す。
- ローカル変数を定義するにはvar
- ブロックスコープで変数を定義するにはlet(推奨。スコープはできる限り限定すべき)
4.4 4.5 引数の記法
- ES2015以前
- argumentsオブジェクトで引数に関数する情報を利用できる
function showMessage(value){
if(arguments.length !== 1){
throw new Error('引数の数が間違っています:' + arguments.length);
}
}
- ES2015以降
- 引数のデフォルト値を設定可能
function getTriangle(base = 1, height = 1) {
return base * height / 2;
}
可変長の引数を定義可能
function sum(...nums){
...
}
4.7 高度な関数のテーマ
- クロージャ…一種の記憶域を提供する仕組み
- 以下の例では、closureが返す匿名関数がローカル変数counterを参照し続けるため、関数closureが終了しててもそれとは独立して参照を続けることが可能。そのため、匿名関数を呼び出すたびにcounter++が働き、値がインクリメントされる
function closure(init) {
let counter = init;
return function(){
return counter++;
}
}
let myClosure = closure(1);
console.log(myClosure()); //2
console.log(myClosure()); //3
console.log(myClosure()); //4
5章 オブジェクト指向構文
JavaScriptには元々クラスはなく、プロトタイプを利用して新たなオブジェクトを生成する。(ES2015ではクラス構文が実装されている)
- 以下のようにクラス定義、および初期化を行うことができる。JavaScriptでは関数(Functionオブジェクト)にクラスとしての役割を与えている。
- メソッドという概念は厳密にはなく、値が関数オブジェクトであるプロパティがメソッドと見なされる。
let Member = function(firstName, secondName){
this.firstName = firstName;
this.secondName = secondName;
this.getName = function(){
return this.firstName + this.secondName;
}
}
※プロトタイプベースのオブジェクト指向とは?…JavaScriptでは、インスタンスに対し動的にプロパティやメソッドを追加することが可能。そのため同じクラスによるインスタンスでも常に同じメンバーを持つとは限らない。一方、クラスベースのオブジェクト指向だと常に同じメンバーを持つ。このようなある種の緩さが、プロトタイプベースの特徴ともいえる。
- thisが使われる場所と、参照先
- トップレベル…グローバルオブジェクト
- 関数…グローバルオブジェクト
- call/applyメソッド…引数で指定されたオブジェクト(call/applyメソッドで実行する関数が参照するthisの中身を指定することが可能)
- イベントリスナー…イベントの発生元
- コンストラクター…生成したインスタンス
- メソッド…呼び出し元のオブジェクト
5.2 コンストラクターの問題点とプロトタイプ
コンストラクターでメソッドを追加すると、毎回無駄なメモリを消費してしまう。
そこで、共通のメソッドはprototypeプロパティに追加する。すると各インスタンスはprototypeオブジェクトに対して暗黙的な参照を持っているので、そのメソッド群を使用できるようになる。
let Member = function(firstName, secondName){
this.firstName = firstName;
this.secondName = secondName;
}
Member.prototype.getName = function(){
return this.secondName + '' + this.firstName;
};
let mem = new Member('真登', '久野');
console.log(mem.getName()); //結果:久野 真登
5.3 オブジェクトの継承(プロトタイプチェーン)
継承したい場合は、
- コンストラクタで対象クラスをcallで呼び出す(コンストラクタを継承する)
- prototypeとして対象クラスのインスタンスをセットする(メソッドを継承する)
let Animal = function(){};
Animal.prototype = {
walk: function(){
console.log('トコトコ…');
}
};
let Dog = function(){
Animal.call(this); //Animalコンストラクタを呼び出す
}
Dog.prototype = new Animal();
let d = new Dog();
d.walk(); //結果:トコトコ…
5.4 大規模な開発に備えるために
- プライベートメンバーの定義
- プライベートメンバーはコンストラクターの中で定義する(このときthisは使わない)
- 特権メソッドを定義し、プライベートメンバーにアクセスする
- 特権メソッドは入れ子の関数(クロージャ)となっており、プライベートメンバーを参照している。そのためプライベートメンバーはインスタンスが存在する間、特権メソッドによって生かされ続けることとなる。
- アクセサーメソッド経由でプロパティを公開する利点としては以下が挙げられる。
- 値を読み取り(書き込み)専用にできる
- 値の参照時にデータを加工できる
- 値の設定時に妥当性を検証できる
function Triangle(){
//プライベートプロパティの定義
let _base;
//プライベートメンバーにアクセスするためのメソッドを定義できるメソッド
Object.defineProperty(
this,
'base',
{
get: function(){
return _base;
},
set: function(base){
if(typeof base === 'number' && val > 0) {
_base = base;
}
}
}
}
}
let t = new Triangle();
t.setBase(10);
console.log(t.getBase()); //結果:10
5.5 ES2015のオブジェクト指向構文
- ES2015ではclass命令が導入された
class Member {
//コンストラクター
constructer(firstName, secondName){
this.firstName = firstName;
this.secondName = secondName;
}
//メソッド
getName(){
return this.secondName + this.firstName;
}
}
let m = new Member('真登', '久野');
console.log(m.getName()); //結果:久野真登
- 継承はextendsキーワードで簡単に定義可能
class BusinessMember extends Member {
...
}
- 継承時、基底クラスのメソッドやコンストラクターをsuperキーワードで呼び出せる
class BusinessMember extends Member {
constructer(firstName, lastName, clazz) {
super(firstName, lastName);
this.clazz = clazz;
}
}
- モジュールが使用可能となった
- exportキーワードを付与したメンバーは以下のようにアクセス可能
import { Member, Area } from './lib/Util.js'
※export default…モジュールに含まれる要素が一つだけの場合に使用。この場合クラス/関数などの名前は不要
- 自作クラスへのイテレータ実装
class MyIterator {
constructor(data) {
this.data = data;
}
//デフォルトイテレーターを取得するためのメソッドを定義
[Symbol.iterator](){
let current = 0;
let that = this;
return {
//dataプロパティの次の要素を取得
next() {
return current < that.data.length ?
{value: that.data[current++], done: false} :
{done: true};
}
};
}
}
- 上記クラスを、ジェネレータで書き直してみる
- ジェネレータではyield命令を使用する。return同様に関数の値を呼び出し元に返すが、returnがその場で関数を終了するのに対してyieldは処理を一時停止するだけ。すなわち、次に呼び出されたらその時点から処理を再開できる。
class MyIterator {
constructor(data) {
this.data = data;
this[Symbol.iterator] = function*() {
let current = 0;
let that = this;
while(current < that.data.length){
yield that.data[current++];
}
};
}
}
まとめ
まだ纏め切れていない内容があるため、随時この記事に書き足していく形で更新したいと思います。