Trong bài trước chúng ta tìm hiểu khái niệm object trong JavaScript. Bài này bàn về việc ghi đè từ Object tới các class ảo/object Number, String…
Chú ý, theo tư duy của một php developer thì Object tạm hiểu là class cha, Number hay String… là các class kế thừa lớp cha này. Trong JS, ta gọi Object, String hay Number và kể cả function là các Constructor.
Trong JS thì hai cách khai báo sau gần như tương đương nhau.
[js]
let x = "hello world";
let y = new String("hello world");
//mặc dù
x instanceof String// false
typeof(x) // string
y instanceof String//true
typeof(x) // object
[/js]
Trong JS cả x và y đều mang các hàm dựng sẵn từ Constructor String (trong class ảo String – theo tư duy PHP developer). JS sẽ “bọc” x và giúp nó mang các method dựng sẵn của “class ảo” String. Quan sát:
Giờ ta sẽ test cơ chế ghi đè phương thức.
1.Cơ chế ghi đè
Đầu tiên ta ghi đè một phương thức trong Object bằng cách:
[js]
Object.prototype.toString() = function toString(){
return "something";
}
[/js]
Kết quả, tất cả các object trong nhóm String, Array, Number… tạo bằng cách thông thường hay cách khởi tạo qua “new” đều không bị tác động. Nguyên nhân là các object khởi tạo từ nhóm này sẽ dùng chính hàm toString() của các constructor này trước, thay vì sử dụng của Object.
Chỉ các object tạo bằng cách {} hoặc new function hoặc new ClassName sẽ bị can thiệp khi gọi toString().
2. Cơ chế khai báo phương thức mới
Khi thêm một method vào trong Object thiên tôn thì tất cả các đối tượng trong JS sẽ sử dụng phương thức này, nếu ở từng nhóm class ảo Array, Number, String… tương ứng ta không tạo method tương tự thông qua prototype.
Giả sử ta khai báo cùng một method trong cả Object và trong Array, String… thì sao?
Trong ví dụ dưới, ta thêm method test() vào Object. Ta cũng thêm method test() vào Number. Tuy nhiên, khi chạy, đối tượng luôn chọn method từ Object. Bình tĩnh, có gì đó sai sai trong hình dưới 🙂
Dù là khai bạo tự nhiên (x = “hello”; hay x = new String (“hello”)) thì chúng đều ưu tiên method từ Object khi method này được thêm mới.
Tuy nhiên, sự thật là, nếu ta tạo method mới bằng cách String.prototype.test thì kết quả hoàn toàn khác.
Như vậy, instance khởi tạo sẽ dùng method có sẵn trong Constructor mà nó thuộc về TRƯỚC KHI dùng method trong constructor Object. Đây là một ví dụ khác:
Trong ví dụ trên, object tạo từ class sẽ dùng method có sẵn trong class đó, nếu không có nó mới lấy từ Object. Function cũng vậy. Xem ví dụ dưới
Vậy ở đây ta hiểu khái niệm prototype chain trong Js. Tức một object sẽ lấy method, properties từ chính prototype của Constructor tạo ra nó như từ function hoặc class, hoặc từ prototype của constructor cao hơn nó như Array, Number, String, cuối cùng là Object đại cụ, cuối cùng là null nếu trong Object đại cụ không có.
Vấn đề kế thừa prototype, hàm constuctor và class constuctor trong JS phức tạp hơn nhiều, ta sẽ tiếp tục bàn ở các bài sau.
Chú ý 1): length là một property của Object, gọi là các native property (và method) của Object, best practice là không nên edit hoặc ghi đè, extend chúng.
Chú ý 2): trong bài có ghi là “instance”, thực chất chúng không tạo ra bản copy giống như class-based inheritance. Có người cho rằng, không nên dùng khái niệm “inheritance” (Tác giả của “You dont known JS”).
Chú ý 3: “When it comes to inheritance, JavaScript only has one construct: objects. Each object has a private property which holds a link to another object called its prototype. That prototype object has a prototype of its own, and so on until an object is reached with null
as its prototype. By definition, null
has no prototype, and acts as the final link in this prototype chain.”
(Theo Mozilla https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain)