Hi 🤓 Cảm ơn bạn đã ghé thăm blog này, nếu những bài viết trên blog giúp ích cho bạn. Bạn có thể giúp blog hiển thị quảng cáo bằng cách tạm ngừng ad blocker 😫 và để giúp blog duy trì hoạt động nếu bạn muốn.
Cảm ơn bạn!

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ế độ strictnon-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:

  1. Function invocation (gọi thông thường)
  2. Method invocation (sử dụng method)
  3. Constructor invocation (sử dụng constructor)
  4. 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()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 blog1blog2 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:

  1. Scope trong JavaScript là gì? Nắm chắc những thứ cơ bản để học JavaScript tốt hơn
  2. 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:

  1. Callback Function trong JavaScript là gì? tại sao lại được sử dụng nhiều như vậy?
  2. Closure trong Javascript là gì?
  3. Higher order functions trong Javascript là gì?
Có thể bạn thích ⚡
homiedev
About Me

Hi, I'm @devnav. Một người thích chia sẻ kiến thức, đặc biệt là về Frontend 🚀. Trang web này được tạo ra nhằm giúp các bạn học Frontend hiệu quả hơn 🎉😄.

Chúc các bạn tìm được kiến thức hữu ích trong blog này 😁😁.