Con trỏ this trong Javascript

Con trỏ this có lẽ là một khái niệm không mấy xa lạ trong lập trình hướng đối tượng, nó là tham chiếu cho đối tượng đang chứa mã lệnh đang thực thi. Con trỏ this được sử dụng nhiều trong javascript. Nó cũng hay gây nhầm lẫn nếu dùng sai context, dẫn đến gây ra nhiều bug trong ngôn ngữ này. Để lập trình tốt Javascript bạn cần hiểu rõ về con trỏ this Trong các ngôn ngữ lập trình hướng đối tượng C#, Java, PHP, Javascript ... con trỏ this đều được dùng để chỉ định đến lớp(object) chứa các hàm đang được thực thi, cách dùng con trỏ this thường là tường minh và dễ hiểu

private String javaFAQ;
void methodName(String javaFAQ) {
    this.javaFAQ = javaFAQ;
}

this ở đây được hiểu như là đại diện cho object và rất tường minh và dễ hiểu, Ở trong javascript thì this thì cách dùng và cách sử dụng sẽ uyển chuyển hơn các ngôn ngữ hướng đối tượng bậc cao trên

function person(first, last, age, eye) {
    this.firstName = first;
    this.lastName = last;
    this.age = age;
    this.eyeColor = eye;
}
var myFather = new person("John", "Doe", 50, "blue");
var myMother = new person("Sally", "Rally", 48, "green");

this được hiểu là đại điện luôn cho function person này. Trong javascript khi bạn thao tác với 1 object bất kỳ bạn không cần phải khai báo thì cả, hoặc sử dụng this hoặc sử dụng chính object đó và gán thuộc tính cho nó là xong Trong hầu hết mọi trường hợp, this sẽ chứa giá trị của đối tượng gọi tới nó, tuy nhiên việc một hàm trong Javascript cũng chính là 1 đối tượng khiến ta có thể dễ dàng truyền nó vào một tham số của hàm nào đó để sử dụng như một hàm callback, chính điều này sẽ dẫn tới các tình huống mà con trỏ this không thực sự chứa giá trị của đối tượng mà nó thuộc về.

Chúng ta sẽ xem xét 4 trường hợp mà con trỏ this uyển chuyển hơn so với ví dụ ở trên, dẫn đến chúng tao đôi khi mắc sai lầm, chúng ta sẽ đi phân tích chi tiết từng trường hợp ở phần tiếp theo:

  1. Con trỏ this trong việc sử dụng hàm callback
  2. Con trỏ this bên trong 1 closure
  3. Con trỏ this trong trường hợp gán hàm cho một biến khác
  4. Con trỏ this trong hàm mượn (borrowing methods)

1. Con trỏ this trong sử dụng hàm callback

Trong hầu hết các ngôn ngữ lập trình đều có sử dụng hàm callback, trong javascript thì việc sử dụng hàm callback là thường xuyên và phổ biến, callback thực ra là chúng ta truyền vào 1 hàm như là 1 tham số vào hàm khác. Chúng ta xem xét ví dụ sau

// Chúng ta có 1 đối tượng employee sau
var Employee = {
    age: 34,
    calculateAge: function(){
       return this.age * 10;
    }
};

// Khi chúng ta thực hiện gọi
$(‘#button’).click(Employee.calculate); 

Ở đây this sẽ không thuộc context của employee lên sẽ dẫn đến this ở đây sẽ bị undefined Trong trường hợp này để có thể thực thi được thì chúng ta phải gán context cho hàm callback để khỏi bị undefined

console.log($(‘#button’).click(Employee.calculate.Bind(Employee));

Bên cạnh cách trên bạn có thể sử dụng hàm call hoặc hàm apply , để thay đổi context trong JS Bạn có thể tham khảo so sánh cách sử dụng ở đây javascript-call-apply-vs-bind

2. Con trỏ this trong closure

Closure là một hàm con(inner function) nằm bên trong 1 hàm khác. Dẫn đến hàm inner thì sẽ không thể truy cập được con trỏ this của parent Tham khảo Closures

var T_CLOSURE = {
    target: "The viblo",
    data: [1, 2, 3, 4 ],
     showTarget: function () {
        //Sử dụng con trỏ this ở đây thì OK, this đang mang giá trị tham chiếu tới đối tượng “T_CLOSURE”
        this.data.forEach (function (score) {
            //Tuy nhiên, trong closure này thì this không còn tham chiếu tới đối tượng “T_CLOSURE” nữa
            // Hàm inner function này không thể truy cập tới this của outer function
            alert (this.target + " is score " + score);
            })
     }
}

T_CLOSURE.showTarget();

Để fix lỗi này giải quyết bằng việc gán giá trị của biến this vào một biến trung gian trước khi gọi closure:

var T_CLOSURE = {
    target: "The viblo",
    data: [1, 2, 3, 4 ],
     showTarget: function () {
        //Sử dụng con trỏ this ở đây thì OK, this đang mang giá trị tham chiếu tới đối tượng “T_CLOSURE”
        this.data.forEach (function (score) {
            var self = this; // Gán con trỏ this ở đây thì sẽ okie
            //Tuy nhiên, trong closure này thì this không còn tham chiếu tới đối tượng “T_CLOSURE” nữa
            // Hàm inner function này không thể truy cập tới this của outer function
            alert (self.target + " is score " + score);
            })
     }
}

T_CLOSURE.showTarget();

3. Con trỏ this trong trường hợp gán hàm cho một biến khác

var x = 9;
var module = {
    x: 81,
    getX: function () {
        return this.x;
    }
};

module.getX(); // 81

var getX = module.getX;
getX(); // 9, bởi vì this ở đây tương ứng với biến global

// create a new function with 'this' bound to module
var boundGetX = getX.bind(module);
boundGetX(); // 81

Nhớ sử dụng bind để gán đúng context

4. Con trỏ this trong hàm mượn (borrowing methods)

var person = {
    firstName:"John",
    lastName: "Doe",
    fullName: function() {
        return this.firstName + " " + this.lastName;
    }
}
var myObject = {
    firstName:"Mary",
    lastName: "Doe",
}
person.fullName.apply(myObject);  // Will return "Mary Doe"

Ở đây đã sử dụng hàm apply để thay đổi context

5. Tổng kết

Qua chia sẻ trên mọi người có thể hiểu rõ hơn về con trỏ this trong lập trình Javascript. Chúng ta có các hàm như apply(), bind(), call() để kiểm soát con trỏ this trong nhiều tình huống khác nhau. Chúng ta thấy rằng, hầu hết mọi rắc rối xảy ra với con trỏ this là do dự thay đổi context của hàm khi được gọi (đã không còn là context ban đầu nơi bản thân hàm được khai báo), Do đó, quy tắc cốt lõi cần nhớ khi làm việc với con trỏ this là: luôn chú tới context của con trỏ this khi hàm được gọi, đảm bảo chúng ta đang sử dụng đúng context của hàm