JavaScript là một ngôn ngữ rất linh hoạt, tuy nhiên ban đầu mình thực sự không có cảm tình với nó cho lắm. Có lẽ do đã quen với OOP và đem cái tư tưởng đó đi để tìm hiểu về nó, mình đã luôn cảm thấy mọi thứ ở đây lúc nào cũng rất lộn xộn. Function và object có ở khắp mọi nơi, chúng hòa trộn vào nhau làm cho bản thân mình nhiều khi không biết điều gì đang xảy ra. Nhưng sau cùng thì mình lại cảm thấy thích nó và nếu như nhìn vào quá trình phát triển và mục đích mà nó hướng tới thì bạn có thể hiểu phần nào lý do mà JavaScript đã trở nên như vậy.
Tìm việc làm Javascript lương upto 2000USD
__proto__
const dog = { name: 'Puppy', run() { console.log(this.name + ' is running...') } } const puppy = Object.create(dog)Mọi thứ nhìn có vẻ khó hiểu nhưng thực ra thì khó hiểu thật. Sẽ không khó để bạn đưa ra nhận định rằng
puppy
dường như đã được kế thừa điều gì đó từdog
thì mới có thểrun
một cách ngon lành như vậy. Nhưng chúng ta cũng đã thấy, thực sự thìpuppy
không có gì bên trong nó cả, vậy mấu chốt của vấn đề này là gì?Như vậy có thể hiểu
Object.create
đã tạo ra một object mới kế thừa từ một object ban đầu, nhưng đây cũng không hẳn là kế thừa, vì như trong ví dụ trên,puppy
đã không thực sự nhận được gì cả. Người thừa kếpuppy
của chúng ta chỉ có một đặc quyền duy nhất đó làdeletgating
(ủy thác) những việc mình muốn làm chodog
thực hiện. Và đặc quyền này đượcpuppy
cất giấu trong một property đặc biệt mà mọi object đều có đó là__proto__
. Chúng ta cũng có thể kết luận rằng khi tạo ra một object từ một object khác bằng methodObject.create
thì__proto__
của object mới sẽ luôn trỏ tới object ban đầu, trừ khi chúng ta sử dụngObject.setPrototypeOf
để bắt nó trỏ tới một object khác.
prototype
Qua ví dụ trên, chúng ta đã biết
__proto__
là một property mà mọi object trong JavaScript đều có. Vậy còn với mộtFunction
thì sao, tất nhiên nó cũng là một object nhưng hơn thế nữa nó còn có một property đặc biệt khác mà đã từng làm mình rất bối rối đó làprototype
. Chúng ta hay xem ví dụ sau để hiểu hơn về nó:function Dog() {} Dog.prototype.name = 'Puppy' Dog.prototype.run = function () { console.log(this.name + ' is running...') }Như bạn đã thấy, mình vừa tạo ra một function có tên là
Dog
và chẳng làm gì với nó cả. Tuy nhiên bên trong function này đã có sẵn một property có tên làprototype
. Đúng như tên gọi của nó,prototype
là nơi để ta xây dựng các nguyên mẫu và sau đó đem nhân bản ra các object khác:puppy = new Dog()Đến đây chắc hẳn bạn cũng đã hiểu điều gì đã xảy ra và có thể khẳng định rằng, mọi object được tạo ra bằng keyword
new
sẽ có__proto__
trỏ tớiprototype
của đối tượng đã tạo ra nó (trong trường hợp trên là functionDog
).Object creation
Chúng ta vừa đi qua hai ví dụ về
__proto__
vàprototype
. Nếu để ý các bạn cũng có thể nhận ra, qua các ví dụ đó mình đã sử dụng tới 3 cách để tạo ra một object:
- Object as literal: Sử dụng cặp dấu ngoặc
{}
và bên trong đó là danh sách các property, method của object. - Object.create: Sử dụng
Object.create
để tạo một object mới với__proto__
trỏ tới object ban đầu. - Function constructor: Tạo object bằng việc sử dụng từ khóa
new
. Object mới sẽ có__proto__
trỏ tớiprototype
của function tạo ra nó.
Có thể bạn sẽ thắc mắc là tại sao mình không nhắc tới một kiểu tạo object nữa từ class. Ví dụ như:
class Animal {
constructor(name) {
chúng tôi = name
}
run() {
console.log('Running...')
}
}
dog = new Animal('Puppy')
Đúng là bạn có thể tạo một object theo cách như trên nhưng sự thật là trong JavaScript không có khái niệm class. Tất cả những gì bạn vừa thấy chỉ là một kiểu syntax khác để JavaScript trở nên thân thiện hơn đối với các lập trình viên đã quá quen với OOP. Ẩn sâu sau lớp vỏ bọc đó vẫn là các Function
. Nếu các bạn còn băn khoăn thì có thể kiểm chứng:
Object as literal
Đây là cách để tạo ra object đơn giản nhất và có thể cũng là thông dụng nhất trong JavaScript:
const student = { name: 'Lam', say: function () { console.log('Hi! I am ' + this.name) } }
Có lẽ cũng không cần nói nhiều về cách tạo ra một object theo kiểu này. Có thể hiểu nó đơn giản như một kiểu dữ liệu dạng key-value
, trong đó value
là bất cứ thứ gì mà bạn muốn, nhưng khi nó là một function
thì người ta sẽ gọi nó là một method.
Object.create
Khi bạn muốn tạo ra nhiều object có chung các thuộc tính hay method thì việc sử dụng object as literal
thực sự không phải là một lựa chọn tốt. Bạn sẽ phải lặp lại rất nhiều các đoạn code giống nhau và khi muốn thay đổi một điều gì đó thì nó còn tồi tệ hơn là khi bạn tạo ra chúng. Lúc này bạn có thể sẽ nghĩ đến Object.create
.
const student = { say: function () { console.log('Hi! I am ' + this.name) } } const studentA = Object.create(student) studentA.name = 'Student A' const studentB = Object.create(student) studentB.name = 'Student B' student.say = function () { console.log('Hello everyone! I am ' + this.name.toUpperCase()) }Như mình đã giải thích ở phần trên,
studentA
vàstudentB
đều có__proto__
trỏ đếnstudent
, vì thế khistudent
thay đổi thì chúng cũng sẽ được cập nhật các thay đổi tương ứng. Có thể thấy rằngObject.create
đã thực hiện hai việc:
- Tạo một empty object mới
- Trỏ
__proto__
của object mới đó đến object ban đầu
Để dễ hình dung hơn, chúng ta cũng có thể biểu diễn chúng thông qua một đoạn code đơn giản sau:
function createObject(object) {
let newObject = {}
Object.setPrototypeOf(newObject, object)
return newObject
}
studentA = createObject(student)
Kết quả nhận được chắc chắn sẽ không có gì khác biệt.
Function constructor
Trở lại ví dụ bên trên về Animal
và thay đổi một chút:
function Animal(name) { chúng tôi = name chúng tôi = function () { console.log(this.name + ' is running...') } } dog = new Animal('Puppy')
function createObjectOf() { let newObject = {} let args = Array.from(arguments) let constructor = args.shift() constructor.apply(newObject, args) Object.setPrototypeOf(newObject, constructor.prototype) return newObject } dog = createObjectOf(Animal, 'Puppy')
Mọi thứ hoạt động đúng như mong muốn và bây giờ là lúc quay lại để xem chúng ta đã làm những gì trong createObjectOf
:
- Đầu tiên là tạo ra một empty object mới.
- Lấy ra constructor và arguments tương ứng từ dữ liệu được truyền vào.
- Sử dụng
Function.prototype.apply
để gọi đến function constructor (ở đây làAnimal
) vớithis
ở đây chính lànewObject
. - Trỏ
__proto__
củanewObject
đếnprototype
của constructor thông quaObject.setPrototypeOf
. - Cuối cùng là trả về
newObject
vừa được tạo.
Đó là tất cả những gì JavaScript đã làm, có thể là với một cách thức khác nhưng kết quả thì không có gì khác biệt.