Lập trình Prototype trong Javascript
Javascript là một ngôn ngữ lập trình hướng đối tượng; tuy nhiên không giống với các ngôn ngữ hướng đối tượng mà bạn đã từng làm việc (C++, Java, C#, Ruby, python...). Các ngôn ngữ này hướng đối tượng theo kiểu class-based. Trong đó bạn sẽ phải define các Class, attribute, method và khởi tạo các ...
Javascript là một ngôn ngữ lập trình hướng đối tượng; tuy nhiên không giống với các ngôn ngữ hướng đối tượng mà bạn đã từng làm việc (C++, Java, C#, Ruby, python...). Các ngôn ngữ này hướng đối tượng theo kiểu class-based. Trong đó bạn sẽ phải define các Class, attribute, method và khởi tạo các object từ class đó. Tất cả các object được khởi tạo từ cùng 1 class sẽ có cùng một quyền access đến các phương thức ở class-level (hoặc các static method). Mỗi một object được phân biệt với nhau bởi chính tên biến lưu trữ nó, các attributes và phương thức cũng sẽ y hệt nhau. Hướng đối tượng trong javascript thì khác!
Sự khác biệt của Javascript
Javascript cũng là hướng đối tượng, tuy nhiên thay vì đi theo hướng class-based thì Javascript lựa chọn prototype-based, theo đó quy định lại toàn bộ các rules trong khởi tạo đối tượng, shared resources access level và cả việc quyết định cách thức để thừa kế một phương thức nữa. Javascript về mặt khác giống như một ngôi ngữ tuần prototype hơn (https://en.wikipedia.org/wiki/Prototype-based_programming#Languages_supporting_prototype-based_programming)
Trong một ngôn ngữ hướng đối tượng prototype-based, bạn sẽ có cảm giác như Class bị lãng quên; lý do đơn giản vì nó chưa từng tồn tại cho đến trước khi es6 ra đời. Thay vì xây dựng một Class với các method, property thì với Javascript bạn chỉ có object, và mỗi object lại có cho riêng mình một schema nữa. (Lưu ý lại chút là với các ngôn ngữ hướng đối tượng khác thì các object được khởi tạo từ cùng 1 class sẽ share chung schema, nhưng trong JS thì mọi object đều sở hữu schema của riêng mình). Trong Javascript, cách để xây dựng mối quan hệ giữa các object đó là tự xây dựng một kiến trúc object-to-object. Điều đó có nghĩa là bạn hoàn toàn có thể tạo ra một object mới từ một object có sẵn, object mới này sẽ được kế thừa toàn bộ các property và method từ object ban đầu.
Việc kế thừa từ Class không hề tồn tại trong Javascript. Khi một object mới tạo ra không có method mà bạn cần thì việc quyết định gọi method đó sẽ được ủy quyền (delegate) cho object ban đầu (object cha). Khái niệm prototype sinh ra từ đây và nó cũng là khởi nguồn cho tên prototypal programming
Điều thú vị là Prototypal programming trong Javascript thực ra lại rất dễ. Nhiều người có xu hướng làm phức tạp vấn đề hơn là nó vốn có chỉ vì họ không quen làm việc với prototype. Hãy cùng xem một ví dụ nhé:
var a = { name: 'Foo', alertMe: function() { alert('Foo!'); } }; var b = Object.create(a); b.name; // Foo b.alertMe(); // alert with 'Foo!'
Trong ví dụ này chúng ta khởi tạo một object đơn giản a chứa 1 property và 1 method. Đây là 1 dạng object thường thấy và không có gì đặc biệt. Tuy nhiên hãy cùng theo dõi đến đoạn tiếp theo. Chúng ta sử dụng Object.create() để tạo một object mới (b) từ chính object a.
Kể từ thời điểm tạo xong thì b sẽ có prototype và có quyền access đến prototype của chính nó. Do đó từ b ta có thể trực tiếp gọi đến các method hoặc property đã được định nghĩa từ a. Sự liên kết từ object con tới object cha này được gọi là protoype chain. Khi chúng ta cần gọi một method không tồn tại từ một object thì quá trình tìm kiếm sẽ tiếp tục cho đến khi method được tìm thấy tại một object nào đó hoặc khi kết thúc chains.
Trong trường hợp đã kết thúc chains hoặc quá trình tìm kiếm không trả về kết quả nào thì kết quả trả về sẽ là undefined.
Trong ví dụ bên trên thì b không hề có property nào tên là alertMe, do đó quá trình tìm kiếm sẽ được chuyển sang thực hiện ở object a. Đây chính là sự khác biệt giữa Javascript với các ngôi ngữ class-based khác.
Có thể bạn sẽ thắc mắc là liệu a có được kế thừa từ object nào không? Câu trả lời là có nhé. Tại root của bất kỳ một chain nào thì tất cả các object đều kế thừa từ Object.prototype. Đó là lý do vì sao tất các object đều có các method mặc định như hasOwnProperty(), valueOf(), toString()
Quay trở lại ví dụ của chúng ta. Một câu hỏi đặt ra là liệu chúng ta có thể thiết lập thêm method cho object b không? (tất nhiên là có nhé)
b.bar = "bar"; b.bar; // bar a.bar; // undefined
như bạn đã thấy thì chúng ta vừa thêm property bar cho object b và có thể access đến property đó từ b. Và hiển nhiên là property bar sẽ không thể được access từ a bởi vì a là object cha và chúng ta không hề thiết lập property bar cho a.
Ngược lại nếu chúng ta thiết lập bar cho a thì ta có thể access đến bar ngay cả từ a và b
a.bazbar = "bazbar"; a.bazbar; // bazbar b.bazbar; // bazbar
Đa hình
Chúng ta đã cùng xem ví dụ về việc kế thừa (ủy quyền) trong object, thế còn với tính đa hình thì sao nhỉ