2011-11-11 63 views
35

Hãy xem xét mã sau đây.Thừa kế JavaScript và thuộc tính hàm tạo

function a() {} 
function b() {} 
function c() {} 

b.prototype = new a(); 
c.prototype = new b(); 

console.log((new a()).constructor); //a() 
console.log((new b()).constructor); //a() 
console.log((new c()).constructor); //a() 
  • Tại sao không phải là các nhà xây dựng được cập nhật cho b và c?
  • Tôi có làm sai kế thừa không?
  • Cách tốt nhất để cập nhật hàm tạo là gì?

Hơn nữa, vui lòng xem xét những điều sau đây.

console.log(new a() instanceof a); //true 
console.log(new b() instanceof b); //true 
console.log(new c() instanceof c); //true 
  • Cho rằng (new c()).constructor bằng a()Object.getPrototypeOf(new c())a{ }, làm thế nào là nó có thể cho instanceof để biết rằng new c() là một thể hiện của c?

http://jsfiddle.net/ezZr5/

+3

Bất kỳ lý do nào bạn cần hàm khởi tạo được cập nhật? Tôi thấy cuộc sống của mình dễ dàng hơn nếu tôi giả vờ rằng tài sản không tồn tại. – hugomg

+0

Tôi đang gặp khó khăn khi đóng bản sao này thành bản sao - tất cả các questinos khác đều quá chi tiết ... – hugomg

+3

'c.prototype' là' b() 'và' b.prototype' là 'a()', do đó 'c.prototype' là' a() ' –

Trả lời

63

Được rồi, chúng ta hãy chơi một chút tâm trí trò chơi:

Từ hình trên chúng ta có thể thấy:

  1. Khi chúng ta tạo một hàm như function Foo() {}, JavaScript tạo ra một trường hợp Function.
  2. Mỗi trường hợp Function (hàm hàm dựng) có thuộc tính prototype là con trỏ.
  3. Thuộc tính prototype của hàm xây dựng trỏ đến đối tượng mẫu thử nghiệm của nó.
  4. Đối tượng nguyên mẫu có thuộc tính constructor cũng là một con trỏ.
  5. Thuộc tính constructor của đối tượng mẫu thử trỏ về hàm khởi tạo của nó.
  6. Khi chúng tôi tạo một phiên bản mới Foo như new Foo(), JavaScript sẽ tạo một đối tượng mới.
  7. Nội bộ [[proto]] thuộc tính của cá thể trỏ đến nguyên mẫu của hàm tạo.

Bây giờ, câu hỏi đặt ra là tại sao JavaScript không đính kèm thuộc tính constructor vào đối tượng mẫu thay vì mẫu thử nghiệm. Xem xét:

function defclass(prototype) { 
    var constructor = prototype.constructor; 
    constructor.prototype = prototype; 
    return constructor; 
} 

var Square = defclass({ 
    constructor: function (side) { 
     this.side = side; 
    }, 
    area: function() { 
     return this.side * this.side; 
    } 
}); 

var square = new Square(10); 

alert(square.area()); // 100 

Như bạn có thể thấy constructor bất động sản chỉ là một phương pháp của nguyên mẫu, như area trong ví dụ trên. Điều làm cho đặc tính constructor đặc biệt là nó được sử dụng để khởi tạo một thể hiện của nguyên mẫu. Nếu không, nó chính xác giống như bất kỳ phương pháp nào khác của nguyên mẫu.

Xác định constructor tài sản trên nguyên mẫu là thuận lợi vì những lý do sau đây:

  1. Đó là logic đúng. Ví dụ: xem Object.prototype. Thuộc tính constructor của Object.prototype trỏ đến Object. Nếu thuộc tính constructor được xác định trên cá thể thì Object.prototype.constructor sẽ là undefinedObject.prototype là một phiên bản null.
  2. Nó được xử lý không khác với các phương pháp thử nghiệm khác. Điều này làm cho công việc của new dễ dàng hơn vì nó không cần phải xác định thuộc tính constructor trên mọi trường hợp.
  3. Mỗi trường hợp có cùng thuộc tính constructor. Do đó nó hiệu quả.

Bây giờ khi chúng ta nói về thừa kế, chúng tôi có các tình huống sau:

Từ hình trên chúng ta có thể thấy:

  1. tài sản prototype Các nhà xây dựng có nguồn gốc được thiết lập để trường hợp của hàm tạo cơ sở.
  2. Do đó thuộc tính nội bộ [[proto]] của cá thể của hàm tạo dẫn xuất cũng trỏ đến nó.
  3. Do đó, thuộc tính constructor của cá thể khởi tạo có nguồn gốc hiện trỏ đến hàm tạo cơ sở.

Đối với nhà điều hành instanceof, trái với niềm tin phổ biến, nó không phụ thuộc vào tài sản constructor của cá thể. Như chúng ta có thể thấy từ trên, điều đó sẽ dẫn đến kết quả sai lầm.

Toán tử instanceof là toán tử nhị phân (có hai toán hạng). Nó hoạt động trên một đối tượng instance và một hàm khởi tạo. Như giải thích trên Mozilla Developer Network, nó chỉ đơn giản nào sau đây:

function instanceOf(object, constructor) { 
    while (object != null) { 
     if (object == constructor.prototype) { //object is instanceof constructor 
      return true; 
     } else if (typeof object == 'xml') { //workaround for XML objects 
      return constructor.prototype == XML.prototype; 
     } 
     object = object.__proto__; //traverse the prototype chain 
    } 
    return false; //object is not instanceof constructor 
} 

Nói một cách đơn giản là nếu Foo thừa hưởng từ Bar, sau đó là chuỗi nguyên mẫu cho trường hợp của Foo sẽ là:

  1. foo.__proto__ === Foo.prototype
  2. foo.__proto__.__proto__ === Bar.prototype
  3. foo.__proto__.__proto__.__proto__ === Object.prototype
  4. foo.__proto__.__proto__.__proto__.__proto__ === null

Như bạn có thể thấy, mọi đối tượng kế thừa từ hàm tạo Object. Chuỗi nguyên mẫu kết thúc khi một thuộc tính nội bộ [[proto]] trỏ đến null.

Chức năng instanceof chỉ cần đi qua chuỗi mẫu của đối tượng thể hiện (toán hạng đầu tiên) và so sánh thuộc tính nội bộ [[proto]] của mỗi đối tượng với thuộc tính hàm xây dựng (toán hạng thứ hai). Nếu chúng phù hợp, nó trả về true; và nếu chuỗi nguyên mẫu kết thúc, nó trả về false.

+4

xem thêm http://joost.zeekat.nl/constructors-considered-mildly-confusing.html – Christoph

+3

+1 Tôi thích 'Object.getPrototypeOf' thay vì' .__ proto__'. – pimvdb

+3

Tôi chỉ sử dụng thuộc tính '__proto__' để giải thích. Đó là cách nó được giải thích trên Mạng nhà phát triển Mozilla. Tuy nhiên, thuộc tính '__proto__' không có lợi thế so với phương thức' Object.getPrototypeOf' hơn là nó nhanh hơn (không có chức năng gọi trên đầu) và nó được thực hiện bởi tất cả các trình duyệt chính. Lý do duy nhất tôi sẽ sử dụng 'Object.getPrototypeOf' là làm việc xung quanh việc triển khai như Rhino mà không hỗ trợ thuộc tính' __proto__'. Tất cả chúng ta đều có sở thích riêng của mình. Tôi thích thuộc tính '__proto__' vì nó dễ đọc hơn. Chúc mừng. =) –

12

Theo mặc định,

function b() {} 

sau đó b.prototype có một tài sản được thiết lập để tự động b.constructor. Tuy nhiên, bạn đang ghi đè lên các nguyên mẫu và do đó loại bỏ rằng biến:

b.prototype = new a; 

Sau đó b.prototype không có một tài sản .constructor nữa; nó đã bị xóa với ghi đè. Nó hiện được kế thừa từ a mặc dù và (new a).constructor === a và do đó (new b).constructor === a (nó đề cập đến cùng một thuộc tính trong chuỗi mẫu thử nghiệm).

tốt nhất để làm là chỉ cần cài đặt nó bằng tay sau đó:

b.prototype.constructor = b; 

Bạn cũng có thể tạo ra một chức năng nhỏ cho việc này:

function inherit(what, from) { 
    what.prototype = new from; 
    what.prototype.constructor = what; 
} 

http://jsfiddle.net/79xTg/5/

+1

Đó là tất cả sự thật. Nhưng sau đó bạn sẽ không biết rằng một đối tượng nhất định là * kế thừa *. 'new b() instanceof a' trả về' false' khi bạn sử dụng '$ .extend' có thể không mong muốn trong trường hợp OP nếu anh ta cần kiểm tra thừa kế. –

+0

@Robert Koritnik: Điểm tốt, nhưng 'b instanceof b' luôn luôn là sai vì hàm tạo không phải là một thể hiện của chính nó. Hàm mở rộng dường như làm việc cho 'b instanceof b' mới, trừ khi tôi hiểu lầm bạn: http://jsfiddle.net/79xTg/3/. – pimvdb

+0

Tôi đã chỉnh sửa nhận xét của mình. Đó là tất nhiên một lỗi đánh máy. Và hãy để tôi chỉnh sửa ví dụ của bạn: http://jsfiddle.net/79xTg/4/ '$ .extend' không làm thừa kế mà là bản sao' nguyên mẫu'. Do đó bạn không thể kiểm tra thừa kế bằng cách sử dụng nó. –

4

constructor là một thường xuyên, không thuộc tính có thể đếm được của giá trị mặc định của thuộc tính prototype của các đối tượng hàm. Do đó, chỉ định cho prototype sẽ mất thuộc tính.

instanceof vẫn sẽ làm việc như nó không sử dụng constructor, nhưng thay vì quét chuỗi ban đầu của các đối tượng cho giá trị (hiện tại) của tài sản prototype của chức năng, tức là foo instanceof Foo tương đương với

var proto = Object.getPrototypeOf(foo); 
for(; proto !== null; proto = Object.getPrototypeOf(proto)) { 
    if(proto === Foo.prototype) 
     return true; 
} 
return false; 

Trong ECMAScript3 , không có cách nào để đặt thuộc tính constructor hoạt động giống với thuộc tính được tích hợp sẵn vì thuộc tính do người dùng xác định luôn luôn là số đếm (tức là hiển thị với for..in).

Điều này đã thay đổi bằng ECMAScript5. Tuy nhiên, ngay cả khi bạn đặt constructor theo cách thủ công, mã của bạn vẫn gặp sự cố: Cụ thể, bạn nên đặt prototype thành cá thể của lớp cha-'class '- hàm khởi tạo gốc không được gọi khi lớp con 'được định nghĩa, nhưng đúng hơn là khi các cá thể con được tạo ra.

Dưới đây là một số ví dụ ECMAScript5 mã cho nó như thế nào nên được thực hiện:

function Pet(name) { 
    this.name = name; 
} 

Pet.prototype.feed = function(food) { 
    return this.name + ' ate ' + food + '.'; 
}; 

function Cat(name) { 
    Pet.call(this, name); 
} 

Cat.prototype = Object.create(Pet.prototype, { 
    constructor : { 
     value : Cat, 
     writable : true, 
     enumerable : false, 
     configurable : true 
    } 
}); 

Cat.prototype.caress = function() { 
    return this.name + ' purrs.'; 
}; 

Nếu bạn đang mắc kẹt với ECMAScript3, bạn sẽ cần phải sử dụng một tùy chỉnh clone() function thay vì Object.create() và sẽ không thể làm constructor không đếm được:

Cat.prototype = clone(Pet.prototype); 
Cat.prototype.constructor = Cat;