Cảm ơn bạn!
Tất tần tật về this trong JavaScript
Hi, lại là homiedev với các bài viết trong chủ đề học JavaScript cơ bản đây 😁. Trong bài viết này, chúng ta sẽ cùng nhau tìm hiểu về giá trị this
trong JavaScript và xem this
trong các context (ngữ cảnh) khác nhau như thế nào nhé.
Nếu bạn đang làm việc với các ngôn ngữ lập trình khác như: Java, C# hay PHP, các bạn có thể đã quen thuộc với từ khóa this
. Trong các ngôn ngữ này, từ khóa this
đại diện cho instance của class. Và nó chỉ có liên quan trong class.
Trong JavaScript cũng có từ khóa this
. Tuy nhiên, từ khóa this
trong JavaScript hoạt động không giống với các ngôn ngữ lập trình khác. Chúng ta có thể sử dụng từ khóa this
ở global và function contexts. Sau đây chúng ta cùng tìm hiểu về nó nhé!
this trong JavaScript là gì?
this trong object method
Khi this
được sử dụng bên trong method của object thì this
chính là object đã chứa method này, hay nói cách khác this
references (tham chiếu) đến object hiện đang gọi hàm.
Giả sử ta có một object gọi là blog
có method greet()
. Khi chúng ta gọi method greet()
, thì chúng ta có thể truy cập object này.
const blog = {
name : 'homiedev.com',
categories: ['JS', 'TS', 'REACTJS', 'GATSBYJS'],
// this trong method
// this tham chiếu đến object person
greet() {
console.log(this);
console.log(this.name);
}
}
blog.greet();
Kết quả chúng ta được:
{name: 'homiedev.com', categories: Array(4), greet: ƒ}
homiedev.com
Chúng ta cùng nhìn vào lệnh gọi method như sau:
blog.greet();
Hàm greet()
là một method của object blog
. Do đó, bên trong hàm greet()
, this
sẽ tham chiếu đến object blog
. Chúng ta có thể nhìn vào cái đã .greet()
, ở đây là object blog
. Chính vì vậy this
trong greet()
sẽ tham chiếu đến blog
.
this trong global context
Trong global context, this
tham chiếu đến global object, là object window
trên trình duyệt web hoặc object global
trên Node.js.
Điều này là giống nhau ở cả hai chế độ strict và non-strict. Đây là kết quả trên trình duyệt web:
console.log(this === window); // true
Nếu chúng ta thêm một property cho this
trong global context, JavaScript sẽ thêm property này vào global object như trong ví dụ sau:
this.blogName = 'homiedev';
console.log(window.blogName); // 'homiedev'
this trong function context
Trong JavaScript, chúng ta có thể gọi một hàm theo những cách sau:
- Function invocation (gọi thông thường)
- Method invocation (sử dụng method)
- Constructor invocation (sử dụng constructor)
- Indirect invocation (gọi gián tiếp)
Với mỗi kiểu gọi hàm thì sẽ có context khác nhau. Do đó, this
hoạt động khác nhau.
Function invocation
Trong chế độ non-strict, this
tham chiếu đến global object khi hàm được gọi như sau:
function display() {
console.log(this === window); // true
}
display();
Khi chúng ta gọi hàm display()
, hàm này tham chiếu đến global object, là window
trên trình duyệt web (web browser) hoặc global
trên Node.js.
Cách gọi hàm trên tương tư như bên dưới:
window.display();
// hoặc
this.display();
Trong chế độ strict, JavaScript đặt giá trị this
bên trong một hàm là undefined. Ví dụ:
"use strict";
function display() {
console.log(this); // undefined
}
display();
Để sử dụng chế độ nghiêm ngặt (strict mode), chúng ta đặt "use strict"
ở đầu file JavaScript. Nếu chỉ muốn sử dụng chế độ này cho một hàm cụ thể, bạn đặt nó ở đầu thân hàm.
function display() {
"use strict";
console.log(this === undefined); // true
function blogName() {
// strict mode áp dụng cho cả nested functions
console.log(this === undefined); // true
}
blogName();
}
display();
Ở ví dụ trên, bên trong hàm blogName()
, this
cũng được đặt thành undefined.
Method invocation
Khi bạn gọi một method của object, JavaScript sẽ đặt this
thành đối tượng sở hữu method như ví dụ ở đầu bài viết ^^.
let helicopter = {
symbol: '🚁',
getSymbol: function () {
return this.symbol;
}
}
console.log(helicopter.getSymbol()); // 🚁
Trong ví dụ này, this
trong method getSymbol()
tham chiếu đến object helicopter
.
Chúng ta có thể gán giá trị của getSymbol
vào một biến như này:
let symbol = helicopter.getSymbol;
Sau đó, chúng ta gọi method thông qua biến symbol
:
console.log(symbol()); // undefined
🤨 Kết quả chúng ta nhận được là undefined thay vì "🚁". Xảy ra điều này là vì khi bạn gọi một method mà không kèm theo object sở hữu nó, JavaScript sẽ đặt this
thành global object ở chế độ non-strict và undefined trong chế độ strict.
Để xử lý vấn đề này, chúng ta sử dụng method bind()
của object Function.prototype
. Method bind()
sẽ tạo một hàm mới có từ khóa this
là một giá trị được chỉ định.
let symbol = helicopter.getSymbol.bind(helicopter);
console.log(symbol()); // 🚁
Trong ví dụ dưới đây, khi chúng ta gọi symbol()
, từ khóa this
được liên kết với object bicycle
. Ví dụ:
let bicycle = {
symbol: '🚲',
}
let helicopter = {
symbol: '🚁',
getSymbol: function () {
return this.symbol;
}
}
let symbol = helicopter.getSymbol.bind(bicycle);
console.log(symbol()); // 🚲
Trong ví dụ này, chúng ta đã sử dụng method bind()
và đặt giá trị this
thành object bicycle
, do đó ta sẽ thấy giá trị symbol
của object bicycle
trên bảng console.
Constructor invocation
Trong JavaScript, constructor functions được sử dụng để tạo các object. Khi một hàm được sử dụng như một constructor function, this
sẽ tham chiếu đến object mới được tạo ra.
Ví dụ:
function Blog(name) {
this.name = name;
}
Blog.prototype.getName = function () {
return this.name;
}
let blog = new Blog('homiedev');
console.log(blog.getName()); // homiedev
Khi sử dụng từ khóa new
trong ví dụ trên, JavaScript tạo một object mới và đặt this
thành object mới được tạo. Tuy nhiên có một vấn đề là nếu ta không sử dụng new
như dưới đây:
let blog1 = Blog('homiedev.com');
// Uncaught TypeError: Cannot read properties of undefined (reading 'name')
Lúc này giá trị this
trong Blog()
được đặt thành global object. Do không sử dụng từ khóa new
, nên khi gọi Blog
thì vẫn là gọi theo kiểu thông thường, hàm Blog
return undefined. Nên khi sử dụng blog1.name
sẽ gây ra lỗi vì blog1
lúc này là undefined.
Để chắc rằng Blog()
luôn được gọi bằng constructor invocation, chúng ta nên thêm một đoạn code ở đầu thân hàm Blog()
để check như sau:
function Blog(name) {
if (!(this instanceof Blog))
throw Error('✋ Bạn cần sử dụng từ khóa new để gọi function!');
this.name = name;
}
Indirect Invocation
Trong Function có hai method: call()
và apply()
. Các method này cho phép chúng đặt giá trị cho this
khi gọi một hàm.
Ví dụ:
function getBlogName(prefix) {
console.log(prefix + this.name);
}
let blog1 = {
name: 'Homiedev'
};
let blog2 = {
name: 'homiedev.com'
};
getBlogName.call(blog1, "Đây là blog: ");
getBlogName.call(blog2, "Bạn đang đọc bài viết tại: ");
Kết quả ví dụ trên:
Đây là blog: Homiedev
Bạn đang đọc bài viết tại: homiedev.com
Trong ví dụ trên, chúng ta đã gọi hàm getBlogName()
một cách gián tiếp (Indirect Invocation) bằng cách sử dụng method call()
. Chúng ta đã đưa object blog1
và blog2
làm đối số đầu tiên của call()
, do đó ta có tên blog tương ứng trong mỗi lệnh gọi.
Method apply()
tương tự như call()
, khác nhau ở đối số thứ hai của nó là một mảng các đối số.
getBlogName.call(blog1, ["Đây là blog: "]); // Đây là blog: Homiedev
getBlogName.call(blog2, ["Học JavaScript tại: "]); // Học JavaScript tại: homiedev.com
this trong Arrow functions
ES6 đã giới thiệu một khái niệm mới là arrow function. Trong arrow function không có execution context (ngữ cảnh thực thi) của riêng nó mà kế thừa điều này từ hàm bên ngoài nơi chứa arrow function (parent scope).
Bạn có thể tìm hiểu về scope tại:
- Scope trong JavaScript là gì? Nắm chắc những thứ cơ bản để học JavaScript tốt hơn
- Lexical scope trong JavaScript là gì?
Chúng ta cùng xem ví dụ sau:
let thisInArrowFunc = () => this;
console.log(thisInArrowFunc() === window); // true
Ở ví dụ trên, giá trị this
được đặt thành global object (object window
trong trình duyệt web).
Vì arrow function không tạo execution context của riêng nó, nên việc định nghĩa một method bằng arrow function sẽ gây ra vấn đề không mong muốn.
Ví dụ:
function Person() {
this.name = "Hà";
}
Person.prototype.getName = () => {
return this.name;
};
let person1 = new Person();
console.log(person1.getName()); // undefined 😐
Bên trong method getName()
, giá trị this
sẽ tham chiếu đến global object, không phải object Person
. Vì global object không có thuộc tính name
, do đó this.name
trong getName()
sẽ là undefined.
Trong ví dụ dưới đây, chúng ta cùng xem this
là gì bên trong arrow function của method nhé:
const person = {
name : 'Hà',
age: 22,
// this trong method, tham chiếu đến person
greet() {
console.log(this);
console.log(this.age);
// inner function
let innerFunc = () => {
// this tham chiếu đến this của parent scope
console.log(this);
console.log(this.age);
}
innerFunc();
}
}
person.greet();
Kết quả của ví dụ trên:
{name: 'Hà', age: 22, greet: ƒ}
22
{name: 'Hà', age: 22, greet: ƒ}
22
Ở đây, innerFunc()
được định nghĩa bằng cách sử dụng arrow function. Nó lấy this
từ parent scope của nó. Do đó, this.age
có giá trị 22.
🎉 Như vậy là trong bài viết này, chúng ta đã cùng nhau tìm hiểu về this
trong JavaScript. Hi vọng bài viết giúp ích cho các bạn 😀.
Nếu có thắc mắc gì, chúng ta cùng nhau thảo luận dưới phần bình luận nhé. Hẹn gặp các bạn trong những bài viết tiếp theo!
Một số bài viết nên đọc: