C++プログラマやJavaプログラマにとってJavaScriptの分かりづらいところを解説。
スコープ
C++では{と}で括ったブロックがスコープになるがJavaScriptではスコープにならない。
1 2 3 4 5 |
var a = 10; { var a = 20; } console.log(a); // 20 |
2つのaは同じaになる。
関数はスコープを持つ
1 2 3 4 5 6 7 |
var a = 10; function func() { var a = 20; } console.log(a); // 10 func(); console.log(a); // 10 |
上のコードでfunction内のvarを外すと同じaを参照する。
1 2 3 4 5 6 7 |
var a = 10; function func() { a = 20; } console.log(a); // 10 func(); console.log(a); // 20 |
コンパイルと実行
JavaScriptの実行は2段階で行われる。最初は変数を割り当て、次に実行する。割当時にはどのスコープにどの変数があるかを確定する。var
による変数の宣言や変数への書き込みは割当であり、変数の読み込みは割当処理ではスルーされる。
1 2 3 4 5 6 7 8 9 |
var a; // assign var b; // assign c = 100; // assin console.log(d); // error /* Exception: ReferenceError: d is not defined @Scratchpad/1:4:1 */ |
上記のコードではa,b,c
は割当時に割り当てられるが、d
は割り当てられないので実行時にエラーになる。c
は書き込み処理なので割当時に割り当てられる。c
でもエラーにしたい場合はファイルの最初に"use strict"
(ダブルクオーテーション含む)と記述する。
オブジェクトと辞書
オブジェクトと辞書は同じ。
1 2 3 4 5 6 |
var a = {}; var b = new Object(); console.log(typeof a); // object console.log(typeof b); // object console.log(typeof Object); // function |
Object
は実際は関数である(後述)。
関数とオブジェクト
JavaScriptにはクラスがない。クラスのようなものをつくりたいときは関数で行う。
1 2 3 4 5 6 7 8 9 10 |
function func() { this.a = 10; this.b = "aaa"; } var f = new func(); console.log(f); // Object { a: 10, b: "aaa" } console.log(typeof f); // object |
new
を使うと、空のオブジェクトをつくりそれを暗黙の引数として渡す。関数内ではthis
として参照しそのオブジェクトを返す。
this
C++と同じようにオブジェクトのメンバ関数をコールすると暗黙の引数this
が渡される。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
function func() { // public this.a = 10; // private var c = 555; this.geta = function() { return this.a; } this.seta = function(val) { return this.a = val; } this.getc = function() { return c; } this.setc = function(val) { c = val; } } var f = new func(); // Access public var console.log(f.a); // 10 f.a = 1000; console.log(f.a); // 1000 console.log(f.geta()) // 1000 // Access private var console.log(f.c); // undefined f.c = 200; console.log(f.c); // 200 console.log(f.getc()); // 555 |
geta
やseta
ではthis
を参照しているので関数を呼び出したオブジェクトがthis
になりその値が変わる。
getc
やsetc
ではthis
を参照していないのでオブジェクトは変更されない。そもそもこのc
はオブジェクトに含まれてはおらず関数func
が持っている変数である。getc
やsetc
はそのc
を参照するがstaticでもなく、new
でオブジェクトがつくられるごとにつくられそれが参照されている。よってc
はオブジェクトがもつprivate変数のように扱える。
ここでnew
を使わずにfunc
をコールするとthis
はWindow
オブジェクトになる。このオブジェクトは実行環境で変わり、ブラウザ上ではWindow
オブジェクトになる。グローバルに宣言された変数はこのWindow
オブジェクトのプロパティになっている。
1 2 |
var a = 10; console.log(this.a); // 10 |
prototypeと__proto__
関数はprototype
メンバとしてObject
をもつ。この関数でnew
してつくられたオブジェクトは__proto__
メンバをもち同じObject
をもつ。
1 2 3 4 5 |
function func(){} var f = new func(); console.log(typeof func.prototype); // object console.log(func.prototype === f.__proto__); // true |
オブジェクトのプロパティを参照するとき、そのプロパティが見つからなかった場合は、__proto__
から探す。よって関数のprototype
にあるプロパティを持たしておくと、オブジェクトで共通のプロパティを持つことができる。
1 2 3 4 5 6 7 8 9 10 |
function func(){} func.prototype.myHello = function(){ console.log("Hello!!"); } var f = new func(); f.myHello(); // Hello!! var f2 = new func(); f2.myHello(); // Hello!! |
prototype
自体もオブジェクトでObject
は関数なのでprototype
も__proto__
をもつ。よってあるプロパティを参照してそれが見つからないとき__proto__
から見つけようとするがそれもないときは__proto__.__proto__
から見つけようとする。よってObject.prototype
にプロパティを設定しておくとすべてのオブジェクトで共通のプロパティを持つことができる。
1 2 3 4 5 6 7 8 9 10 11 12 |
Object.prototype.myRootFunc = function(){ console.log("myRootFunc!!"); } function func(){} function func2(){} var f = new func(); f.myRootFunc(); // myRootFunc!! var f2 = new func(); f2.myRootFunc(); // myRootFunc!! |
Object.prototype
の__proto__
はnull
になっているので無限ループにはならない。