Bạn không thể tiến xa trong Javascript mà không biết tới objects (đối tượng). Objects là nền tàng của hầu hết các ngôn ngữ lập trình trong JavaScript. Trong bài viết này, bạn sẽ học về sự đa dạng của patterns (các mẫu) trong việc tạo objects (đối tượng) mới, và để làm được như thế, bạn sẽ dần dần được dẫn đến sự hiểu biết tường tận về prototype (cha của các object) trong JavaScript.
Đây là một phần của khoá học nâng cao JavaScript. Nếu bạn thích bài viết này, thì thử trải nghiệm qua.
POST
Bạn không thể tiến xa trong Javascript mà không biết tới objects (đối tượng). Objects là nền tàng của hầu hết các ngôn ngữ lập trình trong JavaScript. Thực tế, học để biết tạo một objects có lẽ là thứ đầu tiên bạn đã học khi mà bạn mới bắt đầu.
let animal = {}
animal.name = 'Leo'
animal.energy = 10
animal.eat = function (amount) {
console.log(`${this.name} is eating.`)
this.energy += amount
}
animal.sleep = function (length) {
console.log(`${this.name} is sleeping.`)
this.energy += length
}
animal.play = function (length) {
console.log(`${this.name} is playing.`)
this.energy -= length
}
Bây giờ, odds (sự chênh lệch) xuất hiện trong ứng dụng của chúng ta nên chúng ta cần tạo nhiều hơn là 1 animal (ở đây là tên của object trong dòng code bên trên). Bước tiếp theo là chúng ta cần gói gọn logic nằm bên trong function ( hàm), cái mà có thể gọi ra khi mà chúng ta cần object với tên là animal. Chúng ta gọi pattern ( mẫu) này là Functional Instantiation, và chúng ta gọi bản thân nó là 1 "hàm tạo" ("constructor function"), bởi vì nó có trách nhiệm tạo ra 1 object (đối tượng) mới.
Functional Instantiation
function Animal (name, energy) {
let animal = {}
animal.name = name
animal.energy = energy
animal.eat = function (amount) {
console.log(`${this.name} is eating.`)
this.energy += amount
}
animal.sleep = function (length) {
console.log(`${this.name} is sleeping.`)
this.energy += length
}
animal.play = function (length) {
console.log(`${this.name} is playing.`)
this.energy -= length
}
return animal
}
const leo = Animal('Leo', 7)
const snoop = Animal('Snoop', 10)
Bây giờ, khi mà chúng ta cần tạo một object tên là animal mới (hay là 1 ví dụ mới), thì chúng ta chỉ phải gọi function ( hàm) Animal, chúng ta còn có thể chuyền vào giá trị cho name (ten con vật) và energy (năng lượng). Cái này vừa hay mà lại đơn giản có phải không.
Tuy nhiên, bạn có thấy được điểm yếu của pattern ( mẫu) này không? Điểm yếu lớn nhất mà chúng ta có thể sửa được liên quan đến - eat, sleep, và play. Ba cái methods ( phương thức) đấy có quá nhiều điểm chung. Không có một lí do nào mà lại phải tạo lại những cái methods đó mỗi khi chúng ta tạo một animal mới. Chúng ta đang lãng phí bộ và làm cho animal object to ra một cách không cần thiết. Vậy bạn đã nghĩ ra một giải pháp nào chưa? Thay vì tạo lại những methods mỗi lần tạo một animal, chúng ta có thể chuyển những methods đó đến object riêng của nó, sau đó mỗi con vật animal sẽ có đường dẫn đến object của nó. Chúng ta có thể gọi mẫu (pattern) này là Functional Instantiation with Shared Methods (hàm dẫn với phương thức chung có thể chia sẻ cho nhau), hơi dài nhưng dễ hiểu hơn.
Functional Instantiation with Shared Methods
const animalMethods = {
eat(amount) {
console.log(`${this.name} is eating.`)
this.energy += amount
},
sleep(length) {
console.log(`${this.name} is sleeping.`)
this.energy += length
},
play(length) {
console.log(`${this.name} is playing.`)
this.energy -= length
}
}
function Animal (name, energy) {
let animal = {}
animal.name = name
animal.energy = energy
animal.eat = animalMethods.eat
animal.sleep = animalMethods.sleep
animal.play = animalMethods.play
return animal
}
const leo = Animal('Leo', 7)
const snoop = Animal('Snoop', 10)
Bằng việc dẫn những cái phương thức chia sẻ được cho nhau vào object rieng của nó và dẫn thẳng cái object vào trong hàm animal (function animal), chúng ta đã giải quyết được vấn đề của những object animal với kích cỡ quá lớn.
Object.create
Củng cố ví dụ của chúng ta một lần nữa bằng việc sử dụng object.create .Object.create cho phép bạn tạo một object ngay cả khi mà có lỗi trong việc tìm property (giá trị) của object đó, nó có thể " hỏi" một object khác xem có property đó không (gia trị) đó không. Nhiều lý thuyết quá phải không. Thế thì xem một chú code nha.
const parent = {
name: 'Stacey',
age: 35,
heritage: 'Irish'
}
const child = Object.create(parent)
child.name = 'Ryan'
child.age = 7
console.log(child.name) // Ryan
console.log(child.age) // 7
console.log(child.heritage) // Irish
Trong ví dụ bên trên, child được tạo với Object.create(parent)
, khi mà cố lỗi tìm thẻ con child
, JavaScript sẽ chuyển quyền đó qua thẻ cha parent
object. có nghĩa là thẻ con child
không có giá trị (property) heritage
,thẻ cha parent
sẽ hiện cho thẻ child với giá trị heritage là Irish
Giờ với Object.create
, Làm thế nào để làm đơn giản đi code của function Animal trên kia? Thay vì cho thêm tất cả shared methods ( phương thức chung) từng cái vào một như chúng ta làm trước kia, Ta có thể sử dụng Object.create để nhượng quyền cho animalMethods
object. Để nghe hay hay hơn, chúng ta có thể gọi hàm mới là Functional Instantiation with Shared Methods and Object.create
🙃 (hàm dẫn với phương thức chung có thể chia sẻ cho nhau và Object.create).
Functional Instantiation with Shared Methods and Object.create
const animalMethods = {
eat(amount) {
console.log(`${this.name} is eating.`)
this.energy += amount
},
sleep(length) {
console.log(`${this.name} is sleeping.`)
this.energy += length
},
play(length) {
console.log(`${this.name} is playing.`)
this.energy -= length
}
}
function Animal (name, energy) {
let animal = Object.create(animalMethods)
animal.name = name
animal.energy = energy
return animal
}
const leo = Animal('Leo', 7)
const snoop = Animal('Snoop', 10)
leo.eat(10)
snoop.play(5)
📈 giờ mỗi lần chúng ta gọi leo.eat
, JavaScript sẽ tìm eat
method trong leo
object. Cái tìm kiếm đó sẽ lỗi, và, vì Object.create, nó sẽ chuyển quyền cho animalMethods
object là cái mà nó sẽ tìm object eat
.
Có vài thứ chúng ta có thể làm cho nó tốt hơn. Nghe có vẻ khó để quán lý object riêng biệt (animalMethods
) để chia sẻ methods giữa các ví dụ. Nghe như đó là cái đặc điểm chung mà bạn muốn thjwc hiện giữa các ngôn ngữ, và đó là lí do tại sao bạn lại đang ở đây - prototype
.
Thế Chính xác
prototype
trong JavaScript là gì ? Đơn giản thì mỗi hàm trong JavaScript thì có 1 giá trị prototype
property cái mà dẫn đến một object. Vậy bạn tự trải nghiệm thử xem.
function doThing () {}
console.log(doThing.prototype) // {}
Thay vì tạo một object riêng để quán lý methods ( như cái mà đã dùng với animalMethods
), chúng ta chỉ cần cho mỗi methods vào hàm của Animal
function's prototype? và tat cả chúng ta cần làm là thay vì dùng Object.create để chuyển quyền cho animalMethods
, chúng ta có thể sử dụng nó để chuyền quyền cho Animal.prototype
. chúng ta gọi nó là pattern ( những cái làm sẵn) Prototypal Instantiation
.
Prototypal Instantiation
function Animal (name, energy) {
let animal = Object.create(Animal.prototype)
animal.name = name
animal.energy = energy
return animal
}
Animal.prototype.eat = function (amount) {
console.log(`${this.name} is eating.`)
this.energy += amount
}
Animal.prototype.sleep = function (length) {
console.log(`${this.name} is sleeping.`)
this.energy += length
}
Animal.prototype.play = function (length) {
console.log(`${this.name} is playing.`)
this.energy -= length
}
const leo = Animal('Leo', 7)
const snoop = Animal('Snoop', 10)
leo.eat(10)
snoop.play(5)
👏👏👏 prototype là một giá trị mà tất cả các function trong JavaScript đều có, và cũng như chúng ta thấy ở trên kia, nó cho phép chúng ta chia sẻ methods giữa các. Tất cả các tính năng vẫn được giữ nguyên nhưng bây giờ thay vì phải quản lý một object riêng cho tất cả các methods, chúng ta có thể dùng một object khác mà có sẵn bên trong animal function, Animal.prototype.
Cùng tìm hiểu sâu thêm một chút
Đến thời điểm này thì chúng ta biết 3 thứ:
1) Làm sao để tạo một constructor function
2) Làm sao để thêm phương thức (methods) vào constructor function
3) Làm sao để dùng Object.create để chuyển hướng lỗi không tìm thấy đến một function's prototype.
Ba cách trên khá là quan trọng với bất kì ngôn ngữ lập trình nào. Có phải JavaScript dở đến nỗi mà không có cái nào dễ hơn như thế, bằng sử dụng từ new
, chúng ta có thể hoàn thành những thứ giống hệt nhau.
Bây giờ chúng ta có thể hiểu tường tận về từ new trong JavaScript.
Quay lại ví dụ về Animal constructor (hàm tạo), hai phần quan trọng nhật là tạo đối tượng(object) và trả nó về. Nếy mà ko sử dụng object.create để tạo đối tượng, thì chúng ta sẽ không thể truyền nó tới function's prototype khi mà không tìm được property (gia trị). Nếu không báo cáo return, thì chúng ta không bao giờ có thể quay lại đc object( đối tượng) đã được tạo trước đó.
function Animal (name, energy) {
let animal = Object.create(Animal.prototype)
animal.name = name
animal.energy = energy
return animal
}
Đây là một cái hay về new - khi mà bạn gọi một hàm sử dụng từ new, hai dòng đó sẽ được thực hiện một cách ngầm, và đối tượng được tạo đuợc gọi là this.
sử dụng comments ( ghi chú) để hiện những gì xảy ra bên trong nó và giả định hàm tạo (constructor) animal được gọi với từ new, nó có thể được viết lại như là this.
function Animal (name, energy) {
// const this = Object.create(Animal.prototype)
this.name = name
this.energy = energy
// return this
}
const leo = new Animal('Leo', 7)
const snoop = new Animal('Snoop', 10)
và nếu không có dòng giải thích ( comments).
function Animal (name, energy) {
this.name = name
this.energy = energy
}
Animal.prototype.eat = function (amount) {
console.log(`${this.name} is eating.`)
this.energy += amount
}
Animal.prototype.sleep = function (length) {
console.log(`${this.name} is sleeping.`)
this.energy += length
}
Animal.prototype.play = function (length) {
console.log(`${this.name} is playing.`)
this.energy -= length
}
const leo = new Animal('Leo', 7)
const snoop = new Animal('Snoop', 10)
Đối tượng this được tạo ra bởi vì hàm tạo với từ khoá new. Nếu mà bạn không cho từ new vào khi goị hàm, thì đối tượng (object) this sẽ không bao giờ được tạo, và nó được trả về mà mình không biết được. Chúng ta có thể tìm hiểu thêm về vấn đề này bằng ví dụ bên dưới.
function Animal (name, energy) {
this.name = name
this.energy = energy
}
const leo = Animal('Leo', 7)
console.log(leo) // undefined
Tên của pattern (mẫu) này được gọi là Pseudoclassical Instantiation.
Nếu JavaScript không phải ngôn ngữ lập trình đầu tiên của bạn, thì bạn có thể thiếu bình biền một chút đó.
Cho những ai chưa quen, một class ( lớp) cho phép bạn tạo một blueprint ( bản thiết kế) cho một đối tượng (object). Bất cứ khi nào bạn tạo một instance( phiên bản) cho Class ( lớp) đó, bạn có thể lấy được đối tượng đó với giá trị (properties) và phương thức ( methods) đã được định sẵn ở blueprint ( bản thiết kế) đó.
Giờ nghe đã quen hơn chưa? nó đơn giản là những gì chúng ta đã làm với hàm taọ (constructor function) animal trên kia. Tuy nhiên, thay vì sử dụng từ khoá class, chúng ta chỉ cần sử dụng hàm một hàm cũ của JavaScript để tạo lại cái đó. Nó cần phải làm thêm một chút công việc, cũng như hiểu biết về những gì xảy ra "bên trong" JavaScript nhưng những kết quả đều giống nhau.
Có một tin tốt là JavaScript không phải là ngôn ngữ chết, nó liên tục đươc trau dồi và phát triển bởi nhóm TC-39 committee. Có nghĩa là mặc dù những phiên bản đầu tiên không hỗ trợ những class ( lớp). Năm 2015, EcmaScript ( phiên bản chi tiết chính thức của JavaScript) 6 được đưa ra với hỗ trợ cho những class ( lớp) và tư khoá class. Giờ thì cùng xem làm thế nào mà hàm tạo animal trên kia có thể nhìn giống với với pháp new class.
class Animal {
constructor(name, energy) {
this.name = name
this.energy = energy
}
eat(amount) {
console.log(`${this.name} is eating.`)
this.energy += amount
}
sleep(length) {
console.log(`${this.name} is sleeping.`)
this.energy += length
}
play(length) {
console.log(`${this.name} is playing.`)
this.energy -= length
}
}
const leo = new Animal('Leo', 7)
const snoop = new Animal('Snoop', 10)
Trông khá là gọn phải không?
Vậy thì nếu đây là một cách mới để tạo các class, vậy tại sao chúng ta phải tốn thời gian cho cái cách cũ? lý do là vì cái cách mới ( với từ khoá class) là "đường tổng hợp" so với cái cách cũ mà chúng ta goị là pseudoclassical pattern. Để có thể hoàn toàn hiểu được sự tiện nghi của cú pháp của ES6 classes, chúng ta phải hiểu được pseudoclassical pattern.
Đến thời điểm này chúng ta đã tìm hiểu về những thứ thất yếu của JavaScript's prototype. Những phần sau đoạn này sẽ được chỉ dẫn để hiểu được những phần nên biết mà chủ đề này liên quan tới. Trong bài khác, chúng tôi dẽ dẫn những thứ thất yếu này và sử dụng chúng để giúp các bạn hiểu được sự kế thừa hoạt động thế nào trong JavaScript.
Source/ Nguồn: https://dev.to/tylermcginnis/a-beginners-guide-to-javascripts-prototype-5kk
Bình luận