2010-09-01 38 views
20

Tôi nghĩ rằng tôi đã hiểu sai cách hoạt động của kế thừa nguyên mẫu Javascript. Cụ thể, các biến nội bộ nguyên mẫu dường như được chia sẻ giữa nhiều đối tượng con khác nhau. Nó là dễ dàng nhất để minh họa với mã:Biến riêng tư trong các mẫu thử được thừa kế

var A = function() 
{ 
    var internal = 0; 
    this.increment = function() 
    { 
    return ++internal; 
    }; 
}; 

var B = function() {}; 
// inherit from A 
B.prototype = new A; 

x = new B; 
y = new B; 

$('#hello').text(x.increment() + " - " + y.increment());​ 

này kết quả đầu ra 1 - 2 (thử nghiệm nó trên JSBin), trong khi tôi hoàn toàn mong đợi kết quả được 1 - 1, vì tôi muốn hai đối tượng riêng biệt.

Làm cách nào để đảm bảo rằng đối tượng A không phải là đối tượng được chia sẻ giữa nhiều phiên bản B?

Cập nhật: This article nổi bật một số vấn đề:

Vấn đề là phạm vi mỗi phương pháp sử dụng để tạo ra một biến tư nhân, trong đó hoạt động tốt, cũng là đóng cửa, trong hành động, mà kết quả nếu bạn thay đổi một biến riêng cho một cá thể đối tượng, nó đang được thay đổi cho tất cả. I E. nó giống như một thuộc tính tĩnh riêng tư hơn là một biến riêng tư thực sự. Vì vậy, nếu bạn muốn có một cái gì đó riêng tư, giống như một hằng số ngoài công lập, bất kỳ phương pháp nào ở trên đều tốt, nhưng không phải cho các biến riêng tư thực tế. Biến riêng tư chỉ hoạt động thực sự tốt với các đối tượng singleton trong JavaScript.

Giải pháp: Như mỗi câu trả lời BGerrissen của, thay đổi tuyên bố B và để lại của nguyên mẫu hoạt động như dự định:

var B = function() { A.apply(this, arguments); }; 

Trả lời

17

Thành viên riêng tư khó sử dụng kế thừa nguyên mẫu. Đối với một, họ không thể được thừa hưởng. Bạn cần tạo các thành viên riêng trong mỗi hàm tạo riêng lẻ. Bạn có thể làm điều này bằng cách áp dụng hàm tạo siêu trong lớp con hoặc tạo một trình trang trí.

Decorator dụ:

function internalDecorator(obj){ 
    var internal = 0; 
    obj.increment = function(){ 
     return ++internal; 
    } 
} 

var A = function(){ 
    internalDecorator(this); 
} 
A.prototype = {public:function(){/*etc*/}} 

var B = function(){ 
    internalDecorator(this); 
} 
B.prototype = new A(); // inherits 'public' but ALSO redundant private member code. 

var a = new B(); // has it's own private members 
var b = new B(); // has it's own private members 

Đây chỉ là một biến thể của cuộc gọi constructor siêu, bạn cũng có thể đạt được như vậy bằng cách gọi các nhà xây dựng siêu thực tế với .apply()

var B = function(){ 
    A.apply(this, arguments); 
} 

Bây giờ bằng cách áp dụng thừa kế thông qua B.prototype = new A() bạn gọi mã hàm xây dựng không cần thiết từ A.Một cách để tránh điều này là sử dụng Douglas Crockfords gây ra phương pháp:

Object.beget = function(obj){ 
    var fn = function(){} 
    fn.prototype = obj; 
    return new fn(); // now only its prototype is cloned. 
} 

nào bạn sử dụng như sau:

B.prototype = Object.beget(A.prototype); 

Tất nhiên, bạn có thể từ bỏ quyền thừa kế hoàn toàn và tận dụng tốt các trang trí, ít nhất nơi các thành viên tư nhân là cần thiết.

5

Mấu chốt của nguyên mẫu là nó được chia sẻ giữa nhiều đối tượng (tức là những cái được tạo ra bởi hàm xây dựng tương tự). Nếu bạn cần các biến không được chia sẻ giữa các đối tượng chia sẻ một mẫu thử nghiệm, bạn cần phải giữ các biến đó trong hàm hàm dựng cho mỗi đối tượng. Chỉ cần sử dụng nguyên mẫu cho các phương pháp chia sẻ.

Trong ví dụ của bạn, bạn không thể làm chính xác những gì bạn muốn bằng cách sử dụng nguyên mẫu. Đây là những gì bạn có thể làm thay vào đó. Xem câu trả lời của Daniel Earwicker để giải thích thêm rằng không có điểm nào tôi bây giờ tái tạo ở đây.

var A = function() {}; 

A.prototype.incrementPublic = function() 
{ 
    return ++this.publicProperty; 
}; 

var B = function() 
{ 
    this.publicProperty = 0; 
    var internal = 0; 
    this.incrementInternal = function() 
    { 
     return ++internal; 
    }; 
}; 

B.prototype = new A(); 

var x = new B(), y = new B(); 
console.log(x.incrementPublic(), y.incrementPublic()); // 1, 1 
console.log(x.incrementInternal(), y.incrementInternal()); // 1, 1 
+1

Nhưng toàn bộ vấn đề ở đây là các chức năng thuộc trong 'A', và được chia sẻ giữa nhiều phụ "lớp"(' b', 'C' và 'D'). Không có khả năng cho các biến riêng tư không tĩnh trong các siêu lớp trong Javascript? –

+1

Một cách phổ biến để biểu thị ý định 'riêng tư' (hay còn gọi là cảnh báo lập trình một cái gì đó đặc biệt đang diễn ra với thuộc tính) là sử dụng dấu gạch dưới làm tiền tố cho tên thuộc tính (ví dụ: obj._privateMember). Mặc dù điều này sẽ làm việc với thừa kế cho chuỗi và số, nó bắt đầu trở nên phức tạp hơn khi sử dụng các đối tượng và mảng làm thuộc tính. – BGerrissen

+0

Tôi không rõ chính xác những gì bạn đang yêu cầu. Bạn có muốn một biến được xác định trong hàm 'A' có sẵn cho các phương thức' B', 'C' và' D' nhưng không ở đâu khác? Cố gắng không nghĩ về những điều trong JavaScript về các tính năng của các ngôn ngữ khác (như Java hoặc C#). JavaScript đơn giản là không có các lớp. Nó có các đối tượng kế thừa các thuộc tính từ các đối tượng khác thông qua một chuỗi nguyên mẫu. –

15

Bạn cần quên ý tưởng về các lớp học. Không thực sự là một điều trong JavaScript như là một 'thể hiện của B'. Chỉ có 'một số đối tượng bạn thu được bằng cách gọi hàm khởi tạo B'. Một đối tượng có các thuộc tính. Một số là thuộc tính "riêng" của nó, một số khác được bao gồm bằng cách tìm kiếm chuỗi nguyên mẫu.

Khi bạn nói new A, bạn đang tạo một đối tượng. Sau đó, bạn gán nó làm nguyên mẫu cho B, có nghĩa là mọi cuộc gọi đến new B sẽ tạo ra một đối tượng mới có cùng nguyên mẫu trực tiếp, và do đó có cùng biến số đếm.

Trong câu trả lời của Tim Down, hai lựa chọn thay thế được hiển thị. incrementPublic của anh ta sử dụng thừa kế, nhưng làm cho biến truy cập công khai (tức là từ bỏ đóng gói). Trong khi đó incrementInternal làm cho bộ đếm riêng tư nhưng thành công bằng cách di chuyển mã vào B (nghĩa là từ bỏ quyền thừa kế).

Những gì bạn muốn là một sự kết hợp của ba điều:

  • hành vi thừa kế - vì vậy nó phải được định nghĩa trong A và không cần mã trong B ngoài việc thiết lập các nguyên mẫu
  • dữ liệu cá nhân, lưu trữ trong đóng cửa -Local biến số
  • dữ liệu từng trường hợp, được lưu trữ trong this.

Vấn đề là mâu thuẫn giữa hai người cuối cùng. Tôi cũng sẽ nói rằng thừa kế là có giá trị giới hạn trong JS anyway. Tốt hơn là coi ngôn ngữ đó là ngôn ngữ chức năng:

// higher-order function, returns another function with counter state 
var makeCounter = function() { 
    var c = 0; 
    return function() { return ++c; }; 
}; 

// make an object with an 'increment' method: 
var incrementable = { 
    increment: makeCounter() 
}; 

Cá nhân tôi có xu hướng tránh hàm khởi tạo và thừa kế phần lớn thời gian. Chúng ít hữu ích hơn trong JS so với những người từ một nền tảng OO giả định.

Cập nhật tôi sẽ cảnh giác với báo cáo kết quả bạn trích dẫn trong bản cập nhật của bạn:

Private biến chỉ có tác dụng thực sự tốt với các đối tượng singleton trong JavaScript.

Điều đó không thực sự đúng. Một đối tượng chỉ là một 'từ điển' của các thuộc tính, bất kỳ thuộc tính nào cũng có thể là các hàm. Vì vậy, quên đối tượng và suy nghĩ về các chức năng. Bạn có thể tạo nhiều phiên bản của một hàm theo một số mẫu, bằng cách viết một hàm trả về một hàm. Ví dụ makeCounter chỉ là một ví dụ đơn giản về điều này. makeCounter không phải là một "đối tượng singleton" và không bị giới hạn sử dụng trong các đối tượng đơn lẻ.

+1

+1. Lời giải thích hay. Tôi sẽ để lại câu trả lời của tôi vì nó và không cố gắng viết lại những gì bạn đã viết ở đây. Cá nhân, tôi tìm thấy nguyên mẫu và các hàm tạo hữu ích, nếu chỉ để chia sẻ các phương thức giữa nhiều đối tượng liên quan. –

+0

+1 để khai sáng tính nguyên mẫu. – BGerrissen

0

Tôi vừa tìm ra giải pháp khôn lanh khác, không xuất bất kỳ phương thức/biến nào vào đối tượng công khai.

function A(inherit) { 
    var privates = { //setup private vars, they could be also changed, added in method or children 
     a: 1, 
     b: 2, 
     c: 3 
    }; 
    //setup public methods which uses privates 
    this.aPlus = bindPlus("aPlus", this, privates); //pass method name as string! 
    this.aGet = bindPlus("aGet", this, privates); 
    if (inherit) { 
     return privates; 
    } 
} 
A.prototype.aPlus = function() { 
    var args = getArgs(arguments), 
     //self is "this" here 
     self = args.shift(), 
     privates = args.shift(), 
     //function real arguments 
     n = args.shift(); 
    return privates.a += n; 
}; 

A.prototype.aGet = function (n) { 
    var args = getArgs(arguments), 
     self = args.shift(), 
     privates = args.shift(); 
    console.log(this, self, privates); 
    return privates.a; 
}; 

//utilites 
function getArgs(arg) { 
    return Array.prototype.slice.call(arg); 
} 

function bindPlus(funct, self, privates) { 
    return function() { 
     return Object.getPrototypeOf(self)[funct].bind(this, self, privates).apply(null, arguments); 
    }; 
} 

//inherited 
function B(inherit) { 
    var privates = Object.getPrototypeOf(this).constructor.call(this, true); 
    privates.d = 4; 
    this.dGet = bindPlus("dGet", this, privates); 
    if (inherit) { 
     return privates; 
    } 
} 

B.prototype = Object.create(A.prototype); 
B.constructor = B; 

B.prototype.aGet = function() { 
    var args = getArgs(arguments), 
     self = args.shift(), 
     privates = args.shift(); 
    console.warn("B.aGet", this, privates); 
    return privates.a; 
}; 

B.prototype.dGet = function() { 
    var args = getArgs(arguments), 
     self = args.shift(), 
     privates = args.shift(); 
    console.warn("B.dGet", this, privates); 
    return privates.d; 
}; 


// tests 
var b = new B(); 
var a = new A(); 

//should be 223 
console.log("223 ?",b.aPlus(222)); 

//should be 42 
console.log("41",a.aPlus(222)); 

Nhiều thử nghiệm và các mẫu ở đây: http://jsfiddle.net/oceog/TJH9Q/

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