2010-05-06 32 views
23

Thông thường, tôi đã nhìn thấy chức năng nguyên mẫu tuyên bố bên ngoài định nghĩa lớp, như thế này:Setting javascript chức năng nguyên mẫu trong khai báo lớp đối tượng

function Container(param) { 
    this.member = param; 
} 
Container.prototype.stamp = function (string) { 
    return this.member + string; 
} 

var container1 = new Container('A'); 
alert(container1.member); 
alert(container1.stamp('X')); 

Mã này tạo ra hai cảnh báo với các giá trị "A" và "AX" .

Tôi muốn xác định hàm nguyên mẫu INSIDE của định nghĩa lớp. Có điều gì sai trái khi làm việc như thế này không?

function Container(param) { 
    this.member = param; 
    if (!Container.prototype.stamp) { 
     Container.prototype.stamp = function() { 
      return this.member + string; 
     } 
    } 
} 

Tôi đã thử cách này để tôi có thể truy cập biến riêng tư trong lớp học. Nhưng tôi đã phát hiện ra rằng nếu hàm nguyên mẫu của tôi tham chiếu đến biến riêng tư, giá trị của biến riêng tư luôn là giá trị được sử dụng khi hàm nguyên mẫu được tạo ra, không phải giá trị trong đối tượng đối tượng:

Container = function(param) { 
    this.member = param; 
    var privateVar = param; 
    if (!Container.prototype.stamp) { 
     Container.prototype.stamp = function(string) { 
      return privateVar + this.member + string; 
     } 
    } 
} 
var container1 = new Container('A'); 
var container2 = new Container('B'); 
alert(container1.stamp('X')); 
alert(container2.stamp('X')); 

Mã này tạo ra hai cảnh báo với các giá trị "AAX" và "ABX". Tôi đã hy vọng đầu ra sẽ là "AAX" và "BBX". Tôi tò mò tại sao điều này không hoạt động, và nếu có một số mô hình khác mà tôi có thể sử dụng để thay thế.

EDIT: Lưu ý rằng tôi hoàn toàn hiểu rằng đối với ví dụ đơn giản này, tốt nhất là chỉ nên sử dụng một đóng như this.stamp = function() {} và không sử dụng nguyên mẫu nào cả. Đó là cách tôi sẽ làm điều đó. Nhưng tôi đã thử nghiệm bằng cách sử dụng mẫu thử nghiệm để tìm hiểu thêm về nó và muốn biết một vài điều:

  • Khi sử dụng chức năng nguyên mẫu thay vì đóng cửa là khi nào? Tôi chỉ cần sử dụng chúng để mở rộng các đối tượng hiện có, như Date. Tôi đã đọc rằng closures are faster.
  • Nếu tôi cần sử dụng hàm nguyên mẫu vì lý do nào đó, có phải là "OK" để xác định nó trong lớp, như trong ví dụ của tôi hay nó được định nghĩa bên ngoài?
  • Tôi muốn hiểu tại sao giá trị privateVar của mỗi cá thể không thể truy cập được vào hàm mẫu, chỉ giá trị của cá thể đầu tiên.
+0

đóng cửa striked một lần nữa ... – Dormilich

+2

Đọc trên đóng cửa (http://www.jibbering.com/faq/faq_notes/closures.html) vì những lý do cụ thể tại sao mã của bạn cư xử theo cách nó. – outis

Trả lời

21

Khi sử dụng hàm nguyên mẫu thay vì đóng cửa là khi nào?

Vâng, đó là cách nhẹ nhất để đi, giả sử bạn có phương thức trong prototype của hàm dựng nhất định và bạn tạo 1000 trường hợp đối tượng, tất cả các đối tượng đó sẽ có phương pháp trong chuỗi nguyên mẫu của bạn trong số họ sẽ chỉ tham chiếu một đối tượng chức năng.

Nếu bạn khởi tạo phương thức đó bên trong hàm tạo, ví dụ: (this.method = function() {};), tất cả 1000 đối tượng của bạn sẽ có đối tượng hàm làm thuộc tính riêng.

Nếu tôi cần sử dụng hàm nguyên mẫu vì lý do nào đó, có phải là "OK" để xác định nó trong lớp, như trong ví dụ của tôi hay nó được xác định bên ngoài?

Xác định các thành viên của nguyên mẫu của nhà xây dựng bên trong chính nó, không có ý nghĩa nhiều, tôi sẽ giải thích thêm về nó và tại sao mã của bạn không hoạt động.

Tôi muốn hiểu tại sao giá trị privateVar của mỗi trường hợp không thể truy cập được vào hàm mẫu, chỉ giá trị của cá thể đầu tiên.

Hãy xem mã của bạn:

var Container = function(param) { 
    this.member = param; 
    var privateVar = param; 
    if (!Container.prototype.stamp) { // <-- executed on the first call only 
     Container.prototype.stamp = function(string) { 
      return privateVar + this.member + string; 
     } 
    } 
} 

Điểm mấu chốt về hành vi mã của bạn là Container.prototype.stamp chức năng được tạo ra vào phương pháp gọi đầu tiên.

Tại thời điểm bạn tạo đối tượng hàm, nó lưu trữ phạm vi bao quanh hiện tại trong thuộc tính nội bộ được gọi là [[Scope]].

Phạm vi này sau đó được tăng cường khi bạn gọi hàm, bằng số nhận dạng (biến) được khai báo bên trong nó bằng cách sử dụng var hoặc hàm chức năng.

Danh sách các thuộc tính [[Scope]] tạo thành chuỗi phạm vi và khi bạn truy cập số nhận dạng (như biến số privateVar), các đối tượng đó sẽ được kiểm tra.

Và vì hàm của bạn đã được tạo trên lời gọi phương thức đầu tiên (new Container('A')), privateVar bị ràng buộc với Phạm vi của cuộc gọi hàm đầu tiên này và nó sẽ vẫn bị ràng buộc với nó bất kể bạn gọi phương thức như thế nào.

Hãy xem phần này answer, phần đầu tiên là về câu lệnh with, nhưng trong phần thứ hai, tôi nói về cách chuỗi phạm vi hoạt động cho các hàm.

+0

@CMS: Giải thích tuyệt vời, cảm ơn rất nhiều! Vì vậy, nếu chức năng nguyên mẫu của tôi chỉ truy cập 'this' và không phải là vars riêng, thì nó sẽ hoạt động đúng, mặc dù nó được định nghĩa bên trong lớp, phải không? Có bất kỳ hạn chế để làm điều đó theo cách đó thay vì tuyên bố các chức năng nguyên mẫu sau khi lớp học như tôi thường thấy? – Tauren

+1

@Tauren, có, có một nhược điểm, bạn sẽ bị rò rỉ bộ nhớ, ví dụ trong mã của bạn, tất cả các biến được khai báo trong lời gọi đầu tiên trong hàm khởi tạo của bạn sẽ không được thu thập rác, vì như bạn đã biết, phạm vi kèm theo từ đó hàm 'Container.prototype.stamp' được tạo ra vẫn có thể truy cập được ngay cả sau khi hàm tạo kết thúc việc thực hiện của nó (một đóng được tạo). Vì lý do đó, một số thư viện như [Đóng cửa của Google] (http://code.google.com/closure/library/) tránh đóng cửa cho các thành viên * tư nhân *, chúng chỉ đơn giản là gắn với các quy ước đặt tên, ví dụ: 'obj .__ privateMember'. – CMS

+0

có ý nghĩa hoàn hảo, cảm ơn! – Tauren

1

Bạn cần phải đặt các chức năng trên từng trường hợp cụ thể thay vì nguyên mẫu, như thế này:

Container = function(param) { 
    this.member = param; 
    var privateVar = param; 

    this.stamp = function(string) { 
     return privateVar + this.member + string; 
    } 
} 
+0

@Slaks: Tôi đã sửa đổi câu hỏi để rõ ràng hơn về chính xác những gì tôi đang yêu cầu. – Tauren

1

Để có được hành vi bạn muốn, bạn cần phải gán từng đối tượng cá nhân riêng biệt stamp() chức năng với đóng cửa độc đáo :

Container = function(param) { 
    this.member = param; 
    var privateVar = param; 
    this.stamp = function(string) { 
     return privateVar + this.member + string; 
    } 
} 

Khi bạn tạo một chức năng duy nhất trên nguyên mẫu từng đối tượng sử dụng các chức năng tương tự, với sự đóng chức năng trong container đầu tiên của privateVar.

Bằng cách gán this.stamp = ... mỗi lần hàm tạo được gọi là mỗi đối tượng sẽ nhận được hàm stamp() riêng của nó. Điều này là cần thiết vì mỗi stamp() cần phải đóng trên biến số privateVar khác.

+0

@John: Cảm ơn câu trả lời của bạn. Câu hỏi của tôi không đủ rõ ràng, nên tôi đã sửa đổi nó. Vui lòng xem bản chỉnh sửa. – Tauren

0

Đó là vì privateVar không phải là thành viên riêng tư của đối tượng, nhưng là một phần đóng dấu của đóng dấu.Bạn có thể có được hiệu quả bằng cách luôn luôn tạo ra các chức năng:

Container = function(param) { 
    this.member = param; 
    var privateVar = param; 
    this.stamp = function(string) { 
     return privateVar + this.member + string; 
    } 
} 

Giá trị của privateVar được thiết lập khi các chức năng được xây dựng, vì vậy bạn cần phải tạo ra nó mỗi lần.

EDIT: sửa đổi không để đặt mẫu thử nghiệm.

+0

@Kathy: Cảm ơn, tôi đồng ý đóng cửa là cách để đi. Vui lòng xem câu hỏi đã chỉnh sửa của tôi, vì tôi đã thêm nhiều chi tiết hơn về chính xác những gì tôi đang cố hỏi. – Tauren

+0

@Tauren Lý do tại sao biến chỉ được đặt thành privateVar đầu tiên là hàm chỉ được tạo một lần, vì vậy việc đóng chỉ được tạo một lần. –

+0

cảm ơn vì đã làm rõ, tôi thấy điều gì đang xảy ra. – Tauren

11

Xin lỗi vì đã hồi sinh một câu hỏi cũ, nhưng tôi muốn thêm một cái gì đó mà gần đây tôi đã phát hiện ở đây trên SO (tìm liên kết, sẽ chỉnh sửa/thêm nó khi tôi tìm thấy) : found it. Tôi thích cá nhân phương pháp dưới đây vì tôi có thể trực quan nhóm tất cả các mẫu thử nghiệm và định nghĩa 'mẫu' cùng với định nghĩa chức năng, trong khi tránh đánh giá chúng nhiều hơn một lần. Nó cũng cung cấp một cơ hội để làm bao đóng với các phương thức nguyên mẫu của bạn, có thể hữu ích cho việc tạo các biến 'riêng tư' được chia sẻ bởi các phương thức nguyên mẫu khác nhau.

var MyObject = (function() { 
    // Note that this variable can be closured with the 'instance' and prototype methods below 
    var outerScope = function(){}; 

    // This function will ultimately be the "constructor" for your object 
    function MyObject() { 
     var privateVariable = 1; // both of these private vars are really closures specific to each instance 
     var privateFunction = function(){}; 
     this.PublicProtectedFunction = function(){ }; 
    } 

    // "Static" like properties/functions, not specific to each instance but not a prototype either 
    MyObject.Count = 0; 

    // Prototype declarations 
    MyObject.prototype.someFunction = function() { }; 
    MyObject.prototype.someValue = 1; 

    return MyObject; 
})(); 

// note we do automatic evalution of this function, which means the 'instance' and prototype definitions 
// will only be evaluated/defined once. Now, everytime we do the following, we get a new instance 
// as defined by the 'function MyObject' definition inside 

var test = new MyObject(); 
+0

đó là một ý tưởng khá hay, cảm ơn bạn đã chia sẻ nó! – Tauren

+0

Đó chính xác là những gì tôi cần, cảm ơn bạn rất nhiều! – seahorsepip

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