Perl 初心者がとある JavaScript コードを読むための基礎知識
Perl の最低限の知識がある人向けに、とあるコード1を読むために知っておくと良さそうな JavaScript の基礎的な知識をまとめてみました。
偏った内容なので、これが一般的な JavaScript の基礎知識だとは思わないでください。
アジェンダはこんな感じです。
- はじめに
- イディオム
- 文字列化と数値化
- 整数化
- && と ||
- 練習問題
- primitive values と object references
- 練習問題
- Function#apply()
- 練習問題
- switch 文
- 練習問題
- スコープ
- クロージャ
- 練習問題
- クラスの定義
- 練習問題
- function constructor
- 練習問題
- その他
- XMLHttpRequest
- HTML5 Canvas
- Chrome Developer Tools
- 練習問題解答
- イディオム
- primitive values と object references
- Function#apply()
- switch 文
- スコープ
- クラスの定義
- function constructor
- 最後に
はじめに
JavaScript の言語仕様は Standard ECMA-262 に載っているので、仕様を知りたい場合はこちらを参照してください。
さくっと挙動を確認する場合は Node.js の REPL を使うと便利でしょう。例えば次のようにして Array#splice() の挙動を確認することができます。
$ node
> var a = [1, 2, 3, 4, 5];
undefined
> a.splice(2, 1, 100);
[ 3 ]
> a
[ 1,
2,
100,
4,
5 ]
DOM の仕様を確認したい場合はキーワードに “w3c” を加えてググるとよいでしょう。例えば Canvas の仕様を知りたければ「canvas w3c」でググると HTML Canvas 2D Context のページにすぐ行き着きます。
最近はあまり気にする必要がないかもしれませんが、仕様はあくまで仕様であり、ブラウザの実装は仕様と異なることもあります。
イディオム
JavaScript のイディオムについては nmi.jp でまとめられているのでこちらに目を通すとよいでしょう。
JavaScript イディオム集 » nmi.jp
その中でも、とあるコードを読む上で理解しておくべきイディオムをピックアップしておきます。
文字列化と数値化
JavaScript では Perl と違って数値の足し算も文字列の結合も + 二項演算子で行います。文字列と数値に対して + 二項演算子を使うと、数値が文字列に変換された上で文字列の結合を行います。
この特性を利用すると、空文字列との結合を行うことで数値が文字列に変換されます。
var n = 1;
console.log(n + "" + 2); // => 12
上記の例では、(n + “”) で最初に文字列 “1” を生成し、”1” + 2 を行っているので “12” が表示されます。
また、+ 単項演算子を適用すると文字列は数値化されます。数値化できない文字列 (e.g. ‘a1’) に適用した場合は NaN を生成します。
次の例では、(+str) で数値 2 を生成し、1 + 2 を行っているので 3 が表示されます。
var str = "2";
console.log(1 + (+str)); // => 3
整数化
0 との OR 演算を行うと signed 32 bit integer(-231 ~ 231 - 1 の整数)が生成されます。
231 以上の値や -231 - 1 以下の値に適用すると所望の結果にならないので注意してください。
console.log( 1.3 | 0); // => 1
console.log(-1.3 | 0); // => -1
console.log(Math.pow(2, 31) | 0); // => -2147483648
console.log(-Math.pow(2, 31) - 1 | 0); // => 2147483647
&& と ||
Perl では次のようなコードをよく見かけると思います。
system($cmd) && die 'error';
$dbh = DBI->connect($data_source, $username, $password)
or die $DBI::errstr;
最初のコードは system 関数の返り値が truthy であれば終了し、次のコードは DBI->connect の返り値が falsy であれば終了するコードですね。
JavaScript でもこのような使い方ができます。
function foo(callback, value) {
// 第 1 引数が指定されていればその関数を実行する(関数であることが前提)
// 第 2 引数が falsy であれば 'foo' という文字列を callback 関数に渡す
callback && callback(value || 'foo');
}
foo();
foo(function(value) { console.log(value); }); // => 'foo'
foo(function(value) { console.log(value); }, 'bar'); // => 'bar'
foo(function(value) { console.log(value); }, 0); // => 'foo'
練習問題
次のコードを実行するとコンソールに何が表示されるでしょうか?
function showMsg(option) {
var msg = (option && option.msg) || 'default msg';
console.log(msg);
}
console.log(+"1" + 2); // => ?
console.log(1 + "" + 2); // => ?
console.log(2.3 | 0); // => ?
console.log(-1.9 | 0); // => ?
showMsg(); // => ?
showMsg({}); // => ?
showMsg({ msg: 'message' }); // => ?
showMsg({ msg: null }); // => ?
primitive values と object references
ES 5.1 には primitive values と objects について次のように定義されています。
A primitive value is a member of one of the following built-in types: Undefined, Null, Boolean, Number, and String; an object is a member of the remaining built-in type Object; and a function is a callable object. A function that is associated with an object via a property is a method.
つまり、primitive values は typeof の結果が “undefined”, “boolean”, “number”, “string” になるものと null で、それ以外は objects です。逆に考えると、variable instanceof Object の結果が true になるものが objects で、それ以外が primitive values です(たぶん)。
っで、変数に代入する際、primitive values は変数に値が格納され、objects は参照 (references) が格納されます。
例えば、Perl では次のように配列を作成して別の配列に代入すると配列の内容がコピーされるため、一方の内容を変更しても、もう一方の内容に影響はありません。
my @a = 1..5;
my @b = @a;
say join(', ', @a); # => 1, 2, 3, 4, 5
say join(', ', @b); # => 1, 2, 3, 4, 5
$a[0] = 10;
say join(', ', @a); # => 10, 2, 3, 4, 5
say join(', ', @b); # => 1, 2, 3, 4, 5
ところが、JavaScript では参照をコピーするため、一方の内容を変更するともう一方の内容も変更されます。
var a = [1, 2, 3, 4, 5];
var b = a;
console.log(a.join(', ')); // => 1, 2, 3, 4, 5
console.log(b.join(', ')); // => 1, 2, 3, 4, 5
a[0] = 10;
console.log(a.join(', ')); // => 10, 2, 3, 4, 5
console.log(b.join(', ')); // => 10, 2, 3, 4, 5
これは、次の Perl コードに相当します。
my $a = [1..5];
my $b = $a;
say join(', ', @$a); # => 1, 2, 3, 4, 5
say join(', ', @$b); # => 1, 2, 3, 4, 5
$$a[0] = 10;
say join(', ', @$a); # => 10, 2, 3, 4, 5
say join(', ', @$b); # => 10, 2, 3, 4, 5
primitive values を要素とする配列に限定すると、JavaScript で配列の内容をコピーする場合は Array#slice() や Array#concat() を使うのが常套手段です。
var a = [1, 2, 3, 4, 5];
var b = a.slice();
console.log(a.join(', ')); // => 1, 2, 3, 4, 5
console.log(b.join(', ')); // => 1, 2, 3, 4, 5
a[0] = 10;
console.log(a.join(', ')); // => 10, 2, 3, 4, 5
console.log(b.join(', ')); // => 1, 2, 3, 4, 5
練習問題
次のコードを実行するとコンソールに何が表示されるでしょうか?
function setFoo(obj, value) {
obj.foo = value;
}
var obj1 = { foo: 1 };
var obj2 = obj1;
obj2.foo = 2;
console.log(obj1.foo); // => ?
console.log(obj2.foo); // => ?
setFoo(obj1, 10);
console.log(obj1.foo); // => ?
console.log(obj2.foo); // => ?
obj2 = { foo: 1 };
console.log(obj1.foo); // => ?
console.log(obj2.foo); // => ?
Function#apply()
とあるコードの中での Function#apply() のほぼ全ては関数の引数に配列を渡す用途で使われています。なので、第 1 引数のことはあまり気にせず、Function#apply() は次のような書き換えと理解しておけばよいでしょう。
obj.method(arg1, arg2, ..., argN);
obj.method.apply(obj, [arg1, arg2, ..., argN]);
func(arg1, arg2, ..., argN);
func.apply(null, [arg1, arg2, ..., argN]);
Array#push() の例を以下に示します。
var array1 = [];
array1.push(1, 2, 3);
console.log(array1); // => [ 1, 2, 3 ]
var array2 = [];
array2.push.apply(array2, [1, 2, 3]);
console.log(array2); // => [ 1, 2, 3 ]
Function#apply() がどういう時に便利かというと、速度云々の話もありますが、可変長引数をとる関数を使う時に便利です。
練習問題
任意個の引数の和を計算する sum という関数があります。
function sum() {
var sum = 0;
for (var i = 0; i < arguments.length; ++i) {
sum += arguments[i];
}
return sum;
}
console.log(sum(1, 2, 3)); // => 6
また、連続した整数の配列を返す seq という関数があります。
function seq(first, last) {
var ret = [];
for (var i = first; i <= last; ++i) {
ret.push(i);
}
return ret;
}
console.log(seq(1, 3)); // => [ 1, 2, 3 ]
この2つの関数を使って 1 ~ 5 の整数の和と 1 ~ 100 の整数の和をコンソールに出力するコードを書いてください。
switch 文
Perl には組み込みレベルで switch 文に相当する機能がありませんが2、JavaScript にはあります。
よく使われる書き方は次のような感じです。
switch (expression) {
case expression1:
// some codes
break;
case expression2:
// some codes
break;
...
default:
// some codes
break;
}
(expression === expression1)、(expression === expression2)、… というように上から順に case 節の式との比較を === 演算子で行い、true になった節のコードブロックを実行します。一度 true になったら次の case 節のコードブロックも評価されるため、各 case 節では break 文で switch 文を抜けるのが一般的です。
例えば、Perl で次のような分岐をするコードがあるとします。
sub foo {
my $n = shift;
if ($n == 1) {
say '1';
} elsif ($n == 2) {
say '2';
} else {
say 'others';
}
}
foo(1); # => 1
foo(2); # => 2
foo(3); # => others
JavaScript で switch 文を使うと次のように記述できます。各 case 節
function foo(n) {
switch (n) {
case 1:
console.log('1');
break;
case 2:
console.log('2');
break;
default:
console.log('others');
break;
}
}
foo(1); // => 1
foo(2); // => 2
foo(3); // => others
また、Perl で次のようなコードがあるとします。
sub foo {
my $n = shift;
if ($n == 1 || $n == 2) {
say '1 or 2';
} else {
say 'others';
}
}
foo(1); # => 1 or 2
foo(2); # => 1 or 2
foo(3); # => others
これは switch 文を使うと次のように書けます。
function foo(n) {
switch (n) {
case 1:
case 2:
console.log('1 or 2');
break;
default:
console.log('others');
break;
}
}
foo(1); // => 1 or 2
foo(2); // => 1 or 2
foo(3); // => others
上記の例は割りと使われるのではないかと思いますが、次のように case 節で何らかの処理をした上で敢えて break せず次の case 節の処理を行うというようなこともできます。個人的にオススメはしませんが。
どうしてもこのような書き方をしたい場合、何も知らない開発者でもわかるように、敢えて break していないことと次に処理されるべき case 節をコメントで明示しておくべきだと思います。
function factorial(n) {
var ret = 1;
switch (n) {
default:
console.warn('not supported');
return -1;
case 3:
ret *= 3;
case 2:
ret *= 2;
case 1:
case 0:
}
return ret;
}
console.log(factorial(0)); // => 1
console.log(factorial(1)); // => 1
console.log(factorial(2)); // => 2
console.log(factorial(3)); // => 6
console.log(factorial(4)); // => -1
switch 文の細かい仕様を知りたければ仕様の The switch statement のところを読んでください。
練習問題
次のコードに case 節を追加して、引数 n が 3 で割り切れる場合は “Fizz”、5 で割り切れる場合は “Buzz”、両者で割り切れる場合は “FizzBuzz”、それ以外はそのままの値を返す関数を完成させてください。
function fizzbuzz(n) {
switch (n % 15) {
}
return n;
}
for (var i = 1; i <= 15; ++i) {
console.log(fizzbuzz(i));
}
出力結果は次のようになります。
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
スコープ
Perl はブロックスコープですが、JavaScript は関数スコープです。
例えば、次のコードは 1 を出力する Perl コードです。
my $foo = 1;
{
my $foo = 2;
}
say $foo; # => 1
JavaScript で同じように記述すると 2 が出力されます。
var foo = 1;
{
var foo = 2;
}
console.log(foo); // => 2
これを避けるため、JavaScript では無名関数 (anonymous functions) を作成し、すぐに実行することでスコープを作成するのが常套手段です。
var foo = 1;
(function() {
var foo = 2;
})();
console.log(foo); // => 1
他のデベロッパーにも使ってもらうライブラリを作成する際、何でもかんでもグローバル空間に変数を定義してしまうと意図せずデベロッパーが定義した変数を上書きしてしまう可能性があります。
よって、ライブラリを作成する際は全体を無名関数で囲むのが一般的です。
例えば、jQuery も次のように全体が無名関数で囲まれています。
/*!
* jQuery JavaScript Library v2.0.3
* http://jquery.com/
*
* Includes Sizzle.js
* http://sizzlejs.com/
*
* Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors
* Released under the MIT license
* http://jquery.org/license
*
* Date: 2013-07-03T13:30Z
*/
(function( window, undefined ) {
...
})( window );
さっきと違って window と undefined という引数を受け取っていますが、おそらく window という変数が途中で全く別のオブジェクト(値や参照)に書き換えられる場合を考慮して、実行時のものを保存しているんだと思います。
第 2 引数には何も指定せず実行しているのでどのタイミングで実行しても必ず undefined が渡ります。
クロージャ
Wikipedia によれば、クロージャは
プログラミング言語における関数オブジェクトの一種。いくつかの言語ではラムダ式や無名関数で実現している。引数以外の変数を実行時の環境ではなく、自身が定義された環境(静的スコープ)において解決することを特徴とする。
らしいです。
Perl と JavaScript に限定すれば、「親のスコープの変数を read/write できる」ぐらいに捉えておけばよいかと思います。3
親のスコープとは何かですが、構文上外側に定義されている関数のスコープ(静的スコープ)です。
次の例では、関数 h からは外側の関数である関数 f の foo が参照できますが、関数 g はグローバル空間に定義されているため、関数 f の foo が参照できずグローバル空間の foo を参照することになります。
var foo = 1;
function f() {
var foo = 2;
function h() {
console.log(foo);
}
g(); // => 1
h(); // => 2
}
function g() {
console.log(foo);
}
f();
次はクロージャの例でよく用いられるカウンターの Perl コードです。
無名関数を定義してすぐ実行しているため、$counter には sub { say ++$count; } という関数が入っています。ただし、無名関数内で定義した $count にアクセスできるのがポイントです。
my $counter = sub {
my $count = 0;
return sub {
say ++$count;
};
}->();
$counter->(); # => 1
$counter->(); # => 2
$counter->(); # => 3
JavaScript で同様のコードを書くと次のようになります。
counter には function() { return ++count; } という関数が入っています。
var counter = (function() {
var count = 0;
return function() {
console.log(++count);
};
})();
counter(); // => 1
counter(); // => 2
counter(); // => 3
練習問題
次のコードを実行するとコンソールに何が表示されるでしょうか?
var foo = 1;
function f1() {
var foo = 2;
function h() {
console.log(foo);
}
g(); // => ?
h(); // => ?
}
function f2() {
foo = 2;
function h() {
console.log(foo);
}
g(); // => ?
h(); // => ?
}
function g() {
console.log(foo);
}
f1();
f2();
(function(foo) {
foo = -1;
console.log(foo); // => ?
})(foo);
console.log(foo); // => ?
クラスの定義
JavaScript はプロトタイプベースのオブジェクト指向ですが、とりあえずプロトタイプチェーン云々については知らなくて大丈夫です。
素の Perl でクラスを定義する場合は次のようにパッケージ名にクラス名を割り当て、コンストラクタに相当する new というメソッドを定義するのが一般的かと思います。
インスタンスメソッドを定義する場合はパッケージ内に関数を定義し、インスタンス経由でその関数(メソッド)をコールします。
インスタンスメソッドの第1引数にはインスタンス自身が渡り、インスタンス変数を参照する際はこのオブジェクトを経由してアクセスします。
package Person;
sub new {
my ($class, $name) = @_;
my $self = {
_name => $name,
};
return bless $self, $class;
}
sub showName {
my $self = shift;
say $self->{'_name'};
}
package main;
my $me = Person->new('arabiki');
$me->showName(); # arabiki
JavaScript で同様のコードを記述すると次のようになります。
コンストラクタに相当する関数 Person を定義し、インスタンスメソッドは Person.prototype にプロパティを追加する形で定義します。
基本的にはメソッド内で this にアクセスするとインスタンス自身にアクセスすることになり、インスタンス変数を参照する際は this を経由してアクセスします。
var Person = function(name) {
this._name = name;
};
Person.prototype.showName = function() {
console.log(this._name);
};
var me = new Person('arabiki');
me.showName(); // arabiki
メソッド内で this にアクセスしてもインスタンス自身にアクセスできないケースがいくつかあります。
とりあえず次のように、メソッド内で別の関数を定義し、その中で this を参照するケースだけ覚えておけば大丈夫でしょう。
var Person = function(name) {
this._name = name;
};
Person.prototype.showNameAsync = function() {
setTimeout(function() {
console.log(this._name); // this が Window オブジェクトになってる!
});
};
var me = new Person('arabiki');
me.showNameAsync(); // => undefined
setTimeout 関数の場合、特別なことをしない限りは第 1 引数 に指定した関数内で this を参照すると Window オブジェクトを参照することになります。4
cf. 6.3 Timers ― HTML
よって、次のように this の内容を別の変数に退避するのが古い慣習です。
var Person = function(name) {
this._name = name;
};
Person.prototype.showNameAsync = function() {
var that = this;
setTimeout(function() {
console.log(that._name);
});
};
var me = new Person('arabiki');
me.showNameAsync(); // => arabiki
最近だと Function#bind() を使って thisArg をセットするのが一般的なんじゃないかと思います。
var Person = function(name) {
this._name = name;
};
Person.prototype.showNameAsync = function() {
setTimeout(function() {
console.log(this._name);
}.bind(this));
};
var me = new Person('arabiki');
me.showNameAsync(); // => arabiki
練習問題
次のコードを実行するとコンソールに何が表示されるでしょうか?strict mode ではないものとします。
var A = function(foo, bar, baz) {
this._foo = foo;
this._bar = bar;
this._baz = baz;
};
A.prototype.showFoo = function() {
console.log(this._foo);
};
A.prototype.showBar = function() {
(function() {
console.log(this._bar);
})();
};
A.prototype.showBaz = function() {
(function(that) {
console.log(that._baz);
})(this);
};
var a = new A('hoge', 'fuga', 'piyo');
a.showFoo(); // => ?
a.showBar(); // => ?
a.showBaz(); // => ?
function constructor
function constructor (new Function) は次のような形式の関数で、動的に関数を作成するのに使われます。例えば、汎用的な処理を行う関数であれば、引数の値によって様々な分岐を通ることになりますが、同じ引数で何度も呼ばれるのであればその引数に特化した関数を動的に作った方が処理速度向上が期待できます。
new Function(p1, p2, …, pn, body)
これは次と等価です。
new Function([p1, p2, …, pn].join(), body)
p1, p2, …, pn は作成する関数のそれぞれ第 1 引数、第 2 引数、…、第 n 引数の名前で、body は関数の中身を表す文字列です。
例えば、new Function(‘a’, ‘b’, ‘console.log(a); console.log(b);’) を実行すると次のような関数が得られます。
function(a, b) {
console.log(a);
console.log(b);
}
仕様の new Function の項にあるように、function constructor で作成した関数はグローバル空間に定義した関数と同様のスコープを持つことになります。
なので、変数の参照コストが下がり、処理速度が向上するという話も聞きますが、単純な例に対して jsPerf で比較してもよくわかりませんでした・・・。
cf. http://jsperf.com/function-constructor-vs-functions
次のコードは foo 関数がグローバル空間に定義されていないため、参照エラーになります。
(function() {
function foo() {
console.log('foo');
}
new Function('foo()')(); // => ReferenceError: foo is not defined
})();
これが意図したとおりに動くようにするためには foo 関数をグローバル空間に定義するか、引数として渡す必要があります。
(function() {
function foo() {
console.log('foo');
}
new Function('foo', 'foo()')(foo); // => foo
})();
(function() {
// new Function('return this')() はブラウザの場合 window、Node.js の場合 global
new Function('return this')().foo = function() {
console.log('foo');
};
new Function('foo()')(); // => foo
})();
練習問題
次のコードを実行することによって動的に作成される drawLines 関数を関数定義によって静的に作成するとどうなりますか?
function createDrawLines(points) {
var body = 'ctx.beginPath();';
body += 'ctx.moveTo(' + points[0].join() + ');';
for (var i = 1; i < points.length; ++i) {
body += 'ctx.lineTo(' + points[i].join() + ');';
}
body += 'ctx.stroke();';
return new Function('ctx', body);
}
var points = [
[100, 10],
[124, 68],
[186, 72],
[138, 112],
[153, 173],
[100, 140],
[ 47, 173],
[ 62, 112],
[ 14, 72],
[ 76, 68],
[100, 10]
];
var drawLines = createDrawLines(points);
その他
XMLHttpRequest
JavaScript から HTTP リクエストを送る方法として XMLHttpRequest (XHR) があります。
XHR Level 1 で GET リクエストを送る基本的な形は次のとおりです。
var xhr = new XMLHttpRequest();
xhr.open('GET', location.href);
xhr.onreadystatechange = function() {
if (this.readyState !== 4) {
return;
}
console.log(this.responseText);
};
xhr.send();
open() メソッドの第 2 引数には URL を指定しますが、上記の例では実行ページの HTML ファイルの URL を指定しています。
onreadystatechange handler は readyState プロパティが変わった時と、データをロード中(readyState が 3 の間)に定期的に呼ばれます。仕様には readyState が変わった時に呼ばれるとしか書かれていない気がしますが。
なお、Level 2 では onload handler や onerror handler も定義されており、読み込み完了後の処理のみを記述するのであればこちらで簡潔に記述することもできます。
readyState は次の値を取ります。cf. http://www.w3.org/TR/XMLHttpRequest1/#states
状態 | 値 | ざっくりした意味 |
---|---|---|
UNSENT | 0 | インスタンスが作成された |
OPENED | 1 | open() メソッドが正常に実行された |
HEADERS_RECEIVED | 2 | HTTP リクエストに対してレスポンスヘッダを受け取った |
LOADING | 3 | レスポンスボディを受け取っている最中 |
DONE | 4 | 全てのレスポンスの取得が完了した |
XHR Level 1 の場合、バイナリの取得が少し厄介です。
例えば、画像データを取得して data URI scheme から HTMLImageElement を作成するには次のように MIME type の charset に “x-user-defined” をセットし、取得したデータに対しても修正を加える必要があります。5
var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/png');
xhr.overrideMimeType('text/plain; charset=x-user-defined');
xhr.onreadystatechange = function() {
if (this.readyState !== 4) {
return;
}
var binary = '';
var responseText = this.responseText;
for (var i = 0, size = responseText.length; i < size; ++i) {
// 0x80 ~ 0xFF のバイトは U+F780 ~ U+F7FF に割り当てられるので
// U+0080 ~ U+00FF に戻す
// cf. http://javascript.g.hatena.ne.jp/edvakf/20100607/1275931930
binary += String.fromCharCode(responseText.charCodeAt(i) & 0xff);
}
var img = new Image();
img.src = 'data:image/png;base64,' + btoa(binary);
document.body.appendChild(img);
};
xhr.send();
HTML5 Canvas
ソースコードを呼んでいてわからない API の内容を随時調べるのでもよいですが、ある程度体系立って勉強しておくと良いと思います。
例えば次のチュートリアルが参考になると思います。
canvas チュートリアル - Web developer guide | MDN
Chrome Developer Tools
Chrome Developer Tools を使いこなせると JavaScript の開発効率が格段に上がります。次の 2 つの内容に目を通せば事足りると思います。
Chrome Developer Tools でググれば素晴らしい資料がたくさんヒットするので、物足りない場合はそれらの資料にも目を通してみるとよいでしょう。
練習問題解答
イディオム
function showMsg(option) {
var msg = (option && option.msg) || 'default msg';
console.log(msg);
}
console.log(+"1" + 2); // => 3
console.log(1 + "" + 2); // => 12
console.log(2.3 | 0); // => 2
console.log(-1.9 | 0); // => -1
showMsg(); // => default msg
showMsg({}); // => default msg
showMsg({ msg: 'message' }); // => message
showMsg({ msg: null }); // => default msg
primitive values と object references
function setFoo(obj, value) {
obj.foo = value;
}
var obj1 = { foo: 1 };
var obj2 = obj1;
obj2.foo = 2;
console.log(obj1.foo); // => 2
console.log(obj2.foo); // => 2
setFoo(obj1, 10);
console.log(obj1.foo); // => 10
console.log(obj2.foo); // => 10
obj2 = { foo: 1 };
console.log(obj1.foo); // => 10
console.log(obj2.foo); // => 1
Function#apply()
function sum() {
var sum = 0;
for (var i = 0; i < arguments.length; ++i) {
sum += arguments[i];
}
return sum;
}
function seq(first, last) {
var ret = [];
for (var i = first; i <= last; ++i) {
ret.push(i);
}
return ret;
}
console.log(sum.apply(null, seq(1, 5))); // => 15
console.log(sum.apply(null, seq(1, 100))); // => 5050
switch 文
function fizzbuzz(n) {
switch (n % 15) {
case 0:
return 'FizzBuzz';
case 3:
case 6:
case 9:
case 12:
return 'Fizz';
case 5:
case 10:
return 'Buzz';
}
return n;
}
for (var i = 1; i <= 15; ++i) {
console.log(fizzbuzz(i));
}
スコープ
var foo = 1;
function f1() {
var foo = 2;
function h() {
console.log(foo);
}
g(); // => 1
h(); // => 2
}
function f2() {
foo = 2;
function h() {
console.log(foo);
}
g(); // => 2
h(); // => 2
}
function g() {
console.log(foo);
}
f1();
f2();
(function(foo) {
foo = -1;
console.log(foo); // => -1
})(foo);
console.log(foo); // => 2
クラスの定義
var A = function(foo, bar, baz) {
this._foo = foo;
this._bar = bar;
this._baz = baz;
};
A.prototype.showFoo = function() {
console.log(this._foo);
};
A.prototype.showBar = function() {
(function() {
console.log(this._bar);
})();
};
A.prototype.showBaz = function() {
(function(that) {
console.log(that._baz);
})(this);
};
var a = new A('hoge', 'fuga', 'piyo');
a.showFoo(); // => hoge
a.showBar(); // => undefined
a.showBaz(); // => piyo
function constructor
function drawLines(ctx) {
ctx.beginPath();
ctx.moveTo(100, 10);
ctx.lineTo(124, 68);
ctx.lineTo(186, 72);
ctx.lineTo(138, 112);
ctx.lineTo(153, 173);
ctx.lineTo(100, 140);
ctx.lineTo( 47, 173);
ctx.lineTo( 62, 112);
ctx.lineTo( 14, 72);
ctx.lineTo( 76, 68);
ctx.lineTo(100, 10);
ctx.stroke();
}
最後に
以上、最低限必要そうな知識をざっくり紹介しました。もうちょっと勉強したい人は以下の資料などにも目を通してみるとよいと思います!
- スクリプト言語:JavaScriptのイロハ - builder by ZDNet Japan
- JavaScript 「再」入門 - JavaScript | MDN
- 最強オブジェクト指向言語 JavaScript 再入門!
- JavaScript And Keywords
- JavaScript And Debug
あと、JavaScript: The Good Parts はさくっと読めて個人的に好きです。通な人は付録だけ読めばよいと言いますが、本編も初心者には良い内容だと思います。