他言語経験者のためのJS入門
この資料について
この資料は、「他言語の経験はあるが、JSは…」という方にJavaScriptの特徴部分だけを短時間で理解していただくため、書かれたものです。
(既に全く同じ目的の書籍があることを後で知りました…) ご注意
JavaScriptに関して一から説明するものではありません。
少しでも何かの言語の経験がある方を対象としています。
1. オブジェクト
1-1. 値には基本型と参照型がある 1-2. オブジェクトとはプロパティの集合である 1-3. 配列もオブジェクトである 1-4. 関数もオブジェクトである 1-5. メソッドとは関数を値とするプロパティである 1-6. 数値, 文字列, 論理値もメソッドを持っている?
1-1. 値には基本型と参照型がある
JSの値は、基本型か参照型かのどちらかである。 基本型 : 数値, 文字列, 論理値, null, undefined
参照型 : オブジェクト(基本型でない全ての値) 基本型でない値はすべて参照型=オブジェクトである。配列も、関数も、オブジェクトである。 変数代入時や引数に渡す際、基本形は値渡し、参照型は参照渡しで扱われる。
var numA = 10; var numB = numA; numA += numB; alert(numA); // 20 alert(numB); // 10 var objA = {p1: 10}; var objB = objA; objA.p2 = 20; alert(objA.p2); // 20 alert(objB.p2); // 20
1-2. オブジェクトとはプロパティの集合である
JSにおけるオブジェクトは、名前(キー)と値からなるプロパティの集合である。
連想配列の性質を持つと言える。
var obj = { name: 'Taro', 'age': 20, "favorite_color": ['blue', 'white'] }; alert(obj.name); // Taro alert(obj['name']); // Taro var key = 'age'; alert(obj[key]); // 20 alert(obj.key); // undefined
予めの宣言(クラス)を必要とせず、自由にプロパティの追加・削除が可能。
var obj = {}; obj.name = 'Jiro'; alert(obj.name); // Jiro delete obj.name; alert(obj.name); // undefined
1-3. 配列もオブジェクトである
JSにおける配列とは、配列の性質を持った特殊なオブジェクトである。 非負の整数をキーとするプロパティの集合であり、
要素数を値に持つlengthプロパティと、様々な配列操作メソッドを持つ。
var arr = ['Taro', 'Ichiro', {name: 'Jiro'}]; alert(arr[0]); // Taro alert(arr[1]); // Ichiro alert(arr[2].name); // Jiro alert(arr.length); // 3 arr.push('Saburo'); alert(arr[3]); // Saburo alert(arr.length); // 4 alert(arr['0']); // Taro // alert(arr.0); // error
ただし、配列の性質を持つオブジェクトを自作しても、配列の特殊性は得られない。
var arrayLike = { 0: 'a', 1: 'b', length: 2 }; arrayLike['2'] = 'c'; alert(arrayLike.length); // 2 arrayLike.push('d'); // error
1-4. 関数もオブジェクトである
JSにおける関数とは、関数の性質を持った特殊なオブジェクトである。 後ろに()を付けることで、引数をとり、呼び出すことができ、定義中のreturn文により値を返す。
var sayTaro = function() { alert('Taro'); }; sayTaro(); // Taro var sayName = function(name) { alert(name); }; sayName('Jiro'); // Jiro var getSaburo = function() { return 'Saburo'; }; alert(getSaburo()); // Saburo
1-5. メソッドとは関数を値とするプロパティである
JSにおけるメソッドとは、関数を値とするプロパティである。
var obj = { name: 'Saburo', sayName: function() { alert(this.name); } }; obj.sayName(); // Saburo var funcName = 'sayName'; obj[funcName](); // Saburo obj.askName = function() { alert("What's your name?"); }; obj.askName(); // What's your name? delete obj.askName; obj.askName(); // error
1-6. 数値, 文字列, 論理値もメソッドを持っている?
数値, 文字列, 論理値は、オブジェクトでないにも関わらず、自身からメソッドを呼び出すことができる。
var num = 0.0001; alert(num.toExponential()); // 1e-4 alert('Taro'.charAt(2)); // r alert(true.toString()); // true
数値, 文字列, 論理値はそれぞれにラッパーオブジェクト Number, String, Boolean を持ち、
ラッパーオブジェクトのプロパティ、メソッドは自身のプロパティ、メソッドかの様に扱うことができる。 ラッパーオブジェクトは、メソッド呼び出し時に内部で自動的に生成・破棄されている。
alert('Taro'.charAt(2)); // r // var tempString = new String('Taro'); // var tempResult = tempString.charAt(2); // tempString = null; // alert(tempResult);
2. 関数
2-1. 関数の宣言・定義 2-2. 宣言は巻き上げられる 2-3. ローカルスコープは関数ブロックにのみ存在する 2-4. クロージャがよく使われる(のでその説明)
2-1. 関数の宣言・定義
他言語と同じように宣言もできるが、
function func(message) { alert(message); } func('Hello!');
JSにおける関数はオブジェクトであるため、変数に代入することができる。
var func = function(message) { alert(message); }; func('Hello!');
また、変数に代入せず、()で囲った定義からそのまま呼び出すこともできる。
→ 即時関数
(function(message) { alert(message); })('Hello!');
2-2. 宣言は巻き上げられる
JSでは、変数・関数の宣言がスコープの開始部分まで巻き上げられる。
ただし、定義は元の位置にあるものとして扱われる。
// alert(a); // error alert(b); // undefined var b; b = 1; alert(b); // 1
関数も、変数に代入する場合は同様に扱われるが、
alert(func); // undefined func('Hello!'); // error var func = function(message) { alert(message); };
変数に代入しない場合、宣言が定義ごと巻き上げられる。
alert(func); // function func(... func('Hello!'); // Hello! function func(message) { alert(message); }
2-3. ローカルスコープは関数ブロックにのみ存在する
JSでは、関数以外のブロックにスコープが存在しない。
if (true) { var str = 'str'; } alert(str); // str
関数ブロックにのみスコープが存在する。
var func = function () { var str = 'str'; }; alert(str); // error
2-4. クロージャがよく使われる(のでその説明)
JSでは、スコープのローカル変数はスコープ開始の度に新たに作られ、スコープ終了の度に消去される。
→ ガベージコレクション
var func = function() { var num = 0; alert(++num); }; func(); // 1 func(); // 1
ただし、スコープ終了後も参照されるローカル変数は、ガベージコレクションの対象とならない。
var func = (function() { var num = 0; return function() { alert(++num); }; })(); func(); // 1 func(); // 2
変数 func に代入された関数オブジェクトの様に、
関数と、その関数が定義されたスコープとが一体となった関数オブジェクトを クロージャ と呼ぶ。
クロージャの実用例1:private属性の模倣
var counter = (function() { var privateVal = 0; // private変数の模倣 var add = function(val) { // privateメソッドの模倣 privateVal += val; }; return { increment: function() { add(1); }, value: function() { return privateVal; } }; })(); alert(counter.privateVal); // undefined alert(counter.value()); // 0 counter.increment(); alert(counter.value()); // 1 counter.add(1); // error
変数privateVal, addには、counterオブジェクトのメソッドからのみアクセス可能であり、直接はアクセスできない。
変数privateVal, addは、counterオブジェクトのprivate属性のような働きをする。
クロージャの実用例2:非同期処理におけるthisの保存
var person = { name: 'Taro', who: function() { alert(this.name); // this -> person } }; person.who(); // Taro
thisは、オブジェクト内ではオブジェクト自身を指すが、
var person = { name: 'Taro', who: function() { $.ajax({ success: function() { alert(this.name); // this -> $.ajaxの引数 } }); }, }; person.who(); // undefined
コールバック関数内では、オブジェクト内であっても別のオブジェクトを指す。
コールバック関数内からオブジェクト自身を参照するには、コールバック関数外でthisを変数に代入し、コールバック関数をクロージャにすればよい。
var person = { name: 'Taro', who: function() { var _this = this; // this -> person $.ajax({ success: function() { alert(_this.name); // _this -> person (this -> $.ajaxの引数) } }); } }; person.who(); // Taro
3. プロトタイプ
3-1. オブジェクトはオブジェクトを元に作られる 3-2. インスタンスはプロトタイプのプロパティを継承する 3-3. 組み込みのプロトタイプが存在する 3-4. 全てのオブジェクトはプロトタイプを持つ 3-5. プロトタイプについてより深く理解するならコチラ
3-1. オブジェクトはオブジェクトを元に作られる
JSのオブジェクトは、クラスではなく、オブジェクトを元に生成される。
生成されたオブジェクトを インスタンス 、元となったオブジェクトを プロトタイプ と呼ぶ。 クラスを元にオブジェクトを生成する言語がクラスベースのオブジェクト指向言語と呼ばれるのに対し、
プロトタイプを元にオブジェクトを生成する言語はプロトタイプベースのオブジェクト指向言語と呼ばれる。 JSは、プロトタイプベースのオブジェクト指向言語である。
プロトタイプを元にオブジェクトを生成するには、主に次のどちらかが使用される。 Object.create() 引数に渡したオブジェクトをプロトタイプとするオブジェクトを生成する。
プロトタイプベースでの生成をシンプルに表現できるが、ECMAScript5未満の環境では使用できない。
var parent = {}; var child = Object.create(parent);
new Constructor() 関数オブジェクトをnew演算子に続けて呼び出すことで、関数オブジェクトのprototypeプロパティに格納したオブジェクトをプロトタイプとするオブジェクトを生成する。
オブジェクト生成に使われる場合の関数オブジェクトをコンストラクタ と呼び、関数の内容はオブジェクト生成時に実行される。
ECMAScript5未満の環境で使用でき、クラスベースに似た書き方ができるため、Object.create()に比べこちらの方が多く使われている。
var parent = {}; var Child = function() {}; Child.prototype = parent; var child = new Child();
3-2. インスタンスはプロトタイプのプロパティを継承する
インスタンスは、プロトタイプのプロパティを参照(継承)し、自身のプロパティと同様に扱うことができる。 参照の関係にあるため、プロトタイプのプロパティの状態は常にインスタンスに共有される。
var parent = { species: 'human', saySpecies: function() { alert('I am a ' + this.species + '.'); } }; var child1 = Object.create(parent); alert(child1.species); // human child1.saySpecies(); // I am a human. var Child = function() {}; Child.prototype = parent; var child2 = new Child(); alert(child2.species); // human child2.saySpecies(); // I am a human. parent.species = 'dog'; alert(child1.species); // dog alert(child2.species); // dog
プロパティにインスタンス間で異なる値を持つには、プロトタイプではなく、一つ一つのインスタンスに同名のプロパティを持たせることで実現する。 Object.create()で生成する場合は、生成されたインスタンスに直接プロパティを追加する。 new演算子で生成する場合は、コンストラクタ内でthisを介してインスタンスにプロパティを追加する。
下記の例では、Object.create()とプロパティ追加を関数にまとめることで、コンストラクタと同様に扱っている。
var parent = { sayName: function() { alert('I am ' + this.name + '.'); } }; var createChild = function(_name) { var child = Object.create(parent); child.name = _name; return child; }; var child1 = createChild('Taro'); var child2 = createChild('Jiro'); alert(child1.name); // Taro alert(child2.name); // Jiro child1.sayName(); // I am Taro. child2.sayName(); // I am Jiro. var Child = function(_name) { this.name = _name; }; Child.prototype = parent; var child3 = new Child('Saburo'); var child4 = new Child('Shiro'); alert(child3.name); // Saburo alert(child4.name); // Shiro child3.sayName(); // I am Saburo. child4.sayName(); // I am Shiro.
3-3. 組み込みのプロトタイプが存在する
JSには、予めいくつかのプロトタイプが用意されている。
同時に、そのインスタンスを生成するコンストラクタも用意されており、プロトタイプはコンストラクタのprototypeプロパティに予め設定されている。 Object.prototype
{}リテラルにより生成されるオブジェクトのプロトタイプ Function.prototype
function文により生成されるオブジェクト(関数)のプロトタイプ Array.prototype
[]リテラルにより生成されるオブジェクト(配列)のプロトタイプ
RegExp.prototype
//リテラルにより生成されるオブジェクト(正規表現)のプロトタイプ Number / String / Boolean.prototype
数値、文字列、論理値それぞれのラッパーオブジェクトのプロトタイプ Date.prototype
new Date()により生成される時間を表すオブジェクトのプロトタイプ Error.prototype
new Error()により生成されるエラーを表すオブジェクトのプロトタイプ
組み込みプロトタイプから生成されるインスタンス間で共用できるメソッドは、全てプロトタイプが持つメソッドを継承したものである。
var arr = ['Taro', 'Jiro', 'Saburo']; arr.push('Shiro'); alert(arr.hasOwnProperty('push')); // false alert(Array.prototype.hasOwnProperty('push')); // true var func = function() { alert('Taro'); }; var newFunc = func.bind(this); alert(func.hasOwnProperty('bind')); // false alert(Function.prototype.hasOwnProperty('bind')); // true var str = 'Taro'; var thirdChar = str.charAt(2); alert(str.hasOwnProperty('charAt')); // false alert(String.prototype.hasOwnProperty('charAt')); // true
※hasOwnPropertyメソッドは、全てのオブジェクトから利用でき、引数に渡された文字列をキーとするプロパティが、呼び出したオブジェクト自身に存在するかどうかを判定する。
3-4. 全てのオブジェクトはプロトタイプを持つ
全てのオブジェクトはプロトタイプを持つ。プロトタイプ自身もオブジェクトであるためプロトタイプを持ち、そのプロトタイプもまたプロトタイプを持つ。
このプロトタイプによるオブジェクト同士の連なりをプロトタイプチェーン と呼ぶ。 プロトタイプチェーンを遡っていくと、全てのチェーンはいずれObject.prototypeに辿り着く。
Object.prototypeは特別で、プロトタイプとしてnullを持ち、 全てのプロトタイプチェーンの終端に位置している。
プロトタイプチェーンは、プロパティの要求時に用いられる。 あるオブジェクトのプロパティが要求されると、JSエンジンはまず、オブジェクト自身がプロパティを持っていないか探す。
もし存在しなかった場合、次はオブジェクトのプロトタイプがプロパティを持っていないか探す。 これを繰り返して、見つかれば値を返して次の処理へ移る。
プロトタイプがnullになるまで見つからない場合、言い換えると、Object.prototypeにも存在しない場合は、undefinedを返して次の処理へ移る。
また、見つかった段階で、更に祖先のプロトタイプが同名プロパティを持っていた場合、祖先のプロパティは無視される。→ オーバーライド
var grandParent = { species: 'homo sapiens' }; var parent = Object.create(grandParent); parent.species = 'human'; // override var child = Object.create(parent); alert(child.species); // human alert(parent.species); // human alert(grandParent.species); // homo sapiens
3-5. プロトタイプについてより深く理解するならコチラ
社外の他人の記事ですが、ここから更に深くプロトタイプを理解したいときは、ぜひコチラを読んでみて下さい。個人的には他の何よりわかりやすい解説でした。 「プロトタイプ」の指すものが4つもあること
プロトタイプ (用語としての)prototype プロパティ[[prototype]] プロパティ__proto__ プロパティ
に、私は非常に混乱したのですが、この記事のおかげで上手く整理することが出来ました。 4つの「プロパティ」と、オマケで2つの「コンストラクタ」の 指すものを、以降にまとめました。
記事を読んだ後で振り返る時に使えるといいな。。
プロトタイプ 全てのオブジェクト※がそれぞれに一つ参照している、自身とは別のオブジェクトを指す用語。
オブジェクトは、プロトタイプのプロパティを参照し、自身のプロパティと同様に扱える。
また、参照する側のオブジェクトを、プロトタイプのインスタンスと呼ぶ。 prototype プロパティ 全てのFunctionオブジェクトが持つプロパティ。
値は、自身をコンストラクタとして生成されるオブジェクトのプロトタイプ。
[[prototype]] プロパティ 全てのオブジェクト※が持つ内部プロパティ。値は、自身のプロトタイプ。
内部プロパティであるため、直接参照することはできない。 __proto__ プロパティ 全てのオブジェクト※が持つプロパティ。値は、[[prototype]]プロパティ。
このプロパティが実装されていない環境もあり、その場合はObject.getPrototypeOf()で代用可能。 ※Object.prototypeと、Object.create(null)の返り値を除く。これらの[[prototype]]にはnullが格納されている。
コンストラクタ 自身のprototypeプロパティのインスタンス生成を目的に(new演算子と共に)使用される場合のFunctionオブジェクトを指す用語。 constructor プロパティ 全てのオブジェクトが持つプロパティ。値は、自身のコンストラクタにあたるFunctionオブジェクト。