JavaScript権威ガイド学習ノート(五)

25027 ワード

クラスとプロトタイプ
前にinheit関数を定義しましたが、この関数は新しく作成したオブジェクトを返します.後者はある原型オブジェクトから継承されます.プロトタイプのオブジェクトを定義し、inheit()関数によってそのオブジェクトから継承されるオブジェクトを作成すると、JavaScriptクラスが定義されます.
//range.js:             

//            "    "
function range(from, to) {
    //  inherit()       ,                 
    //               ,     "    "      (  )
    var r = inheirt(range.methods);

    //    "    "          (  )
    //           ,            
    r.from = from;
    r.to = to;

    //          
    return r;
}
//        ,              
range.methods = {
    //  x    ,   true,    false
    //            ,             
    includes: function(X) {
        return this.from <= x && x <= this.to;
    },

    //               f
    //            
    foreach: function(f) {
        for (var x = Math.ceil(this.from); x <= this.to; x++) f(x);
    },
    //                
    toString: function() {
        return "(" + this.from + "..." + this.to + ")";
    }
}
//     "    "     
var r = range(1, 3); //        
r.includes(2); //=> true:2        
r.foreach(console.log); //    1 2 3
console.log(r) //  (1...3)
ここではレンゲ()関数に対して定義された属性レンゲ.methodsを定義し、定義されたクラスの原型オブジェクトを迅速に保存します.なお、ランジュ()関数は、各関数オブジェクトにfromとto属性を定義し、範囲の起実位置と終了位置を定義するために使用されます.この2つの属性は非共有であり、もちろん引き継ぎはできません.最後に、レンゲ・methodsで定義されている共有可能・継承可能な方法は、fromとto属性を使用しており、また、thisキーワードを使用しており、これらを指すために、両者はthisキーワードを使用して、この方法を呼び出す対象を指す.
クラスとコンストラクタ
newを使って立体関数を呼び出して新しいオブジェクトを自動的に作成します.この新しいオブジェクトの状態を初期化するだけで結構です.コンストラクタを呼び出す重要な特徴は,新しいオブジェクトのプロトタイプとしてコンストラクタのプロファイルを使用することである.これは、同じ構造関数で作成されたすべてのオブジェクトが同じオブジェクトから継承されていることを意味し、したがって、それらは同じクラスのメンバーです.工場関数の代わりに構造関数を使用します.
//range2.js:              

//        ,         "    "
//  ,              ,      
function Range(from, to) {
    //  "    "          (  )
    //           ,            
    this.from = from;
    this.to = to;
}
//   "    "       
//  ,        "prototype"
Range.prototype = {
    //  x    ,   true,    false
    //            ,             
    includes: function(x) {
        return this.from <= x && x <= this.to;
    },
    //               f
    //            
    foreach: function(f) {
        for (var x = Math.ceil(this.from); x <= this.to; x++) f(x);
    },
    //                
    toString: function() {
        return "(" + this.from + "..." + this.to + ")";
    }
}
var r = range(1, 3); //        
r.includes(2); //=> true:2        
r.foreach(console.log); //    1 2 3
console.log(r) //  (1...3)
ある意味では、コンストラクタを定義すると定義されるクラスで、クラス名の頭文字は大文字になります.普通の関数と方法は全部イニシャル小文字です.
Range()構造関数は初期化されたthisにすぎない.コンストラクタはこの新しく作成したオブジェクトに戻る必要もなく、コンストラクタは自動的にオブジェクトを作成し、このオブジェクトとしてのコンストラクタを呼び出す方法で、最後にこの新しいオブジェクトに戻ります.コンストラクタは「新しいオブジェクトを作成する」ために使用されます.キーワードnewで呼び出す必要があります.コンストラクタを普通の関数として使用すると、正常に動作しない場合があります.
コンストラクタとクラスの標識
プロトタイプオブジェクトはクラスの一意の識別であり、2つのオブジェクトが同じプロトタイプオブジェクトから継承される場合にのみ、同じクラスに属する例である.オブジェクトの状態を初期化するコンストラクタはクラスの識別として使えません.二つのコンストラクターのprototype属性は同じプロトタイプのオブジェクトを指すことがあります.この二つの構造関数が作成した例は同じクラスに属する.
constructorのプロパティ
いずれのJavaScript関数も構造関数として使用できます.構造関数を呼び出すにはプロトタイプの属性が必要です.したがって、JavaScript関数ごとにプロトタイプの属性が自動的に備わっています.この属性の値はオブジェクトです.このオブジェクトには唯一の列挙できない属性のconstructorが含まれています.constructor属性の値は関数オブジェクトです.
var F = function() {}; //        
var P = F.prototype; //  F        
var c = P.prototype; //           
c === F //=>true:      F.prototype.constructor==F
構造関数のプロトタイプにはあらかじめ定義されたconstructor属性が存在することが見られ、これはオブジェクトが通常継承するconstructorがそれらの構造関数を指すことを意味する.コンストラクタはクラスの「公共標識」なので、このconstructor属性は対象にクラスを提供します.
var o = new F(); //   F     
o.constructor === F // =>true,constructor       
前に定義されたRangeクラスでは、それ自体の新しいオブジェクトを使用して定義済みのRange.prototypeオブジェクトを書き換えます.この新規定義の原型オブジェクトにはconstructor属性が含まれていません.したがって、Range類の例もconstructor属性を含んでいない.この問題を補完措置で修正でき、プロトタイプに構造関数を追加することを示します.
Rnage.prototype = {
    constructor: Range, //            
    includes: function(x) {
        return this.from <= x && x <= this.to;
    },
    foreach: function(f) {
        for (var x = Math.ceil(this.from); x <= this.to; x++) f(x);
    },
    toString: function() {
        return "(" + this.from + "..." + this.to + ")";
    }
}
もう一つは事前に定義された原型オブジェクトを使用して、予め定義された原型オブジェクトはconstructor属性を含む:
//      Range.prototype  ,     
//       Range.prototype.constructor  
Range.prototype.includes = function(x) {
    return this.from <= x && x <= this.to;
};
Range.prototype.foreach = function(f) {
    for (var x = Math.ceil(this.from); x <= this.to; x++) f(x);
};
Range.prototype.toString = function() {
    return "(" + this.from + "..." + this.to + ")";
}
instance of演算子
oがc.prototypeから継承されると、表現o instance of c値はtrueとなります.ここでの継承は直接継承ではなく、oが継承したオブジェクトが他のオブジェクトから継承された場合、後のオブジェクトはc.prototypeから継承されます.この表式の演算結果もtrueです.
isPrototypeOf()方法は、例えば、オブジェクトrが事故で定義されている範囲のメンバーをコードで検出することができます.
range.methods.isPrototypeOf(r); //range.method      
2つの異なるフレームワークページで作成された2つの配列は、同じ2つのオリジナルオブジェクトから継承されていますが、1つのフレームワークページの配列は別のフレームページのAray()構造関数の例ではなく、instance of演算結果はfalseです.
アヒル形弁形
「対象の種類は何ですか?」ではなく、「対象が何ができるか」に注目しましょう.この考え方はPythonとRubyの中で非常に一般的で、「アヒル式弁論型」と呼ばれています.
アヒルのように歩き、泳ぎ、そして大きな鳥がアヒルです.
JavaScriptプログラマーにとって、この言葉は「もし相手がアヒルのように歩いたり、泳いだりして、こつこつと鳴いたりできるなら、この対象はアヒルだと思っています.たとえそれがアヒルの原型の対象から継承されたものでなくても」と理解できます.
多くの場面では、オブジェクトが本当にArayであるかどうかは分かりません.もちろん、非負のlength属性が含まれているかどうかを判断することによって、Arayの例が分かります.「1つの値を含む負の整数のlength」は行列の特徴である「歩くことができる」で、「歩くことができる」という特徴を持つオブジェクトはいずれも行列として待つことができます.しかし、実際の配列のlength属性には、新しい要素を追加すると、配列長が自動的に更新され、length属性により小さな整数を設定すると、配列は自動的に切断されるという特徴があることを理解しておく必要があります.これらの特徴は「泳げる」と「カンカン」です.実現コードに「泳げる」「カンカン」が必要であれば、「歩くことができる」という配列だけのオブジェクトは使用できません.
鴨式弁論型の実現方法は「自由奔流」を感じさせます.入力対象を想定して必要な方法が実現されただけで、さらなる検査は行われていません.入力対象が「仮定」に従わない場合、コードが存在しない方法を呼び出してもエラーが発生します.もう一つの実施方法は、入力対象を検査することである.しかし、それらの種類を検査するのではなく、適切な名前でそれらの実現方法を確認します.このように不法入力をできるだけ早く傍受し、より多くのヒントを持ってエラーを報告することができる.
標準変換方法
一番重要な方法は最初にtoString()です.この方法の役割は、このオブジェクトを表す文字列を返すことです.文字列を使用したい場所でオブジェクトを使用すると(例えばオブジェクトが属性名を作ったり、「+」演算子を使って文字列接続を行います)、JavaScriptは自動的にこの方法を呼び出します.この方法が実現されていない場合、クラスはデフォルトでObject.prototypeからtostring()方法を継承します.この方法の演算結果は「oject Object Object」です.この文字列はあまり役に立たないです.String()方法は、読み取り可能な文字列を返すべきで、最終的にはユーザがこの出力値を利用することができますが、必ずしもそうではない場合があります.いずれにしても、読み取り可能な文字列のtoString()に戻る方法は、プログラムの調整をより容易にすることができます.
toLocale String()とtoStering()は極めて類似しています.toLocale String()は、ローカル感度(locale-sensitive)の方式で対象を文字列に変換します.デフォルトでは、オブジェクト継承のためのtoLocal String()メソッドは単にtoString()メソッドを呼び出すだけです.いくつかの内蔵型は、実際にローカライズに関連する文字列を返すための有用なtoLocareStering()法を含む.オブジェクトから文字列への変換が必要な場合.
第3の方法は、オブジェクトを元の値に変換するためのvalueOf()である.例えば、数学演算子(「+」演算子を除く)と関係演算子が、数字テキスト表示のオブジェクトに作用すると、自動的にvalueOf()メソッドが起動されます.ほとんどのオブジェクトは、それらを表す適切なオリジナル値を持っていません.また、この方法を定義していません.
第4の方法はtoJSON()であり、この方法はJSON.strigify()によって自動的に呼び出される.JSONフォーマットは、序列化されたデータ構造に使用され、JavaScriptの元の値、配列、純粋なオブジェクトを処理することができます.クラスとは関係なく、オブジェクトのプロトタイプと構造関数は無視されます.例えば、RangeオブジェクトやComplexオブジェクトをパラメータとしてJSON.strigify()に導入すると、{from”:1,to”または{r”:1,“i”:−1}のような文字列が返されます.これらの文字列をJSON.parse()に入力すると、RangeオブジェクトとComplexオブジェクトと同じ属性の純粋なオブジェクトが得られるが、このオブジェクトはRangeオブジェクトとComplexオブジェクトから継承される方法を含まない.
比較方法
JavaScriptの等しい演算子がオブジェクトを比較する場合、比較は参照であり、値ではない.つまり、2つのオブジェクトの参照が与えられていますが、それらが同じオブジェクトを指すかどうかではなく、この2つのオブジェクトが同じ属性名と同じ属性値を持つかどうかを確認するのではなく、この2つの個別のオブジェクトを直接比較するか、またはそれらの順序を比較します.クラスを定義し、クラスのインスタンスを比較したい場合、適切な方法を定義して比較動作を実行するべきである.
簡単なクラスでは、それらのconstructor属性を簡単に比較することにより、2つのオブジェクトが同じタイプであることを確認し、その後、2つのオブジェクト属性を比較して、それらの値が同じであることを保証することができる.
たとえば、JavaScriptの関係にオブジェクトを使用すると、演算子が比較されます.