脱初心者のために
これだけは知っておきたい
JavaScriptネタ
Tsuyoshi Akase
福岡Haxe勉強会
feat.
HTML5+α @福岡
第0x00回
アジェンダ
Tsuyoshi Akase
Groovy Mobile Inc.
We are creating a CMS. (PHP / JavaScript)
akase244 (Twitter / Facebook)
Fukuoka.php / HTML5+α @Fukuoka
みなさん、JavaScriptは
毎日さわってますか?
当然さわってますよね。
ところで、10年くらい前のJavaScriptって
<script>
function blink() {
if (document.getElementById('blink').style.display == 'none') {
document.getElementById('blink').style.display = 'block';
} else {
document.getElementById('blink').style.display = 'none';
}
setTimeout('blink()', 500);
}
</script>
</head>
<body onload="blink();">
<div id="blink" style="color:red;">blink</div>
</body>
ところが
Google Map以降
劇的に変わりましたよね?
最近は
Ajax JSON jQuery zepto.js
Node.js Backbone.js QUnit
Jasmine AngularJS Sencha
Titanium PhoneGaP three.js
RequireJS Knockout.js
enchant.js ...
サーバーからクライアント、Web、
ゲーム、3D、テストにいたるまで、
正直、もうついていけません。
そんなことも言ってられないですし、
今日は、奥深いJavaScriptの世界に触れて、
初心者という殻を破って行こうじゃありませんか。
さて、本題入りますか
先ほど、たくさんのJavaScriptの
ライブラリや技術などを列挙しましたが、
そもそもその前に、JavaScriptが持つ
様々な特徴についてしっかりと理解されていらっしゃいますか?
自分自身、数日前まで全然
理解していませんでした。。。
なので、今日は皆さんと一緒にJavaScriptというヤツを紐解いていってやろうと思います。
とは言え、時間も限られていますので、ここではいくつかピックアップして、説明を進めていきたいと思います。
いい加減、本題に入ります
まずは、関数の話から
JavaScriptの関数は再代入可能(変数として扱える)なオブジェクトで、
関数名は関数オブジェクトを代入するための変数名として利用可能です。
無名関数(匿名関数):宣言と代入を同時に行う
function hello() {
alert("こんにちは");
}
var copyHello = hello;
copyHello(); // こんにちは
var hello = function() {
alert("こんにちは");
}
hello(); // こんにちは
高階関数:関数は引数にすることも可能です。
function hello(fn) {
fn();
}
hello(function() {
alert("こんにちは");
});
高階関数:関数は戻り値にすることも可能です。
var counter = function() {
var cnt = 0;
return function() {
return cnt++;
};
}
var callCounter = counter();
alert(callCounter()); // 0
alert(callCounter()); // 1
alert(callCounter()); // 2
今、何かおかしくなかった!?
関数ブロック内で宣言した変数は外側のブロックからは参照できませんが、
内側のブロックから外側のブロックの変数は参照できます。
var counter = function() {
var cnt = 0;
return function() {
return cnt++;
};
}
var callCounter = counter();
alert(callCounter()); // 0
alert(callCounter()); // 1
alert(callCounter()); // 2
参照可能
変数オブジェクトを外側のブロックに向かって探していく仕組みのことを、スコープチェインと呼びます。
var x = 1;
var y = 3;
var outer = function() {
var y = 2;
var inner = function() {
alert(x + y);
}
return inner();
}
outer();
スコープチェイン上で目的の値を発見したら、外側のブロックに同名の
値が存在しても、それ以上辿ることはなく最初に発見した時点で検索を
終了する。
あった!
あった!
なかった。。
いや、だからさっき
何かおかしくなかった!?
ローカル変数を参照し続けることができる関数のことをクロージャと呼びます。
クロージャの条件:
var counter = function() {
var cnt = 0;
return function() {
return cnt++;
};
}
var callCounter = counter();
alert(callCounter()); // 0
alert(callCounter()); // 1 <= 前回の結果に加算されている
alert(callCounter()); // 2 <= 前回の結果に加算されている
var foo = 0; // グローバルスコープ
alert(foo); // 0
var myFunc = function() {
var foo = 1; // ローカルスコープ(myFuncのスコープ)
alert(foo); // 1
var myFuncNest = function() {
var foo = 2; // ローカルスコープ(myFuncNestのスコープ)
alert(foo); // 2
}();
var myFuncNest2 = function() {
alert(foo); // 1 (スコープチェイン)
}();
}();
eval('var foo = 3; alert(foo);'); // evalスコープ
var foo = 0; // グローバルスコープ
if (true) {
foo = 1; // グローバル変数fooを上書き
for (var i = 2; i < 4; i++) {
foo = i; // グローバル変数fooを上書き
alert(foo); // 2, 3
}
}
var foo = function() {
var bar = function() {
hoge = 4; // グローバルスコープ(varで定義していない)
}();
}();
alert(hoge); // 4 (※varで定義すると「Uncaught ReferenceError: hoge is not defined」)
関数のまとめ
次は、配列の話
Array.lengthは格納されている個数ではなく、インデックス最大値+1。
var arr = new Array(3);
arr[0] = 0;
arr[1] = 1;
arr[100] = 2;
alert(arr.length); // 101
arr["a"] = "a"; // 添え字として無効な値は、arr.lengthに影響を与えない
arr["b"] = "b"; // 添え字として無効な値は、arr.lengthに影響を与えない
alert(arr.length); // 101
arr[101] = 3;
arr[102] = 4;
alert(arr.length); // 103
arr.length = 101; // セットした値の大きさまで切り詰められる(lengthは書き込み可能)
alert(arr[100]) // 2
alert(arr[101]) // undefined
alert(arr["a"]) // "a"はクリアされない
alert(arr["b"]) // "b"はクリアされない
問題
次の文字列の左から3文字目のみを抽出したい場合、
どういった方法がありますか?
var hoge = '1234567890';
配列のちょっと変わった使い方
文字列に対して、配列のようなアクセスが可能。
var hoge = '1234567890';
alert(hoge.charAt(2)) // 3
alert(hoge.substr(2,1)) // 3
alert(hoge[2]); // 3 <=このような指定が可能
小ネタ(console.dir)
console.logはオブジェクトの構造が1階層の場合、展開して表示しないが、
console.dirは階層構造で表示する。(※ただしIEは除く)
var arr = [1,2,3];
arr["a"] = "a";
arr["b"] = "b";
console.log(arr);
[1, 2, 3, a: "a", b: "b"]
console.dir(arr);
▼Array[3]
0: 1
1: 2
2: 3
a: "a"
b: "b"
length: 3
▼__proto__: Array[0]
配列のまとめ
JavaScriptは、
「プロトタイプベースのオブジェクト指向言語」
と呼ばれています。
「オブジェクト指向言語」と聞くと、プログラムに慣れた人であれば、すぐに疑問が沸くはずです。
先生、JavaScriptで「クラス」を作成することはできますか?
この処理を実行すると。。。
「Uncaught SyntaxError: Unexpected reserved word」というエラーが発生します。
つまり、JavaScriptでは「class」、「Class」は予約語として扱われます。
しかし、単語自体は予約語になっているものの、JavaScript自体の機能として、クラスを実装する機能は提供されていません。
では、クラスを作ることはできないのかというと、そうでもなさそうで、ググると「それっぽいもの」が作れるという情報がヒットします。
var class = "class";
var Class = "Class";
new演算子を指定することで、コンストラクタ(クラスっぽいもの)が作れます。
コンストラクタは、新規作成されたオブジェクトを初期化するメソッドです。
function Animal (name) {
this.name = name;
this.sayMyName = function() { ←同じ内容なのにメソッドがオブジェクト毎に生成される。
return 'My name is '+this.name;
};
}
var dog = new Animal('dog');
alert(dog.constructor.name); // Animal
alert(dog.sayMyName()); // My name is dog
var cat = new Animal('cat');
alert(cat.constructor.name); // Animal
alert(cat.sayMyName()); // My name is cat
console.dirで確認すると、オブジェクト毎にsayMyNameが生成されていることがわかります。
それ、プロトタイプで解決できるよ
sayMyNameメソッドをAnimalクラスのプロトタイプに持たせることで、
newしたオブジェクト毎に無駄なsayMyNameメソッドを生成しなくなります。
function Animal (name) {
this.name = name;
}
Animal.prototype.sayMyName = function() {
return 'My name is '+this.name;
}
var dog = new Animal('dog');
alert(dog.sayMyName()); // My name is dog
var cat = new Animal('cat');
alert(cat.sayMyName()); // My name is cat
console.dirで確認すると、prototypeとしてsayMyNameが生成されていることがわかります。
静的メンバ(static変数)
コンストラクタ名の後にドットを繋げて変数名を定義すると、newを必要としない変数、いわゆる、static変数(静的メンバ)として利用可能です。
function Animal () {
}
Animal.STATIC_VALUE = 'static value'; // ←static変数のように使える
Animal.prototype.staticValue = Animal.STATIC_VALUE;
var dog = new Animal();
alert(dog.staticValue); // static value
var cat = new Animal();
alert(cat.staticValue); // static value
あれ、何かに気づきました!?
プロトタイプチェイン
dog.__proto__.sayMyNameではなく、dog.sayMyNameで呼び出せる理由は、
親のオブジェクトを順に辿って、メソッドやプロパティを探す仕組みがあるため。
←sayMyNameがプロトタイプにあった
※__proto__はプロトタイプチェインを実現するためのオブジェクト。
※チェインはundefinedになるまで、最終的にObjectオブジェクトまで辿ります。
※既に説明したスコープチェインと良く似た考え方であることが分かります。
function Animal (name) {
this.name = name;
}
Animal.prototype.sayMyName = function() {
return 'My name is '+this.name;
}
var dog = new Animal('dog');
alert(dog.sayMyName()); // My name is dog
←sayMyNameが自オブジェクトにない
コンストラクタの見栄えが悪いと感じたら
即時関数内にまとめることで、関数ブロックの外が汚れない感じに。
メソッドとして呼び出したい関数はprototypeに列挙したらよい。
var Animal = (function(name) {
function Animal(name){
this.name = name;
}
function sayMyName() {
return 'My name is '+this.name;
}
Animal.prototype = {
constructor: Animal,
sayMyName: sayMyName,
};
return Animal;
})();
var dog = new Animal('dog');
alert(dog.constructor.name); // Animal
alert(dog.sayMyName()); // My name is dog
コンストラクタ/プロトタイプのまとめ
本日、伝えられなかった内容
参考資料
ご清聴ありがとうございました