Master về con trỏ this - cách khắc phục các lỗi thường gặp


Trong blog lần này mình sẽ chia sẽ tổng quan về con trỏ this , và một số lỗi thường gặp , các câu hỏi này rất rất hay xuất hiện trong các buổi interview và nó nằm trong các vấn đề nâng cao của javascript





 This đối với những bạn mới học thì có vẻ nó khá đơn giản , vì các bạn chỉ nghĩ đơn giản là nó sẽ refer đến object chứa nó ,  đúng nhưng chưa đủ ,  vì có lẽ các bạn chưa gặp qua nhiều vấn đề chuyên sâu về this





Tổng quan this keyword





Như trên , thì cơ bản this là refer(tham chiếu) đến object chứa nó , nhớ nhé : LÀ OBJECT CHỨA NÓ để tí nữa mình còn nhắc lại , đối với các newbie thì sẽ hiểu như thế này





Thái là con trai nhưng anh ấy thích trai
Thắng là con trai nhưng anh ấy lại thích ngắm trai 6 múi








 ở ví dụ trên , thì Thái và Thắng là một đối tượng , và anh ấy là đại từ nhân xưng thay thế cho Thái và Thắng => this =  anh ấy , và object =  Thái và Thắng









Ví dụ:





var user = {
    name : "Vo Minh Tan",
    age : 19,
 
    getName : function(){
        return this.name;
    },
    getAge : function(){
        return this.age;
    }
}
console.log(user.getName());   // Vo Minh Tan
console.log(user.getAge()); //19




Như ví trụ trên thì this thay thế có object chưa nó cụ thể là user , thay vì gọi user.name thì dùng this.nameBasic là thế , sau đây là một số lưu ý khi dùng this , mình hay gọi là 5 rules of THIS :





  • Trong một phương thức, this được refer đến chính đối tượng chứa nó
  • Nếu đứng một mình this refer đến Global Object ( Window )
  • Trong một hàm this refer đến Global Object ( Window )
  • Trong một hàm ( strict mode ) this không được định nghĩa
  • Trong một sự kiện, this được refer đến chính element nhận sự kiện đó




Note : Đối tượng Window là container chính của toàn bộ code JavaScript của trang Web , check trong chrome dev tool để hiểu rõ hơn





Ví dụ :





1.  Trong một phương thức, this được refer đến chính đối tượng chứa nó





let student = {
       name: "Vo Minh Tan",
       getName: function() {
              return this.name;
       },
};
console.log(student.getName()); // Vo Minh Tan




2.  This đứng một mình





console.log(this); // [object Window] có thể test ở chrome developer tool




3. This khi nằm trong một hàm





functionwhoAmI()
{
console.log(this);
}
whoAmI();// [object Window]




4.Trong strict mode





"use strict";
functionwhoAmI()
{
console.log(this);
}
whoAmI();// undefined




5.Trong event





<button data-id="5"id="btn"onclick="alert(this.getAttribute('data-id'))">Click me</button>




Cơ bản như vậy là quá đủ , và sau đây mới là vấn đề chính :





TRƯỚC khi đi qua các nhầm lần thì mình sẽ giới thiệu sơ qua về các function prototype mà sẽ hỗ trợ các bạn suốt quá trình thao tác với các object





Call – apply – bind trong js :





Hàm call apply và bind là các prototype trong function ( vì trong js thì function cũng chính là object mà object thì có các prototype của nó) , thường thì các prototype này sẽ được dùng rất nhiều trong oop cụ thể là trong inheritance nhưng nó cũng được ứng dụng trong cách khắc một số lỗi this pointer





1.Call





function.call(thisArg, arg1, arg2, ...)





Khi gọi cho phép bạn truyền vào object làm context , param 1 , param 2 là các tham số ứng với function gọi , ví dụ :





var person1 = {firstName: 'Nguyen', lastName: 'Thai'};
var person2 = {firstName: 'Hong', lastName: 'Thang'};
function fcku(sentence1,sentence2) {
 console.log(sentence1+ ',' + sentence2+ ' ' + this.firstName + ' ' + this.lastName);
}
say.call(person1, 'Hello', 'f*ck you'); // => Hello, f*ck you Nguyen Thai
say.call(person2, 'Hello', 'and f*ck you too'); // => Hello, and f*ck you too Hong Thang




This được gán một context rõ ràng là person1 và person2 và các param được truyền theo thứ tự rõ ràng





2.Apply





fun.apply(thisArg, [argsArray])





Tương tự call nhưng các tham số phía sau đc truyền dưới dạng array
var person1 = {firstName: 'Nguyen', lastName: 'Thai'};
var person2 = {firstName: 'Hong', lastName: 'Thang'};
 
function fcku(sentence1,sentence2) {
 console.log(sentence1+ ',' + sentence2+ ' ' + this.firstName + ' ' + this.lastName);
}
say.apply(person1, ['Hello', 'f*ck you']); // => Hello, f*ck you Nguyen Thai
say.apply (person2, ['Hello', 'and f*ck you too']); // => Hello, and f*ck you too Hong Thang




3.bind





var newFunction = fun.bind(thisArg[, arg1[, arg2[, ...]]])





Trả về một hàm mới và hàm này có context của thisArg
var person1 = {firstName: 'Nguyen', lastName: 'Thai'};
var person2 = {firstName: 'Hong', lastName: 'Thang'};
 
function fcku(sentence1,sentence2) {
 console.log(sentence1+ ',' + sentence2+ ' ' + this.firstName + ' ' + this.lastName);
}
say.bind(person1, 'Hello', 'f*ck you'); // => Hello, f*ck you Nguyen Thai
say.bind(person2, 'Hello', 'and f*ck you too'); // => Hello, and f*ck you too Hong Thang




Kết luận





  • Nhìn chung, hàm call và apply là gần giống nhau. Chúng đều gọi hàm trực tiếp. Chỉ khác ở cách truyền tham số vào (với call thì đối số phân cách bởi dấu phẩy và với apply thì đối số cho bởi mảng array)
  • Hàm bind thì hơi khác hơn một chút. Hàm này không gọi hàm trực tiếp mà nó sẽ trả về một hàm mới. Và bạn có thể sử dụng hàm số mới này sau. Về cách truyền tham số vào thì nó giống với hàm call.




Một số lỗi hay gặp khi dùng this





Những điều về this rất cơ bản đã được trình bày ở trên. Tuy nhiên, khi làm việc thực tế, có những trường hợp this trở nên rất khó hiểu và khó nắm bắt. Trong phần này, chúng ta sẽ cùng tìm hiểu những tình huống như vậy, và cách xử lý tương ứng





Đại đa số mọi người đều sẽ sai về “function context” tức là ngữ cảnh trong lúc gọi this , ví dụ câu nói ở trên ta có Thái là còn trai nhưng anh ấy thích trai . Ta dùng được đại từ nhân xưng anh ấy bởi vì trong đây ngữ cảnh lúc này đang nói đến Thái , nhưng nếu ta chỉ để “ anh ấy “ độc lập thì ngữ cảnh lúc này mơ hồ và không rõ ràng -> đây cũng là lỗi nhiều người mắc phải





1.Gán method của object có dùng this  cho một biến mới





var student = {
    firstName: "Vo",
    lastName: "Minh Tan",
    showFullName: function() {
        console.log(this.firstName + " " + this.lastName);
    }
}
var newFunction = student.showFullName;
 
newFunction(); // undifined undifined




Lúc này newFunction sẽ là một function bằng với student.showFullName , tức là content sẽ giống nhau , nhưng “this” thì sao?





Lúc này this sẽ là this trong ngữ cảnh của newFunction , mà this trong ngữ cảnh của 1 function như mình đề cặp lý thuyết 5 rules of THIS thì this sẽ là global object , nhưng firstName không nằm trong global object nên => undifined





CÁCH XỬ LÝ





Dùng bind như giới thiệu ở trên thì newFunction được set context như của student





var newFunction = student.showFullName.bind(student) // Vo Minh Tan




2.Truyền method chứa THIS như một callback





Hãy thử đoán xem đoạn code dưới đây khi click button thì console.log sẽ in ra gì?





Hint :  nhớ dựa vào 5 rules of this nhé





var student = {
    firstName: "Vo",
    lastName: "Minh Tan",
    showFullName: function() {
        console.log(this.firstName + " " + this.lastName);
    }
}
 
document.getElementById('button').addEventListener("click",student.showFullName);




YES , lại tiếp tục undefined , theo 5 rules of thi trong một event thì this refer đến chính element đó mà element đó không tồn tại firstName => undifined





CÁCH XỬ LÝ





Để đoạn code trên chạy đúng như mong muốn – in ra được name của Student – thì ta phải đảm bảo được context của hàm callback Student.showFullName là chính đối tượng Student lúc hàm này được gọi. Cụ thể trong trường hợp này chúng ta có thể dùng hàm Bind() để gắn context vào callback đó





document.getElementById('button').addEventListener("click",student.showFullName.bind(student));




3. This dùng trong closure





Closure tức là một hàm lồng trong hàm khác(basic closure)





Ta có ví dụ sau





var student = {
    firstName: "Vo",
    lastName: "Minh Tan",
    showFullName: function() {
        console.log(this.firstName)  // Vo
        
        var getLastName = function(){
            console.log(this.lastName); // undefined
        }
 
        getLastName();  
    }
}
student.showFullName();




trong ví dụ trên getLastName là inner function (hàm bên trong) muốn trỏ để this.lastName là điều không thể , ta có this.firstName = Vo thì đúng vì nó trỏ đến object chứa nó , NHƯNG this.lastName thì lại nằm ở getLastName  mà function này không có một context rõ ràng tại vì khi thực thi thì chỉ gọi getLastName(,, nó không có một object nào chứa nó và đại diện để thực thi nó cả => nó chỉ là một function bình thường , không phải là một method của object, tiếp tục căn cứ vào 5 rules of THIS thì this lúc này trỏ đến global window





CÁCH XỬ LÝ





Có 3 giải pháp , 1 là dùng biến tạm , 2 là dùng bind , 3 là dùng arrow function





CÁCH 1 : DÙNG BIẾN TẠM





var student = {
    firstName: "Vo",
    lastName: "Minh Tan",
    showFullName: function() {
        console.log(this.firstName)  // Vo
        var that = this;
        var getLastName = function(){
            console.log(that.lastName); // Minh Tan
        }
 
        getLastName();  
    }
}
 
student.showFullName();




CÁCH 2 : DÙNG BIND





var student = {
    firstName: "Vo",
    lastName: "Minh Tan",
    showFullName: function() {
        console.log(this.firstName)  // Vo
        var getLastName = function(){
            console.log(this.lastName); // Minh Tan
        }.bind(student)
 

        getLastName();  
    }
}
 
student.showFullName();




CÁCH 3 : ARROW FUNCTION





var student = {
    firstName: "Vo",
    lastName: "Minh Tan",
    showFullName: () => {
        console.log(this.firstName)  // Vo
        var getLastName = function(){
            console.log(this.lastName); // Minh Tan
        }
 
        getLastName();  
    }
}
 
student.showFullName();




KẾT





Qua mấy vị dụ trên thì cũng hiểu được đại khái this rồi nhỉ , 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 và 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 , mình thấy ứng dụng nó rất oke , nhất là trong lập trình game thì THIS là không thể thiếu , cho nên hãy luôn chú ý đến context của con trỏ this khi hàm được gọi nhé


Nhận xét

  1. Nhờ học con trỏ this của anh, em mới thấy tên mình có ví dụ trong đó. Ví dụ thật sắc xảo và sát với thực tế. Nó không thể thiếu như 1 buổi cơm chiều được mẹ nấu cho hằng ngày. Nhờ nó mà em mới biết được sự thay đổi trong phương pháp lập trình cũng như là kiến thức bao la về nó. Nhưng em có 1 chỗ chưa hiểu. Anh có thể cho em xin infor hay không. Để em liên lạc với anh và nghe anh giảng thêm về những kiến thức cuộc sống. Em cảm ơn. Em là Thái.

    Trả lờiXóa

Đăng nhận xét