開眼!JavaScriptを読んだときの自分用メモです。
やや長文、誤字脱字あったらすいません。
コンストラクタ関数はオブジェクトインスタンスを構築して返す
コンストラクタ関数の役割は、ある程度共通の性質やふるまいを持ったオブジェクトを複数生成することです。
デフォルトのプロパティとメソッドを持ったオブジェクトの型のようなものです。
new 演算子で呼び出された場合、コンストラクタ関数は特別な役割を果たします。
- コンストラクタ関数のthisの値として、新たに生成されるオブジェクトを設定します
- 本来、returnを宣言しないとfalseを返すところ、コンストラクタ関数は新たに生成されるオブジェクト(thisで参照するオブジェクト)を返す
/* Personはnew演算子を伴っての利用を想定したコンストラクタ関数。 */
var Person = function Person(living, age, gender) {
//thisはここで生成される新たなオブジェクト。(つまり、this = new Object();)
this.living = living;
this.age = age;
this.gender = gender;
this.getGender = function() {
return this.gender;
};
/* 関数がnew演算子とともに呼ばれた場合、return文が宣言されていなくてもthisを返す。 */
};
// Personオブジェクトのインスタンスを生成
var cody = new Person(true, 33, 'male');
// codyはオブジェクトで、Person()のインスタンスである
console.log(typeof cody); // 出力:object
console.log(cody); // codyのプロパティと値を出力
console.log(cody.constructor); // Person関数そのものを出力
インスタンスはコンストラクタ関数にポイントするconstructorプロパティを持つ
コンストラクタ関数でインスタンスを生成すると、そのインスタンスにはconstructorプロパティが自動的に生成されます。
var CustomConstructor = function CustomConstructor(){
return 'Wow!';
};
var instanceOfCustomObject = new CustomConstructor();
// 出力:true
console.log(instanceOfCustomObject.constructor === CustomConstructor);
// CustomConstructor()関数へのリファレンスを返す
// 出力:'CustomConstructor()' * 環境によって出力内容は異なる場合があります
console.log(instanceOfCustomObject.constructor);
instanceof演算子でコンストラクタ関数を特定する
instanceofを使ってあるオブジェクトが特定のコンストラクタ関数のインスタンスかどうか判定できます。
// ユーザ定義コンストラクタ
var CustomConstructor = function() {this.foo = 'bar';};
// CustomConstructorのインスタンスを生成
var instanceOfCustomObject = new CustomConstructor();
console.log(instanceOfCustomObject instanceof CustomConstructor); // 出力:true
// ネイティブオブジェクトも同様に動作
console.log(new Array('foo') instanceof Array) // 出力:true
hasOwnProperty()を使ってプロパティがプロトタイプチェーン経由でないことを確認する
in演算子はオブジェクトが持つプロパティの存在を確認することができます。
これにはプロトタイプチェーンで見つけることができるプロパティも含まれます。
hasOwnProperty()は指定したプロパティがプロトタイプチェーンで発見するものでなく、オブジェクト自身が保持しているかものかどうかを確認することができます。
var myObject = {foo: 'value'};
console.log(myObject.hasOwnProperty('foo')); // 出力:true
// プロトタイプチェーンから継承したプロパティはfalseを出力
console.log(myObject.hasOwnProperty('toString')); // 出力:false
for-inループでプロパティを列挙する
for-inループでオブジェクトのプロパティを列挙することができます。
var cody = {
age : 33,
gender : 'male'
};
for (var key in cody) { // keyはループ内でそれぞれのプロパティ名を格納する変数
// プロトタイプチェーンから継承されたプロパティを除く
if(cody.hasOwnProperty(key)) {
console.log(key);
}
}
for-inループでプロパティがアクセスされる順番は、プロパティが定義された順番とは限りません。
for-inループでは「列挙可能」なプロパティのみ登場します。
例えばconstructorプロパティは登場しません。
プロパティが列挙可能かどうかはpropertyIsEnumerable()で確認できます。
関数の実行方法
- 関数として
- メソッドとして
- コンストラクタとして
- apply()またはcall()を使って
// 関数
var myFunction = function(){return 'foo'};
console.log(myFunction()); // 出力:'foo'
// メソッド
var myObject = {myFunction: function(){return 'bar';}}
console.log(myObject.myFunction()); // 出力:'bar'
// コンストラクタ
var Cody = function(){
this.living = true;
this.age = 33;
this.gender = 'male';
this.getGender = function() {return this.gender;};
}
// newとともにCodyコンストラクタを実行することによりインスタンスが生成される
var cody = new Cody();
// codyオブジェクトとそのプロパティを出力
console.log(cody);
// apply()とcall()
var greet = {
runGreet: function(){
console.log(this.name,arguments[0],arguments[1]);
}
}
var cody = {name: 'cody'};
var lisa = {name: 'lisa'};
// greetオブジェクトのrunGreetメソッドをcodyオブジェクトから呼び出しているように実行
greet.runGreet.call(cody,'foo','bar'); // 出力:cody foo bar
// lisaオブジェクトの中から呼び出しているように実行
greet.runGreet.apply(lisa, ['foo','bar']); // 出力:lisa foo bar
/* call()とapply()実行時のパラメータの渡し方の違いに注目 */
callとapplyの違いは引数の渡し方のみです。
入れ子の関数内ではthisはグローバルオブジェクトを参照する(ECMA3)
入れ子内の関数のthisはECMA Script3では関数が定義されているオブジェクトではなく、グローバルオブジェクトを参照してしまうので注意する。
この仕様はECMA5で修正されます。
入れ子関数内でthisを見失わないために親関数にthisへの参照を保存しておいてスコープチェーンを使えばよいです。
var myObject = {
myProperty: 'I can see the light',
myMethod : function(){
// this(myObject)への参照をmyMethodスコープに保持
var that = this;
var helperFunction = function() { // ネストされた子関数
// thatに親関数のthisの値が保持されているため、myObject.myPropertyにアクセスできる
console.log(that.myProperty); // 出力:'I can see the light'
console.log(this); // thisの値そのものは変更されている 出力:window
}();
}
}
myObject.myMethod();
call()やapply()でthisの値をコントロールする
callやapplyを使うことでその関数内のthisがポイントするオブジェクトを設定することができます。
こうすることで、thisを決定するプロセスをオーバライドすることができます。
var myObject = {};
var myFunction = function(param1, param2) {
// 後にcall()で呼ばれる際、thisはmyObjectを参照
this.foo = param1;
this.bar = param2;
console.log(this) // 出力:Object {foo = 'foo', bar = 'bar'}
};
// myObjectをthisとしてmyFunctionを呼ぶ
myFunction.call(myObject, 'foo', 'bar');
JavaScriptのスコープ
JSには3つのスコープがあります。
- グローバルスコープ
- ローカルスコープ(関数スコープ)
- evalスコープ
var foo = 0; // グローバル変数
console.log(foo); // 出力:0
var myFunction = function() {
var foo = 1; // ローカルスコープ(myFunction)
console.log(foo); // 出力:1
var myNestedFunction = function() {
var foo = 2; // ローカルスコープ(myNestedFunction)
console.log(foo); // 出力:2
}();
var myNestedFunctionTwo = function() {
// ここではローカルスコープにfooを定義しない
console.log(foo); // 入れ子の外の関数のfooを参照するため、1を出力
}();
}();
eval('var foo = 3; console.log(foo);'); // evalスコープ
なお、JSはブロックスコープを持ちません。
また、JSはvarを伴わずに変数を定義すると、たとえ関数内で定義してもグローバルスコープのプロパティとして定義されます。
そのため、関数内で変数を定義する際には常にvarを使い、スコープの穴を避けるようにします。
スコープは関数実行時ではなく、関数定義時に決められる
スコープチェーンは関数定義時の場所にもとづいて決定されます。
実行時ではありません。
定義済みの関数を別のスコープの変数に渡してもスコープチェーンが変わることはありません。
var parentFunction = function() {
var foo = 'foo';
return function() { // 無名関数を返す
console.log(foo); // 出力:'foo'
}
}
// nestedFunctionはparentFunction()から返される無名関数を参照
var nestedFunction = parentFunction();
nestedFunction(); // 出力:'foo' - parentFunction()のスコープに定義された変数に、グローバルスコープからアクセスしている
クロージャはスコープチェーンによって生成される
var countUpFromZero = function() {
var count = 0;
return function() { // 子関数を返す
return ++count; // count変数は親関数で定義されている
};
}(); // countUpFromZeroは呼ばれると即時実行し、子関数を返す
console.log(countUpFromZero()); // 出力:1
console.log(countUpFromZero()); // 出力:2
console.log(countUpFromZero()); // 出力:3
次の例は、配列の要素自身のインデックスを出力する関数を生成し、配列に格納しようとします(これは失敗例です)
var logElementNumber = function() {
// 関数を格納する配列を準備
var funcArray = [];
var i;
for (i = 0; i < 3; i++) {
funcArray[i] = function(){ console.log(i); }; // インデックスを出力する関数をfuncArrayのそれぞれの要素に格納...するはず
}
return funcArray;
}(); // 即時実行して、戻り値の配列を格納する
// 配列に格納された関数をそれぞれ実行
logElementNumber[0](); // 0...ではなくて3が出力される
logElementNumber[1](); // 1...ではなくて3が出力される
logElementNumber[2](); // 2...ではなくて3が出力される
クロージャは値そのものでなく、参照を保持しているためスコープチェーンを辿って親関数のスコープにあるiを参照している。
この問題を解決するために、以下のように修正します。
var logElementNumber = function() {
var funcArray = [];
var i;
var func = function(i) {
// パラメータにiを指定しているため、この関数スコープにローカル変数iが定義される
return function() { console.log(i); };
};
for (i = 0; i < 3; i++) {
funcArray[i] = func(i); // インデックスを出力する関数をfuncArrayのそれぞれの要素に格納
}
return funcArray;
}();
// 配列に格納された関数をそれぞれ実行
logElementNumber[0](); // 出力:0
logElementNumber[1](); // 出力:1
logElementNumber[2](); // 出力:2
クロージャを一言で表すと「スコープチェーンに存在する変数への参照を保持している関数」と言えます。
クロージャが生成されるとスコープチェーン上の変数への参照は保持されます。
親関数スコープで定義されている変数は、たとえ親関数自身が使われなくなってもそれらを参照するクロージャが生き残っている限り保持されます。
グローバルスコープで宣言されている関数もクロージャである
グローバルスコープへの参照もクロージャできます。
// グローバルスコープで変数とその変数を出力する関数を生成
var a = 1;
var func = function() { console.log(a); };
// func()関数を引数として渡し、即時実行する
(function (f){
var a = 100; // 関数スコープでaを定義
f(); // console.log(a);が実行され、1を出力
})(func);
関数のprototypeプロパティ
prototypeプロパティはすべてのFunction()インスタンスに自動的に付与されます。
Function()コンストラクタを明示的に呼び出していない場合でも、すべての関数はFunction()コンストラクタによって生成されます。
生成時には、その関数にprototypeプロパティに空オブジェクトが自動的に付与されます。
prototyepプロパティはFunction()コンストラクタによって生成されるものです。
プロトタイプチェーンは最初に見つけたプロパティを返す
スコープチェーンと同様に、プロトタイプチェーンもチェーン検索で最初に見つけた値を返します。
Object.prototype.foo = 'object-foo';
Array.prototype.foo = 'array-foo';
var myArray = [];
console.log(myArray.foo); // 出力:'array-foo'
myArray.foo = 'bar';
console.log(myArray.foo) // 出力:'bar'
// ローカルプロパティが優先される
継承チェーンを生成する
JavaScriptにおいて、あるオブジェクトが他のオブジェクトから継承する場合にやらなければならないことは、継承するprototypeプロパティの値を持つオブジェクトをインスタンス化するだけです。
var Person = function(){this.bar = 'bar'};
Person.prototype.foo = 'foo';
var Chef = function(){this.goo = 'goo'};
Chef.prototype = new Person();
var cody = new Chef();
console.log(cody.foo); // 出力:'foo'
console.log(cody.bar); // 出力:'bar'
console.log(cody.goo); // 出力:'goo'
この例ではChef()コンストラクタ関数のprototypeにPerson()インスタンスを代入して、Chef()の各インスタンスがPerson.protoypeを継承するようにしています。
nullとundefinedについて
nullは、値は利用可能ではないが、今後利用できる見込みがある場合に使用します。
undefinedは、存在自体が設定されずプロトタイプチェーンでも発見できない場合に使用します。