2012-10-18 25 views
5

Chỉ khi tôi nghĩ tôi đã có JS tìm ra, tôi nhận được hung lên về vấn đề này:đối tượng instance và tính năng mà tài liệu tham khảo

function Obj() { 
    console.log('x: %s, o.x: %s', this.x++, this.o.x++); 
}  
Obj.prototype.x = 1; 
Obj.prototype.o = {x: 1}; 

dự kiến:

> new Obj 
x: 1, o.x: 1 
> new Obj 
x: 1, o.x: 1 
> new Obj 
x: 1, o.x: 1 

Thực tế:

> new Obj 
x: 1, o.x: 1 
> new Obj 
x: 1, o.x: 2 
> new Obj 
x: 1, o.x: 3 

Vì vậy, có vẻ như nếu một thuộc tính nguyên mẫu là một kiểu tham chiếu, thì nó được chia sẻ trên tất cả các trường hợp, nhưng nếu đó là một kiểu không tham chiếu vi nó được khởi tạo lại cho mỗi trường hợp; để xác nhận giả thuyết này, tôi đã thử nghiệm một vài loại khác, chẳng hạn như string (hoạt động như number) và array (hoạt động như object).

tôi đã xác định rằng tôi có thể tránh cái bẫy này nếu tôi reinitialize thuộc tính đối tượng trong ctor, như thế này:

function Obj() { 
    this.o = {x: 1}; 
} 

Mà chỉ có vẻ thực sự không chính thống (mà tôi muốn được yêu cầu tự reinitialize tính nhưng chỉ khi chúng là đối tượng tham chiếu).

Có ai có thể làm sáng tỏ những gì đang xảy ra không?

+1

'(mới Obj) .o === (mới Obj) .o' - thuộc tính [được kế thừa] tất cả trỏ đến cùng một đối tượng, có thuộc tính' x' bạn tăng. – Bergi

+0

* ".. chẳng hạn như chuỗi (hoạt động như số) và mảng (hoạt động như đối tượng) .." * tốt, nguyên thủy được sao chép theo giá trị (chuỗi, số, booleans) trong khi đối tượng (có, 'mảng' là một cũng vậy) được sao chép bằng tham chiếu. Vấn đề là khi bạn sửa đổi thuộc tính 'x' của đối tượng' o', bạn đang sửa đổi tham chiếu đến cùng đối tượng 'o' trong chuỗi nguyên mẫu. –

Trả lời

3

Hãy suy nghĩ theo cách này. Bạn sẽ luôn sửa đổi đối tượng mà bạn đang hoạt động, bất kể giá trị đến từ đâu.

Khi bạn làm điều này cho lần đầu tiên:

this.x++; 

Đó là nhận được giá trị của x từ Obj.prototype.x vì không có x sở hữu trực tiếp trên đối tượng this, tuy nhiên các ++ vẫn là hoạt động trên đối tượng this, vì vậy giá trị được đặt trên đối tượng đó. Giá trị đó bây giờ là trực tiếp trên cá thể cho các sửa đổi trong tương lai. (The prototype.x được shadowed cho đến khi tài sản trực tiếp là delete d.)

Tuy nhiên, khi bạn làm điều này:

this.o.x++ 

Các hoạt động chỉ trên đối tượng this là tra cứu của o. Vì một lần nữa không có thuộc tính o trên this, bạn sẽ nhận được tham chiếu đến đối tượng được lưu trữ tại Obj.prototype.o. Tại thời điểm này, không có sửa đổi thực tế của đối tượng this.

Sau khi tham chiếu được trả lại, sau đó bạn tìm kiếm thuộc tính x trên đối tượng Obj.prototype.o và sửa đổi đối tượng đó.

Vì vậy, nó thực sự khá nhất quán. Với this.o.x++ bạn đã thực hiện tra cứu trên this, nhưng không có đột biến. Đột biến là trên đối tượng được tham chiếu. Nhưng với this.x++, bạn đang đột biến trực tiếp đối tượng.


Vì vậy, có, trừ khi bạn muốn loại tài liệu tham khảo của bạn sẽ được chia sẻ giữa tất cả các trường được tạo ra từ các nhà xây dựng, bạn nên đặt các loại tài liệu tham khảo trực tiếp trên các trường hợp và không phải trên .prototype.

+0

Cảm ơn! Bạn đã xác nhận những gì tôi nghi ngờ (mặc dù bạn đã nói rõ hơn). – ken

+0

Bạn được chào đón. –

0

Tôi nghĩ câu trả lời đầu tiên có ý chính của nó, nhưng tôi trả lời từ một góc hơi khác.

Chìa khóa để hiểu nó là hành vi đối tượng liên quan đến việc gán cho một đối tượng.

Tra cứu sẽ trở lại nguyên mẫu nếu không có thuộc tính mẫu. Luôn luôn.

Bài tập, tuy nhiên, sẽ tự động tạo thuộc tính mẫu mới mỗi lần. Điều này sẽ không thực sự quan trọng nếu bạn gán một thuộc tính prototyped bằng tham chiếu đến một thuộc tính instance mới vì cả hai đều chỉ là con trỏ tới cùng một thứ. Và nếu bạn thay đổi tại chỗ, sẽ không có thuộc tính thể hiện mới nào được tạo.

function Obj(){ 
    //this.refArr resolves to what's in the prototype and that's what we change 
    this.refArr.push(1); 

    //now copy sliceArray and assign a new but identical array 
    this.sliceArray = this.sliceArray.slice(0); 

    //no length increase would mean a new instance property is created 
    //... and then the assigment took place. 
    this.sliceArray.push(1); 
    console.log('ref:'+this.refArr.length +', sliceArray:'+this.sliceArray.length); 
} 
Obj.prototype.refArr = []; 
Obj.prototype.sliceArray = []; 
new Obj; new Obj; 
//ref:1, sliceArray:1 
//ref:2, sliceArray:1 

Và đó thực chất là sự khác biệt về những gì bạn đang làm. this.x = this.x + 1 tạo thuộc tính mới. Vì bạn chỉ định cho thuộc tính của this.constructor.prototype.o, không có thuộc tính mới nào được tạo. Nếu bạn tăng lên và sau đó được giao this.o để this.o nó sẽ không quan trọng anyway, kể từ khi nguyên mẫu và phiên bản tài sản mới sẽ chỉ trỏ vào cùng một đối tượng.

+1

Tất nhiên 'this.refArr = this.refArr;' sẽ tạo một thuộc tính mới, nhưng nó trỏ tới cùng mảng với thuộc tính được thừa kế từ trước. Bạn có thể dễ dàng kiểm tra bằng cách thay đổi 'Obj.prototype.refArr' – Bergi

+0

Ah, bạn nói đúng. Tôi đã sử dụng sai hasOwnProperty trong một bài kiểm tra. Đang sửa. –

Các vấn đề liên quan