[英]Crockford's Prototypal inheritance - Issues with nested objects
我一直在閱讀道格拉斯·克羅克福德 (Douglas Crockford) 所著的“Javascript: The Good Parts”——雖然它有點極端,但我對他要說的很多內容都持贊同態度。
在第 3 章中,他討論了對象,並有一次展示了一種模式(也可在此處找到)以簡化和避免使用內置“new”關鍵字時出現的一些混淆/問題。
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
(也在小提琴上)
我做錯了什么,還是這種模式的限制?
沒有不一致之處。 只是不要考慮嵌套對象:對象的直接屬性總是在其原型或自己的屬性上。 屬性值是原始值還是對象無關緊要。
所以,當你做
var parent = {
x: {a:0}
};
var child = Object.create(parent);
child.x
將引用與parent.x
相同的對象 - 那個{a:0}
對象。 當您更改它的屬性時:
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
這意味着它們不是絕對獨立的——但仍然是兩個不同的對象。
我認為發生的事情是,當您創建person2
,它的sex
和info
屬性指的是nestObj
那些。 當您引用person2.info
,由於person2
沒有重新定義info
屬性,它會通過原型並在那里修改對象。
看起來“正確”的方法是您構建person3
的方式,以便對象有自己的info
對象要修改並且不會上升到原型。
我也在看這本書(慢慢地),所以我很同情你。 :)
我已經更改了示例,以便更好地演示此處發生的情況。 演示
首先我們創建一個具有三個屬性的對象; 一個數字、一個字符串和一個具有一個字符串值的屬性的對象。
然后我們使用Object.create()
從第一個對象創建第二個對象;
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"
}
看起來不錯吧? 我們有第一個對象和第二個復制對象。
沒那么快; 讓我們看看當我們更改第一個對象上的某些值時會發生什么。
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"
所以, num
和str
改變了obj2
而不是obj1
就像我們想要的那樣,但是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
請求首先需要對obj2.obj
的get
請求, obj2.obj
obj2
中不存在該請求,因此它轉發到obj1.obj
並依次轉發給obj1.obj.less
。
那么最后我們再次讀取obj2
時候,仍然沒有設置obj2.obj
這樣get
請求就會被轉發到obj1.obj
並返回我們之前修改過的設置,造成改變了第二個objects對象的一個屬性的效果child 似乎同時改變了兩者,但實際上它只是真正改變了第一個。
您可以使用此函數以遞歸方式返回與原始對象完全分離的新對象。
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"
}
一切都按照您預期的方式工作。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.