簡體   English   中英

Crockford 的原型繼承 - 嵌套對象的問題

[英]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 ,它的sexinfo屬性指的是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"

所以, numstr改變了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.objget請求, 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM