JavaScript Gardenの読書メモ
■感想
まず発見したときにサイドバーのデザインが綺麗だなと思った。また文章内でちゃんと内部リンクが貼ってあってとても見やすい作りになっている。内容もそこまで難しい書き方はしてないので何とか読める。またコード(or 文字)で書くようにしているのかは知らないけど、画像を一切使わずにprototypeとかをちゃんと説明できていて凄いと思った。
かなり仕様も囓ってる感じの人が書いてて、noteにES5の時についてなども触れられていてかなり細かいとこも書かれていた。難易度的にはJavaScript Patternsと同じくらいだと思うけど、とてもいい文章をWebで公開してくれているので是非読んでおくべきだと思う。
著者であるIvo Wetzel (Writing)とZhang Yi Jiang (Design)に感謝を。
- JavaScript Garden
- http://bonsaiden.github.com/JavaScript-Garden/
■Objects
JavaScriptではnullとundefined以外はオブジェクトのように動いてる。
false.toString() // 'false'
[1, 2, 3].toString(); // '1,2,3'
function Foo(){}
Foo.bar = 1;
Foo.bar; // 1
ただし、数値だけは2.toString(); // raises SyntaxErrorのようになる。それは.を小数点と理解してしまうため。
これを回避する方法はいくつかある
2..toString(); // 2番目の.がドット演算子として認識される。 2 .toString(); // note the space (2).toString(); // 2 is evaluated first二番目は数値とドットの間にスペースを空けるのが正解。
Objects as a data type
JavaScriptのオブジェクトはハッシュマップ。波括弧{}はプレーンなオブジェクトを作り、それはObject.prototypeから継承している。
まだhasOwnPropertyでかかるもの(自分自身のプロパティ)は持ってない。
Accessing properties
var foo = {name: 'Kitten'}
foo.name; // ドット表記法
foo['name']; // ブラケット表記法
var get = 'name';
foo[get]; // kitten - ブラケット表記法は変数を使える
foo.1234; // SyntaxError
foo['1234']; // works
オブジェクトのプロパティのアクセス方法は2つあって、ドット表記法かブラケット表記法が使える。2つの違いとしてブラケット表記法は動的に設定できることと、構文エラーとなってしまう名前も使用できるDeleting properties
オブジェクトのプロパティを削除する唯一の方法はdelete演算子を使用すること。プロパティにundefined や null を入れてもキーは削除されず、値が変わるだけです。(GCとかで消えるかもしれないけど)
この二つの違いがわかる例
barにundefined, fooにnullを入れているがkeyが残っているのでbar undefined, foo nullというアウトプットになる。bazはdeleteされているのでそもそも出てこなくなる。
var obj = {
bar: 1,
foo: 2,
baz: 3
};
obj.bar = undefined;
obj.foo = null;
delete obj.baz;
for(var i in obj) {
if (obj.hasOwnProperty(i)) {
console.log(i, '' + obj[i]);
}
}
Notation of keys
JavaScriptの予約語をキーとして使うとエラーになる。(ES5以降とかブラウザによる)まあどっちにしても予約語をキーに使わない方がいい。strict modeだと予約語も増える
var test = {
'case': 'I am a keyword so I must be notated as a string',
delete: 'I am a keyword too so me' // raises SyntaxError
};
■The prototype
JavaScriptではprototypicalが使われています。プロトタイプ継承はclassic model(クラス)より強力。二つの違いを示すものとしてプロトタイプチェーンと呼ばれる継承があります。
function Foo() {
this.value = 42;
}
Foo.prototype = {
method: function() {}
};
function Bar() {}
// Set Bar's prototype to the prototype object of Foo
Bar.prototype = Foo.prototype;
var test = new Bar() // create a new bar instance
// The resulting プロトタイプチェーン
Object.prototype: {toString: ... /* etc. */};
Foo.prototype: {method: ...};
Bar.prototype: Foo.prototype
Bar.method()
Property lookup
オブジェクトのプロパティにアクセスしたときにJavaScriptではその名前が見つかるまでプロトタイプチェーンを探索する。最後のObject.prototypeまでいって見つからなかったときはundefinedを返します。■The prototype property
prototypeプロパティはjs側によってプロトタイプチェーンを構築するために使われますが、こちら側で任意の値を設定することも可能です。prototypeプロパティにプリミティブな値を入れた場合は単純に無視されます。
function Foo() {}
Foo.prototype = 1; // no effect
一方オブジェクトを割り当てた場合は動的にプロトタイプチェーンを構築できます。
function Foo() {
this.value = 42;
}
Foo.prototype = {
method: function() {}
};
function Bar() {}
Bar.prototype = new Foo();
var boo = new Bar();
// Resulting prototype chain
Object.prototype: {toString: ... /* etc. */};
Foo.prototype: {method: ...};
[Foo Instance]: {value: 42};
Bar.prototype: [Foo Instance]
Bar.method()
このようにBar.prototypeはFooのインスタンスを示すようになります。Performance
プロトタイプチェーンの特性上、パフォーマンスに影響を与える可能性もあります。また存在しないプロパティにアクセス使用とした場合は常にプロトタイプチェーンを最後まで探索してしまうでしょう。また列挙を行う際にもプロトタイプチェーンを探索してしまう。(いわゆるプロトタイプ汚染されていると困る)
■Extension of native prototypes
頻繁にある間違いはObject.prototypeを拡張してしまう事です。このテクニックはMonkey patchと呼ばれ、カプセル化を壊します。
Object.prototypeを拡張していい、唯一の場合はJavaScriptの新しいバージョンで追加された機能をバックポートする際だけです。
これを行っていい条件は下記にでも
ネイティブのprototypeを拡張していいのは新しいJavaScript機能との互換性がある場合のみにしておくべきです。
■hasOwnProperty
hasOwnPropertyはプロトタイプチェーンを探索せずにそのオブジェクトが持っているプロパティなのかをチェックするメソッドです。
// Poisoning Object.prototype
Object.prototype.bar = 1;
var foo = {goo: undefined};
foo.bar; // 1
'bar' in foo; // true - こっちはプロトタイプチェーンを探索する
// foo自体が持っているプロパティかどうか
foo.hasOwnProperty('bar'); // false
foo.hasOwnProperty('goo'); // true
いわゆるfor-inループと組み合わせて、プロトタイプ汚染されていた場合も列挙できるように使われていたりする。パフォーマンスについては下記
■Functions
function declaration - 関数宣言
function foo() {}
function expression - 関数式
var foo = function() {};
二つの違いはパースのステージで定義されてるのか、実行のステージで定義されるのかである。Named function expression
名前付き関数式での名前はローカルスコープのみで利用可能です。外から呼び出そうとした場合はエラーになります(しかしIEでは漏れてる…
var foo = function bar() {
bar(); // Works
}
bar(); // ReferenceError
Order of parsing
varステートメントは関数宣言よりも前にパースされるため、下のような場合は一つ前にオーバーライドされる。
function foo() {}
var foo;
foo; // [function foo]
var foo = 2;
foo; // 2
■How this works
JavaScriptでのthisは他の言語とは違うコンセプトを持っています。そこで5つの違いをあげてる。//コメントはthisが示しているオブジェクト
this;// global object
foo();// global object - strict modeではundefinedに置き換わる
test.foo(); // test
new foo();// その場で作られたObject
// applyやcallではthisを指定できる
function foo(a, b, c) {}
var bar = {};
foo.apply(bar, [1, 2, 3]); // array will expand to the below
foo.call(bar, 1, 2, 3); // results in a = 1, b = 2, c = 3
Common pitfalls
さきほども言ったようにJavaScriptではthisの意味が他の言語と異なるため、実装方法にも気をつける必要がある。
Foo.method = function() {
function test() {
// this は global object を示す
}
test();// thisはFooであることを期待する
}
このようによく間違った書き方をしてしまう事があります。なので次のようにthatにthisを入れて、クロージャーにすることでこれを解決できます。
Foo.method = function() {
var that = this;
function test() {
// that を this の代わりに使う
}
test();
}
Assigning methods
以下のように変数に代入してしまうと、thisがsomeObjectを示すということはなくなってしまいます。var test = someObject.methodTest; test();// thisはglobal object
■Closures and references
JavaScriptの強力な機能の一つであるクロージャーについて
function Counter(start) {
var count = start;
return {
increment: function() {
count++;
},
get: function() {
return count;
}
}
}
var foo = Counter(4);
foo.increment();
foo.get(); // 5
上記のgetやincrementはCounterスコープへの参照を持っていて、count変数にいつでもアクセスできる。Why private variables work
count変数には外からアクセスできません。
function Counter(start) {
var count = start;
return {
increment: function() {
count++;
},
get: function() {
return count;
}
}
}
var foo = Counter(4);
foo.increment();
foo.get(); // 5
var foo = new Counter(4);
foo.hack = function() {
count = 1337;// グローバルのcountに書き込んでいる
};
foo.hack();
foo.get(); // 5
上記のコードでもcountは変更できません。それはfoo.hackがCounterのスコープに定義されたわけではないからです。Closures inside loops
よくあるループでのミスこれを目的通りの振る舞いにするためにはiのコピーを持つ必要があります。
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i); // 10がぞろぞろ
}, 1000);
}
Avoiding the reference problem
先ほどの失敗を踏まえて、iのコピーをそれぞれ持たせてみる。これを簡単に行う方法として無名関数で囲む方法があります。
for(var i = 0; i < 10; i++) {
(function(e) {
setTimeout(function() {
console.log(e);
}, 1000);
})(i);
}
無名関数が呼ばれたときにiの値はパラーメータeとしてコピーされて受け取れます。似たような書き方として以下のようにも書く事が可能です
for(var i = 0; i < 10; i++) {
setTimeout((function(e) {
return function() {
console.log(e);
}
})(i), 1000)
}
■The arguments object
すべての関数スコープには特別な値としてargumentsがあります。argumentsは引数がありlengthプロパティもあるため配列のように見えるが、じっさいにはArray.prototypeから継承しているわけではなくオブジェクトです。
そのためpush, pop, sliceのような配列のメソッドを使うことはそのままではできません。
Converting to an array
arguments(Array likeオブジェクト)をArrayオブジェクトにする例。しかしこの変換はとても遅いので性能を気にする場面では避けるべきかもしれません。
Array.prototype.slice.call(arguments);パフォーマンス比較(concatは無視)
Modification "magic"
argumentsオブジェクトは書き換えができてしまうため下のような事が行える。ただし、strict modeではこういう事はできなくなった。
function foo(a, b, c) {
arguments[0] = 2;
a; // 2
b = 4;
arguments[1]; // 4
var d = c;
d = 9;
c; // 3
}
foo(1, 2, 3);
Performance myths and truths
arguments.calleeはパフォーマンスがよろしくないため、できるだけ関数名などを使うようにするべき。またarguments.calleeはコンテキスト依存となるため、カプセル化を壊してしまう。
■Scopes and namespaces
JavaScriptでは{...}でスコープができるわけではなく、あくまで関数スコープである。
function test() { // a scope
for(var i = 0; i < 10; i++) { // スコープじゃない
// count
}
console.log(i); // 10
}
JavaScriptでもはっきりと区別できるものがあります。それはグローバルスコープです。すべてが一つのグローバル名前空間を共有しているからです。
The bane of global variables
script A,Bはそれぞれ意味が異なる。// script A foo = '42';// グローバルスコープにfooを定義 // それぞれ違う // script B var foo = '42';// ローカルスコープにfooを定義またglobalのfooをlocalで上書きすることができてしまう。
これは短いコードならまだわかるが、コード量が増えるとどこで上書きされているかの発見は困難となるためバグの原因になりやすい。
// global scope
var foo = 42;
function test() {
// local scope
foo = 21;// varを使ってない
}
test();
foo; // 21
そのため、外側のスコープに対して影響を与える事を考えている場合以外はvarステートメントを省略することは控えるべきである。Name resolution order
グローバルも含めて、thisキーワードは"現在のオブジェクト"を参照している。関数スコープはargumentsが定義されており、それは関数に渡された引数を含んでいる。
たとえば関数スコープの内側でfooという変数にアクセスしたときJavaScriptではどのようにルックアップしていくかを見ていきましょう。
- var fooステートメントが現在のスコープにあるならそれを使う
- 関数のパラーメータにfooがあるならそれを使う
- 関数自体がfooという名前ならそれを使う
- 外のスコープに行き、1から繰り返す
Namespaces
グローバル名前空間で変数定義などを行うと、名前の衝突などが起こることもあるため、その問題を回避するために無名関数で囲うという方法があります。
(function() {
// a self contained "namespace"
window.foo = function() {
// an exposed closure
};
})(); // execute the function immediately
この無名関数の即時実行は無名関数を()で囲い式にすることで実行することができています。
( // evaluate the function inside the paranthesis
function() {}
) // and return the function object
() // call the result of the evaluation
これは先頭がfunctionキーワードだと関数宣言と見なされてしまうための回避方法としてよく使われていて、他にも下のような書き方ができます。(上のやり方が好ましい気もするけど)
+function(){}();
(function(){}());
コードをカプセル化するのに無名関数で囲うことは有効なのでモジュール化をするために使うべき。■Constructors
newキーワードがついてるものはコンストラクタとして関数呼び出しされます。コンストラクタ内でのthisは新しく作ったnew Objectを示します。
new Objectのprototypeはコンストラクタ関数のprototypeが設定されます。
コンストラクタ関数のreturnでオブジェクトが返されているとき以外はthis(new Object)が返されます。
function Foo() {
this.bla = 1;
}
Foo.prototype.test = function() {
console.log(this.bla);
};
var test = new Foo();// new忘れない
newキーワードをつけないでFooを読んだ場合はthisがグローバルオブジェクトを示してしまい悲惨な事になる。Factories
newキーワードを外しても呼べるようにするためには明示的に値を返す必要がある。
function Bar() {
var value = 1;
return {// オブジェクトを返すのでthis(new Object)は返されない
method: function() {
return value;
}
}
}
Bar.prototype = {// これが無意味になってる
foo: function() {}
};
new Bar();
Bar();
この方法ならnewをつけてもつけなくても同じ値が返ってくるが、new Bar()とした場合でもprototypeに影響を与えない。(つまりprototype継承が行われない)そのためthis(new Object)にprototype拡張しても、実際にthis(new Object)を返すことがないためBar.prototypeは意味のない記述になってしまいます。
Creating new objects via factories
factoryの典型的な例
function Foo() {
var obj = {};
obj.value = 'blub';
var private = 2;
obj.someMethod = function(value) {
this.value = value;
}
obj.getPrivate = function() {
return private;
}
return obj;
}
内部でオブジェクトを作って返すfactoryパターンはメリットもありますが、デメリットも存在します。- 作ったオブジェクトでメソッドなどを共有しないのでメモリをより多く使う
- factoryとから継承するためにはメソッドなどすべてコピーする必要がある
- prototypeチェーンを捨てているため、言語の精神に反する面もある
factoryを使ってnewを省略できるようにすることでバグは少なくなるかもしれないが、必ずしもそれはprototypeを捨てる事に直結はしない。最終的にはアプリケーションのニーズに合わせたスタイルをとり、そのスタイルにこだわる事が重要。
■Equality and comparisons
JavaScriptには==と===の二つの比較演算子がある。== は型変換を行って比較するため、見た目とは違った結果になることもある。
また型変換をするためパフォーマンスへの影響もある。
"" == "0" // false 0 == "" // true 0 == "0" // true false == "false" // false false == "0" // true false == undefined // false false == null // false null == undefined // true " \t\r\n" == 0 // true
The strict equals operator
厳密比較演算子===を使うと、型変換による微妙なバグは避けられるでしょう。また型変換を行わないのでパフォーマンス的にもこちら方が早い
"" === "0" // false 0 === "" // false 0 === "0" // false false === "false" // false false === "0" // false false === undefined // false false === null // false null === undefined // false " \t\r\n" === 0 // false
Comparing objects
オブジェクト同士の比較オブジェクトの比較では値が等しいかではなく、identityを比較しています。
つまり同じオブジェクトのインスタンスであるかを比較している。
{} === {}; // false
new String('foo') === 'foo'; // false
new Number(10) === 10; // false
var foo = {};
foo === foo; // true
厳密な比較演算子の使用を強く推奨する(個人的には緩い比較の利用法もあるんじゃないかなーと思う
■Arrays
Iteration
forで配列をなめる時はlengthをキャッシュしておけばより高速に動作する。
var list = [1, 2, 3, 4, 5, ...... 100000000];
// lengthが毎回参照されるのがいやなのでキャッシュする
for(var i = 0, l = list.length; i < l; i++) {
console.log(list[i]);
}
The length property
lengthに数値を代入することで配列の切り捨てを行える。var foo = [1, 2, 3, 4, 5, 6]; foo.length = 3; foo; // [1, 2, 3] foo.length = 6; foo; // [1, 2, 3]
■The Array constructor
配列はArrayコンストラクタを使っても作れますが、ほとんどの場合を除いてこれを使う必要はないため、リテラルを使って配列を作成する。
[1, 2, 3]; // Result: [1, 2, 3]
new Array(1, 2, 3); // Result: [1, 2, 3]
[3]; // Result: [3]
new Array(3); // Result: [undefined, undefined, undefined]
new Array('3') // Result: ['3']
一つ使える方法があるならば、文字列の繰り返しを生成する場合には便利です。new Array(count + 1).join(stringToRepeat);
- Array Literalを推奨する理由とか
■The for in loop
オブジェクトを列挙するためにはfor inループを使用します。ただしそのままではオブジェクト汚染の影響を受けてしまうので、hasOwnPropertyを使ってオブジェクト汚染を避ける方法があります。
// Poisoning Object.prototype
Object.prototype.bar = 1;
var foo = {moo: 2};
for(var i in foo) {
console.log(i); // prints both bar and moo
}
// hasOwnPropertyを使ってオブジェクト汚染を避ける
for(var i in foo) {
if (foo.hasOwnProperty(i)) {
console.log(i);
}
}
// こういう方法も
// Object.prototype.hasOwnPropertyを使う
var hasOwn = Object.prototype.hasOwnProperty;
for(var i in foo) {
if (hasOwn.call(foo, i)) {
console.log(i);
}
}
■The typeof operator
typeof(instanceof共々)はJavaScriptの最大の欠陥ともいえる。(完全にぶっ壊れてるぜ)instanceofはまだ限られた使い道があるが、typeofは一つのuse caseしかない。
The JavaScript type table
typeofの比較表
Value Class Type
-------------------------------------
"foo" String string
new String("foo") String object
1.2 Number number
new Number(1.2) Number object
true Boolean boolean
new Boolean(true) Boolean object
new Date() Date object
new Error() Error object
[1,2,3] Array object
new Array(1, 2, 3) Array object
new Function("") Function function
/abc/g RegExp object (function in Nitro/V8)
new RegExp("meow") RegExp object (function in Nitro/V8)
{} Object object
new Object() Object object
一貫性が存在していない…Classはオブジェクトの[[Class]]プロパティ値を示している。この[[Class]]の値をとる方法はObject.prototype.toStringを使う事があげられる。
The Class of an object
Object.prototype.toStringを使って[[Class]]プロパティ値を使った判定方法の例
function is(type, obj) {
var clas = Object.prototype.toString.call(obj).slice(8, -1);
return obj !== undefined && obj !== null && clas === type;
}
is('String', 'test'); // true
is('String', new String('test')); // true
Testing for undefined variables
typeof foo !== 'undefined';fooが本当に定義されているかを判定する方法。これが唯一のtypeofが役立つ利用法。
■The instanceof operator
instanceof演算子は2つのオペランドのコンストラクタを比較します。カスタムメイドオブジェクトの比較のみに使えて、native typeにはtypeofと同様に使いにくいものです。
Comparing custom objects
function Foo() {}
function Bar() {}
Bar.prototype = Foo;
new Bar() instanceof Bar; // true
new Bar() instanceof Foo; // false
Using instanceof with native types
予想とは異なる感じになる点がtypeofに似ている。
new String('foo') instanceof String; // true
new String('foo') instanceof Object; // true !
'foo' instanceof String; // false
'foo' instanceof Object; // false
そのためinstanceofはカスタムメイドオブジェクトについてのみ扱い、そのほかの場合はtypeofと同様に使うのを避けるべきです。■Type casting
Madness with new and built in types
ネイティブコンストラクタにnewをつけるかどうかで振る舞いは変わります。new Number(10) === 10; // False, Object and Number Number(10) === 10; // True, Number and Number new Number(10) + 0 === 10; // True, 暗黙の変換をさせるキャストで使える3つの方法
//Casting to a string
'' + 10 === '10'; // true
// Casting to a number
+'10' === 10; // true
// Casting to a boolean
!!'foo'; // true
!!''; // false
!!'0'; // false
!!'1'; // true
!!'-1' // true
!!{}; // true
!!true; // true
■undefined and null
他とは明らかに異なった値The value undefined
undefinedはグローバル変数にundefinedの値が定義されているため、上書きすることが可能になってしまってる。undefinedを返すケースは以下のような時
- グローバル変数のundefinedにアクセスしたとき
- 関数にreturn文がないときの返値
- return文で何も返さないことを明示的にしている場合return;
- ルックアップしてプロパティが存在しなかったとき
- 関数のパラメーターに何も渡されなかったときの仮引数
- 値がundefinedに設定されているもの
The case of the "overridden" undefined
undefinedがオーバーライドされているときには無名関数を利用してローカルで使えるようにするなどのテクニックがある(jQueryとかもやってた)
var undefined = 123;
(function(something, foo, undefined) {
// undefinedが取得し直せる
// undefined in the local scope does
// now again refer to the value
})('Hello World', 42);
Uses of null
nullの用途。JavaScriptでは未定義な状態をundefinedで表したりしますが、本当のnullはデータの型がちがうだけのundefinedと同じもの。
つまり、Foo.prototype = null;のように書いたりする部分はundefinedに置き換えることが可能。
■The evil eval
99.9%はevalを使うことなく実現できるはずなので使うのはできるだけ避けましょう。eval in disguise
setTimeout と setInterval文字列を渡すとevalのように実行する。(グローバルスコープで実行される)Security issues
出所が信用できない文字列を元にevalするのは絶対に避けるべき事。■setTimeout and setInterval
setTimeout と setIntervalはECMAではなくDOMの一部として実装されている。どちらも第一引数で渡したパラーメータをグローバルオブジェクトによって呼び出すため、thisはグローバルオブジェクトを示します。
function Foo() {
this.value = 42;
this.method = function() {
// thisはグローバルオブジェクトを参照してしまう
console.log(this.value); // will log undefined
};
setTimeout(this.method, 500);
}
new Foo();
Stacking calls with setInterval
setIntervalは名前の通りxミリ秒ごとに呼び出しますが、あまり推奨できないものです。下ようなコードは100ミリ秒ごとにfooを実行して欲しいですが、実際にはdropを起こしてずれることが多いです
function foo(){
// something that blocks for 1 second
}
setInterval(foo, 100);
ただアニメーションなどで一定時間内で処理が終わるようにした場合はsetIntervalを使った方が安定するそうです。Dealing with possible blocking code
解決方法としてはsetTimeoutを使って再帰的に呼び出すことがあげられる。
function foo(){
// something that blocks for 1 second
setTimeout(foo, 100);
}
foo();
- setIntervalとsetTimeoutを調べた結果余分なことになった - 三等兵
- John Resig - How JavaScript Timers Work
- Detecting browser’s activity within JavaScript | Notes of Maks Nemisj
Manually clearing timeouts
タイマーを消すにはclear*メソッドを使うvar id = setTimeout(foo, 1000); clearTimeout(id);タイマーIDがわからなければ力尽くでやる方法もあるが、普通にIDを追いかけるようにしましょう。
// clear "all" timeouts
for(var i = 1; i < 1000; i++) {
clearTimeout(i);
}
Hidden eval magic
setTimeoutで呼び出す関数に引数を渡すには無名関数を使いましょう。
function foo(a, b, c) {}
// つかっちゃだめ
setTimeout('foo(1,2, 3)', 1000)
// 代わりに無名関数を使う
setTimeout(function() {
foo(a, b, c);
}, 1000)
■Automatic semicolon insertion
JavaScriptでは自動的にセミコロンを挿入する機能がある。そのため変なところに改行があるだけで意図しない動作になったりする。
return // ここでreturn
{//ここら辺は死んでるコードになってしまう
foo: 1
}
もう一つの例
var foo = function() {
} // missing semicolon after assignment
(function() {
// do something in it's own scope
})();
// 上と下でまるで意味が変わる。
var foo = function(){
}( // セミコロンは入らないで、無名関数が引数として渡される。
function() {
}
)() // now call the result of the previous call
これらのようにセミコロンを明示的に入れないと意図しない動作になることがあるため、できるだけセミコロンを入れましょう。またif / elseのように{}を省略できるものもありますが、それも同様に省略しないようにするべきです。- TB-URL http://efcl.info/adiary/095/tb/
1: uupaa 2011年02月13日(日) 午前5時48分
(ε・◇・)з ややっ!
argumentsをArray likeオブジェクトにする例。
は
argumentsをArrayオブジェクトにする例。
かも(ちがってたらごめんなさい)
2: azu 2011年02月13日(日) 午後1時13分
修正ed
なんでArray likeにしてたんだろ…