Khái niệm “this” trong JavaScript là một trong những khía cạnh khó hiểu nhất của ngôn ngữ này. Tuy nhiên lại là nhân tố quan trọng để viết code “advance”, nâng cao hơn. Trong JS, this cho phép:
Nhắc tới this thì điều đầu tiên phải làm đó là biết gọi funtion nào, bởi sẽ không biết cái gì ở bên trong this cho tới khi function được gọi. Và các trường hợp của this có thể chia thành 5 khía cạnh binding khác nhau.
Trong JavaScript thì Lexical Environment hay nói theo nghĩa đen là môi trường từ vựng, cũng như là môi trường chứa đựng code được viết ra theo mặt vật lý (physically) . Hãy xem ví dụ bên dưới, tên biến bên trong function sayName( ) là lexically
function sayName() {
let name = 'someName';
console.log('The name is, ', name);
}
Trong đó Execution Context thì liên quan đến code hiện thời đang chạt và những thứ giúp code hoạt động. Có thể có nhiều lexical environments có sẵn nhưng chỉ có một cái đang chạy lúc này, và được Execution Context quản lý.
Mỗi Execution Context lại chứa một Environment Record Binding trong JavaScript có nghĩa là ghi lại identifier (variable và tên function) trong một Environment Record cụ thể.
Lưu ý: Binding giúp kết hợp identifier (variable và tên function) với this cho một execution context .
Khúc này còn hơi rắc rối nhưng những phần sau sẽ giải thích rõ hơn.
Trong implicit binding thì cần phải xem đối tượng bên trái của hàm sử dụng dấu chấm (dot operater), từ đó xác định được this đang tham chiếu tới cái gì.
let user = {
name: 'Tapas',
address: 'freecodecamp',
getName: function() {
console.log(this.name);
}
};
user.getName();
Ví dụ này thì this đang trỏ tới user object, bởi vì bên trái hàm dấu chấm là function getName (), ta thấy có user object, vì vậy this.name trong console sẽ hiển thị Tapas.
Một ví dụ khác
function decorateLogName(obj) {
obj.logName = function() {
console.log(this.name);
}
};
let tom = {
name: 'Tom',
age: 7
};
let jerry = {
name: 'jerry',
age: 3
};
decorateLogName(tom);
decorateLogName(jerry);
tom.logName();
jerry.logName();
Trong ví dụ này thì có 2 object, tom và jerry. Và chúng được decorate (nâng cao) bằng cách đính kèm method logName( )
Khi gọi hàm tom.logName( ) , object tom
nằm bên trái dấu chấm cuả function logName( ) . Nên this sẽ trỏ tới object tom và nhận giá trị của tom ( this.name tương đương với tom). Tương tự khi gọi hàm jerry.logName( )
JavaScript tạo môi trường để thực hiện code mà chúng ta viết. Trong đó nó đảm nhiệm memory creation (dành cho variables, functions, objects) trong creation phase. Cuối cùng nó chãy code trong execution phase. Lúc này môi trường mới được Execution Context
.
Có nhiều loại environment trong JavaScript application. Mỗi execution context thực hiện, chạy lệnh độc lập với nhau. Nhưng sẽ có lúc cần sử dụng thứ này trong execution context này trong cái context khác. Lúc này explicit binding sẽ pht huy công dụng.
Trong explicit binding, chúng ta có thể gọi function với object khi funtion đó nằm ngoài execution context của object đó.
Để thực hiện explicit biding thì có 3 method đó là call()
, apply()
và bind()
.
Với phương thức call()
thì context với function được gọi sẽ được chuyển tới called
dưới dạng tham số (parameter).
let getName = function() {
console.log(this.name);
}
let user = {
name: 'Tapas',
address: 'Freecodecamp'
};
getName.call(user);
Ở đây, hàm call()
sẽ được gọi trên function getName()
. Function getName()
nhận giá trị this.name
. Nhưng this
trong đây là gì? Lúc này sẽ được quyết định bởi giá trị đã được chuyển đến hàm call()
.
Với trường hợp này, this
sẽ được bind đến user object bởi vì đã chuyển user như một tham số đến hàm call()
. Vì vậy this.name
sẽ nhận giá trị của thuộc tính name của user object, Tapas.
Ví dụ trên mới chỉ chuyển một argument tới hàm call()
, nhưng thực tế có thể chuyển nhiều argument như sau:
let getName = function(hobby1, hobby2) {
console.log(this.name + ' likes ' + hobby1 + ' , ' + hobby2);
}
let user = {
name: 'Tapas',
address: 'Bangalore'
};
let hobbies = ['Swimming', 'Blogging'];
getName.call(user, hobbies[0], hobbies[1]);
Argument đầu tiên đuợc chuyển tới call()
là object context với function đã được gọi. Những thuộc tính khác chỉ có thể là giá trị được sử dụng. Ở đây mình đang chuyển Swimming và Blogging là 2 thuộc tính tới function getName()
.
Tuy nhiên sẽ có trường hợp cần pass từng argument một trong call()
thì sao? Lúc này sẽ tới lượt của apply()
.
Cách mà apply()
thực thi lệnh cũng tương đồng với call()
nhưng cho phép pass argument thuận tiện hơn.
let getName = function(hobby1, hobby2) {
console.log(this.name + ' likes ' + hobby1 + ' , ' + hobby2);
}
let user = {
name: 'Tapas',
address: 'Bangalore'
};
let hobbies = ['Swimming', 'Blogging'];
getName.apply(user, hobbies);
Lúc này ta có thể chuyển một mảng (array) chứa argument luôn, tiện hơn nhiều so với việc chuyển từng cái.
Tips: nếu bạn cần chuyển một giá trị argument hay argument mà không có giá trị thì hãy dùng call()
. Còn nếu chuyển nhiều giá trị argument thì dùng apply()
.
Method bind()
cũng tương tự như call()
nhưng có 1 điểm khác biệt nhỏ. Thay vì gọi function trực tiếp thì bind()
trả về một hàm mới.
let getName = function(hobby1, hobby2) {
console.log(this.name + ' likes ' + hobby1 + ' , ' + hobby2);
}
let user = {
name: 'Tapas',
address: 'Bangalore'
};
let hobbies = ['Swimming', 'Blogging'];
let newFn = getName.bind(user, hobbies[0], hobbies[1]);
newFn();
Ở đây getName.bind()
không gọi function getName()
trực tiếp, nó trả về function mới, newFn
và chúng ta đang gọi hàm dưới dạng newFn()
.
Từ khóa new
được dùng để tạo một object mới từ constructor function.
let Cartoon = function(name, animal) {
this.name = name;
this.animal = animal;
this.log = function() {
console.log(this.name + ' is a ' + this.animal);
}
};
Bạn có thể tạo nhiều object với từ khóa new
như sau:
let tom = new Cartoon('Tom', 'Cat');
let jerry = new Cartoon('Jerry', 'Mouse');
Với quy tắc new biding, khi một function được gọi với từ khóa new, thì this bên trong function sẽ tham chiếu tới cái object mới được lập.
let tom = new Cartoon('Tom', 'Cat');
Đây là function Cartoon được gọi với từ khóa new. Thì this sẽ tham chiếu tới object mới, tom.
Đoạn code dưới đây có kết quả thế nào? this
tham chiếu tới cái gì?
let sayName = function(name) {
console.log(this.name);
};
window.name = 'Tapas';
sayName();
Nếu this
không giải quyết được với một trong những quy tắc binding, implicit
, explicit
hay new
, thì this
lúc này tham chiếu tới object window(global)
.
Tuy nhiên quy tắc strict mode của JavaScript sẽ không cho phép default binding như sau:
"use strict";
function myFunction() {
return this;
}
Trường hợp này thì this
là undefined
.
Trong HTML event handlers, this
tham chiếu tới element HTML nào nhận event đó.
<button onclick="console.log(this)">Click Me!</button>
Đây là giá trị trả về console khi click vào button:
"<button onclick='console.log(this)'>Click Me!</button>"
Bạn có thể thay đổi style của this
:
<button onclick="this.style.color='teal'">Click Me!</button>
Nhưng hãy cẩn thận khi call function trên button click và dùng this
bên trong function đó.
<button onclick="changeColor()">Click Me!</button>
và JavaScript:
function changeColor() {
this.style.color='teal';
}
Đoạn code trên sẽ không trả kết quả như ý được, bởi theo như rule số 4 thì this
sẽ tham chiếu tới object global, và không có object style để set màu.
this
trỏ tới object bên trái của hàm dấu chấm.call()
, apply()
, bind()
.new
thì this
bên trong dunction sẽ trỏ tới object mới được lập.this
không được giải quyết với implicit
, explicit
hay new
thì this
trỏ tới object window(global)
. Với strict mode của JavaScript, this
trở nên undefined.this
trỏ tới HTML element nào nhận event đó.Tham khảo freeCodeCamp