Javascript inheritance behind the scene __proto__, [[prototype]] and prototype

Bài viết được dịch từ https://hackernoon.com/understand-nodejs-javascript-object-inheritance-proto-prototype-class-9bd951700b29

Prototype là 1 thuộc tính của hàm khởi tạo, quyết định cấu trúc của thuộc tính __proto__ trong đối tượng được khởi tạo.

Điều cốt lõi để hiểu được kế thừa trong javascript là hiểu quá trình mà con gà (prototype) đẻ ra quả trứng (__proto__) trong Javascript. Kế thừa trong javascript thể hiện bằng việc kế thừa prototype nhưng khái niệm class truyền thống (trong oop) về cả mặt công nghệ lẫn khái niệm đều không tồn tại (trong Javascript, class trong es6 thực ra cũng chỉ là function).

Đây là một bài viết sẽ xóa tan sự hoang mang về protype, __proto__ và kế thừa trong javascript. Hầu hết nội dung ở đây được sưu tập từ bài viết của 2 tác giả tuyệt vời là Dmitry Soshnikov và Kenneth Kin Lum : http://dmitrysoshnikov.com/ecmascript/javascript-the-core/ https://kenneth-kin-lum.blogspot.tw/2012/10/javascripts-pseudo-classical.html?showComment=1484288337339#c1393503225616140233

1.[[prototype]] vs __proto__ vs prototype

speakingjs.comjavascripttutorial.com là 2 nguồn tốt nhất giải thích quan hệ giữa [[prototype]], __proto__ và prototype từ cơ bản cho tới việc ứng dụng, một điều rất quan trọng nữa là họ biểu diễn mối quan hệ này bằng hình ảnh.

Mỗi đối tượng có thể chứa những đối tượng khác như là prototype của nó. Sau đó khuôn mẫu của đối tượng kế thừa tất cả thuộc tính prototype của nó. Một đối tượng chỉ định thuộc tính prototype của nó qua thuộc tính nội bộ [[Prototype]]. Chuỗi những đối tượng kết nối bởi thuộc tính [[Prototype]] được gọi là chuỗi prototype.

Để thấy cách mà kế thừa dự trên prototype (hay prototypal (nguyên mẫu)) làm việc, hãy nhìn vào ví dụ dưới đây (với syntax được tạo ra để chỉ định thuộc tính [[Prototype]])

var proto = {
    describe: function () {
        return 'name: '+this.name;
    }
};
var obj = {
    [[Prototype]]: proto,
    name: 'obj'
};> obj.describe
[Function]> obj.describe()
'name: obj'

__proto__ là thuộc tính truy cập của đối tượng Object.prototype. Nó hiển thị liên kết nội bộ prototype ([[Prototype]]) của một đối tượng mà nó được truy cập (by javascripttutorial).

function Foo(name) {
  this.name = name;
}var b = new Foo('b');
var a = new Foo('a');
b.say = function() {
  console.log('Hi from ' + this.whoAmI());
}console.log(a.__proto__ === Foo.prototype); // true
console.log(a.__proto__ === b.__proto__); // true

Javascript engine thêm phương thức say() vào đối tượng b, không phải đối tượng Foo.prototype. Như bạn có thể nhìn thấy trong biểu đồ, a.__proto__ hiển thị [[Prototype]] trỏ đến đối tượng Foo.prototype. Tương tự, b.__proto__ cũng trỏ đến đối tượng tương tự như là a.__proto__.

2.Tạo đối tượng bằng hàm khởi tạo

Đây là 1 ví dụ từ Dmitry, về việc tạo đối tượng bằng hàm khởi tạo, nó sẽ chỉ ra cách mà prototype và __proto__ làm việc trong cơ chế kế thừa.

Bên cạnh việc tạo đối tượng bởi mẫu được chỉ định, một hàm khởi tạo làm một điều có ích khác - nó tự động đặt một đối tượng prototype cho đối tượng mới được khởi tạo. Đối tượng prototype này được lưu trong thuộc tính ConstructorFunction.prototype.

ví dụ: Chúng ta có thể viết lại ví dụ trước với đối tượng b sử dụng hàm khởi tạo. Do vậy, đối tượng Foo.prototype đóng vai trò:

Tạo đối tượng Foo với prototype x và calculate()

function Foo(y) {
  this.y = y;
}Foo.prototype.x = 10;
Foo.prototype.calculate = function (z) {
  return this.x + this.y + z;
};

Tạo instance b sử dụng đối tượng Foo

var b = new Foo(20);b.calculate(30); // 60console.log(
  b.__proto__ === Foo.prototype, // true
  b.__proto__.calculate === Foo.prototype.calculate // true
  b.__proto__.calculate === b.calculate, // true
  Foo === b.constructor, // true
  Foo === Foo.prototype.constructor, // true
);

Như đã được chỉ ra ở trên, b được kế thừa phương thức từ Foo(). “Foo.prototype” tự động tạo thuộc tính đặc biệt “constructor”, là một con trỏ đến hàm khởi tạo của chính nó.

Instances “b” có thể tìm thấy nó qua sự ủy quyền và sử dụng để kiểm tra cách nó được khởi tạo.

3 : Mô hình kế thừa cổ điển trong Javascript

Đây là một ví dụ từ Kenneth, cũng về việc khởi tạo đối tượng bằng hàm khởi tạo nhưng chúng ta tập trung vào vấn đề của chuỗi prototype xuyên suốt đối tượng và instances. Đối tượng prototype vẫn chỉ là đối tượng bình thường và có thể có prototype của nó. Nếu một prototype có một địa chỉ không null trỏ đến prototype của nó, và như vậy, điều này được gọi là chuỗi prototype (by Dmitry).

Điều tiếp theo là một biểu đồ của JavaScript Pseudo Classical Inheritance (chả biết dịch sao nữa). Hàm khởi tạo Foo chỉ là một tên lớp của một lớp trong tưởng tượng. Đối tượng foo là một instance của Foo.

Và bây giờ chúng ta có thể nhìn thấy từ biểu đồ trên tại sao, khi nào chúng ta kế thừa Dog từ Animal, chúng ta làm như sau:

function Dog() {} // the usual constructor function
 Dog.prototype = new Animal();
 Dog.prototype.constructor = Dog;

Điều gì xảy ra khi new() một instance:

Chú ý rằng prototype trong Foo.prototype không giống với chuỗi prototype. Foo.prototype trỏ đến một điểm nào đó trong chuỗi prototype, nhưng thuộc tính prototype này của Foo không giống với chuỗi prototype. Cái gì cấu tạo nên một chuỗi prototype là __proto__ trỏ đến chuỗi này, và đối tượng trỏ đến bởi __proto__, như là từ foo.__proto__, đến foo.__proto__.__proto__, và cho đến khi chạm đến null.

Quan hệ của proto và prototype

JavaScript’s Pseudo Classical Inheritance làm việc theo cách này: Tôi là một hàm khởi tạo, và tôi chỉ là một function, và tôi mang một địa chỉ đến prototype, và bất cứ khi nào foo = new Foo() được gọi, tôi sẽ để foo.__proto__ trỏ đến đối tượng prototype của tôi. Do đó Foo.prototype và obj.__proto__ là 2 khái niệm khác nhau. Foo.prototype chỉ ra rằng khi một đối tượng của Foo được tạo ra, nó là một con trỏ đến nơi mà chuỗi prototype của đối tượng mới này trỏ đến, foo.__proto__ trỏ đến nơi mà Foo.prototype trỏ đến.


All Rights Reserved