@blog.justoneplanet.info

日々勉強

JavaScript Complex Class(複素数のクラス)

以下のように複素数のクラスを定義することができる。

function Complex(real, imaginary){
    this.x = real;
    this.y = imaginary;
}
Complex.prototype.magnitude = function(){
    return Math.sqrt(this.x * this.x + this.y * this.y);
}
Complex.prototype.negative = function(){
    return new Complex(-this.x, -this.y);
}
Complex.prototype.add = function(cmp){
    return new Complex(this.x + cmp.x, this.y + cmp.y);
}
Complex.prototype.multiply = function(cmp){
    return new Complex(this.x * cmp.x - (this.y * that.y), this.x * cmp.y + this.y + cmp.x);
}
Complex.prototype.toString = function(){
    return '{' + this.x + ', ' + this.y + '}';
}
Complex.prototype.equals = function(cmp){
    return this.x === cmp.x && this.y === cmp.y;
}
Complex.prototype.valueOf = function(){
    return this.x;
}
Complex.sum = function(a, b){
    if(a instanceof Complex && b instanceof Complex){
        return new Complex(a.x + b.x, a.y + b.y);
    }
    else{
        return new TypeError();
    }
}
Complex.product = function(a, b){
    if(a instanceof Complex && b instanceof Complex){
        return new Complex(a.x * b.x - (a.y * b.y), a.x * b.y + a.y * b.x);
    }
    else{
        return new TypeError();
    }
}
var a = new Complex(1, 5);//1+5i
alert(a);//{1, 5}

JavaScript Class Basic(クラスの基本)

JavaScriptには現時点でclassという概念がない(JavaScript2.0から使える予定)。

■メンバ

インスタンスプロパティ

インスタンス化した後のオブジェクトのプロパティ。

function Person(name){
    this.name = name;
    this.introduce = function(){
        alert('My name is ' + this.name + '.');
    };
}
var john = new Person('John');
//an instance property is john.name

インスタンスメソッド

インスタンス化した後のオブジェクトのメソッド。

function Person(name){
    this.name = name;
    this.introduce = function(){
        alert('My name is ' + this.name + '.');
    };
}
var john = new Person('John');
//an instance property is john.introduce()

但し、以下のようにprototypeによってインスタンスメソッドを定義した場合、introduce()メソッドのnameにはthisが必須となる。

var undefined;
function Person(name){
    this.name = name;
}
Person.prototype.introduce = function(){
    alert(this.name);
}
var john = new Person('John');
john.introduce();

クラスプロパティ

以下のようにすると、クラスプロパティが定義できる。インスタンス化されたオブジェクトからは当然アクセスできない。クラスプロパティは(クラス定数のように)慣例で全て大文字にする。

function Person(name){
    this.name = name;
    this.introduce = function(){
        alert('My name is ' + this.name + '.');
    };
}
Person.GENUS = 'genus Homo';
var john = new Person('John');
alert(john.GENUS);//none
alert(Person.GENUS);//genus Homo

クラスメソッド

クラスメソッドには以下のようなものがある。PHPで言い換えるならばstaticをつけてクラスメソッドを宣言したような感じだ。自身で定義したクラスにメソッドを付加することも可能である。

alert(Date.parse('2009/09/23 17:30:37'));//1253694637000

■プライベートメンバ

JavaやPHPではアクセス修飾詞を使用してコントロールする機能。但し、以下のようにプロパティに保持しないというだけであり、グローバル空間からインスタンス化したオブジェクトを介してnameを表示させることはできる。メリットはnameを外から変更できない事だ。

function Person(name){
    this.getName = function(){return name;};
}
Person.prototype.introduce = function(){
    alert(this.getName());
}
var john = new Person('John');
alert(john.getName());//John
john.introduce();//John

JavaScript Constructor and Prototype

■コンストラクタ関数

以下のようにするとコンストラクタ関数を定義できる。

function Person(name){
    this.name = name;
    this.introduce = function(){
        alert("My name is " + this.name + ".");
    }
}
var john = new Person('John');
var mike = new Person('Mike');
john.introduce();//My name is John.
mike.introduce();//My name is Mike.

慣例でコンストラクタ関数(クラス)は大文字で定義する。

■プロトタイプ

上述のコードは全てのインスタンスが同じintroduce()メソッドを保持するようになる。もし全てのインスタンスが同じintroduce()メソッドを参照すればメモリの節約につながる。そこで以下のようにprototypeを用いる。

function Person(name){
    this.name = name;
}
Person.prototype.introduce = function(){
    alert("My name is " + this.name + ".");
}
var john = new Person('John');
var mike = new Person('Mike');
john.introduce();//My name is John.
mike.introduce();//My name is Mike.

この場合、以下のように書くとメソッドが増えたときにスッキリする。

function Person(name){
    this.name = name;
}
Person.prototype = {
    introduce : function(){
        alert("My name is " + this.name + ".");
    }
}
var john = new Person('John');
var mike = new Person('Mike');
john.introduce();//My name is John.
mike.introduce();//My name is Mike.

参考

以下のようにしてArrayクラスを継承させることもできる。

var undefined;
function Stack(){
    this.T = -1;
}
Stack.prototype = Array.prototype;

■プロパティの性質

以下のようにコンストラクタ関数で記述したintroduceメソッドは、直属のプロパティ(メソッド)になる。

function Person(name){
    this.name = name;
    this.introduce = function(){
        alert("My name is " + this.name + ".");
    }
}
var john = new Person('John');
alert(john.hasOwnProperty('name'));//true
alert(john.hasOwnProperty('introduce'));//true
alert("introduce" in john);//true

一方で以下のようにprototypeを使用した場合、introduceメソッドはPersonから継承されたプロパティ(メソッド)ということになる。

function Person(name){
    this.name = name;
}
Person.prototype = {
    introduce : function(){
        alert("My name is " + this.name + ".");
    }
}
var john = new Person('John');
alert(john.hasOwnProperty('name'));//true
alert(john.hasOwnProperty('introduce'));//false
alert("introduce" in john);//true

■prototypeにおける読み込みと書き込み

読み込み

プロパティを読み込む場合、インスタンスはプロトタイプのプロパティを参照するので(継承している)、prototypeが使われる。

function Person(name){
    this.name = name;
}
Person.prototype = {
    introduce : function(){
        alert("My name is " + this.name + ".");
    }
}
var john = new Person('John');
var mike = new Person('Mike');
john.introduce();//My name is John.
mike.introduce();//My name is Mike.

インスタンスは自身にプロパティが見つからない場合、コンストラクタ関数のprototypeプロパティを参照する。コンストラクタ関数で見つからない場合は、さらにprototypeをたどる。

書き込み

プロパティを書き込む場合、JavaScriptはprototypeを使用しない。

function Person(name){
    this.name = name;
}
Person.prototype = {
    introduce : function(){
        alert("My name is " + this.name + ".");
    }
}
var john = new Person('John');
var mike = new Person('Mike');
john.introduce = function(){
    alert("I'm " + this.name + ".");
}
john.introduce();//I'm John.
mike.introduce();//My name is Mike.

■ネイティブコンストラクタ関数の拡張

通常は行ってはならないが、古いブラウザに対して標準に準拠したメソッドを付加するには便利である。

array.forEach(callback[, thisObj]);

配列の各要素を引数にして指定された関数を呼び出す。

if(!Array.prototype.forEach){
    Array.prototype.forEach = function(func/*, thisObj*/){
        var length = this.length >>> 0;
        if(typeof func !== 'function'){
            throw new TypeError();
        }
        var thisObj = arguments[1];
        for(var i = 0; i < length; i++){
            if(i in this){
                func.call(thisObj, this[i], i, this);
            }
        }
    }
}
サンプル
[1, 2, 3, 4].forEach(
	function(element, index, array){
		alert("[" + index + "] is " + element);
	}
);
/*
[0] is 1
[1] is 2
[2] is 3
[3] is 4
*/

array.map(callback[, thisObj]);

配列の各要素(を引数として)に対して関数を実行し、返された値を配列にまとめて返す。

if(!Array.prototype.map){
    Array.prototype.map = function(func/*, thisObj*/){
        var length = this.length >>> 0;
        if(typeof func !== 'function'){
            throw new TypeError();
        }
        res = new Array(length);
        var thisObj = arguments[1];
        for(var i = 0; i < length; i++){
            if(i in this){
                res[i] = func.call(thisObj, this[i], i, this);
            }
        }
        return res;
    }
}
サンプル
alert([1, 16, 81].map(Math.sqrt));//1,4,9

function.apply()

if(!Function.prototype.apply){
    Function.prototype.apply= function(object, parameters){
        var f = this;
        var o = object || window;
        var args = parameters || [];
        o._$_apply_$_ = f;
        var stringArgs = [];
        for(var i = 0; i < args.length; i++){
            stringArgs[i] = "args[" + i + "]";
        }
        var arglist = stringArgs.join(",");
        var methodcall = "o._$_apply_$_(" + arglist + ");";
        var result = eval(methodcall);
        delete o._$_apply_$_;
        return result;
    }
}

各メソッドについて

array.forEach(callback([element[, index[, array]]])[, thisObj]);
callback…各要素を引数として実行される関数。
thisObj…callbackが実行するされるときにthisとして扱われるオブジェクト。
array.map(callback[, thisObj]);
callback…各要素を引数として実行される関数。
thisObj…callbackが実行するされるときにthisとして扱われるオブジェクト。
var result = fun.apply(thisArg, [argsArray]);
thisArg…カレントオブジェクト。省略された場合はグローバルオブジェクト。
argsArray…関数に渡される引数の配列。

参考