Sử dụng 'nguyên mẫu' so với 'cái này' trong JavaScript?


776

Sự khác biệt giữa

var A = function () {
    this.x = function () {
        //do something
    };
};

var A = function () { };
A.prototype.x = function () {
    //do something
};


khái niệm về từ khóa NÀY được giải thích rõ ràng ở đây scotch.io/@alZami/under Hiểu
-this-in-javascript

1
Đọc chủ đề "này" cho thấy mức độ khủng khiếp của JS và bao nhiêu nguyên tắc của nó không rõ ràng đối với nhiều nhà phát triển. Điều gì là chính xác sai với ngôn ngữ dễ hiểu hơn? Tôi nghĩ rằng đã đến lúc các nhà phát triển lên tiếng để từ chối các công nghệ khó hiểu không phục vụ hoặc ít giá trị cho công việc kinh doanh hoặc phát triển.
NoChance

Về đối tượng : a1.x !== a2.x; trên nguyên mẫu:a1.x === a2.x
Juan Mendes

Câu trả lời:


467

Các ví dụ có kết quả rất khác nhau.

Trước khi nhìn vào sự khác biệt, cần lưu ý những điều sau:

  • Nguyên mẫu của hàm tạo cung cấp một cách để chia sẻ các phương thức và giá trị giữa các thể hiện thông qua thuộc tính riêng của thể hiện [[Prototype]].
  • Hàm này được đặt theo cách hàm được gọi hoặc bằng cách sử dụng liên kết (không được thảo luận ở đây). Trong đó một hàm được gọi trên một đối tượng (ví dụ myObj.method()) thì điều này trong phương thức tham chiếu đến đối tượng. Trong trường hợp này không được đặt bởi cuộc gọi hoặc bằng cách sử dụng liên kết , nó mặc định là đối tượng toàn cầu (cửa sổ trong trình duyệt) hoặc trong chế độ nghiêm ngặt, vẫn chưa được xác định.
  • JavaScript là một ngôn ngữ hướng đối tượng, tức là hầu hết các giá trị là các đối tượng, bao gồm các hàm. (Chuỗi, số và booleans không phải là đối tượng.)

Vì vậy, đây là đoạn trích trong câu hỏi:

var A = function () {
    this.x = function () {
        //do something
    };
};

Trong trường hợp này, biến Ađược gán một giá trị tham chiếu đến hàm. Khi chức năng đó được gọi bằng cách sử dụng A(), chức năng này không được thiết lập bởi cuộc gọi để nó mặc định cho đối tượng toàn cầu và biểu thức this.xcó hiệu lực window.x. Kết quả là một tham chiếu đến biểu thức chức năng ở phía bên tay phải được gán cho window.x.

Trong trường hợp:

var A = function () { };
A.prototype.x = function () {
    //do something
};

một cái gì đó rất khác nhau xảy ra. Trong dòng đầu tiên, biến Ađược gán tham chiếu cho hàm. Trong JavaScript, tất cả các đối tượng hàm có thuộc tính nguyên mẫu theo mặc định, do đó không có mã riêng để tạo đối tượng A.prototype .

Trong dòng thứ hai, A.prototype.x được gán tham chiếu cho hàm. Điều này sẽ tạo ra một thuộc tính x nếu nó không tồn tại hoặc gán một giá trị mới nếu nó có. Vì vậy, sự khác biệt với ví dụ đầu tiên trong đó thuộc tính x của đối tượng có liên quan đến biểu thức.

Một ví dụ khác là dưới đây. Nó tương tự như cái đầu tiên (và có thể là những gì bạn muốn hỏi về):

var A = new function () {
    this.x = function () {
        //do something
    };
};

Trong ví dụ này, newtoán tử đã được thêm vào trước biểu thức hàm để hàm được gọi là hàm tạo. Khi được gọi với new, hàm này được đặt để tham chiếu một Đối tượng mới có thuộc tính riêng [[Prototype]]được đặt để tham chiếu nguyên mẫu chung của hàm tạo . Vì vậy, trong câu lệnh gán, thuộc xtính sẽ được tạo trên đối tượng mới này. Khi được gọi là hàm tạo, một hàm trả về đối tượng này theo mặc định, do đó không cần một return this;câu lệnh riêng .

Để kiểm tra xem A có thuộc tính x không :

console.log(A.x) // function () {
                 //   //do something
                 // };

Đây là một cách sử dụng mới không phổ biến vì cách duy nhất để tham chiếu hàm tạo là thông qua A.constructor . Nó sẽ là phổ biến hơn nhiều để làm:

var A = function () {
    this.x = function () {
        //do something
    };
};
var a = new A();

Một cách khác để đạt được kết quả tương tự là sử dụng biểu thức hàm được gọi ngay lập tức:

var A = (function () {
    this.x = function () {
        //do something
    };
}());

Trong trường hợp này, Agán giá trị trả về của việc gọi hàm ở phía bên tay phải. Ở đây một lần nữa, vì điều này không được thiết lập trong cuộc gọi, nó sẽ tham chiếu đối tượng toàn cầu và this.xcó hiệu quả window.x. Vì hàm không trả về bất cứ thứ gì, Asẽ có giá trị là undefined.

Những khác biệt giữa hai cách tiếp cận này cũng biểu hiện nếu bạn tuần tự hóa và khử tuần tự hóa các đối tượng Javascript của bạn đến / từ JSON. Các phương thức được xác định trên nguyên mẫu của một đối tượng không được tuần tự hóa khi bạn tuần tự hóa đối tượng, điều này có thể thuận tiện khi bạn muốn tuần tự hóa chỉ các phần dữ liệu của một đối tượng, nhưng không phải là phương thức:

var A = function () { 
    this.objectsOwnProperties = "are serialized";
};
A.prototype.prototypeProperties = "are NOT serialized";
var instance = new A();
console.log(instance.prototypeProperties); // "are NOT serialized"
console.log(JSON.stringify(instance)); 
// {"objectsOwnProperties":"are serialized"} 

Câu hỏi liên quan :

Sidenote: Có thể không có bất kỳ sự tiết kiệm bộ nhớ đáng kể nào giữa hai cách tiếp cận, tuy nhiên sử dụng nguyên mẫu để chia sẻ các phương thức và thuộc tính có thể sẽ sử dụng ít bộ nhớ hơn so với mỗi phiên bản có bản sao riêng.

JavaScript không phải là ngôn ngữ cấp thấp. Có thể không có giá trị lắm khi nghĩ về tạo mẫu hoặc các mẫu thừa kế khác như một cách để thay đổi rõ ràng cách phân bổ bộ nhớ.


49
@keparo: Bạn sai rồi. Mọi đối tượng đều có một đối tượng nguyên mẫu [bên trong] (có thể null), nhưng điều này rất khác với thuộc prototypetính - đó là về các chức năng và nguyên mẫu của tất cả các thể hiện được đặt khi chúng được xây dựng new. Không thể tin điều này thực sự có 87 lượt
upvote

8
"The language is functional"Bạn có chắc chắn rằng đây là những gì có nghĩa là chức năng?
phant0m

23
Tôi thứ hai những gì @Bergi nói về nguyên mẫu. Các chức năng có một thuộc tính nguyên mẫu. Tất cả các đối tượng, bao gồm các hàm, có một thuộc tính nội bộ khác có thể được truy cập bằng Object.getPrototypeOf (myObject) hoặc với myObject .__ proto__ trong một số trình duyệt. Các proto tài sản chỉ ra cha mẹ của đối tượng trong chuỗi ban đầu (hoặc đối tượng mà từ đó đối tượng này kế thừa). Thuộc tính nguyên mẫu (chỉ có trên các hàm) đã chỉ ra đối tượng sẽ trở thành cha mẹ của bất kỳ đối tượng nào sử dụng hàm để tạo đối tượng mới bằng từ khóa mới.
Jim Cooper

11
Bài viết này khá sai lầm và nhầm lẫn làm thế nào điều này được thiết lập. Làm việc trên một bản viết lại.
RobG

37
Câu trả lời này khá kỳ quái và dường như hoàn toàn bỏ lỡ điểm của câu hỏi. Câu hỏi dường như là một câu hỏi rất phổ biến về việc xác định các thuộc tính loại trong hàm tạo so với protoype, nhưng một nửa câu trả lời là về những gì sẽ xảy ra nếu bạn sử dụng Anhư một hàm và nửa còn lại là về các cách tối nghĩa và không chính thống để làm một cái gì đó đơn giản
JLRishe

235

Như những người khác đã nói phiên bản đầu tiên, sử dụng kết quả "này" trong mọi trường hợp của lớp A có bản sao độc lập của phương thức hàm "x". Trong khi sử dụng "nguyên mẫu" sẽ có nghĩa là mỗi phiên bản của lớp A sẽ sử dụng cùng một bản sao của phương thức "x".

Dưới đây là một số mã để hiển thị sự khác biệt tinh tế này:

// x is a method assigned to the object using "this"
var A = function () {
    this.x = function () { alert('A'); };
};
A.prototype.updateX = function( value ) {
    this.x = function() { alert( value ); }
};

var a1 = new A();
var a2 = new A();
a1.x();  // Displays 'A'
a2.x();  // Also displays 'A'
a1.updateX('Z');
a1.x();  // Displays 'Z'
a2.x();  // Still displays 'A'

// Here x is a method assigned to the object using "prototype"
var B = function () { };
B.prototype.x = function () { alert('B'); };

B.prototype.updateX = function( value ) {
    B.prototype.x = function() { alert( value ); }
}

var b1 = new B();
var b2 = new B();
b1.x();  // Displays 'B'
b2.x();  // Also displays 'B'
b1.updateX('Y');
b1.x();  // Displays 'Y'
b2.x();  // Also displays 'Y' because by using prototype we have changed it for all instances

Như những người khác đã đề cập, có nhiều lý do để chọn phương pháp này hay phương pháp khác. Mẫu của tôi chỉ nhằm thể hiện rõ sự khác biệt.


5
Đây là những gì tôi mong đợi sẽ xảy ra, nhưng khi tôi khởi tạo một đối tượng mới sau khi thay đổi Axe như trên, tôi vẫn hiển thị 'A' trừ khi tôi sử dụng A như một đơn. jsbin.com/omida4/2/edit
sứa biển

19
Đó là vì ví dụ của tôi đã sai. Nó chỉ sai trong hai năm. Thở dài. Nhưng quan điểm vẫn còn hiệu lực. Tôi đã cập nhật ví dụ với một cái mà thực sự hoạt động. Cảm ơn đã chỉ ra điều đó.
Benry

4
Đó là một phương pháp tĩnh! : D

6
có ... 'nguyên mẫu' có nghĩa là cấp tĩnh hoặc cấp lớp .. sẽ được chia sẻ bởi tất cả các phiên bản được tạo ... trong khi 'đây' là một phương thức cá thể mà mỗi phiên bản sẽ có bản sao riêng
Aneer Dev

7
Nó không tĩnh. Tĩnh, như được sử dụng trong hầu hết các ngôn ngữ OO, ngụ ý rằng không có sự phụ thuộc vào thisđối tượng, là chủ sở hữu của phương thức. tức là phương thức không có đối tượng là chủ sở hữu của nó. Trong trường hợp này, có một thisđối tượng, như thể hiện trong lớp A trong ví dụ.
CJStuart

152

Lấy 2 ví dụ sau:

var A = function() { this.hey = function() { alert('from A') } };

so với

var A = function() {}
A.prototype.hey = function() { alert('from prototype') };

Hầu hết mọi người ở đây (đặc biệt là các câu trả lời được xếp hạng hàng đầu) đã cố gắng giải thích chúng khác nhau như thế nào mà không giải thích TẠI SAO. Tôi nghĩ điều này là sai và nếu bạn hiểu các nguyên tắc cơ bản trước, sự khác biệt sẽ trở nên rõ ràng. Trước tiên, hãy thử giải thích các nguyên tắc cơ bản ...

a) Hàm là một đối tượng trong JavaScript. MỌI đối tượng trong JavaScript có một thuộc tính bên trong (nghĩa là bạn không thể truy cập nó như các thuộc tính khác, ngoại trừ có thể trong các trình duyệt như Chrome), thường được gọi là __proto__(bạn thực sự có thể nhập anyObject.__proto__vào Chrome để xem những gì nó tham chiếu. Đây chỉ là , một thuộc tính, không có gì nữa. Một thuộc tính trong JavaScript = một biến bên trong một đối tượng, không có gì nữa. Các biến làm gì? Chúng chỉ đến các thứ.

Vì vậy, __proto__tài sản này chỉ ra những gì? Chà, thường là một đối tượng khác (chúng tôi sẽ giải thích lý do tại sao sau này). Cách duy nhất để buộc JavaScript cho thuộc __proto__tính KHÔNG trỏ đến đối tượng khác là sử dụng var newObj = Object.create(null). Ngay cả khi bạn làm điều này, thuộc __proto__tính VẪN tồn tại như một thuộc tính của đối tượng, chỉ là nó không trỏ đến một đối tượng khác, nó trỏ đến null.

Đây là nơi mà hầu hết mọi người bị lẫn lộn:

Khi bạn tạo một hàm mới trong JavaScript (cũng là một đối tượng, hãy nhớ?), Thời điểm được xác định, JavaScript sẽ tự động tạo một thuộc tính mới trên hàm đó được gọi prototype. Thử nó:

var A = [];
A.prototype // undefined
A = function() {}
A.prototype // {} // got created when function() {} was defined

A.prototypelà HOÀN TOÀN KHÁC BIỆT từ __proto__tài sản. Trong ví dụ của chúng tôi, 'A' hiện có HAI thuộc tính được gọi là 'nguyên mẫu' và __proto__. Đây là một sự nhầm lẫn lớn cho mọi người. prototype__proto__các thuộc tính không có cách nào liên quan, chúng là những thứ riêng biệt chỉ đến các giá trị riêng biệt.

Bạn có thể tự hỏi: Tại sao JavaScript có thuộc __proto__tính được tạo trên mỗi đối tượng? Vâng, một từ: đoàn . Khi bạn gọi một thuộc tính trên một đối tượng và đối tượng không có nó, thì JavaScript sẽ tìm đối tượng được tham chiếu bởi __proto__để xem liệu nó có thể có nó không. Nếu nó không có nó, thì nó nhìn vào __proto__tài sản của đối tượng đó và cứ thế ... cho đến khi chuỗi kết thúc. Do đó, chuỗi nguyên mẫu tên . Tất nhiên, nếu __proto__không trỏ đến một đối tượng và thay vào đó null, chỉ là may mắn, JavaScript sẽ nhận ra điều đó và sẽ trả lại undefinedcho bạn tài sản.

Bạn cũng có thể tự hỏi, tại sao JavaScript tạo một thuộc tính được gọi prototypecho hàm khi bạn xác định hàm? Bởi vì nó cố gắng đánh lừa bạn, vâng, đánh lừa bạn rằng nó hoạt động như các ngôn ngữ dựa trên lớp.

Hãy tiếp tục với ví dụ của chúng tôi và tạo một "đối tượng" trong số A:

var a1 = new A();

Có điều gì đó xảy ra trong nền khi điều này xảy ra. a1là một biến thông thường được gán một đối tượng mới, trống.

Thực tế là bạn đã sử dụng toán tử newtrước khi gọi hàm A()đã làm một cái gì đó THÊM trong nền. Các newtừ khóa tạo ra một đối tượng mới mà hiện nay tài liệu tham khảo a1và đối tượng đó là trống rỗng. Đây là những gì đang xảy ra bổ sung:

Chúng tôi đã nói rằng trên mỗi định nghĩa hàm có một thuộc tính mới được gọi là prototype(mà bạn có thể truy cập vào nó, không giống với thuộc __proto__tính) được tạo? Vâng, tài sản đó đang được sử dụng bây giờ.

Vì vậy, bây giờ chúng ta đang ở điểm mà chúng ta có một a1đối tượng trống mới nướng . Chúng tôi đã nói rằng tất cả các đối tượng trong JavaScript đều có thuộc tính bên __proto__trong trỏ đến một thứ gì đó ( a1cũng có nó), cho dù đó là null hay đối tượng khác. Những gì người newvận hành làm là nó đặt thuộc tính đó __proto__để trỏ đến thuộc tính của hàm prototype. Đọc lại lần nữa Về cơ bản là thế này:

a1.__proto__ = A.prototype;

Chúng tôi đã nói rằng A.prototypekhông có gì khác hơn là một đối tượng trống rỗng (trừ khi chúng tôi thay đổi nó thành một thứ khác trước khi xác định a1). Vì vậy, bây giờ về cơ bản a1.__proto__chỉ đến cùng một điều A.prototypechỉ đến, đó là đối tượng trống rỗng. Cả hai đều trỏ đến cùng một đối tượng được tạo khi dòng này xảy ra:

A = function() {} // JS: cool. let's also create A.prototype pointing to empty {}

Bây giờ, có một điều khác xảy ra khi var a1 = new A()tuyên bố được xử lý. Về cơ bản A()được thực thi và nếu A là một cái gì đó như thế này:

var A = function() { this.hey = function() { alert('from A') } };

Tất cả những thứ bên trong function() { }sẽ thực hiện. Khi bạn đạt đến this.hey..dòng, thisđược thay đổi thành a1và bạn nhận được điều này:

a1.hey = function() { alert('from A') }

Tôi sẽ không bao gồm lý do tại sao thisthay đổi a1nhưng đây là một câu trả lời tuyệt vời để tìm hiểu thêm.

Vì vậy, để tóm tắt, khi bạn làm var a1 = new A()có 3 điều xảy ra trong nền:

  1. Một đối tượng trống hoàn toàn mới được tạo và gán cho a1.a1 = {}
  2. a1.__proto__thuộc tính được gán để trỏ vào cùng một A.prototypeđiểm với điểm tới (một đối tượng trống khác {})

  3. Hàm A()đang được thực thi với thisthiết lập thành đối tượng mới, trống được tạo ở bước 1 (đọc câu trả lời tôi đã tham chiếu ở trên để biết lý do thisthay đổi thành a1)

Bây giờ, hãy thử tạo một đối tượng khác:

var a2 = new A();

Các bước 1,2,3 sẽ lặp lại. Bạn có nhận thấy điều gì không? Từ khóa được lặp lại. Bước 1: a2sẽ là một đối tượng trống mới, bước 2: thuộc tính của nó __proto__sẽ trỏ đến cùng một thứ A.prototypechỉ đến và quan trọng nhất, bước 3: hàm A()được thực hiện LẠI, có nghĩa là a2sẽ lấy thuộc heytính chứa hàm. a1a2có hai thuộc tính SEPARATE được đặt tên heytrỏ đến 2 hàm SEPARATE! Bây giờ chúng ta có các hàm trùng lặp trong hai đối tượng khác nhau làm cùng một việc, rất tiếc ... Bạn có thể tưởng tượng hàm ý bộ nhớ của điều này nếu chúng ta có 1000 đối tượng được tạo new A, sau khi tất cả các khai báo hàm chiếm nhiều bộ nhớ hơn so với số 2. Vì vậy Làm thế nào để chúng ta ngăn chặn điều này?

Hãy nhớ tại sao __proto__tài sản tồn tại trên mọi đối tượng? Vì vậy, nếu bạn truy xuất thuộc yoMantính trên a1(không tồn tại), thuộc tính của nó __proto__sẽ được tham khảo, nếu đó là một đối tượng (và hầu hết là trường hợp), nó sẽ kiểm tra xem nó có chứa hay không yoMan, và nếu không, nó sẽ tham khảo ý kiến ​​của đối tượng đó, __proto__v.v. Nếu có, nó sẽ lấy giá trị thuộc tính đó và hiển thị cho bạn.

Vì vậy, ai đó đã quyết định sử dụng thực tế này + thực tế là khi bạn tạo a1, thuộc tính của nó __proto__trỏ đến cùng một đối tượng (trống) A.prototypetrỏ đến và thực hiện điều này:

var A = function() {}
A.prototype.hey = function() { alert('from prototype') };

Mát mẻ! Bây giờ, khi bạn tạo a1, nó lại trải qua tất cả 3 bước trên và trong bước 3, nó không làm gì cả, vì function A()không có gì để thực thi. Và nếu chúng ta làm:

a1.hey

Nó sẽ thấy rằng a1nó không chứa heyvà nó sẽ kiểm tra __proto__đối tượng thuộc tính của nó để xem nếu nó có nó, đó là trường hợp.

Với cách tiếp cận này, chúng tôi loại bỏ phần từ bước 3 trong đó các hàm được sao chép trên mỗi lần tạo đối tượng mới. Thay vì a1a2có một heytài sản riêng , giờ đây KHÔNG có ai trong số họ có nó. Mà, tôi đoán, bây giờ bạn đã tìm ra chính mình. Đó là điều tốt đẹp ... nếu bạn hiểu __proto__Function.prototype, những câu hỏi như thế này sẽ khá rõ ràng.

LƯU Ý: Một số người có xu hướng không gọi thuộc tính Nguyên mẫu nội bộ vì __proto__, tôi đã sử dụng tên này qua bài đăng để phân biệt rõ ràng với thuộc Functional.prototypetính là hai điều khác nhau.


1
Câu trả lời thực sự kỹ lưỡng và nhiều thông tin. Tôi đã thực hiện một số kiểm tra bộ nhớ bằng cách sử dụng các cấu trúc đối tượng ở trên (A.prototype.hey vs object this.hey) và tạo ra 1000 trường hợp của mỗi đối tượng. Dấu chân bộ nhớ cho cách tiếp cận thuộc tính đối tượng lớn hơn khoảng 100kb so với nguyên mẫu. Sau đó tôi đã thêm một chức năng khác có cùng mục đích gọi là "ngớ ngẩn" và nó tăng tuyến tính lên 200kb. Không đáng kể, nhưng cũng không phải đậu phộng.
jookyone

Điều thú vị hơn là phương thức nguyên mẫu chậm hơn một chút so với phương thức thuộc tính đối tượng chạy cục bộ. Nhìn chung, tôi không chắc chắn rằng javascript nên được sử dụng để thao tác dữ liệu của các đối tượng được đánh số trên 10k, do đó phủ nhận mọi lý do để thay đổi cách tiếp cận dựa trên các hiệu ứng bộ nhớ tiềm năng. Tại thời điểm đó, công việc nên được giảm tải lên một máy chủ.
jookyone

Vấn đề là __proto__.prototypelà những thứ hoàn toàn khác nhau.
Wayou

1
Tôi không cảm thấy hài lòng khi chỉ cung cấp cho bạn một upvote ... Tốt lắm!
Kristianmitk

58

Trong hầu hết các trường hợp về cơ bản chúng giống nhau, nhưng phiên bản thứ hai tiết kiệm bộ nhớ vì chỉ có một phiên bản của chức năng thay vì một chức năng riêng biệt cho mỗi đối tượng.

Một lý do để sử dụng mẫu đầu tiên là để truy cập "các thành viên tư nhân". Ví dụ:

var A = function () {
    var private_var = ...;

    this.x = function () {
        return private_var;
    };

    this.setX = function (new_x) {
        private_var = new_x;
    };
};

Do các quy tắc phạm vi của javascript, private_var có sẵn cho hàm được gán cho this.x, nhưng không nằm ngoài đối tượng.


1
Xem bài đăng này: stackoverflow.com/a/1441692/654708 để biết ví dụ về cách truy cập các thành viên tư nhân thông qua các nguyên mẫu.
GFoley83

@ GFoley83 câu trả lời đó không cho thấy điều đó - các phương thức nguyên mẫu chỉ có thể truy cập các thuộc tính "công khai" của đối tượng đã cho. Chỉ các phương thức đặc quyền (không phải trên nguyên mẫu) có thể truy cập các thành viên tư nhân.
Alnitak

27

Ví dụ đầu tiên chỉ thay đổi giao diện cho đối tượng đó. Ví dụ thứ hai thay đổi giao diện cho tất cả các đối tượng của lớp đó.


Cả hai sẽ xcung cấp chức năng cho tất cả các đối tượng có nguyên mẫu được gán một phiên bản mới của A:function B () {}; B.prototype = new A(); var b = new B(); b.x() // Will call A.x if A is defined by first example;
Spencer Williams

21

Vấn đề cuối cùng với việc sử dụng thisthay vì prototypelà khi ghi đè một phương thức, hàm tạo của lớp cơ sở vẫn sẽ tham chiếu đến phương thức được ghi đè. Xem xét điều này:

BaseClass = function() {
    var text = null;

    this.setText = function(value) {
        text = value + " BaseClass!";
    };

    this.getText = function() {
        return text;
    };

    this.setText("Hello"); // This always calls BaseClass.setText()
};

SubClass = function() {
    // setText is not overridden yet,
    // so the constructor calls the superclass' method
    BaseClass.call(this);

    // Keeping a reference to the superclass' method
    var super_setText = this.setText;
    // Overriding
    this.setText = function(value) {
        super_setText.call(this, "SubClass says: " + value);
    };
};
SubClass.prototype = new BaseClass();

var subClass = new SubClass();
console.log(subClass.getText()); // Hello BaseClass!

subClass.setText("Hello"); // setText is already overridden
console.log(subClass.getText()); // SubClass says: Hello BaseClass!

đấu với:

BaseClass = function() {
    this.setText("Hello"); // This calls the overridden method
};

BaseClass.prototype.setText = function(value) {
    this.text = value + " BaseClass!";
};

BaseClass.prototype.getText = function() {
    return this.text;
};

SubClass = function() {
    // setText is already overridden, so this works as expected
    BaseClass.call(this);
};
SubClass.prototype = new BaseClass();

SubClass.prototype.setText = function(value) {
    BaseClass.prototype.setText.call(this, "SubClass says: " + value);
};

var subClass = new SubClass();
console.log(subClass.getText()); // SubClass says: Hello BaseClass!

Nếu bạn nghĩ rằng đây không phải là một vấn đề, thì nó phụ thuộc vào việc bạn có thể sống mà không có các biến riêng tư hay không và liệu bạn có đủ kinh nghiệm để biết rò rỉ khi nhìn thấy hay không. Ngoài ra, việc phải đặt logic xây dựng sau khi định nghĩa phương thức là bất tiện.

var A = function (param1) {
    var privateVar = null; // Private variable

    // Calling this.setPrivateVar(param1) here would be an error

    this.setPrivateVar = function (value) {
        privateVar = value;
        console.log("setPrivateVar value set to: " + value);

        // param1 is still here, possible memory leak
        console.log("setPrivateVar has param1: " + param1);
    };

    // The constructor logic starts here possibly after
    // many lines of code that define methods

    this.setPrivateVar(param1); // This is valid
};

var a = new A(0);
// setPrivateVar value set to: 0
// setPrivateVar has param1: 0

a.setPrivateVar(1);
//setPrivateVar value set to: 1
//setPrivateVar has param1: 0

đấu với:

var A = function (param1) {
    this.setPublicVar(param1); // This is valid
};
A.prototype.setPublicVar = function (value) {
    this.publicVar = value; // No private variable
};

var a = new A(0);
a.setPublicVar(1);
console.log(a.publicVar); // 1

20

Mỗi đối tượng được liên kết với một đối tượng nguyên mẫu. Khi cố gắng truy cập một thuộc tính không tồn tại, JavaScript sẽ tìm trong đối tượng nguyên mẫu của đối tượng cho thuộc tính đó và trả lại nếu thuộc tính đó tồn tại.

Các prototypetài sản của một nhà xây dựng chức năng đề cập đến đối tượng nguyên mẫu của tất cả các trường được tạo ra với chức năng khi sử dụng new.


Trong ví dụ đầu tiên của bạn, bạn đang thêm một thuộc tính xcho mỗi phiên bản được tạo bằng Ahàm.

var A = function () {
    this.x = function () {
        //do something
    };
};

var a = new A();    // constructor function gets executed
                    // newly created object gets an 'x' property
                    // which is a function
a.x();              // and can be called like this

Trong ví dụ thứ hai, bạn đang thêm một thuộc tính vào đối tượng nguyên mẫu mà tất cả các thể hiện được tạo với Ađiểm tới.

var A = function () { };
A.prototype.x = function () {
    //do something
};

var a = new A();    // constructor function gets executed
                    // which does nothing in this example

a.x();              // you are trying to access the 'x' property of an instance of 'A'
                    // which does not exist
                    // so JavaScript looks for that property in the prototype object
                    // that was defined using the 'prototype' property of the constructor

Tóm lại, trong ví dụ đầu tiên, một bản sao của hàm được gán cho mỗi thể hiện . Trong ví dụ thứ hai, một bản sao duy nhất của hàm được chia sẻ bởi tất cả các trường hợp .


1
Bỏ phiếu này là câu trả lời thẳng thắn nhất cho câu hỏi.
Nick Pineda

1
Tôi thích cách tiếp cận thẳng về phía trước của bạn !! nhảy lên
Hoàng tử Vijay Pratap

16

Có gì khác biệt? => Rất nhiều.

Tôi nghĩ rằng, thisphiên bản được sử dụng để cho phép đóng gói, tức là ẩn dữ liệu. Nó giúp thao tác các biến riêng tư.

Chúng ta hãy xem ví dụ sau:

var AdultPerson = function() {

  var age;

  this.setAge = function(val) {
    // some housekeeping
    age = val >= 18 && val;
  };

  this.getAge = function() {
    return age;
  };

  this.isValid = function() {
    return !!age;
  };
};

Bây giờ, prototypecấu trúc có thể được áp dụng như sau:

Những người trưởng thành khác nhau có độ tuổi khác nhau, nhưng tất cả những người trưởng thành đều có quyền như nhau.
Vì vậy, chúng tôi thêm nó bằng cách sử dụng nguyên mẫu, thay vì này.

AdultPerson.prototype.getRights = function() {
  // Should be valid
  return this.isValid() && ['Booze', 'Drive'];
};

Hãy nhìn vào việc thực hiện ngay bây giờ.

var p1 = new AdultPerson;
p1.setAge(12); // ( age = false )
console.log(p1.getRights()); // false ( Kid alert! )
p1.setAge(19); // ( age = 19 )
console.log(p1.getRights()); // ['Booze', 'Drive'] ( Welcome AdultPerson )

var p2 = new AdultPerson;
p2.setAge(45);    
console.log(p2.getRights()); // The same getRights() method, *** not a new copy of it ***

Hi vọng điêu nay co ich.


3
+1 Một câu trả lời ít phức tạp hơn và nhiều đồ họa hơn các câu trả lời khác. Nhưng bạn nên giải thích thêm một chút trước khi cung cấp các ví dụ (tốt) này.
yerforkferchips

1
Tôi không chắc chắn về "phiên bản này được sử dụng để cho phép đóng gói, tức là ẩn dữ liệu". Nếu một thuộc tính bên trong một hàm được định nghĩa bằng cách sử dụng "this" như trong "this.myProperty = ...", thì một thuộc tính đó không phải là "riêng tư" và có thể được truy cập từ các đối tượng bên ngoài lớp bằng cách sử dụng "new".
NoChance

14

Nguyên mẫu là mẫu của lớp; áp dụng cho tất cả các trường hợp trong tương lai của nó. Trong khi đó đây là ví dụ cụ thể của đối tượng.


14

Tôi biết điều này đã được trả lời cho đến chết nhưng tôi muốn đưa ra một ví dụ thực tế về sự khác biệt về tốc độ.

Chức năng trực tiếp trên đối tượng

Chức năng trên nguyên mẫu

Ở đây chúng tôi đang tạo 2.000.000 đối tượng mới bằng một printphương thức trong Chrome. Chúng tôi đang lưu trữ mọi đối tượng trong một mảng. Đưa printvào nguyên mẫu mất khoảng 1/2 thời gian.


13

Hãy để tôi cung cấp cho bạn một câu trả lời toàn diện hơn mà tôi đã học được trong một khóa đào tạo JavaScript.

Hầu hết các câu trả lời đã đề cập đến sự khác biệt, tức là khi tạo nguyên mẫu hàm được chia sẻ với tất cả các trường hợp (tương lai). Trong khi khai báo hàm trong lớp sẽ tạo một bản sao cho mỗi thể hiện.

Nói chung không có đúng hay sai, đó là vấn đề sở thích hay quyết định thiết kế tùy thuộc vào yêu cầu của bạn. Tuy nhiên, nguyên mẫu là kỹ thuật được sử dụng để phát triển theo hướng đối tượng, như tôi hy vọng bạn sẽ thấy ở cuối câu trả lời này.

Bạn đã cho thấy hai mẫu trong câu hỏi của bạn. Tôi sẽ cố gắng giải thích thêm hai và cố gắng giải thích sự khác biệt nếu có liên quan. Hãy chỉnh sửa / mở rộng. Trong tất cả các ví dụ, đó là về một đối tượng xe có vị trí và có thể di chuyển.

Mẫu trang trí đối tượng

Không chắc chắn nếu mô hình này vẫn còn liên quan ngày nay, nhưng nó tồn tại. Và thật tốt khi biết về nó. Bạn chỉ cần truyền một đối tượng và một thuộc tính cho chức năng trang trí. Trình trang trí trả về đối tượng với thuộc tính và phương thức.

var carlike = function(obj, loc) {
    obj.loc = loc;
    obj.move = function() {
        obj.loc++;
    };
    return obj;
};

var amy = carlike({}, 1);
amy.move();
var ben = carlike({}, 9);
ben.move();

Các lớp học chức năng

Một hàm trong JavaScript là một đối tượng chuyên biệt. Ngoài việc được gọi, một hàm có thể lưu trữ các thuộc tính như bất kỳ đối tượng nào khác.

Trong trường hợp Carnày là một hàm ( cũng nghĩ đối tượng ) có thể được gọi như bạn đã từng làm. Nó có một thuộc tính methods(là một đối tượng có movechức năng). Khi Carđược gọi, extendhàm được gọi, thực hiện một số phép thuật và mở rộng Carhàm (think object) với các phương thức được định nghĩa bên trong methods.

Ví dụ này, mặc dù khác nhau, đến gần nhất với ví dụ đầu tiên trong câu hỏi.

var Car = function(loc) {
    var obj = {loc: loc};
    extend(obj, Car.methods);
    return obj;
};

Car.methods = {
    move : function() {
        this.loc++;
    }
};

var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();

Các lớp học nguyên mẫu

Hai mẫu đầu tiên cho phép thảo luận về việc sử dụng các kỹ thuật để xác định các phương thức được chia sẻ hoặc sử dụng các phương thức được xác định nội tuyến trong phần thân của hàm tạo. Trong cả hai trường hợp, mọi trường hợp đều có movechức năng riêng .

Mẫu nguyên mẫu không cho vay chính nó cho cùng một kiểm tra, bởi vì chia sẻ chức năng thông qua một ủy quyền nguyên mẫu là mục tiêu rất chính cho mẫu nguyên mẫu. Như những người khác chỉ ra, dự kiến ​​sẽ có một bộ nhớ tốt hơn.

Tuy nhiên, có một điểm thú vị cần biết: Mọi prototypeđối tượng đều có một thuộc tính tiện lợi constructor, nó quay lại chức năng (đối tượng nghĩ) mà nó đi kèm.

Liên quan đến ba dòng cuối cùng:

Trong ví dụ này Carliên kết đến các prototypeđối tượng, trong đó liên kết qua constructorđể Carchính nó, tức Car.prototype.constructorCarchính nó. Điều này cho phép bạn tìm ra hàm constructor nào đã xây dựng một đối tượng nhất định.

amy.constructorViệc tra cứu thất bại và do đó được ủy quyền Car.prototype, trong đó có thuộc tính hàm tạo. Và như vậy amy.constructorCar.

Hơn nữa, amylà một instanceof Car. Các instanceofnhà điều hành hoạt động bằng cách nhìn thấy nếu đối tượng nguyên mẫu tử bên phai của ( Car) có thể được tìm thấy ở bất cứ đâu trong nguyên mẫu các toán hạng trái của ( amy) chuỗi.

var Car = function(loc) {
    var obj = Object.create(Car.prototype);
    obj.loc = loc;
    return obj;
};

Car.prototype.move = function() {
        this.loc++;
};

var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();

console.log(Car.prototype.constructor);
console.log(amy.constructor);
console.log(amy instanceof Car);

Một số nhà phát triển có thể bị nhầm lẫn ngay từ đầu. Xem ví dụ dưới đây:

var Dog = function() {
  return {legs: 4, bark: alert};
};

var fido = Dog();
console.log(fido instanceof Dog);

Các instanceoflợi nhuận điều hành false, bởi vì Dog's nguyên mẫu không thể tìm thấy bất cứ nơi nào trong fido' s chuỗi nguyên mẫu. fidolà một đối tượng đơn giản được tạo ra với một đối tượng theo nghĩa đen, tức là nó chỉ ủy thác cho Object.prototype.

Mẫu giả

Đây thực sự chỉ là một dạng khác của mẫu nguyên mẫu ở dạng đơn giản và quen thuộc hơn để làm những người lập trình trong Java chẳng hạn, vì nó sử dụng hàm newtạo.

Nó thực hiện giống như trong mẫu nguyên mẫu thực sự, nó chỉ là cú pháp vượt trội của mẫu nguyên mẫu.

Tuy nhiên, điểm khác biệt chính là có các tối ưu hóa được triển khai trong các công cụ JavaScript chỉ áp dụng khi sử dụng mẫu giả. Hãy nghĩ về mẫu giả cổ điển một phiên bản có thể nhanh hơn của mẫu nguyên mẫu; các quan hệ đối tượng trong cả hai ví dụ là như nhau.

var Car = function(loc) {
    this.loc = loc;
};

Car.prototype.move = function() {
        this.loc++;
};

var amy = new Car(1);
amy.move();
var ben = new Car(9);
ben.move();

Cuối cùng, không quá khó để nhận ra cách lập trình hướng đối tượng có thể được thực hiện. Có hai phần.

Một phần xác định các thuộc tính / phương thức chung trong nguyên mẫu (chuỗi).

Và một phần khác nơi bạn đặt các định nghĩa phân biệt các đối tượng với nhau ( locbiến trong các ví dụ).

Đây là những gì cho phép chúng ta áp dụng các khái niệm như siêu lớp hoặc lớp con trong JavaScript.

Hãy thêm hoặc chỉnh sửa. Một lần nữa tôi có thể biến nó thành một wiki cộng đồng.


Không gõ một bài rất kỹ lưỡng, nhưng tôi nghĩ rằng OO và thừa kế nguyên mẫu là những trường phái khác nhau về cơ bản.
Nick Pineda

Họ là, nhưng người ta có thể "làm OO" với các kỹ thuật / suy nghĩ khác nhau, phải không?
Ely

Không chắc lắm. Nhiều người chỉ nói rằng triết lý nguyên mẫu chỉ khác nhau và nhiều người cố gắng so sánh nó với OO bởi vì đó là trường phái tư tưởng mà nhiều người đang sử dụng.
Nick Pineda

Ý tôi là, nếu bạn muốn thực hành phong cách OO và ngôn ngữ cung cấp một tập hợp các kỹ thuật giúp thực hiện điều đó, điều đó không nhất thiết là sai.
Ely

11

Tôi tin rằng @Matthew Crumley là đúng. Chúng có chức năng , nếu không có cấu trúc, tương đương. Nếu bạn sử dụng Fireorms để xem xét các đối tượng được tạo bằng cách sử dụng new, bạn có thể thấy rằng chúng giống nhau. Tuy nhiên, sở thích của tôi sẽ là như sau. Tôi đoán rằng nó có vẻ giống với những gì tôi đã sử dụng trong C # / Java. Đó là, xác định lớp, xác định các trường, hàm tạo và phương thức.

var A = function() {};
A.prototype = {
    _instance_var: 0,

    initialize: function(v) { this._instance_var = v; },

    x: function() {  alert(this._instance_var); }
};

EDIT Không có nghĩa là phạm vi của biến là riêng tư, tôi chỉ cố gắng minh họa cách tôi xác định các lớp của mình trong javascript. Tên biến đã được thay đổi để phản ánh điều này.


2
_instance_var như trong thuộc tính initializex methods do not refer to the _instance_var` trên một Aví dụ, nhưng đối với toàn cầu. Sử dụng this._instance_varnếu bạn có ý định sử dụng thuộc _instance_vartính của một Athể hiện.
Lekensteyn

2
Điều buồn cười là, Benry cũng mắc một lỗi như vậy, điều này cũng đã được phát hiện sau hai năm: p
Lekensteyn

10

Như đã thảo luận trong các câu trả lời khác, đây thực sự là một sự cân nhắc về hiệu suất bởi vì chức năng trong nguyên mẫu được chia sẻ với tất cả các cảnh báo - chứ không phải là chức năng được tạo cho mỗi lần khởi tạo.

Tôi kết hợp một jsperf để thể hiện điều này. Có một sự khác biệt lớn về thời gian cần thiết để khởi tạo lớp, mặc dù nó thực sự chỉ có liên quan nếu bạn đang thực hiện nhiều trường hợp.

http://jsperf.com/fifts-in-constructor-vs-prototype


8

Hãy suy nghĩ về ngôn ngữ gõ tĩnh, những thứ trên prototypelà tĩnh và những thứ trên thiscó liên quan đến thể hiện.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.