JavaScriptで引っ掛かりがちな箇所を凝縮して解説する
オブジェクトすなわち連想配列
JavaScriptは基本型(string
, number
)以外のオブジェクトは全て連想配列として扱えます。
※ string
と String
, number
と Number
の違いについてコメントで指摘頂き追記しました。
array = new Array(1, 2, 3)
// ええんやで
array.a = 'A'
array.a; // -> A
function func() {}
// ええんやで
func.b = 'B';
func.b; // -> B
オブジェクトとしての関数
JavaScriptでは関数は Function
型のオブジェクトです。(まあ型の概念はないのですが)
Javaの Consumer
のように変数に格納もできますし、関数の引数として渡すこともできます。(よくコールバックとして使われる)
// 以下2つは同義
function myFunc() { console.log('Hi'); }
var myFunc = function() { console.log('Hi'); };
typeof myFunc; // -> function
myFunc.constructor === Function; // -> true
myFunc.__proto__ === Function.prototype; // -> true
var array = [];
array.push(myFunc);
array[0](); // -> Hi
function executeTwice(func) {
func();
func();
}
executeTwice(myFunc); // -> Hi Hi
プロトタイプチェーン
全てのオブジェクトは .__proto__
というプロパティを持っており、これは .constructor.prototype
と同じです。(実は昔はなかった)
この __proto__
直下にあるプロパティは全てオブジェクトから直接アクセスできます。(コードを見たほうが早い)
var obj = new Object();
Object.prototype.something = 'something';
obj.__proto__ === Object.prototype; // -> true
obj.__proto__.something; // -> something
obj.something; // -> something
obj.constructor === Object // -> true
// オマケ
Object.constructor === Function; // -> true
// やはり直接オブジェクトがもっているプロパティが優先される
({ something: 'another' }).something; // -> another
さて
function Class() {
this.something = 'Something';
}
// どのオブジェクトにもObject型のプロパティ prototype は必ず存在する
// よって prototype の初期化は不要
Class.prototype.hello = {
console.log('Hello!');
};
var instance = new Class();
// これらと同義
// -> var instance = new(Class);
// -> var instance = new Class;
instance.hello(); // -> Hello!
を実行すると何が起こるのでしょう。
- オブジェクトが生成されます。このオブジェクトを以降
object
とします。 -
object.__proto__ = Class.prototype
とする。 -
this
をobject
としてClass
関数が実行される。 -
Class
の返り値があればそれを、なければobject
を返す。 -
instance
に代入される。
前述の __proto__
と併せて分かることは、JavaScriptはなんちゃってクラス機構をもっているということです。
グローバルスコープ
グローバルスコープの変数は window
オブジェクトのプロパティとしてアクセスできます。
// グローバルスコープ
var a = 'something';
a; // -> something
window.a; // -> something
var prop = 'a';
window[prop]; // -> something
this.a; // -> something
NaN
と null
と undefined
-
NaN
-Number
型のオブジェクトとして扱われているが、数字でないことを示す。NaN
を含む計算式の答えは必ずNaN
になる。 -
null
- 明示的に何もないことを示すときに使う。 -
undefined
- プロパティが存在しない場合に定義されていないことを伝えるもの。
(Number('1') + 2); // -> 3
Number('Oops!'); // -> NaN
var array = [1, 2, 3];
array[100]; // <- undefined
delete array[0];
array[0]; // <- undefined
参照エラー
以下の場合参照エラーが発生します。
- スコープ直下の存在しない変数を参照。
-
undefined
,null
の存在しないプロパティを参照。
var obj = {};
// これらは全部 undefined
obj.undef;
// これらは全部参照エラー
undef;
obj.undef.undef;
(function() { undef; })();
// グローバル変数が存在するかどうか確認したい場合
if (window.jQuery) {}
比較演算子
基本形(number
, string
)以外のオブジェクト"同士"は、ポインタで比較されます。つまり左右の変数が同じ場所に格納しているデータを指し示している場合のみ真を返します。
var array1 = [1, 2, 3];
var array2 = [1, 2, 3];
var obj1 = { a: 'A' };
var obj2 = { a: 'A' };
array1 == array2; // -> false
obj1 == obj2; // -> false
obj1.a == obj2.a; // -> true
obj1 == "[object Object]"; // -> true
-
a == b
- 型をどちらかに統一してから比較を実行します。 -
a === b
- 型をどちらかに統一してから比較を実行します。
// string は Date に変換できない
// Date は string に変換できる
new Date() == "Fri May 19 2017 23:44:07 GMT+0900 (JST)";
// つまりこれは
// (new Date()).toString() === "Fri May 19 2017 23:44:07 GMT+0900 (JST)"
// と同義
// Date と文字列の比較、つまり偽
new Date() === "Fri May 19 2017 23:44:07 GMT+0900 (JST)"; // -> false
this
について
まず、3つ例外があります。
- グローバルスコープでの
this
はwindow
を示す。 - 関数を
new
した場合、その関数の中のthis
はnew
で生成したオブジェクト。 - 関数内の
this
は.call(context, ...args)
,.apply(context, args)
,.bind(context)
でcontext
に変更できます。
ではそれ以外はというと、非常に伝え辛いのですが
ざっくり結論: context.method()
という形で関数が呼ばれた場合は context
、それ以外は undefined
.
var context = {
method: function() {
return this;
}
};
context.method(); // <- context と同義
var something = context.method;
// ただし、これは window.something() として扱われる
something(); // <- よってこれは window と同義
下3行をご覧いただくと、関数を別の変数に入れて実行すると this
の内容が変わってしまっています。
要するに context.method
関数で context
を取得するために this
を使うのは、思わぬ副作用に繋がるので良くないということですね。
即時関数
関数を生成して、変数に代入する間もなくその場で実行するという手法です。よく変数を隠蔽(or グローバル汚染回避)するために使われます。
(function() {
var text = 'Cannot access me outside of this function';
console.log(text);
})()
並行処理
並"列"処理は「Web Worker」などでggってみてください。
setTimeout
や setInterval
を使うと簡単に並行処理を実装できます。
window.setTimeout(function() {
// 処理1
}, 10); // 0 でもいいが念の為 10
//処理2
コメント欄より: リテラルと Object の違い (Thanks to @mpyw)
文字列リテラル(string
)と文字列オブジェクト(String
)は違うという指摘を頂きました。今まで知らなかった...。
new String(str)
で文字列を生成するとオブジェクト扱いのようです。(number
と Number
も同様)
"a" == "a"; // -> true
new String("a") == new String("a"); // -> false
typeof "a" // -> string
typeof new String("a") // -> object
"a" instanceof String // -> false
new String("a") instanceof String // -> false
String型はオブジェクトになるので、参照渡しになります、が、文字列オブジェクトには破壊的メソッドが存在しないので、確かめる方法といえば前述した連想配列として使うことくらいですかね。
objStr = new String("a");
objStr2 = objStr
objStr.a = 'A';
objStr2.a; // -> A
後書き
私が初めてJavaScriptを学んだのは小学生の頃で「とほほのWWW入門」がきっかけでした。
懐かしく思って最近見に行くとまだサイトは残っているばかりか、いつの間にかナウいコンテンツ(AngularJSなど)まで拡充していますね。