2012-04-12 47 views
12

ダグラス・クロフォードの "Javascript:The Good Parts"を読んできましたが、それはちょっと極端ですが、私は多くのことを言っています。Crockfordのプロトタイプ継承 - ネストされたオブジェクトの問題

第3章では、オブジェクトについて説明し、ある時点で&を簡略化して組み込みの "new"キーワードの使用に伴ういくつかの混乱や問題を回避するためのパターン(found here)をレイアウトしています。

if (typeof Object.create !== 'function') { 
    Object.create = function (o) { 
     function F() {} 
     F.prototype = o; 
     return new F(); 
    }; 
} 
newObject = Object.create(oldObject); 

だから私は、私が働いているプロジェクトで、これを使ってみたこと、およびネストされているオブジェクトを継承しようとしたとき、私はこの問題に気づきました。このパターンを使用して継承したネストされたオブジェクトの値を上書きすると、ネストされた要素がプロトタイプチェーンの上に上書きされます。

Crockfordの例は、次の例のflatObjとよく似ています。

私が何か間違ったことをやっている

var flatObj = { 
    firstname: "John", 
    lastname: "Doe", 
    age: 23 
} 
var person1 = Object.create(flatObj); 

var nestObj = { 
    sex: "female", 
    info: { 
     firstname: "Jane", 
     lastname: "Dough", 
     age: 32 
    } 
} 
var person2 = Object.create(nestObj); 

var nestObj2 = { 
    sex: "male", 
    info: { 
     firstname: "Arnold", 
     lastname: "Schwarzenneger", 
     age: 61 
    } 
} 
var person3 = { 
    sex: "male" 
} 
person3.info = Object.create(nestObj2.info); 

// now change the objects: 
person1.age = 69; 
person2.info.age = 96; 
person3.info.age = 0; 

// prototypes should not have changed: 
flatObj.age // 23 
nestObj.info.age // 96 ??? 
nestObj2.info.age // 61 

// now delete properties: 
delete person1.age; 
delete person2.info.age; 
delete person3.info.age; 

// prototypes should not have changed: 
flatObj.age // 23 
nestObj.info.age // undefined ??? 
nestObj2.info.age // 61 

(もfiddle上)、またはこれは、このパターンの制限である:行動は、しかし、ネストされたオブジェクトと矛盾していますか?

+0

関連:[JavaScript Object.create - 継承したプロパティを継承する](http://stackoverflow.com/q/3191103/1048572) – Bergi

答えて

10

矛盾はありません。ネストされたオブジェクトを考えないでください。オブジェクトの直接プロパティは、常にそのプロトタイプまたは独自のプロパティのいずれかにあります。プロパティの値をプリミティブまたはオブジェクトにするのは無関係です。 1つの{a:0}オブジェクトこと - あなたは

var parent = { 
    x: {a:0} 
}; 
var child = Object.create(parent); 

child.xparent.xと同じオブジェクトを参照することになるんので

、。あなたがそのプロパティを変更したとき:

var prop_val = child.x; // == parent.x 
prop_val.a = 1; 

両方が影響を受けます。独立して「ネストされた」プロパティを変更するには、最初に独立したオブジェクトを作成する必要があります:あなたは何ができるか

child.x = {a:0}; 
child.x.a = 1; 
parent.x.a; // still 0 

を、彼らは絶対に独立していないこと

child.x = Object.create(parent.x); 
child.x.a = 1; 
delete child.x.a; // (child.x).a == 0, because child.x inherits from parent.x 
delete child.x; // (child).x.a == 0, because child inherits from parent 

である - が、それでも異なる2オブジェクト。

+0

これは事柄をかなり明確にしています。ありがとう:)すべてのオブジェクトが参照されているので... Duh。これほど便利ではない。しかたがない。 – 1nfiniti

+0

* "オブジェクトの直接プロパティは、常にそのプロトタイプまたは独自のプロパティのいずれかにあります" * - 私にとって直接的なプロパティは、オブジェクト自身のプロパティを意味し、プロトタイプチェーンのものは*間接*と呼ばれることがあります。あなたは、直接財産、または間接*財産とは何かを正確に説明できますか? –

+0

@TJ私は、ここでは "入れ子"の反対を意味するために "直接"という言葉を使用したと思います。プロパティチェーンの単一のレベルと言えます。 – Bergi

1

私はperson2を作成するとき、sexinfoのプロパティはnestObjのものを参照していると思います。 person2.infoを参照すると、person2infoプロパティを再定義しないため、プロトタイプに移動してそこでオブジェクトを変更します。

をビルドする方法は「正しい」方法のように見えます。そのため、オブジェクトには修正するオブジェクトがあり、プロトタイプまでは移動しません。

私も(ゆっくりと)本を読んでいるので、私はあなたに共感します。 :)

1

私はここで起こっていることのより良いデモンストレーションを与えるために例を変更しました。Demo

まず、3つのプロパティを持つオブジェクトを作成します。数値、文字列、および文字列値を持つ1つのプロパティを持つオブジェクト。

次に、Object.create()を使用して最初のオブジェクトから2番目のオブジェクトを作成します。

var obj1 = { 
    num : 1, 
    str : 'foo', 
    obj : { less: 'more' } 
}; 
var obj2 = Object.create(obj1); 

console.log('[1] obj1:', obj1); 
console.log('[1] obj2:', obj2); 
"[1] obj1:" 
[object Object] { 
    num: 1, 
    obj: [object Object] { 
    less: "more" 
    }, 
    str: "foo" 
} 
"[1] obj2:" 
[object Object] { 
    num: 1, 
    obj: [object Object] { 
    less: "more" 
    }, 
    str: "foo" 
} 

十分な権利を見えますか?最初のオブジェクトと2番目にコピーされたオブジェクトがあります。

あまり速くない。最初のオブジェクトの値の一部を変更するとどうなるかを見てみましょう。

obj1.num = 3; 
obj1.str = 'bar'; 
obj1.obj.less = 'less'; 

console.log('[2] obj1:', obj1); 
console.log('[2] obj2:', obj2); 
"[2] obj1:" 
[object Object] { 
    num: 3, 
    obj: [object Object] { 
    less: "less" 
    }, 
    str: "bar" 
} 
"[2] obj2:" 
[object Object] { 
    num: 3, 
    obj: [object Object] { 
    less: "less" 
    }, 
    str: "bar" 
} 

は今、再び、私たちは私たちの最初の変化を持つオブジェクト、およびそのオブジェクトのコピーを持っています。ここで何が起こっていますか?

オブジェクトに独自のプロパティがあるかどうかを確認しましょう。

for(var prop in obj1) console.log('[3] obj1.hasOwnProperty(' + prop + '): ' + obj1.hasOwnProperty(prop)); 
for(var prop in obj2) console.log('[3] obj2.hasOwnProperty(' + prop + '): ' + obj2.hasOwnProperty(prop)); 
"[3] obj1.hasOwnProperty(num): true" 
"[3] obj1.hasOwnProperty(str): true" 
"[3] obj1.hasOwnProperty(obj): true" 
"[3] obj2.hasOwnProperty(num): false" 
"[3] obj2.hasOwnProperty(str): false" 
"[3] obj2.hasOwnProperty(obj): false" 

obj1我々が定義されたばかりのような、独自のプロパティのすべてを持っていますが、obj2ません。

obj2の一部を変更したらどうなりますか?

obj2.num = 1; 
obj2.str = 'baz'; 
obj2.obj.less = 'more'; 

console.log('[4] obj1:', obj1); 
console.log('[4] obj2:', obj2); 
for(var prop in obj1) console.log('[4] obj1.hasOwnProperty(' + prop + '): ' + obj1.hasOwnProperty(prop)); 
for(var prop in obj2) console.log('[4] obj2.hasOwnProperty(' + prop + '): ' + obj2.hasOwnProperty(prop)); 
"[4] obj1:" 
[object Object] { 
    num: 3, 
    obj: [object Object] { 
    less: "more" 
    }, 
    str: "bar" 
} 
"[4] obj2:" 
[object Object] { 
    num: 1, 
    obj: [object Object] { 
    less: "more" 
    }, 
    str: "baz" 
} 
"[4] obj1.hasOwnProperty(num): true" 
"[4] obj1.hasOwnProperty(str): true" 
"[4] obj1.hasOwnProperty(obj): true" 
"[4] obj2.hasOwnProperty(num): true" 
"[4] obj2.hasOwnProperty(str): true" 
"[4] obj2.hasOwnProperty(obj): false" 

ので、numstrは、我々が望んでいただけのようobj1obj2ないで変更しましたが、それは持つべきではないときobj1.obj.lessが変更されました。

hasOwnProperty()から、obj2.obj.lessを変更しても、obj2.objを最初に設定していないことがわかります。つまり、まだobj1.obj.lessを参照しています。

obj1.objからオブジェクトを作成し、それをobj2.objに割り当てて、それが私たちが探しているものを与えるかどうかを確認します。良いことだ

obj2.obj = Object.create(obj1.obj); 

console.log('[5] obj1:', obj1); 
console.log('[5] obj2:', obj2); 
for(var prop in obj1) console.log('[5] obj1.hasOwnProperty(' + prop + '): ' + obj1.hasOwnProperty(prop)); 
for(var prop in obj2) console.log('[5] obj2.hasOwnProperty(' + prop + '): ' + obj2.hasOwnProperty(prop)); 
"[5] obj1:" 
[object Object] { 
    num: 3, 
    obj: [object Object] { 
    less: "more" 
    }, 
    str: "bar" 
} 
"[5] obj2:" 
[object Object] { 
    num: 1, 
    obj: [object Object] { 
    less: "more" 
    }, 
    str: "baz" 
} 
"[5] obj1.hasOwnProperty(num): true" 
"[5] obj1.hasOwnProperty(str): true" 
"[5] obj1.hasOwnProperty(obj): true" 
"[5] obj2.hasOwnProperty(num): true" 
"[5] obj2.hasOwnProperty(str): true" 
"[5] obj2.hasOwnProperty(obj): true" 

、今obj2は独自のobj性質を持っています。今すぐobj2.obj.lessを変更するとどうなるか見てみましょう。

obj2.obj.less = 'less'; 

console.log('[6] obj1:', obj1); 
console.log('[6] obj2:', obj2); 
"[6] obj1:" 
[object Object] { 
    num: 3, 
    obj: [object Object] { 
    less: "more" 
    }, 
    str: "bar" 
} 
"[6] obj2:" 
[object Object] { 
    num: 1, 
    obj: [object Object] { 
    less: "less" 
    }, 
    str: "baz" 
} 

がそれでは、このすべてを教えてくれることはプロパティがまだ作成されたオブジェクトに変更されていない場合、そのプロパティの作成したオブジェクトへのget要求が元のオブジェクトに転送される、ということです。

前のコードブロックからobj2.obj.less = 'more'ためset要求は、最初に、obj1.objおよびターンobj1.obj.lessに転送するように、その時点でobj2に存在しないobj2.objためget要求を必要とします。

、我々は再びobj2を読んだときに最後に、我々はまだget要求がobj1.objに転送されるようにobj2.objを設定していないと第二のオブジェクトオブジェクトのプロパティを変更するという効果を引き起こし、我々は以前に変更した設定値を返します子供は両方を変えているようですが、実際は最初は実際に変わっています。


この関数を使用すると、元のオブジェクトから完全に分離した新しいオブジェクトを再帰的に返すことができます。

Demo

var obj1 = { 
    num : 1, 
    str : 'foo', 
    obj : { less: 'more' } 
}; 
var obj2 = separateObject(obj1); 

function separateObject(obj1) { 

    var obj2 = Object.create(Object.getPrototypeOf(obj1)); 
    for(var prop in obj1) { 
     if(typeof obj1[prop] === "object") 
      obj2[prop] = separateObject(obj1[prop]); 
     else 
      obj2[prop] = obj1[prop]; 
    } 

    return obj2; 
} 

console.log('[1] obj1:', obj1); 
console.log('[1] obj2:', obj2); 
for(var prop in obj1) console.log('[1] obj1.hasOwnProperty(' + prop + '): ' + obj1.hasOwnProperty(prop)); 
for(var prop in obj2) console.log('[1] obj2.hasOwnProperty(' + prop + '): ' + obj2.hasOwnProperty(prop)); 
"[1] obj1:" 
[object Object] { 
    num: 1, 
    obj: [object Object] { 
    less: "more" 
    }, 
    str: "foo" 
} 
"[1] obj2:" 
[object Object] { 
    num: 1, 
    obj: [object Object] { 
    less: "more" 
    }, 
    str: "foo" 
} 
"[1] obj1.hasOwnProperty(num): true" 
"[1] obj1.hasOwnProperty(str): true" 
"[1] obj1.hasOwnProperty(obj): true" 
"[1] obj2.hasOwnProperty(num): true" 
"[1] obj2.hasOwnProperty(str): true" 
"[1] obj2.hasOwnProperty(obj): true" 

のは、私たちは今、いくつかの変数を変更したときに何が起こるか見てみましょう。

obj1.num = 3; 
obj1.str = 'bar'; 
obj1.obj.less = 'less'; 

console.log('[2] obj1:', obj1); 
console.log('[2] obj2:', obj2); 
"[2] obj1:" 
[object Object] { 
    num: 3, 
    obj: [object Object] { 
    less: "less" 
    }, 
    str: "bar" 
} 
"[2] obj2:" 
[object Object] { 
    num: 1, 
    obj: [object Object] { 
    less: "more" 
    }, 
    str: "foo" 
} 

すべてはまさにあなたはそれが期待されるように動作します。

+0

継承構造を表示せずに 'console.log'オブジェクトがどのようなコンソールを好みますか? – Bergi

+0

'separateObject'については' Object.create(Object.getPrototypeOf(obj1)) 'をお勧めします – Bergi

+0

私はJSBinのコンソールから出力をコピーしました。私は 'Object.getPrototypeOf(obj1)'を試しましたが、空のオブジェクトを返しました。何か不足していますか? I –

関連する問題