Author Khoaa 2020-08-24
14951 127
Tất Tần Tật Về Con Trỏ This Trong Javascript

Tất Tần Tật Về Con Trỏ This Trong Javascript

Trước đây, mình từng đề cập đến một khái niệm khá khó nhằn trong javascript là callback function. Ở bài viết này, mình sẽ giải thích về this -  một từ khóa dễ làm đau đầu các lập trình viên js.

This là gì?

Trong javascript, chúng ta dùng từ khóa this để đại diện cho một đối tượng (Object). Đối tượng đó là chủ thế của ngữ cảnh, hoặc là chủ thế của code đang được chạy.

Ví dụ:

var person = {
  firstName: 'Khoa',
  lastName: 'Nguyen',
  showName: function() {
    console.log(this.firstName + ' ' + this.lastName);
  }
};

//Ở đây this sẽ là object person
person.showName(); //Khoa Nguyen

Một trường hợp khác, khi ta khai báo biến global và hàm global, toàn bộ các biến và hàm đó sẽ nằm trong một object có tên là window. Lúc này, khi ta gọi hàm showName, chính object window là object gọi hàm đó, this trỏ tới chính object window.

var firstName = 'Khoa', lastName = 'Nguyen';
// 2 biến này nằm trong object window

function showName()
{
  console.log(this.firstName + ' '+ this.lastName);
}

window.showName(); //Khoa Nguyen - this trỏ tới object window
showName(); //Khoa Nguyen - Object gọi hàm showName vẫn là object window

This trong Javascript dùng như thế nào?

Trước hết, các bạn cần hiểu rằng tất các các Function đều có Property, giống như mọi Object khác. Khi thực thi Function đó, nó sẽ có Property this chứa item của 1 Object đang gọi tới function này.

Hiểu đơn giản, mình có 1 function tạm gọi là functionA, thì con trỏ this chứa item của Object gọi tới functionA, và ta cũng có thể thông qua this này để lấy các item khác nằm trong Object gọi tới functionA.

Lưu ý rằng, nếu chúng ta sử dụng strict modethis sẽ là undefined trong các function Global.

Ví dụ:

"use strict";

const ObjA ={
  firstName: "Khoa",
  lastName: "Nguyen"
}

function functionA(){
  console.log(this.ObjA)
}

functionA();

Một vài lưu ý về this:

  • this chính là context ( ngữ cảnh ) của nơi mà function có chứa từ khóa this gọi.
  • Có 2 loại context đối với từ khóa this : Object chứa method được gọi hoặc Global, không còn gì nữa cả.
  • Vậy nên khi gặp this bạn đừng quan tâm đến nó là cái gì ? mà chỉ quan tâm đến cái nơi gọi function chứa nó.

Một ví dụ khác về việc sử dụng this với jQuery:

// Code rất thông dụng khi sử dụng jQuery
$("button").click(function(event) {
    // $(this) sẽ có giá trị của đối tượng button (`$("button")`) bởi
    // vì đối tượng này gọi phương thức click()
    console.log($(this).prop("name"));
});

Việc sử dụng jQuery tương đối đặc biệt một chút. Cú pháp $(this) là cú pháp của jQuery cho từ khóa this của JavaScript, được sử dụng bên trong hàm vô danh (hàm này được thực thi trong phương thức click của button). Lý do $(this) được ràng buộc với button là bởi vì thư viện jQuery đã "gán" $(this) với đối tượng gọi phương thức click. Vì vậy, $(this) sẽ có giá trị của đối tượng button trong jQuery ($("button")), ngay cả khi $(this) được sử dụng bên trong một hàm vô danh và đúng ra nó không thể tham chiếu đến đối tượng "this" ở ngoài.

Chú ý rằng, button là một phần tử DOM trong trang HTML, nên nó cũng là một đối tượng. Trong trường hợp này, nó là một đối tượng jQuery bởi vì nó được bao với hàm $() của jQuery.

Top 4 trường hợp dễ nhầm lẫn với "this"

1. Hàm được truyền vào như một callback

Giả sử, ta muốn khi người dùng click vào một button, ta sẽ gọi hàm showName của user. Vô cùng đơn giản, ta chỉ cần truyền hàm showName vào như một callback cho hàm click là xong.

var person = {
  firstName: 'Khoa',
  lastName: 'Nguyen',
  showName: function() {
    console.log(this.firstName + ' ' + this.lastName);
  }
};

//Ở đây this sẽ là object person
person.showName(); //Khoa Nguyen

$('button').click(person.showName); //showName truyền vào như callback

Tuy nhiên, hàm lại không chạy như ta mong muốn. Mở developer tools lên thì thấy object this không có trường firstName và lastName. Kiểm tra kĩ chút nữa thì ta thấy this ở đây là chính button ta đã click vào, chứ không còn là object person như ví dụ trên nữa.

Trong trường hợp này, ta có thể sửa lỗi bằng cách sử dụng anonymous function, hoặc dùng hàm bind để xác định tham số this cho hàm truyền vào là được.

var person = {
  firstName: 'Khoa',
  lastName: 'Nguyen',
  showName: function() {
    console.log(this.firstName + ' ' + this.lastName);
  }
};

$('button').click(person.showName); //showName truyền vào như callback, ở đây this chính là button

// Dùng anonymous function
$('button').click(function(){ person.showName() });

// Dùng bind
$('button').click(person.showName.bind(person)); //this ở đây vẫn là object person

2. Sử dụng this trong anonymous function

Giả sử, object person có một danh sách bạn bè, bạn muốn viết một function show toàn bộ bạn bè của person đó. Theo lý thuyết, ta sẽ viết như sau:

var person = {
  firstName: 'Khoa',
  lastName: 'Nguyen',
  friends : ['Van', 'Hang', 'Hien', 'Vinh'],
  showFriend: function() {
    for(var i = 0; i < this.friends.length; i++) 
      console.log(this.firstName + ' have a friend named ' + this.friends[i]);
  },
  showFriendThis: function() {
    this.friends.forEach(function(fr){
      console.log(this.firstName + ' have a friend named ' + fr);
    });
  }
};

person.showFriend(); //Hàm chạy đúng

person.showFriendThis(); // Hàm chạy sai

Với hàm showFriend, khi ta dùng hàm for thường, hàm chạy đúng như mong muốn. Tuy nhiên, trong trường hợp dưới, khi ta dùng hàm forEach, truyền vào một anonymous function, this ở đây lại thành object window, do đó trường firstName bị underfined.

Trong trường hợp này, cách giải quyết ta thường dùng là tạo một biến để gán giá trị this vào, và truy xuất tới giá trị đó trong anonymous function.

var person = {
  firstName: 'Khoa',
  lastName: 'Nguyen',
  friends : ['Van', 'Hang', 'Hien', 'Vinh'],
  showFriendFixed: function() {
    var personObj = this; //Gán giá trị this vào biến personObj
    this.friends.forEach(function(fr){
      console.log(personObj.firstName + ' have a friend named ' + fr);
    });
  }
};

person.showFriendFixed(); //Hàm chạy đúng

3. Khi hàm được gán vào một biến

Trường hợp này ít xày ra, đó là trường hợp khi ta gán một hàm vào một biến, sau đó gọi hàm đó. Hàm sẽ không chạy như ta mong muốn, vì object gọi hàm lúc này chính là object window.

var person = {
  firstName: 'Khoa',
  lastName: 'Nguyen',
  showName: function() {
    console.log(this.firstName + ' ' + this.lastName);
  }
};

//Ở đây this sẽ là object person, chạy đúng
person.showName(); //Khoa Nguyen

var showNameFunc = person.showName; //Gán function vào biến showNameFunc
showNameFunc(); //Chạy sai, ở đây this sẽ là object window

Để giải quyết, ta cũng sử dụng hàm bind như trường hợp trên cùng, quá đơn giản phải không nào?

var person = {
  firstName: 'Khoa',
  lastName: 'Nguyen',
  showName: function() {
    console.log(this.firstName + ' ' + this.lastName);
  }
};

var showNameFunc = person.showName.bind(person); //Gán function vào biến showNameFunc
showNameFunc(); //Chạy đúng vì this bây giờ là object person, do ta đã bind

4. Sử dụng this trong borrowing method

Việc mượn các phương thức là việc rất phổ biến trong viết code JavaScript. Và nếu bạn làm việc với JavaScript, thì chắc chắn bạn đã mượn các phương thức rất nhiều lần. Việc mượn các phương thức sẽ giúp chúng ta tiết kiệm thời gian phát triển những tính năng đã có người làm giúp chúng ta rồi. Tuy nhiên, tự tiện lợi đó đi kèm với một số vấn đề khi sử dụng this.

Ví dụ:

// Chúng ta có 2 đối tượng.  Một đối tượng có phương thức avg() và đối
// tượng còn lại không có.  Vì thế chúng ta sẽ mượn phương thức avg().
var gameController = {
    scores: [20, 34, 55, 46, 77],
    avgScore: null,
    players: [
        {name: "Khoa", playerID: 987, age: 23},
        {name: "Van", playerID: 87, age: 33}
    ]
}

var appController = {
    scores: [900, 845, 809, 950],
    avgScore: null,
    avg: function() {
        var sumOfScores = this.scores.reduce(function(prev, cur, index, array) {
            return prev + cur;
        });

        this.avgScore = sumOfScores / this.scores.length;
    }
}

// Nếu chúng ta chạy code dưới đây, thuộc tính gameController.avgScore
// sẽ được gán giá trị trung bình của đối tượng appController
gameController.avgScore = appController.avg();

this trong phương thức avg không tham chiếu đến đối tượng gameController mà nó tham chiếu đến đối tượng appController bởi vì nó được gọi bởi appController.

Giải pháp

Để giải quyết vấn đề trên, và biến giá trị this trong appController.avg() tham chiếu đến gameController, chúng ta có thể sử dụng phương thức apply:

// Lưu ý, chúng ta sử dụng phương thức apply(), nên tham số thứ 2 phải
// là một array - tham số sẽ được truyền cho phương thức
// appController.avg().
appController.avg.apply(gameController, gameController.scores);

// avgScore được tính cho đối tượng gameController, ngay cả khi chúng
// ta mượn phương thức avg() từ đối tượng appController.
console.log(gameController.avgScore); // 46.4

// appController.avgScore vẫn null, chỉ có gameController.avgScore
// được tính toàn mà thôi.
console.log(appController.avgScore); // null

Đối tượng gameController mượn phương thức avg() từ đối tượng appController. Giá trị của this trong phương thức này sẽ được gán cho đối tượng gameController bởi vì chúng ta truyền đối tượng này là tham số đầu tiên của apply. Tham số đầu tiên của apply luôn được sử dụng là this.

Tạm kết

Như vậy trong bài viết này, chúng ta đã cùng tìm hiểu về từ khóa this trong javascript. Và mình cũng trình thêm một số công cụ (bindapply, v.v...) để gán giá trị cho this trong những tình huống đặc biệt. Các bạn có thể đọc thêm về cách sử dụng các phương bind, apply và call trong javascript ở đây.

Như các bạn đã thấy, this có thể trở nên phức tạp trong một số tình huống mà ngữ cảnh ban đầu bị thay đổi. Tuy nhiên, có một điều không bao giờ thay đổi, đó là this được gán giá trị của đối tượng gọi hàm this. Hy vọng bài viết giúp ích các bạn trong quá trình code JavaScript của mình. Nếu các bạn thấy bài viết hữu ích hãy rate 5* và share cho mọi người tham khảo!

Hãy để lại comment để mình có thể hoàn thiện bản thân hơn trong tương lai. Cám ơn các bạn! 

5.0 (11 votes)
Please login to comment