__Proto__ trong javascript

Hôm nay rảnh rỗi, đảo qua đảo lại trên Kipalog, cơ mà chưa thấy ai viết bài về vấn đề này [hay có ai viết rồi mà tìm hoài hổng ra]. Thế nên là thôi thì rảnh rỗi viết tí cho đỡ buồn. Prototype - Dịch là nguyên mẫu, bản thân là một hình thái phân tách và lưu trữ dữ liệu.

Có thể rất nhiều người đã từng biết đến Prototype Design Pattern, Prototype Function-base or function prototype [hay còn gọi là abstract method], tuy nhiên thì theo quan điểm cá nhân của em thì Prototype là một hệ tư tưởng [giống như OOP vs Functional Programming, SQL và NoSQL, ... ] thì Prototype sẽ đối trọng với hệ tư tưởng Object Class.

Nghe vui tai không :]]. Cơ mà bình thường Object Class là OOP rồi, vậy thì cái Prototype này là Functional Programming à ? À cũng không hẳn, nó là một cách khác để tổ chức object class song song và tương đối biệt lập với Object Class. Chúng ta sẽ đi qua Prototype Function-base nhé:

Prototype là object cơ bản [hiểu nôm na là abstract object] mỗi khi có một object được tạo ra.

var myBird = {
    fly: [] => { console.log["Fly"] },
    wings: 2
}

Tại đây, để truy cập prototype của object myBird, chúng ta sử dụng từ khóa

var a = {
  x: 10,
  calculate: function [z] {
    return this.x + this.y + z;
  }
};

var b = {   // This mean object b inherite from object a]
  y: 20,
  __proto__: a
};

var c = {
  y: 30,
  __proto__: a
};

// call the inherited method
b.calculate[30]; // 60
c.calculate[40]; // 80
0
var a = {
  x: 10,
  calculate: function [z] {
    return this.x + this.y + z;
  }
};

var b = {   // This mean object b inherite from object a]
  y: 20,
  __proto__: a
};

var c = {
  y: 30,
  __proto__: a
};

// call the inherited method
b.calculate[30]; // 60
c.calculate[40]; // 80
1 trỏ tới prototype của object myBird và nếu khai báo như trên, prototype của myBird chính là
var a = {
  x: 10,
  calculate: function [z] {
    return this.x + this.y + z;
  }
};

var b = {   // This mean object b inherite from object a]
  y: 20,
  __proto__: a
};

var c = {
  y: 30,
  __proto__: a
};

// call the inherited method
b.calculate[30]; // 60
c.calculate[40]; // 80
4.

1.1 Tìm kiếm theo thứ tự inheritance.

Chúng ta có một ví dụ như sau:

var a = {
  x: 10,
  calculate: function [z] {
    return this.x + this.y + z;
  }
};

var b = {   // This mean object b inherite from object a]
  y: 20,
  __proto__: a
};

var c = {
  y: 30,
  __proto__: a
};

// call the inherited method
b.calculate[30]; // 60
c.calculate[40]; // 80

Ở ví dụ phía trên chúng ta thấy:

  • Khi gọi một property của một object, nếu bản thân chưa được khai báo, chương trình sẽ tìm kiếm từ prototype của nó. Nếu prototype của nó không có, lại tiếp tục tìm kiếm trong prototype của prototype của nó. Cứ như vậy chúng ta có prototype chain. Điều này rất giống với override trong OOP thông tường. Vậy nên, khi sử dụng từ khóa this trong một hàm, tốt hơn nên biết rằng mình đang có ý định gọi ở đâu.

1.2 Constructor

Trong OOP thông thường, khi khởi tạo mỗi class, chúng ta có constructor để khởi tạo object đó. Tuy nhiên vì trong Prototype-based programming, chúng ta khởi tạo một object từ một vị trí bất kỳ, không cần khai báo thuộc kiểu class nào. Do vậy, trong các ngôn ngữ Prototype-based programming, người ta sinh ra một kiểu constructor như sau:

const Foo = function [y]{
    this.y = y
}

Foo là một hàm thông thường, nhưng chúng ta đã biết, function thực chất chỉ là một loại object đặc biệt, do vậy, Foo ở đây cũng là một class.

var a = {
  x: 10,
  calculate: function [z] {
    return this.x + this.y + z;
  }
};

var b = {   // This mean object b inherite from object a]
  y: 20,
  __proto__: a
};

var c = {
  y: 30,
  __proto__: a
};

// call the inherited method
b.calculate[30]; // 60
c.calculate[40]; // 80
5 là câu lệnh gán cho object Foo, một biến nội tại bằng giá trị truyền vào. Khi khởi tạo một object mới dựa trên một object cũ đã có, chúng ta khởi tạo bằng từ khóa
var a = {
  x: 10,
  calculate: function [z] {
    return this.x + this.y + z;
  }
};

var b = {   // This mean object b inherite from object a]
  y: 20,
  __proto__: a
};

var c = {
  y: 30,
  __proto__: a
};

// call the inherited method
b.calculate[30]; // 60
c.calculate[40]; // 80
6:

let b = new Foo[20]
let c = new Foo[30]

Ở đây b và c là 2 object mới. Theo như OOP, chúng ta mong muốn

var a = {
  x: 10,
  calculate: function [z] {
    return this.x + this.y + z;
  }
};

var b = {   // This mean object b inherite from object a]
  y: 20,
  __proto__: a
};

var c = {
  y: 30,
  __proto__: a
};

// call the inherited method
b.calculate[30]; // 60
c.calculate[40]; // 80
7 và
var a = {
  x: 10,
  calculate: function [z] {
    return this.x + this.y + z;
  }
};

var b = {   // This mean object b inherite from object a]
  y: 20,
  __proto__: a
};

var c = {
  y: 30,
  __proto__: a
};

// call the inherited method
b.calculate[30]; // 60
c.calculate[40]; // 80
8 có chung một số property hoặc method. Vậy chúng ta làm thế nào ? Sử dụng từ khóa
var a = {
  x: 10,
  calculate: function [z] {
    return this.x + this.y + z;
  }
};

var b = {   // This mean object b inherite from object a]
  y: 20,
  __proto__: a
};

var c = {
  y: 30,
  __proto__: a
};

// call the inherited method
b.calculate[30]; // 60
c.calculate[40]; // 80
9

Foo.prototype.x = 10

let b = new Foo[20]
let c = new Foo[30]

// -> This will make

// b.x = 10
// c.x = 10

Khi chúng ta khai báo

const Foo = function [y]{
    this.y = y
}
0, thực tế,
var a = {
  x: 10,
  calculate: function [z] {
    return this.x + this.y + z;
  }
};

var b = {   // This mean object b inherite from object a]
  y: 20,
  __proto__: a
};

var c = {
  y: 30,
  __proto__: a
};

// call the inherited method
b.calculate[30]; // 60
c.calculate[40]; // 80
7 đã link
var a = {
  x: 10,
  calculate: function [z] {
    return this.x + this.y + z;
  }
};

var b = {   // This mean object b inherite from object a]
  y: 20,
  __proto__: a
};

var c = {
  y: 30,
  __proto__: a
};

// call the inherited method
b.calculate[30]; // 60
c.calculate[40]; // 80
0 tới
const Foo = function [y]{
    this.y = y
}
3.

Vậy nên khi chúng ta gọi

const Foo = function [y]{
    this.y = y
}
4 -> thực tế đang gọi
const Foo = function [y]{
    this.y = y
}
5 hay nói cách khác là
const Foo = function [y]{
    this.y = y
}
6. Nếu thay đổi giá trị
const Foo = function [y]{
    this.y = y
}
7 này, toàn bộ các object link
var a = {
  x: 10,
  calculate: function [z] {
    return this.x + this.y + z;
  }
};

var b = {   // This mean object b inherite from object a]
  y: 20,
  __proto__: a
};

var c = {
  y: 30,
  __proto__: a
};

// call the inherited method
b.calculate[30]; // 60
c.calculate[40]; // 80
0 tới
const Foo = function [y]{
    this.y = y
}
3 [hay nói cách khác là toàn bộ object kế thừa từ
let b = new Foo[20]
let c = new Foo[30]

0] sẽ thay đổi giá trị.

=> Việc khai báo Foo.prototype thực tế không khác gì việc chúng ta đang khai báo property hay method trong OOP thông thường. Nói cách khác, đây chính là cách khai báo khởi tạo những giá trị trong object của Prototype-based programming. Nhưng nên lưu ý, đặc điểm của Prototype là nó chỉ link, chứ không tạo mới. Do vậy thực tế các giá trị này đều trỏ vào cùng một nơi.

Đối với OOP thông thường, vì những property hay method được định nghĩa sẵn từ trước khi sử dụng nên hầu như không có trường hợp thay đổi lúc Run-time. Tuy nhiên với Prototype-based programming thì có thể. Giả dụ, chúng ta có thể làm như sau:

// Continue from previous code
console.log[b.x] // => 10
b.__proto__.x = 11
console.log[c.x] // => 11

NOTE: Ở đây có một lưu ý, tại sao lại dùng

const Foo = function [y]{
    this.y = y
}
5 mà không dùng
let b = new Foo[20]
let c = new Foo[30]

2 ?

Đó là vì, nếu sử dụng

let b = new Foo[20]
let c = new Foo[30]

2, hệ thống sẽ hiểu là chúng ta đang muốn set một biến
const Foo = function [y]{
    this.y = y
}
7 cho
var a = {
  x: 10,
  calculate: function [z] {
    return this.x + this.y + z;
  }
};

var b = {   // This mean object b inherite from object a]
  y: 20,
  __proto__: a
};

var c = {
  y: 30,
  __proto__: a
};

// call the inherited method
b.calculate[30]; // 60
c.calculate[40]; // 80
7 tại scope
var a = {
  x: 10,
  calculate: function [z] {
    return this.x + this.y + z;
  }
};

var b = {   // This mean object b inherite from object a]
  y: 20,
  __proto__: a
};

var c = {
  y: 30,
  __proto__: a
};

// call the inherited method
b.calculate[30]; // 60
c.calculate[40]; // 80
7, chứ không phải tại scope
let b = new Foo[20]
let c = new Foo[30]

7. Khi đã set
let b = new Foo[20]
let c = new Foo[30]

2 rồi, theo như 1.1, hệ thống sẽ luôn lấy giá trị x này. Kể cả khi chúng ta set giá trị
let b = new Foo[20]
let c = new Foo[30]

9 như sau:

b.x = undefined
// b.x -> undefined

b.x = null
// b.x -> null

delete b.x
// b.x == b.__proto__.x = 11

Chỉ khi delete biến

const Foo = function [y]{
    this.y = y
}
7 tại scope
var a = {
  x: 10,
  calculate: function [z] {
    return this.x + this.y + z;
  }
};

var b = {   // This mean object b inherite from object a]
  y: 20,
  __proto__: a
};

var c = {
  y: 30,
  __proto__: a
};

// call the inherited method
b.calculate[30]; // 60
c.calculate[40]; // 80
7 thì khi gọi
const Foo = function [y]{
    this.y = y
}
4 lần nữa, hệ thống sẽ tìm từ
var a = {
  x: 10,
  calculate: function [z] {
    return this.x + this.y + z;
  }
};

var b = {   // This mean object b inherite from object a]
  y: 20,
  __proto__: a
};

var c = {
  y: 30,
  __proto__: a
};

// call the inherited method
b.calculate[30]; // 60
c.calculate[40]; // 80
0 của nó.

1.3 How about static ?

Trong OOP, đôi khi, chúng ta cần sử dụng những static property, static method, khai báo trong class. Vậy đối với Prototype-based programming thì liệu có thể làm vậy không ? Chúng ta khai báo trực tiếp trong object Foo phía trên:

Foo.myStaticMethod = [] => {
    // do smt
}

Foo.myStaticVariable = 10

Với ECMAScript 6 ra đời, chúng ta lại được quay lại các từ khóa

Foo.prototype.x = 10

let b = new Foo[20]
let c = new Foo[30]

// -> This will make

// b.x = 10
// c.x = 10

4,
Foo.prototype.x = 10

let b = new Foo[20]
let c = new Foo[30]

// -> This will make

// b.x = 10
// c.x = 10

5,
Foo.prototype.x = 10

let b = new Foo[20]
let c = new Foo[30]

// -> This will make

// b.x = 10
// c.x = 10

6,
Foo.prototype.x = 10

let b = new Foo[20]
let c = new Foo[30]

// -> This will make

// b.x = 10
// c.x = 10

7, ... tuy nhiên, tất cả những thứ đó, thực ra chỉ là thay thế cho những thứ chúng ta vừa tìm hiểu phía trên. Có nghĩa, bản chất của Prototype-based programming thì không thay đổi, chỉ có từ khóa giúp chúng ta dễ viết mã code hơn giống với OOP.

NOTE : Những thứ mà prototype base làm được, còn OOP thì không ?
Nói về điều này, có lẽ hơi khó có thể đánh giá. Nhìn chung những gì mà OOP làm được, thì prototype cũng làm được. [Chúng hoàn toàn có thể hoán đổi cho nhau trong một số trường hợp]. Nhưng cũng có vài điểm mà chúng ta cần xem xét sự khác biệt.

Trong OOP đối với các ngôn ngữ biên dịch, việc ép kiểu [Đặc biệt là ép kiểu đối với các lớp kế thừa] là chuyện thường xuyên. Tuy nhiên JS không như vậy, vì nó không yêu cầu khai báo kiểu dữ liệu trước khi gọi. Ấy thế nhưng cũng có một số trường hợp, chúng ta cần ép kiểu cho một đối tượng.

Chủ Đề