2009-09-10 2 views
6

Tôi đang viết lại một dự án JavaScript và tôi muốn có thể sử dụng các phương pháp hướng đối tượng để tổ chức mớ hỗn độn mà mã hiện tại là. Mối quan tâm chính là JavaScript này được cho là chạy dưới dạng tiện ích bên trong các trang web của bên thứ ba và tôi không thể xung đột với các thư viện JavaScript khác mà các trang web khác có thể sử dụng.Phương pháp kế thừa Javascript tối giản tốt là gì?

Vì vậy, tôi đang tìm kiếm một cách để viết "đẳng cấp như" thừa kế trong JavaScript có các yêu cầu sau:

  1. Không thư viện bên ngoài hoặc những thứ đó sẽ mâu thuẫn với một thư viện bên ngoài (mà ngăn cản bản sao & dán từ thư viện bên ngoài).
  2. Minimalistic - Tôi không muốn mã hỗ trợ lớn hơn sau đó một vài dòng mã và tôi không muốn các nhà phát triển cần nhiều nồi hơi mỗi khi họ xác định một lớp hoặc phương pháp mới.
  3. Nên cho phép các đối tượng cha mẹ mở rộng động để các đối tượng con nhìn thấy các thay đổi (nguyên mẫu).
  4. Nên cho phép chuỗi hàm tạo.
  5. Phải cho phép các cuộc gọi loại super.
  6. Vẫn nên cảm thấy JavaScript-ish.

Ban đầu tôi đã cố gắng để làm việc với đơn giản nguyên mẫu chaining:

function Shape(x,y) { 
    this.x = x; 
    this.y = y; 

    this.draw = function() { 
    throw new Error("Arbitrary shapes cannot be drawn"); 
    } 
} 

function Square(x,y,side) { 
    this.x = x; 
    this.y = y; 
    this.side = side; 

    this.draw = function() { 
    gotoXY(this.x,this.y); lineTo(this.x+this.side, this.y); ... 
    } 
} 
Square.prototype = new Shape(); 

Và đó giải quyết các yêu cầu 1, 2 và 6 nhưng id không cho phép các cuộc gọi siêu (chức năng mới ghi đè lên chức năng mẹ), nhà xây dựng chaining và mở rộng động một phụ huynh không cung cấp các phương thức mới cho một lớp con.

Mọi đề xuất sẽ được hoan nghênh.

+0

bạn có thể gọi các phương thức siêu. Nếu bạn định nghĩa vẽ trong hình tam giác, bạn có thể lấy siêu vẽ bởi Triangle.prototype.draw –

+0

Vấn đề là phương thức "siêu" không có quyền truy cập vào các trường của đối tượng hiện tại - 'this' là nguyên mẫu chứ không phải' this' của vẽ tranh. Tôi có thể làm 'Square.prototype.draw.apply (điều này, đối số)' nhưng đó là clanky và tôi thường không thích các phương thức gọi class container của chúng theo tên (chúng nên sử dụng 'this' ở khắp mọi nơi). – Guss

+0

và những gì về this.constructor.prototype.draw.apply (điều này, đối số) –

Trả lời

5

tôi muốn đề nghị mô hình sau đây mà làm cho việc sử dụng một clone function kế thừa từ protoypes và không trường hợp:

function Shape(x, y) { 
    this.x = x; 
    this.y = y; 
} 

Shape.prototype.draw = function() { 
    throw new Error('Arbitrary shapes cannot be drawn'); 
}; 

function Square(x,y,side) { 
    Shape.call(this, x, y); // call super constructor 
    this.side = side; 
} 

// inherit from `Shape.prototype` and *not* an actual instance: 
Square.prototype = clone(Shape.prototype); 

// override `draw()` method 
Square.prototype.draw = function() { 
    gotoXY(this.x,this.y); lineTo(this.x+this.side, this.y); // ... 
}; 

Điều quan trọng là phương pháp nằm trong nguyên mẫu (đó là như nó phải được anyway cho lý do hiệu suất), do đó bạn có thể gọi phương thức của một lớp siêu qua

SuperClass.prototype.aMethod.call(this, arg1, arg2); 

với một số syntactic sugar, bạn có thể làm cho JS trông giống như một ngôn ngữ dựa trên lớp cổ điển:

var Shape = Class.extend({ 
    constructor : function(x, y) { 
     this.x = x; 
     this.y = y; 
    }, 
    draw : function() { 
     throw new Error('Arbitrary shapes cannot be drawn'); 
    } 
}); 

var Square = Shape.extend({ 
    constructor : function(x, y, side) { 
     Shape.call(this, x, y); 
     this.side = side 
    }, 
    draw : function() { 
     gotoXY(this.x,this.y); lineTo(this.x+this.side, this.y); // ... 
    } 
}); 
+0

PS: thay vì một hàm 'clone()' tùy chỉnh, bạn có thể sử dụng 'Object.create()' nếu hỗ trợ triển khai ES5 – Christoph

+0

I giống như nó, mặc dù tôi có lẽ sẽ không thực hiện điều cú pháp. Một câu hỏi mặc dù - tại sao sử dụng clone() và không kế thừa từ một cá thể? có vẻ với tôi để làm việc theo cùng một cách nhưng sử dụng clone() là ít có thể đọc được. – Guss

+0

@Guss: theo ý kiến ​​của tôi, việc sử dụng các cá thể thừa kế là hành vi xấu vì nó sẽ chạy hàm tạo của lớp siêu; tùy thuộc vào những gì các nhà xây dựng thực sự làm, điều này có thể có tác dụng phụ không mong muốn; gọi hàm tạo của lớp siêu rõ ràng trong hàm tạo của lớp con là sạch hơn vì điều này sẽ chạy mã khởi tạo chỉ khi bạn thực sự muốn tạo một cá thể mới – Christoph

4

Douglas Crockford có các bài viết hay về cả thừa kế classicalprototypal trong Javascript, điều này sẽ tạo nên điểm khởi đầu tốt.

+3

Tôi quen thuộc với các bài viết của Crockford, và tôi không thích chúng. Ông yêu cầu rất nhiều mã tấm nồi hơi (hãy xem ví dụ đầu tiên về việc xây dựng một đối tượng trong liên kết thừa kế nguyên mẫu của bạn) hoặc yêu cầu hàng chục dòng mã cho "đường". Hoặc cả hai. Và kết quả không giống với JavaScript với tất cả các loại 'uber' và' beget' - mục đích là một nhà phát triển JavaScript được đào tạo có thể xem mã và ngay lập tức hiểu những gì đang xảy ra. – Guss

+0

Thật khó để không kết thúc với một số tấm nồi hơi, cố gắng thực hiện các chức năng mà bạn muốn, mặc dù. Và .. Công bằng: Mọi nhà phát triển JavaScript được đào tạo _should_ đều quen thuộc với công việc của Crockford :) – roosteronacid

-1

Cũng lấy cảm hứng từ Crockford, nhưng tôi đã có kinh nghiệm tốt với những gì ông gọi là "kế thừa chức năng" bằng cách sử dụng "hàm xây dựng". YMMV.

CẬP NHẬT: Xin lỗi, tôi quên: bạn vẫn cần tăng thêm đối tượng bằng phương pháp superior để có quyền truy cập tốt vào phương thức siêu. Không phù hợp với bạn, có thể.

var makeShape = function (x, y) { 
    that = {}; 
    that.x = x; 
    that.y = y; 
    that.draw = function() { 
     throw new Error("Arbitrary shapes cannot be drawn"); 
    } 
    return that; 
}; 

var makeSquare = function (x, y, side) { 
    that = makeShape(x, y); 
    that.side = side; 
    that.draw = function() { 
     gotoXY(that.x,that.y); lineTo(that.x+that.side, that.y); ... 
    } 
    return that; 
}; 
+0

Quá nhiều bản mẫu, nhưng cảm ơn bạn đã thử :-) – Guss

1

OK, thủ thuật tạo lại hệ thống kiểu lớp/kiểu chữ trong JavaScript là bạn chỉ có thể sử dụng kế thừa nguyên mẫu trên các phiên bản. Vì vậy, bạn cần để có thể tạo ra một cá thể 'không thể hiện' chỉ được sử dụng cho kế thừa, và có một phương thức khởi tạo riêng biệt với hàm của hàm tạo.

Đây là hệ thống tối thiểu tôi sử dụng (trước khi thêm rườm rà), đi qua một đặc biệt giá trị một lần vào các nhà xây dựng để có nó xây dựng một đối tượng mà không initialising nó:

Function.prototype.subclass= function() { 
    var c= new Function(
     'if (!(this instanceof arguments.callee)) throw(\'Constructor called without "new"\'); '+ 
     'if (arguments[0]!==Function.prototype.subclass._FLAG && this._init) this._init.apply(this, arguments); ' 
    ); 
    if (this!==Object) 
     c.prototype= new this(Function.prototype.subclass._FLAG); 
    return c; 
}; 
Function.prototype.subclass._FLAG= {}; 

Việc sử dụng new Function() là một cách để tránh tạo thành một đóng cửa không cần thiết trên lớp con(). Bạn có thể thay thế nó bằng biểu thức đẹp hơn function() {...} nếu bạn thích.

Cách sử dụng tương đối sạch sẽ, và nói chung như Python kiểu đối tượng chỉ với cú pháp hơi vụng về:

var Shape= Object.subclass(); 
Shape.prototype._init= function(x, y) { 
    this.x= x; 
    this.y= y; 
}; 
Shape.prototype.draw= function() { 
    throw new Error("Arbitrary shapes cannot be drawn"); 
}; 

var Square= Shape.subclass(); 
Square.prototype._init= function(x, y, side) { 
    Shape.prototype._init.call(this, x, y); 
    this.side= side; 
}; 
Square.prototype.draw= function() { 
    gotoXY(this.x, this.y); 
    lineTo(this.x+this.side, this.y); // ... 
}; 

Khỉ-vá một dựng sẵn (Function) là một chút nghi ngờ, nhưng làm cho nó thú vị để đọc, và không ai có thể muốn for...in trên một chức năng.

+0

Tại sao hàm tạo 'Hàm'? – kangax

+0

'function() {...}' đóng một phạm vi chức năng mà nó đang được sử dụng, để lại tham chiếu đến các đối số và biến cục bộ trong hàm đó. 'new Function()' tránh điều này. Mặc dù nó không thực sự quan trọng bằng cách nào, vì sẽ không có gì nặng nề trong việc đóng cửa. – bobince

+1

Có tất nhiên. Tôi chỉ không chắc chắn lý do tại sao bạn sử dụng "nặng" chức năng 'nơi đồng bằng' function() {} 'suffices. – kangax

0

Bạn có thể sử dụng mẫu chức năng do Crockford đề xuất trong cuốn sách "JavaScript các phần tốt" của mình. Ý tưởng là sử dụng closore để tạo các trường riêng và sử dụng hàm ưu tiên để truy cập vào các trường này. Dưới đây là một trong các giải pháp đáp ứng 6 yêu cầu của bạn:

var people = function (info) { 
    var that = {}; 
    // private 
    var name = info.name; 
    var age = info.age; 
    // getter and setter 
    that.getName = function() { 
     return name; 
    }; 
    that.setName = function (aName) { 
     name = aName; 
    }; 
    that.getAge = function() { 
     return age; 
    }; 
    that.setAge = function (anAge) { 
     age = anAge; 
    }; 
    return that; 
}; 

var student = function (info) { 
    // super 
    var that = people(info); 
    // private 
    var major = info.major; 
    that.getMajor = function() { 
     return major; 
    }; 
    that.setMajor = function (aMajor) { 
     major = aMajor; 
    }; 
    return that; 
}; 

var itStudent = function (info) { 
    // super 
    var that = student(info); 
    var language = info.language; 
    that.getLanguage = function() { 
     return language; 
    }; 
    that.setLanguage = function (aLanguage) { 
     language = aLanguage; 
    }; 
    return that; 
}; 

var p = person({name : "Alex", age : 24}); 
console.debug(p.age); // undefined 
console.debug(p.getAge()); // 24 

var s = student({name : "Alex", age : 24, major : "IT"}); 
console.debug(s.getName()); // Alex 
console.debug(s.getMajor()); // IT 

var i = itStudent({name : "Alex", age : 24, major : "IT", language : "js"}); 
console.debug(i.language); // Undefined 
console.debug(i.getName()); // Alex 
console.debug(i.getMajor()); // IT 
console.debug(i.getLanguage()); // js 
+0

Tôi không thích cú pháp chức năng này vì một mặt nó không cảm thấy mã định hướng đối tượng (do đó gây bất lợi khi bạn cố gắng để các lập trình viên được đào tạo trong OO cổ điển làm việc với bạn), và mặt khác bạn don ' t tận dụng lợi thế của thừa kế nguyên mẫu, nơi bạn có thể sử dụng mẫu trộn. – Guss

1

Mẫu phổ biến nhất mà tôi tìm thấy khi nghiên cứu câu hỏi này được mô tả trên Mozilla Developer Network. Tôi đã cập nhật tấm gương của họ để bao gồm một cuộc gọi đến một phương pháp lớp cha và để hiển thị các bản ghi trong một thông điệp cảnh báo:

// Shape - superclass 
 
function Shape() { 
 
    this.x = 0; 
 
    this.y = 0; 
 
} 
 

 
// superclass method 
 
Shape.prototype.move = function(x, y) { 
 
    this.x += x; 
 
    this.y += y; 
 
    log += 'Shape moved.\n'; 
 
}; 
 

 
// Rectangle - subclass 
 
function Rectangle() { 
 
    Shape.call(this); // call super constructor. 
 
} 
 

 
// subclass extends superclass 
 
Rectangle.prototype = Object.create(Shape.prototype); 
 
Rectangle.prototype.constructor = Rectangle; 
 

 
// Override method 
 
Rectangle.prototype.move = function(x, y) { 
 
    Shape.prototype.move.call(this, x, y); // call superclass method 
 
    log += 'Rectangle moved.\n'; 
 
} 
 

 
var log = ""; 
 
var rect = new Rectangle(); 
 

 
log += ('Is rect an instance of Rectangle? ' + (rect instanceof Rectangle) + '\n'); // true 
 
log += ('Is rect an instance of Shape? ' + (rect instanceof Shape) + '\n'); // true 
 
rect.move(1, 1); // Outputs, 'Shape moved.' 
 
alert(log);

  1. Trong năm năm kể từ khi bạn hỏi câu hỏi này, nó có vẻ như hỗ trợ trình duyệt cho thừa kế đã được cải thiện, vì vậy tôi không nghĩ rằng bạn cần một thư viện bên ngoài.
  2. Đây là kỹ thuật tối thiểu nhất mà tôi từng thấy, tôi không biết liệu bạn có xem xét quá nhiều bản mẫu.
  3. Nó sử dụng mẫu thử nghiệm, theo yêu cầu, vì vậy việc thêm các phương thức mới cho cha mẹ cũng nên cung cấp cho các đối tượng con.
  4. Bạn có thể thấy chuỗi hàm tạo trong ví dụ.
  5. Cuộc gọi siêu loại cũng có trong ví dụ.
  6. Tôi không chắc chắn nếu nó cảm thấy JavaScript-ish, bạn sẽ phải quyết định cho chính mình.
+0

Có vẻ đẹp, và không cảm thấy JavaScript-ish, nhưng khá nhiều boilerplate: -/vẫn còn, một giải pháp tốt. – Guss