javascripterになろう。の巻
3章

yusuke

Press key to advance.
Zoom in/out: Ctrl or Command+ +/-

章 1

  • 基礎(introduction)
  • クロージャ(closure)
  • レキシカルスコープ(lexical scope/static scope)
  • ブロック(block scope)

章 2

  • 高階関数(higher-order function)
  • カリー化(currying)
  • メモ化(memoize)
  • 非同期:遅延(defered)
  • プロトタイプ(prototype)

章 3

  • Argument について
  • Function.prototype{apply/call}とdelegate
  • Array.prototype
  • javascript 1.6

1章が30分で終わればいいとこの予定

是非、手元のjavascript engine(browser)でtrying!

※ alertか、debugモードでの実行を推奨

第三章

  • Argument について
  • Function.prototype{apply/call}とdelegate
  • Array.prototype
  • javascript 1.6

Arguments について

その前に、関数についておさらい

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

関数の中でどう行われているかは呼び出し側にはわからない

どの順序で引数を適用するかは知らないといけない

Arguments - 可変引数

可変個の引数を受け取るものを可変引数とよぶ

たとえば 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 - 可変引数

可変引数を扱いたい場合は、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 オブジェクト

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

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)

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(3)

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とdelegate

Function.prototype.apply または Function.prototype.call 及び、delegate(委譲) について説明する前に

this という javascript において、慣れるまでに時間がかかるであろう、 動的レシーバ(とここでは呼ぶ) についてから説明する

少し長くなるかも

this について

まずは、以下の関数のコピーはどう動くか

var foo = function(a, b){ a + b; }
var bar = foo;

foo(1, 2);
bar(1, 3);

同じ結果となる

これは同じ関数を参照しているから、といえる

this について(2)

では、クラスメソッドの場合はどうなるか

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 について(3)

では、クラスメンバにアクセスする場合は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

関数はコピーされているが、実態の値そのものはコピーされない

this について(4)

前回のは、下記のように解釈できる

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 について(5)

脱線したが、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 について(6)

脱線ついでに、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 の値を固定させる

this について(6.5)

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と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

Function.prototype.applyとdelegate

この第一引数に割り当てるものが無い場合は、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とdelegate

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

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の実装が気に入らない場合

delegate(2)

特異メソッドでインスタンスの置換えを行う?

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();

delegate(3)

例えが悪いが、ワンタイムで置換えを行いたい場合には、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

Array.prototypeには便利なメソッドがいくつか用意されている

  • Array.prototype.forEach
  • Array.prototype.map
  • Array.prototype.filter
  • Array.prototype.indexOf

各メソッドを説明し、拡張なども説明したい

他にもsomeやeveryなどもあるが、ここでは割愛

Array.prototype.forEach

いわゆる 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つの引数を受け取れる

Array.prototype.map

forEach とほぼ同じだが、各要素に与えられた新しい配列を生成する

var list = [1, 2, 3];
var mapResult = list.map(function(value){
  return value * 10;
});
==> 10,20,30

mapで与えられたのと同じ要素数で値を返すので、convert(data exchange)処理などに最適

Array.prototype.filter

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ではない)

Array.prototype.indexOf

※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

Array.prototype 拡張

先の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のような実装が必要

Javascript 1.6

長くなったので割愛

mozillaのドキュメントがかなり良い(E4X除く)
https://developer.mozilla.org/Ja/New_in_JavaScript_1.6

おわり

the end

 

 

 

 

 

第四章は無いよ...