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ờ 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