Master keyword "this" trong JavaScript
Bài đăng này đã không được cập nhật trong 5 năm
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();
Vì 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 gameController và appController. 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
All rights reserved