Object, Function, Method, Constructor in JavaScript

July 24, 2018 (6y ago)

TL;DR

Lưu ý một chút về cách trình bày: .propname tức là public property có tên là "propname" của một đối tượng

Bắt đầu với Object, Function và Method

Prototypes

Properties lookup

object --chứa--> {Prototype}
 bản thân {Prototype} cũng  object
{Prototype} --chứa--> {Prototype}'s {Prototype}
và bản thân {Prototype}'s {Prototype} cũng  object
{Prototype}'s {Prototype} --chứa--> {Prototype}'s {Prototype}'s {Prototype}
.....
..... --chứa--> Build-in Object.prototype
(end)

Setting properties

Tiếp theo sẽ là gì? Ta cùng hình dung

Trước tiên sẽ là mối liên quan giữa public property .prototype{Prototype}. hình ellipse biểu diễn object, mũi tên biểu diễn property của object đó tham chiếu đến một object khác. {Prototype} chain sẽ được tô mũi tên mày xanh lá.

1: Define constructor:

function MyConstructor() {}

alt text

Ta dễ dàng thấy MyConstructor được biểu diễn trong hình Ellipse, tức nó là function và là functionObject và sẽ được dùng như constructor.

Ghi nhớ rằng: chỉ những public property .prototype của functionObject, là những object mặc định sở hữu public property .constructor
Tức là theo ví dụ trên: MyConstructor.prototype là giá trị mặc định cho property .prototype của MyConstructor. Và nó mặc định sở hữu property .constructor trỏ ngược về MyConstructor

Phần còn lại trong bức ảnh minh họa rõ ràng những khái niệm đã đề cập ở phần đầu bài viết

Bước kế tiếp ta sẽ bỏ qua phần {Prototype} chain của MyConstructor cho gọn, vì chúng không thay đổi và cũng không liên quan đến những gì được trình kế tiếp sau đây.

2: Assign new prototype property:

MyConstructor.prototype = {};

alt text

Ta thay .prototype mặc định của MyConstructor bằng một đối tượng mới. Đặc biệt đối tượng này không có property .constructor

3: Call constructor to create new object:

var myobject = new MyConstructor();

alt text

Như đã trình bày ở mục setting propertites của bài viết, ta dễ dàng thấy được {Prototype} của myobject và property .prototype của MyConstructor tham chiếu đến cùng một đối tượng.

Vậy bây giờ dựa theo mục properties lookup, ta thử truy xuất một property bất kì từ myobject. Ta chọn property có tên là constructor, chuyện gì sẽ xảy ra

myobject.constructor = ?

Đơn giản, áp dụng {Property} chain, ta sẽ có ngay đáp án. Bây giờ cùng thu gọn tất cả những đoạn code ta viết lại:

function MyConstructor() {}
MyConstructor.prototype = {};
var myobject = new MyConstructor();

myobject.constructor == Object; // true
myobject.constructor.prototype == Object.prototype; // true

Ghi chú: Nếu nhìn lại ta sẽ hiểu vì sao ngay từ bước #2 ta lược bỏ đi phần {Prototype} của MyConstructor là đúng đắn

Thế nếu dùng instanceof kết quả như thế nào?

Viết lại nguyên văn:
"Javascript provides the instanceof operator that’s intended to check the prototype chain of the object you’re dealing with."

Từ những bước ở trên, ta có thể nghĩ rằng đoạn code sau sẽ trả về false

function MyConstructor() {}
MyConstructor.prototype = {};
var myobject = new MyConstructor();

myobject instanceof MyConstructor; // true

nhưng thực chất, nó chạy ổn và còn hơn thế nữa:

function MyConstructor() {}
MyConstructor.prototype = {};
var myobject = new MyConstructor();

myobject instanceof Object; // true

Khi instanceof được gọi, nó hoạt động dựa trên {Prototype} chain. Và nó chẳng đá động hay lệ thuộc vào property .constructor

cùng nhìn lại hình ở bước #3

alt text

Cứ tới một "trạm" tức đối tượng tham chiếu bởi {Prototype}, nó check xem đối tượng này được tham chiếu thông qua .prototype property của ai.
Đầu tiên dừng tại đối tượng , nó check và phát hiện ra được tham chiếu thông qua .prototype property của MyConstructor, suy ra:

myobject instanceof MyConstructor; // true

Tiếp theo dừng tại đối tượng Object.prototype nó phát hiện ra Object.protoype tham chiếu thông qua .prototype property của build-in Object, suy ra:

myobject instanceof Object; // true

Có ổn rồi, nhưng ta vẫn có thể tìm ra những điều bất thường nếu chịu khó mò mẫm:

function MyConstructor() {}
var myobject = new MyConstructor();
MyConstructor.prototype = {};

[
  myobject instanceof MyConstructor, // false !
  myobject.constructor == MyConstructor, // true !
  myobject instanceof Object,
]; // true

ở đoạn code trên ta đổi thứ tự của hai dòng code, myobject được tạo ra trước sau đó MyConstructor mới đổi giá trị của .prototype property.
Do đó {Prototype} chain sẽ trông như vầy:

alt text

Đúng như dự đoán {Prototype} của object sẽ tham chiếu đến đối tượng cũ mà .prototype property của MyConstructor từng tham chiếu.

Điều này làm thay đổi {Prototype} chain và có thêm sự xuất hiện của .constructor property của old MyConstructor.prototype dẫn đến {Prototype} của object và .prototype property của MyConstructor không tham chiếu đến cùng một đối tượng.
Dừng tại {Prototype} đầu tiên là đối tượng old MyConstructor.prototype, nó không phát hiện ra đối tượng đó được tham chiếu thông qua .prototype property của ai cả. Tiếp tục {Prototype} thứ hai, là Object.prototype thì quá rõ ràng, suy ra:

myobject instanceof MyConstructor, // false !
  myobject instanceof Object; // true

Ngoài ra, thông qua {Prototype} chain (xem lại mục properties lookup), thì ta thấy .constructor property đầu tiên được bắt gặp trong old MyConstructor.prototype nên

myobject.constructor == MyConstructor, // true !

là điều dễ hiểu.

Một số nhận định

Constructors không phải classes

Nhìn lại các class-based OOP language (như Java, C#,...), các classes kế thừa từ những classes khác, và object là instance của những classes đó. Các properties và methods được chia sẻ giữa các instances. Và việc chia sẻ đó bị chi phối bởi access modifier (public, private, internal, protected,......)

Javascript cũng có khái niệm kế thừa, chia sẻ properties và methods thông qua prototype. Nhưng thực tế {Prototype} của constructor và {Prototype} chain của object được tạo ra từ constructor đó, lại hoàn toàn khác biệt, không liên quan đến nhau.

Constructors không hoạt động như class-based initializer

Để có được khái niệm kế thừa thông qua prototype, Khi constructor được gọi nó tiến hành liên kết {Prototype} property của object với .prototype property của chính nó. Những gì còn lại là việc constructor thêm vào một số properties, methods khác cho object

Constructors chỉ là functions

Xem lại bước #1, ta thấy MyConstructor chẳng khác gì một function bình thường. Vì vậy bất kì user-defined function nào trong Javascript cũng tự động có .prototype property tham chiếu đến một đối tượng sở hữu .constructor property tham chiếu ngược về function đó

Bất cứ user-defined function nào cũng được gọi thực thi như một constructor bằng cách thêm vào từ khóa new. Cách làm đó sẽ truyền object mới được tạo bởi từ khóa new vào trong constructor function, và phần việc còn lại của constructor thì như đã nói ở bên trên

References

(tham khảo từ bài viết của zeekat/articles với một số chỉnh sửa theo hiểu biết của bản thân và để phù hợp với Tiếng Việt)
(xem thêm constructor in Javascript object)
(xem thêm Javascript inheritance and the constructor property)
(xem thêm ECMA-262 lastest version)

Footnotes

[1]
John G Harris từng viết trong comp.lang.javascript rằng những gì trình bày ở bên trên cũng tương đối chưa đúng hoàn toàn. Về mặt lý thuyết, host system có thể sẽ đổi Object.prototype property bằng một thứ gì đó khác. Một số thảo luận chấp thuận rằng Object.prototype chỉ được read-only. Nhưng ở một số browser (firefox) thì ta có thể gán giá trị mới cho Object.prototype mà không có lỗi nào xảy ra.

[2]
có 4 cách để gọi thực thi (invocation) một function trong JavaScript
(giữ nguyên văn cho dễ hiểu)

Function form:

functionObject(arguments);

When a function is called in the function form, this is set to the global object.

Method form:

thisObject.methodName(agurments);
thisObject['methodName'](arguments);

When a function is called in the method form, this is set to thisObject, the object containning the function.
this allows method to have a reference to the object of interest

Constructor form:

new functionObject(arguments);

Apply form:

functionObject.apply(thisObject, [arguments]);
functionObject.call(thisObject, arguments....);
// the definition of call method
Funtion.prototype.call = function (thisObject) {
  return this.apply(thisObject, Array.prototype.slice.apply(arguments, [1]));
};

alt text