Master keyword "this" trong JavaScript

Keyword this có thể nói là dễ gây rối nhất đối với những người mới bắt đầu với JavaScript. Trong bài này, chúng ta sẽ tìm hiểu cách sử dụng this một cách đúng đắn trong những tình huống thường gặp.

Sơ lược về keyword this

Trong JavaScript, this được dùng như một shortcut để tham chiếu đến một object. Object này là chủ thể của code đang được chạy. Xét đoạn code sau:

var person = {
    firstName: "Penelope",
    lastName: "Barrymore",
    showFullName: function () {
    	console.log(this.firstName + " " + this.lastName);
	    
        // We could have also written this
    	console.log(person.firstName + " " + person.lastName);
    }
}

person.showFullName();

this được dùng bên trong method showFullName() và object person gọi method này, this tham chiếu đến object person. Trong hầu hết trường hợp, this có giá trị của object đang gọi hàm chứa nó, tuy nhiên cũng có một vài trường hợp ngoại lệ mà ta sẽ xét đến sau. Cũng cần lưu ý rằng this chỉ được gán giá trị khi function chứa nó được gọi. Trong global scope, mọi biến global và hàm đều thuộc về object window. Do đó khi ta dùng this bên trong hàm global, nó sẽ tham chiếu đến object window. Ví dụ

 var firstName = "Peter",
    lastName = "Ally";

function showFullName () {    
    console.log (this.firstName + " " + this.lastName);
}

showFullName (); // Peter Ally
// window is the object that all global variables and functions are defined on, hence:
window.showFullName (); // Peter Ally

Tuy nhiên nếu ta dùng strict mode thì this không được định nghĩa và giá trị của nó là undefined.

Các trường hợp đặc biệt sử dụng this

this khi dùng trong một method được truyền vào như một callback

Xét ví dụ

var user = {
    data:[
        {name:"T. Woods", age:37},
        {name:"P. Mickelson", age:43}
    ],
    clickHandler:function (event) {
        var randomNum = ((Math.random () * 2 | 0) + 1) - 1; // random number between 0 and 1

        // This line is printing a random person's name and age from the data array
        console.log (this.data[randomNum].name + " " + this.data[randomNum].age);
    }
}

$ ("button").click (user.clickHandler); // Cannot read property '0' of undefined

Khi ta truyền method user.clickHandler vào method click() của object $("button"), this giờ đây tham chiếu đến object nơi mà method user.clickHandler được execute - object $("button"). Vậy làm sao để this tham chiếu đến object user thay vì object $("button").

Solution: Ta có thể sử dụng những method như bind(), apply() hoặc call() để gán giá trị cho this một cách tường minh.

Thay vì $ ("button").click (user.clickHandler);

ta sẽ bind method clickHandler đến object user như sau $("button").click (user.clickHandler.bind (user)); // P. Mickelson 43

this trong hàm ẩn danh

Một trường hợp khác mà this cũng dễ gây khó hiểu đó là khi chúng ta dùng hàm ẩn danh (hàm không có tên). Cần nhớ rằng hàm ẩn danh không thể access biến this của hàm bên ngoài. Khi this đặt trong hàm ẩn danh nó sẽ tham chiếu đến object window. Xét ví dụ

var user = {
    tournament:"The Masters",
    data      :[
        {name:"T. Woods", age:37},
        {name:"P. Mickelson", age:43}
    ],
    clickHandler:function () {
        this.data.forEach (function (person) {
            console.log ("What is This referring to? " + this); //[object Window]​
            console.log (person.name + " is playing at " + this.tournament);
        })
    }
}

user.clickHandler(); // What is "this" referring to? [object Window]

Tuy nhiên, cũng tương tự như với hàm global, khi ta sử dụng strict mode, this trong hàm ẩn danh cũng có giá trị undefined. Điều chúng ta muốn là this giữ nguyên giá trị giống this trong hàm bên ngoài.

Solution: Để giải quyết vấn đề này, chúng ta áp dụng một common practice trong JavaScript đó là gán this vào một biến rồi sử dụng biến đó thay cho this bên trong hàm ẩn danh.

var user = {
    tournament:"The Masters",
    data : [
        {name:"T. Woods", age:37},
        {name:"P. Mickelson", age:43}
    ],
    clickHandler:function (event) {
        var theUserObj = this;
        this.data.forEach (function (person) {
            // Instead of using this.tournament, we now use theUserObj.tournament
            console.log (person.name + " is playing at " + theUserObj.tournament);
        })
    }
}
 
user.clickHandler();
// T. Woods is playing at The Masters​
//  P. Mickelson is playing at The Masters

Xét một ví dụ với jQuery

$ ("button").click (function (event) {
    console.log ($ (this).prop ("name"));
});

Vì jQuery bind $(this) đến object gọi method click() do đó $(this) có giá trị của jQuery button $("button") ngay cả khi $(this) được định nghĩa bên trong hàm ẩn danh.

this bên trong method được gán cho biến

Xét ví dụ:

var data = [
    {name:"Samantha", age:12},
    {name:"Alexis", age:14}
];

var user = {
    // this data variable is a property on the user object​
    data : [
        {name:"T. Woods", age:37},
        {name:"P. Mickelson", age:43}
    ],
    showData:function (event) {
        var randomNum = ((Math.random () * 2 | 0) + 1) - 1; // random number between 0 and 1

        // This line is adding a random person from the data array to the text field
        console.log (this.data[randomNum].name + " " + this.data[randomNum].age);
    }
}

// Assign the user.showData to a variable
var showUserData = user.showData;

showUserData (); // Samantha 12 (from the global data array)​

Có thể thấy rằng, khi chúng ta execute showUserData, giá trị được in ra lấy từ global array chứ không phải array trong object user. this tham chiếu đến object window. Điều chúng ta muốn là giá trị in ra sử dụng array data trong object user.

Solution: Dùng bind, chúng ta chỉ rõ object mà this tham chiếu đến là object user.

// Bind the showData method to the user object
var showUserData = user.showData.bind (user);
    
// Now we get the value from the user object, because the <em>this</em> keyword is bound to the user object
showUserData (); // P. Mickelson 43

this trong trường hợp mượn method

Xét ví dụ sau:

var gameController = {
    scores  :[20, 34, 55, 46, 77],
    avgScore:null,
    players :[
        {name:"Tommy", playerID:987, age:23},
        {name:"Pau", 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;
    }
 }

gameController.avgScore = appController.avg();

Ở đây ta có 2 object gameControllerappController. Object gameController mượn method avg() của object appController và assign cho method avgScore() của mình. this trong trường hợp này tham chiếu đến object appController vì method avg() được gọi bởi object appController. Điều ta mong muốn là this phải tham chiếu đến object gameController.

Solution: Để giải quyết vấn đề này ta sử dụng method apply() như sau:

appController.avg.apply (gameController, gameController.scores);

console.log (gameController.avgScore); // 46.4
console.log (appController.avgScore); // null

Theo đó this sẽ tham chiếu đến object gameController.

Kết luận

Hi vọng các bạn đã hiểu về cách keyword this hoạt động trong JavaScript và biết dùng các công cụ như bind, apply, call hay gán this vào biến để có giá trị mong muốn của this trong những trường hợp đặc biệt.

References

Understand JavaScript’s “this” With Clarity, and Master It