yusuke
Press →key to advance.
Zoom in/out: Ctrl or Command+ +/-
1章が30分で終わればいいとこの予定
是非、手元のjavascript engine(browser)でtrying!
※ alertか、debugモードでの実行を推奨
その前に、関数についておさらい
var foo = function(a, b){
return a + b;
};
この関数fooにおいて、引数には名前がつけられている。(ここでは a と b)
javascriptにおいて引数(引数の変数)は順序があり、関数fooは変数aが第一引数、変数bが第二引数となっている
当たり前だけど、変数の名前は、別になんだって良い、順序が重要
var foo = function(a, b){ return a + b };
var hoge = function(b, a){ return a + b };
var bar = function(b, a){ return b - a };
foo(3, 4);
==> 7
hoge(3, 4);
==> 7
bar(2, 7);
==> -5
bar(7, 2);
==> 5
関数の中でどう行われているかは呼び出し側にはわからない
どの順序で引数を適用するかは知らないといけない
可変個の引数を受け取るものを可変引数とよぶ
たとえば Array.prototype.push
var foo = []; foo.push(1, 2, 3, 4, 5, 6, 7); foo.toString(); ==> 1,2,3,4,5,6,7
何個引数を受け取るかわからない場合、どう書くか
var push = function(arg0, arg1, arg2,....argmax?){
....?
};
可変引数を扱いたい場合は、argumentsを使用する
var push = function (){
var args = [];
for(var i = 0; i < arguments.length; ++i){
args.push(arguments[i]);
}
return args;
};
push(1, 2, 3);
==> 1,2,3
push(1, 3, 5, 7, 9, 11, 13, 15);
==> 1,3,5,7,9,11,13,15
argumentsオブジェクトには、arguments.lengthとarguments[index]で引数の個数と、引数のアクセスができる
その他にarguments.callee(後述する)などが含まれる
arguments はその関数が実行された時のコンテキストを持っている
closureなどで、渡した関数は実行時に評価されるのでargumentsの値は変わる
var foo = function (){
var a = Array.prototype.toString.call(arguments);
console.log(a);
return function (){
var b = Array.prototype.toString.call(arguments);
console.log(b);
};
};
var clsr = foo(1, 2, 3);
==> 1,2,3
clsr(4, 5, 6);
==> 4,5,6
同じargumentsオブジェクトでも関数毎に値が設定される
arguments はその関数自体の参照をcalleeで持っている
function foo(){
var callee = arguments.callee;
console.log(callee.name);
console.log(callee.toString());
}
foo();
==> foo
==> function foo() { /* 省略:: 上記foo関数のdescription */}
これは、下記とほぼ同じこと(実行時以外という点を除く)
Function.prototype.getThis = function (){ return this };
var fun = foo.getThis();
console.log(fun.name);
==> foo
console.log(fun);
==> function foo() { /* 省略:: 上記foo関数のdescription */}
arguments はその関数自体の参照をcalleeという形で持っているので
2章で説明した memoize の実態も参照できる
var foo = function(){
var callee = arguments.callee;
callee.value = callee.value + 1;
};
foo.value = 700;
foo();
console.log(foo.value);
==> 701
foo();
console.log(foo.value);
==> 702
※ this での参照はできない(後述)
arguments.calleeが関数自体を指すので、再帰などはこんな感じ(例は ackerman func)
(function (m, n){
var me = arguments.callee;
if(m < 1) { return n + 1 }
if(n < 1) { return me(m - 1, 1) }
return me(m - 1, me(m, n - 1))
})(3, 4);
==> 125
argumentsは宣言無しに利用できたりするので、分かりづらいかもしれないが可変引数を使う場合に利用する。
ただし、ArgumentsはオブジェクトなのでArrayアクセスできない(forEachとかできない)
Array.prototype.slice(後述)やprototype.jsの$A参照
Function.prototype.apply または Function.prototype.call 及び、delegate(委譲) について説明する前に
this という javascript において、慣れるまでに時間がかかるであろう、 動的レシーバ(とここでは呼ぶ) についてから説明する
少し長くなるかも
まずは、以下の関数のコピーはどう動くか
var foo = function(a, b){ a + b; }
var bar = foo;
foo(1, 2);
bar(1, 3);
同じ結果となる
これは同じ関数を参照しているから、といえる
では、クラスメソッドの場合はどうなるか
var Hoge = function (){};
Hoge.prototype.foo = function(a, b){ return a + b };
var hoge = new Hoge;
var bar = hoge.foo;
hoge.foo(1, 2);
bar(1, 2);
これも同じ結果となる
同じ関数を(ry
だが、コンテキストに依存していないというのも一つの要因
では、クラスメンバにアクセスする場合はthisを使う。
この場合ではどうか
var Hoge = function (name){
this.name = name;
};
Hoge.prototype.getName = function(){ return this.name };
var hoge = new Hoge('hello world');
var bar = hoge.getName;
hoge.getName();
==> 'hello world'
bar();
==> undefined
関数はコピーされているが、実態の値そのものはコピーされない
前回のは、下記のように解釈できる
var foo = function(){ return this.name };
var bar = foo;
bar();
==> undefined
this.nameというのは関数には定義されていない実態(※1)
※1: 下記は今回は説明しない
// 例外: this は 暗黙的に window や Global を参照する
window.name = 'hello world';
var foo = function(){ return this.name };
var bar = foo;
bar();
==> 'hello world'
脱線したが、thisはレシーバ側の値を参照する
var Hoge = function (name){
this.name = name;
};
Hoge.prototype.getName = function(){ return this.name };
var hoge = new Hoge('hello world');
var bar = {
name: 'i am bar'
};
bar.getName = hoge.getName;
hoge.getName();
==> 'hello world'
bar.getName();
==> 'i am bar'
this はレシーバに設定されたオブジェクトの参照をもつ
レシーバが変われば参照も変わる
脱線ついでに、this の参照を固定するには、Function.prototype.bind を使用する
var Hoge = function (name){ this.name = name };
Hoge.prototype.getName = function(){ return this.name };
var hoge = new Hoge('hello world');
var foo = hoge.getName.bind(hoge);
hoge.getName();
==> 'hello world'
foo();
==> 'hello world'
var bar = hoge.getName.bind({ name: 'anonym!' });
bar();
==> 'anonym!'
function に bind(束縛) することで、this の値を固定させる
Function.prototype.bind が undefined なら、prototype.js を参考にするか、下記を実装のこと
Function.prototype.bind = function(bindObj){
var __method__ = this;
return function (){
var args = Array.prototype.slice.call(arguments);
return __method__.apply(bindObj, args);
};
};
applyとarguments, this の記述まである上記の定義は、これから説明するdelegate(委譲)で説明するので、この定義の説明は割愛
まず、Function.prototype.applyとFunction.prototype.call について
Function.prototype.applyは、第一引数に this に割り当てるオブジェクトを指定し、Arrayで可変個の引数を設定して実行する
var foo = function(a, b, c){
return this.value * (a + b + c);
};
var obj = { value: 1 };
foo.apply(obj, [1, 2, 3]);
==> 6
foo.apply({ value: 20 }, [1, 2, 3]);
==> 120
この第一引数に割り当てるものが無い場合は、nullで代用できる
var bar = function(a, b, c){
return (a + b + c);
};
bar.apply(null, [4, 5, 6]);
==> 15
重要なのは、applyの第一引数
var Hoge = function (){ this.value = 20; }
Hoge.prototype.calc = function (a){ return this.value + a };
var foo = new Hoge();
foo.calc(5);
==> 25
var obj = { value: 50 };
foo.calc.apply(obj, [5]);
==> 55
Function.prototype.call は apply と同じだが、引数を固定して呼び出す
var foo = function(year, month, day){
return year + '/' + month + '/' + day;
};
foo(2011, 8, 21);
==> 2011/8/21
foo.call(null, 2011, 8, 21);
==> 2011/8/21
apply, callどちらを使っても良いが、
個人的には
可変引数を与える必要がある場合は apply を使い、
this を変更するような場合(上位コンストラクタ呼び出しやArray.prototype.slice等)は call を使っている
delegate(委譲)について
元の実装を変更することなく、処理を他のものに任せること
例えば次のような場合
var Builder = function (){ /* ... */ };
Builder.prototype.createFoo = function (){ // doSomething };
Builder.prototype.createBar = function (){ // doSomething2 };
Builder.prototype.createBaz = function (){ // doSomething3 };
Builder.prototype.build = function (){
var foo = this.createFoo();
var bar = this.createBar();
var baz = this.createBaz();
return { x: foo, y, bar, z: baz };
};
この定義における、createFooの実装が気に入らない場合
特異メソッドでインスタンスの置換えを行う?
var builder = new Builder();
builder.createFoo = function (){
this.foobar = '....';
};
builder.build();
※ 同じインスタンスを参照しているオブジェクトに影響を受ける可能性
var setupBuilderHoge = function (builder){
builder.createFoo = function (){ // override };
builder.setHoge('hoge');
};
var setupBilderQwerty = function(bilder){ builder.setQwery('hoge2'); };
setupBilderHoge(bilder);
setupBuilderQwerty(builder);
builder.build();
例えが悪いが、ワンタイムで置換えを行いたい場合には、apply/call を使い処理を委譲しておく
var BuilderDelegate = function (originalBuilder){
this.original = originalBuilder;
};
BuilderDelegate.prototype = {
createFoo: function (){ /* doSomething */ },
createBar: function(){
return this.original.createBar.apply(this.original, arguments);
},
createBaz: function (){
return this.original.createBaz.apply(this.original, arguments);
},
build: function (){
// ここは、自分のcreateFooで実装
var foo = this.createFoo();
// ここは、builderの参照を使う
var bar = this.original.createBar();
var baz = this.original.createBaz();
return { x: foo, y, bar, z: baz };
};
};
var builderDelegate = new BuilderDelegate(new Builder());
// builderDelegate.build() or
builder.build.call(builderDelegate);
Array.prototypeには便利なメソッドがいくつか用意されている
各メソッドを説明し、拡張なども説明したい
他にもsomeやeveryなどもあるが、ここでは割愛
いわゆる foreach。
for(var props in obj) のようにも扱えるが、Array用
var list = [];
list.push({ name: 'one' });
list.push({ name: 'two' });
list.push({ name: 'three' });
list.forEach(function(value, index){
console.log(index + ':' + value.name);
});
==> 0:one
==> 1:two
==> 2:three
ちなみに、callbackとして受け入れる関数は callback(eachValue, eachIndex, array) と3つの引数を受け取れる
forEach とほぼ同じだが、各要素に与えられた新しい配列を生成する
var list = [1, 2, 3];
var mapResult = list.map(function(value){
return value * 10;
});
==> 10,20,30
mapで与えられたのと同じ要素数で値を返すので、convert(data exchange)処理などに最適
mapは与えられた配列と同じ要素を返すが、filterは適用された要素を返す
var evenFilter = function(value){
if(0 === (value % 2)){
return true;
}
return false;
};
var list = [1, 2, 3, 4, 5, 6];
var filterResult = list.filter(evenFilter);
==> 2,4,6
filterはtrueを返した値だけを含めた配列を返す。(Array.prototype.sortと違い-1とか1ではない)
※forEach, filter, mapのようにiteratableの引数を与えないので注意
与えられた値が配列に含まれているかを検証することができる
var list = ['adorable', 'beautiful', 'charm'];
list.indexOf('charm');
==> 2
list.indexOf('hoge');
==> -1
当然ながら Object に適用する場合は、実装が必要
var list = [{n: 'hoge', n: 'foo', n: 'foo'}];
list.indexOf('hoge');
==> -1
var list = [{n: 'hoge', n: 'foo', n: 'foo'}];
list.indexOf({n: 'hoge'});
==> -1
先のfilterとindexOfを使い、uniqueメソッドの実装(仮)
Array.prototype.unique = function(){
var tmp = [];
return this.filter(function (v, i) {
// 存在していない物をtmpに詰める。比較するときはtmpから
if (-1 === tmp.indexOf(v)) {
tmp.push(v);
return true;
}
return false;
});
};
var list = [1, 2, 2, 3, 4, 2, 1];
var uniqList = list.unique();
==> 1,2,3,4
もちろん、ちゃんと比較する(indexOf)なら、javaのようにhashCodeとequalsのような実装が必要
長くなったので割愛
mozillaのドキュメントがかなり良い(E4X除く)
https://developer.mozilla.org/Ja/New_in_JavaScript_1.6
the end
第四章は無いよ...