javascripterになろう。の巻
2章

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モードでの実行を推奨

第二章

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

高階関数(higher-order function)とは

関数は、ある機能を呼び出せる形式で記述したもの

var add = function(a, b){
  return a + b;
};

add(1, 2); // ==> 3

ここでの変数a, bは引数と呼ばれ、関数の入力"値"として利用出来る(input)

return を使うことで、関数の結果("値")を出力(返す)できる(output)

高階関数とは、関数を引数に取る関数

var add = function(a, b){
  return a + b;
};
var sub = function(a, b){
  return a - b;
};

var calc = function(a, b, c, math){
  var x = math(a, b);
  var y = math(x, c);
  return y;
};
calc(1, 2, 3, add); // => 6
calc(1, 2, 3, sub); // => -4

関数calcは引数a,b,cに数値を受け取り、引数mathに関数を受け取り処理をする関数

closureと高階関数

関数を返す関数と、高階関数の組み合わせ

var calc_gen = function(a, b) {
  return function(math){
    return math(a, b);
  };
};
var add = function(a, b){
  return a + b;
};
var mul = function(a, b){
  return a * b;
};

var calc = calc_gen(4, 5);
calc(add); // => 9
calc(mul); // => 20

値を固定しておいて、関数を"適用"して結果を変える(デザパタによくあるアレです)

カリー化(currying)

複数の引数を取る関数のうち、引数を固定化(bind)すること

var dump = function(a, b, c){
  return 'a = ' + a + ',';msg += 'b = ' + b + ','; msg += 'c = ' + c + ',';
};
var curryOne = function(func, arg1){
  return function(arg2, arg3){
    return func(arg1, arg2, arg3);
  };
};
var curryTwo = function(func, arg1, arg2){
  return function(arg3){
    return func(arg1, arg2, arg3);
  };
};
var curryThree = function(arg1, arg2, arg3){
  return function(func){
    return func(arg1, arg2, arg3);
  };
};

のような定義があるとき

下記のような実行結果となる

alert(dump(1, 2, 3)); // a = 1,b = 2,c = 3

var one = curryOne(dump, 1);
alert(one(2, 3)); // a = 1,b = 2,c = 3
alert(one(3, 4)); // a = 1,b = 3,c = 4

var two = curryTwo(dump, 7, 8);
alert(two(9)); // a = 7,b = 8,c = 9
alert(two(0)); // a = 7,b = 8,c = 0

var three = curryThree(5, 6, 7);
alert(three(dump)); // a = 5,b = 6,c = 7

javascriptは関数クロージャによってカリー化が実現出来ている
(クロージャ内に変数が閉じられているので、関数実行時に値の適用が行える)

メモ化(memoize)

状態を持たない関数は、常に同じ値を返却する

var add = function(a, b){
  return a + b;
};

alert(add(1, 2)); // => 3
for(var i = 0; i < 100000; ++i){
  alert(add(1, 2)); // => 3
}

これは、1000万回実行しても1億回実行しても add(1, 2) の結果はおなじになる。
関数addはステートレスな関数である

memoizeとは一般的な cache と同じこと。同じ引数である限り、キャシュの値を参照する



非同期:遅延(defered)

プロトタイプ(prototype)

ここでのプロトタイプとは、プロトタイプベースのこと。

継承パターンの一つ

数珠つなぎのようび呼び出される様から、プロトタイプチェーンと呼ぶことも

プロトタイプベースとクラスベースとインスタンスベース

(中略)

クラス作成

javascriptにおいて、functionはクラスにおけるコンストラクタになる

var MyClass = function (){
  // constructor
};

var a = new MyClass();

javascriptはクラスベースのような振る舞いをもつ
そのため、新しいインスタンスを生成する場合は new を使う

インスタンス メソッド作成

メソッドは、prototypeに追加する

var MyClass = function (){];
MyClass.prototype.methodA = function (){
  alert('hello world');
};

var a = new MyClass();
a.methodA();

なお、methodAはインスタンスメソッドなので、下記のような呼出は行えない

MyClass.methodA(); // error

インスタンス プロパティの作成

prototypeを利用するか、コンストラクタでthisに設定する

var MyClass = function(){
  this.propertyA = 'this is propertyA';
};
MyClass.prototype.propertyB = 'this is propertyB';

var a = new MyClass();
alert(a.propertyA); // ==> this is propertyA
alert(a.propertyB); // ==> this is propertyB

継承 - その1

prototypeに親クラスのインスタンスを設定する

var ParentClass = function (){
  this.name = 'i am ParentClass';
};
ParentClass.prototype.methodA = function (){
  return 'i am ParentClass:methodA';
};

var MyClass = function (){};
MyClass.prototype = new ParentClass();
MyClass.prototype.methodB = function (){
  return 'i am MyClass:methodB';
};

var a = new MyClass();
alert(a.name); // ==> i am ParentClass
alert(a.methodA()); // ==> i am ParentClass:methodA
alert(a.methodB()); // ==> i am MyClass:methodB

overrideは同じpropertyの上書きで行える

継承 - その2

prototypeを指定する

var ParentClass = function (){
  this.name = 'i am ParentClass';
};
ParentClass.prototype.methodA = function (){
  return 'i am ParentClass:methodA';
};

var MyClass = function (){};
MyClass.prototype = ParentClass.prototype; // <== ここ
MyClass.prototype.methodB = function (){
  return 'i am MyClass:methodB';
};

var a = new MyClass();
alert(a.name); // ==> i am ParentClass
alert(a.methodA()); // ==> i am ParentClass:methodA
alert(a.methodB()); // ==> i am MyClass:methodB

overrideは同じくpropertyの上書きで行える

両者の違い

prototypeのインスタンスが無くなった場合の動きが違う

var Parent = function(){
  this.name = 'hello world';
}
var ClassA = function (){};
ClassA.prototype = new Parent;
var ClassB = function (){};
ClassB.prototype = Parent.prototype;

var a = new ClassA();
var b = new ClassB();

alert(a.name); // ==> hello world
alert(b.name); // ==> hello world

ClassA.prototype = null;
ClassB.prototype = null;

alert(a.name); // ==> hello world
alert(b.name); // ==> undefined

基本的にこんなことは無いだろうが、このような動きをする

動的 メソッド/プロパティ追加

同じprototypeをもつインスタンスで、プロトタイプチェーンの動きを確認

var MyClass = function (){};
MyClass.prototype.methodA = function (){ return 'methodA' };

var a = new MyClass();
var b = new MyClass();

alert(a.methodA()); // ==> methodA
alert(b.methodA()); // ==> methodA

MyClass.prototype.methodB = function (){ return 'methodB' };

alert(a.methodB()); // ==> methodB
alert(b.methodB()); // ==> methodB

alert(a.propertyC); // ==> undefined
MyClass.prototype.propertyC = 'propertyC';
alert(a.propertyC); // ==> propertyC

生成されているインスタンスにも影響を与える

動的 メソッド/プロパティ追加 - つづき

この機能を使うことで、ベースクラスに拡張を行うことができる

Array.prototype.hoge = function (){
  return 'hoge: ' + this;
};
String.prototype.foo = function (){
  return 'foo: ' + this;
};
Boolean.prototype.bar = function (){
  return 'bar: ' + this;
};

alert([1, 2, 3].hoge()); // ==> hoge: 1,2,3
alert('hello world'.foo()); // ==> foo: hello world
alert(false.bar()); // ==> bar: false

Object.prototypeに追加すると、すべてのオブジェクトに影響がある(=> オブジェクト汚染)

親コンストラクタの呼び出し

親コンストラクタは、call/apply(this) で行う

var Root = function(arg1, arg2){
  this.value1 = arg1;
  this.value2 = arg2;
};
var Foo = function(arg1){
  //Root(arg1, 'bar'); // <- この呼出は this の参照が不定になるので、行うことができない
  Root.apply(this, [arg1, 'bar']);
};
Foo.prototype = Root.prototype;

var foo = new Foo('hoge');
alert(foo.value1); // ==> hoge
alert(foo.value2); // ==> bar

Root.apply における Function.prototype.apply や Function.prototype.call は、
次回にて説明。また、 this の参照についても次回を予定

特異メソッド(instance-specific method)

特異メソッドとは、インスタンス固有のメソッドのこと

var Foo = function (){};
Foo.prototype.methodA = function (){ return 'instance method' };

var foo = new Foo();
foo.methodB = function (){ return 'instance specific method' };

alert(foo.methodA()); // ==> instance method
alert(foo.methodB()); // ==> instance specific method

var foo2 = new Foo();
alert(foo2.methodA()); // ==> instance method
alert(foo2.methodB()); // ==> TypeError: foo2.methodB is not a function

動的にメソッド/プロパティは追加できるが、他のインスタンへは影響を及ぼさない

クラスインスタンスもどき

特異メソッドを利用することで、あたかもクラスから生成されたインスタンスのように振る舞える

var Foo = function (){};
Foo.prototype.valueA = function (){ return 'value A' };
Foo.prototype.valueB = function (){ return 'value B' };

var foo = new Foo();

var bar = {};
bar.valueA = function (){ return 'bar value A' };
bar.valueB = function (){ return 'bar value B' };

alert(foo.valueA()); // => value A
alert(foo.valueB()); // => value B
alert(bar.valueA()); // => bar value A
alert(bar.valueB()); // => bar value B

あたかも同じような振る舞いを持つことができるので、proxy パターンや delegate パターン等で活躍する

new を使わない継承 - clone

これぞインスタンスベースの真骨頂

var clone = function (base){
  var f = function (){};
  f.prototype = base;
  return new f();
};
var Animal = clone({});
Animal.say = function(){ alert('...') };

var Dog = clone(Animal);
Dog.say = function(){ alert('bow bow') };

var Bird = clone(Animal);
Bird.say = function(){ alert('tweet') };

var Penguin = clone(Bird);
var Duck = clone(Bird);
Duck.say = function (){ alert('quack!!') };

Animal.say(); // => ...
Dog.say(); // => bow bow
Bird.say(); // => tweet
Penguin.say(); // => tweet
Duck.say(); // => quack!!

第三章へ

to be continued...