Sự khác biệt giữa
var A = function () {
this.x = function () {
//do something
};
};
và
var A = function () { };
A.prototype.x = function () {
//do something
};
a1.x !== a2.x
; trên nguyên mẫu:a1.x === a2.x
Sự khác biệt giữa
var A = function () {
this.x = function () {
//do something
};
};
và
var A = function () { };
A.prototype.x = function () {
//do something
};
a1.x !== a2.x
; trên nguyên mẫu:a1.x === a2.x
Câu trả lời:
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:
[[Prototype]]
.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.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.x
có 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, new
toá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 x
tí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, A
gá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.x
có hiệu quả window.x
. Vì hàm không trả về bất cứ thứ gì, A
sẽ 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ớ.
null
), nhưng điều này rất khác với thuộc prototype
tí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
"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?
A
như 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
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.
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ụ.
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.prototype
là 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
và __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 undefined
cho 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 prototype
cho 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. a1
là 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ử new
trước khi gọi hàm A()
đã làm một cái gì đó THÊM trong nền. Các new
từ khóa tạo ra một đối tượng mới mà hiện nay tài liệu tham khảo a1
và đố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ì đó ( a1
cũng có nó), cho dù đó là null hay đối tượng khác. Những gì người new
vậ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.prototype
khô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.prototype
chỉ đế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 a1
và 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 this
thay đổi a1
như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:
a1
.a1 = {}
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 {})
Hàm A()
đang được thực thi với this
thiế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 this
thay đổ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: a2
sẽ 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.prototype
chỉ đến và quan trọng nhất, bước 3: hàm A()
được thực hiện LẠI, có nghĩa là a2
sẽ lấy thuộc hey
tính chứa hàm. a1
và a2
có hai thuộc tính SEPARATE được đặt tên hey
trỏ đế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 yoMan
tí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.prototype
trỏ đế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 a1
nó không chứa hey
và 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ì a1
và a2
có một hey
tà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__
và 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.prototype
tính là hai điều khác nhau.
__proto__
và .prototype
là những thứ hoàn toàn khác nhau.
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.
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 đó.
x
cung 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;
Vấn đề cuối cùng với việc sử dụng this
thay vì prototype
là 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
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 prototype
tà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 x
cho mỗi phiên bản được tạo bằng A
hà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 .
Có gì khác biệt? => Rất nhiều.
Tôi nghĩ rằng, this
phiê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ờ, prototype
cấ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.
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.
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 độ.
Ở đây chúng tôi đang tạo 2.000.000 đối tượng mới bằng một print
phương thức trong Chrome. Chúng tôi đang lưu trữ mọi đối tượng trong một mảng. Đưa print
vào nguyên mẫu mất khoảng 1/2 thời gian.
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.
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();
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 Car
nà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ó move
chức năng). Khi Car
được gọi, extend
hàm được gọi, thực hiện một số phép thuật và mở rộng Car
hà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();
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ó move
chứ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 Car
liên kết đến các prototype
đối tượng, trong đó liên kết qua constructor
để Car
chính nó, tức Car.prototype.constructor
là Car
chí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.constructor
Việ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.constructor
là Car
.
Hơn nữa, amy
là một instanceof
Car
. Các instanceof
nhà đ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 instanceof
lợ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. fido
là 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
.
Đâ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 new
tạ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 ( loc
biế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.
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.
initialize
và x methods do not refer to the
_instance_var` trên một A
ví dụ, nhưng đối với toàn cầu. Sử dụng this._instance_var
nếu bạn có ý định sử dụng thuộc _instance_var
tính của một A
thể hiện.
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.