JavaScriptシリーズ5:強力なプロトタイプとプロトタイプチェーンを深く理解する


前言JavaScriptは、従来のクラス継承モデルではなく、prototypalプロトタイプモデルを使用する.
これはしばしばJavaScriptの欠点として言及されるが,プロトタイプベースの継承モデルは従来のクラス継承よりも強い.従来のクラス継承モデルを実現するのは簡単ですが、JavaScriptでのプロトタイプ継承を実現するのは難しいことが多いです.
JavaScriptは、プロトタイプ継承に基づいて広く使用されている唯一の言語であるため、2つの継承パターンの違いを理解するには時間がかかるので、今日はプロトタイプとプロトタイプチェーンについて理解してみましょう.
プロトタイプ
14年前、私(TOMおじさん)がJavaScriptを習ったばかりの頃は、一般的に次のようにコードを書きました.
var decimalDigits = 2,
    tax = 5;

function add(x, y) {
    return x + y;
}

function subtract(x, y) {
    return x - y;
}

//alert(add(1, 3));

各functionを実行することで結果が得られ,プロトタイプを学習した後,コードを美化するために以下の方法を用いることができる.
プロトタイプ使用方法1
プロトタイプを使用する前に、コードを小さな変更する必要があります.
var Calculator = function (decimalDigits, tax) {
    this.decimalDigits = decimalDigits;
    this.tax = tax;
};

次に、Calculatorオブジェクトのprototypeプロパティにオブジェクトのフォント量を割り当てることで、Calculatorオブジェクトのプロトタイプを設定します.
Calculator.prototype = {
    add: function (x, y) {
        return x + y;
    },
    subtract: function (x, y) {
        return x - y;
    }
};
//alert((new Calculator()).add(1, 3));

これにより、new Calculatorオブジェクト以降、addメソッドを呼び出して結果を計算することができます.
プロトタイプ使用方式2
2つ目の方法は、プロトタイプprototypeを付与するときに、functionが直ちに実行する式を使用して、以下のフォーマットで値を付与することである.
Calculator.prototype = function () { } ();

その利点は前の文章ですでに知っていて、私有のfunctionをカプセル化することができて、returnの形式を通じて簡単な使用名を暴露して、public/privateの効果を達成して、修正したコードは以下の通りです:
Calculator.prototype = function () {
    add = function (x, y) {
        return x + y;
    },
    subtract = function (x, y) {
        return x - y;
    }
    return {
        add: add,
        subtract: subtract
    }
}();

//alert((new Calculator()).add(11, 3));

同様に、new Calculatorオブジェクトは、addメソッドを呼び出して結果を計算することができる.
もうちょっと
ステップ宣言
上記のプロトタイプを使用する場合、一度にプロトタイプオブジェクトを設定するという制限がありますが、プロトタイプの各属性をどのように分けて設定するかについてお話ししましょう.
var BaseCalculator = function () {
    //              
    this.decimalDigits = 2;
};

//     BaseCalculator  2     
BaseCalculator.prototype.add = function (x, y) {
    return x + y;
};

BaseCalculator.prototype.subtract = function (x, y) {
    return x - y;
};

まず、BaseCalculatorオブジェクトが宣言され、コンストラクション関数には小数点以下の属性decimalDigitsが初期化され、プロトタイプ属性によってfunctionが2つ設定されます.それぞれadd(x,y)subtract(x,y)です.もちろん、前述の2つの方法のいずれかを使用することもできます.我々の主な目的は,kオブジェクトを真のCalculatorのプロトタイプにどのように設定するかを見ることである.
var BaseCalculator = function() {
    this.decimalDigits = 2;
};

BaseCalculator.prototype = {
    add: function(x, y) {
        return x + y;
    },
    subtract: function(x, y) {
        return x - y;
    }
};

上記のコードを作成したら、次のようにします.
var Calculator = function () {
    //              
    this.tax = 5;
};

Calculator.prototype = new BaseCalculator();
CalculatorのプロトタイプはBaseCalculatorのインスタンスに指向されていることがわかります.Calculatoradd(x,y)subtract(x,y)の2つのfunctionに統合することを目的としています.また、そのプロトタイプはBaseCalculatorのインスタンスであるため、Calculatorのオブジェクトインスタンスをいくつ作成しても、彼らのプロトタイプは同じインスタンスを指しています.
var calc = new Calculator();
alert(calc.add(1, 1));
//BaseCalculator     decimalDigits  ,  Calculator        
alert(calc.decimalDigits);

上のコードは、実行後、CalculatorのプロトタイプがBaseCalculatorのインスタンスを指しているため、彼のdecimalDigits属性値にアクセスできることがわかります.では、BaseCalculatorの構造関数で宣言された属性値にCalculatorをアクセスさせたくない場合は、どうすればいいですか.次のようにします.
var Calculator = function () {
    this.tax= 5;
};

Calculator.prototype = BaseCalculator.prototype;

BaseCalculatorのプロトタイプをCalculatorのプロトタイプに割り当てることで、CalculatorのインスタンスではそのdecimalDigits値にアクセスできません.次のコードにアクセスすると、エラーが表示されます.
var calc = new Calculator();
alert(calc.add(1, 1)); //2
alert(calc.decimalDigits); //undefined

プロトタイプの書き換え
サードパーティJSクラスライブラリを使用する場合、彼らが定義したプロトタイプメソッドは私たちのニーズを満たすことができないことがよくありますが、このクラスライブラリから離れられないので、プロトタイプの1つ以上の属性やfunctionを書き換える必要があります.同じaddコードを宣言し続けることで、前のadd機能を上書きすることができます.コードは次のとおりです.
//    Calculator add() function
Calculator.prototype.add = function (x, y) {
    return x + y + this.tax;
};

var calc = new Calculator();
alert(calc.add(1, 1));

このように,計算した結果は従来より1つのtaxの値が多くなったが,書き換えたコードを最後に置く必要があり,前のコードを上書きすることができる点に注意する必要がある.
プロトタイプチェーン
プロトタイプチェーンを作成する前に、次のコードを行います.
function Foo() {
    this.value = 42;
}
Foo.prototype = {
    method: function() {}
};

function Bar() {}

//   Bar prototype   Foo     
Bar.prototype = new Foo();
Bar.prototype.foo = 'Hello World';

//   Bar.prototype.constructor Bar  
Bar.prototype.constructor = Bar;

var test = new Bar() //   Bar      

//    
test [Bar   ]
    Bar.prototype [Foo   ]
        { foo: 'Hello World' }
        Foo.prototype
            {method: ...};
            Object.prototype
                {toString: ... /* etc. */};

上記の例では、testのオブジェクトはBar.prototypeおよびFoo.prototypeから継承される.したがって、Fooのプロトタイプ方法methodにアクセスすることができる.同時に、そのプロトタイプに定義されたFooインスタンス属性valueにもアクセスすることができる.new Bar() は、新しいFooのインスタンスを作成するのではなく、そのプロトタイプ上のインスタンスを繰り返し使用することに注意してください.したがって、すべてのBarインスタンスは、同じvalue属性を共有する.
属性の検索
オブジェクトのプロパティを検索すると、JavaScriptは、指定された名前のプロパティが見つかるまでプロトタイプチェーンを上に移動し、プロトタイプチェーンの上部、つまりObject.prototypeに到達するまで検索しますが、指定されたプロパティが見つからない場合は、undefinedに戻ります.例を見てみましょう.
function foo() {
    this.add = function (x, y) {
        return x + y;
    }
}

foo.prototype.add = function (x, y) {
    return x + y + 10;
}

Object.prototype.subtract = function (x, y) {
    return x - y;
}

var f = new foo();
alert(f.add(1, 2)); //   3,   13
alert(f.subtract(1, 2)); //   -1

コード実行によって、subtractは私たちが言ったように上を向いて検索して結果を得たことを発見しましたが、addの方法は少し違います.これも強調したいのですが、属性は検索するときにまず自分の属性を探して、もしプロトタイプを探していなければ、二度となくて、上を歩いて、Objectのプロトタイプをずっと調べています.だから、あるレベルでは、for in文で属性を巡回する場合、効率も問題です.
もう1つ注意しなければならないのは、任意のタイプのオブジェクトをプロトタイプに割り当てることができますが、次のコードが無効であるなど、元のタイプの値を割り当てることはできません.
function Foo() {}
Foo.prototype = 1; //   

义齿hasOwnPropertyObject.prototypeの1つの方法で、それは良いもので、彼は1つのオブジェクトがプロトタイプチェーン上の属性ではなくカスタム属性を含むかどうかを判断することができます.hasOwnProperty JavaScriptの中で唯一の処理属性ですが、プロトタイプチェーンを検索しない関数です.
//   Object.prototype
Object.prototype.bar = 1;
var foo = {goo: undefined};

foo.bar; // 1
'bar' in foo; // true

foo.hasOwnProperty('bar'); // false
foo.hasOwnProperty('goo'); // true

hasOwnPropertyだけが正確で望ましい結果を与えることができ、これはオブジェクトのプロパティを巡るときに役立ちます.オブジェクト自体のプロパティを定義するのではなく、プロトタイプチェーンのプロパティを除外する方法はありません.
しかし、JavaScriptはhasOwnPropertyが不正に占有されることを保護しないため、オブジェクトにたまたまこの属性が存在する場合は、外部のhasOwnProperty関数を使用して正しい結果を得る必要があります.
var foo = {
    hasOwnProperty: function() {
        return false;
    },
    bar: 'Here be dragons'
};

foo.hasOwnProperty('bar'); //      false

//   {}    hasOwnProperty,         foo
{}.hasOwnProperty.call(foo, 'bar'); // true

オブジェクトのプロパティが存在するかどうかを確認する場合、hasOwnPropertyは唯一の方法です.また、for in loopを使用してオブジェクトを巡回する場合は、常にhasOwnPropertyメソッドを使用することをお勧めします.これにより、プロトタイプオブジェクトの拡張による干渉を回避できます.例を見てみましょう.
//    Object.prototype
Object.prototype.bar = 1;

var foo = {moo: 2};
for(var i in foo) {
    console.log(i); //       :bar   moo
}

for in文の動作を変更することはできませんので、結果をフィルタするにはhasOwnPropertyメソッドしか使用できません.コードは次のとおりです.
// foo        
for(var i in foo) {
    if (foo.hasOwnProperty(i)) {
        console.log(i);
    }
}

このバージョンのコードは唯一正しい書き方です.hasOwnPropertyを使用しているので、今回はmooしか出力されません.hasOwnPropertyを使用しない場合、このコードは、Object.prototypeなどのオリジナルオブジェクトプロトタイプが拡張されたときにエラーが発生する可能性があります.
まとめ:hasOwnPropertyを推奨し、コードが実行されている環境を仮定しないで、オリジナルオブジェクトが拡張されているかどうかを仮定しないでください.
まとめ
プロトタイプは私たちの開発コードを大きく豊富にしていますが、普段使用している過程で上記の注意点に注意しなければなりません.
参考内容:http://bonsaiden.github.com/JavaScript-Garden/zh/
本文について
本文はTOMおじさんの深く理解するJavaScriptシリーズから転じます
【JavaScriptシリーズを深く理解する】オリジナル、翻訳、転載、整理などの各タイプの文章を含む文章で、原文はTOMおじさんの非常に良いテーマで、今それを再整理して発表します.おじさん、ありがとう.もしあなたが本文がいいと思ったら、推薦してください.支持してください.感謝しています.
もっと優秀な文章は私のコラムに注目してください.