Xin chào mọi người ạ! (bow)
Với những ai đã từng học tiếng anh, chẳng còn xa lạ gì với câu: “What is this?”. Vâng, this ở này, this ở chỗ kia, this ở khắp mọi nơi. Vậy với trong vương quốc lập trình Javascript thì con trỏ this là 1 khái niệm quan trọng. Hiểu được nó là this 1 hay this 2 ta sẽ tránh được bug không mong muốn khi làm việc với Javascript. Chính vì vậy trong bài viết này chúng ta cùng đi làm rõ với từ khóa this này nhé.
this
là khỉ gì nhỉ?
Khi bạn gặp từ khóa this
trong các ngôn ngữ lập trình như Java, C#,.. thì gần như bạn sẽ nghĩ tới this chính là tham chiếu tới thể hiện (instance) hiện tại hoặc hàm hiện tại. Đây cũng chính là lý do khiến nhiều bạn hiểu nhầm về từ khóa this
trong Javascript
, nhất là các bạn mới tiếp xúc và sử dụng Js.
Trong Javascript
thì this
là một từ khóa mà bản chất của nó giống như tên gọi của nó, đó là ám chỉ đối tượng hiện tại đang được sử dụng hoặc đang truy cập tới. Khá giống với định nghĩa this ở các ngôn ngữ khác đúng không các bạn, tuy nhiên trong Js this
lại có giá trị khác nhau tùy thuộc vào context (ngữ cảnh) đang sử dụng.
Ví dụ: Qua là ngày Va lung tung, mình có dẫn bạn gái vào nhà hàng sang trọng nhất Hà Nội. (Tưởng tượng thôi các bạn ạ! huhi) Khi đồ ăn được mang ra, bạn gái tôi hỏi “What is this?”. Tôi nói đó là Tôm hùm đại dương. Rồi cô ấy lại chỉ sang bát súp hỏi “What is this?”. Tôi nói đó là Súp vi cá mập. Ở đây, this
lúc thì là Tôm hùm, lúc lại là Súp vi cá mập. Nghĩa của this
luôn đi kèm với context (ngữ cảnh) – bàn nơi 2 người ngồi, rồi món án và ngôn ngữ cơ thể của bạn gái. Điều này cũng tương tự trong Javascript.
Ví dụ 1:
1 2 3 4 5 6 7 8 9 10 11 12 13 | <span class="token keyword">function</span> <span class="token function">greeting</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> name <span class="token operator">=</span> <span class="token string">'Quan Tien'</span><span class="token punctuation">;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Hi'</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// console.log(this.name === global.name) // log ra true nếu chạy trên nodejs</span> <span class="token comment">// console.log(this.name === window.name) // log ra true nếu chạy trên browser</span> <span class="token punctuation">}</span> <span class="token keyword">var</span> name <span class="token operator">=</span> <span class="token string">'Vinh'</span><span class="token punctuation">;</span> <span class="token function">greeting</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 'Hi Vinh' ???</span> <span class="token comment">// `this` ở đây không phải là `greeting()` mà là `global/window object`</span> |
Ví dụ 2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <span class="token keyword">function</span> <span class="token function">greeting</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> name <span class="token operator">=</span> <span class="token string">'Quan Tien'</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">sayHi</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">function</span> <span class="token function">sayHi</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Hi'</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">var</span> name <span class="token operator">=</span> <span class="token string">'Vinh'</span><span class="token punctuation">;</span> <span class="token function">greeting</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 'Hi Vinh' ???</span> <span class="token comment">// Hiển nhiên, `this` ở đây không phải là `sayHi()`.</span> <span class="token comment">// Hàm sayHi() được gọi trong scope của `greeting()` nhưng</span> <span class="token comment">// `this` không phải là `greeting()` mà vẫn là `global/window object`.</span> |
Qua ví dụ trên, ta có thể thấy this
chỉ thực sự là 1 ràng buộc được tạo ra cho đến khi hàm được gọi, và cái gì nó tham chiếu đều được được xác định bởi call-site tại nơi hàm được gọi. Vậy Call site
là gì?
Call site?
Call site là nơi hàm được gọi, không phải nơi nó được khai báo. Vậy hàm được gọi ở đâu?
Call stack là 1 khái niệm chỉ vị trí của thread khi chương trình đang thực thi. Khi hàm được gọi (call), nó được xếp chồng lên nhau thành 1 đống (stack). Call-stack sẽ đẩy function vào (push) khi nó được gọi (call) và ném function ra (pop) khỏi stack khi function đó return.
Ví dụ:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <span class="token keyword">function</span> <span class="token function">baz</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// call-stack là: `baz`</span> <span class="token comment">// call-site sẽ là trong global scope</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span> <span class="token string">"baz"</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">bar</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// <-- call-site cho `bar`</span> <span class="token punctuation">}</span> <span class="token keyword">function</span> <span class="token function">bar</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// call-stack là: `baz` -> `bar`</span> <span class="token comment">// call-site là trong `baz`</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span> <span class="token string">"bar"</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">foo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// <-- call-site cho `foo`</span> <span class="token punctuation">}</span> <span class="token keyword">function</span> <span class="token function">foo</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// call-stack là: `baz` -> `bar` -> `foo`</span> <span class="token comment">// call-site trong `bar`</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span> <span class="token string">"foo"</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">baz</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// <-- call-site cho `baz`</span> |
Các quy tắc áp dụng với this
Quy tắc 1 – New binding (Xuất hiện từ khóa new
):
this
là object mới vừa được tạo với từ khóa new
.
1 2 3 4 5 6 | <span class="token keyword">function</span> <span class="token function">foo</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>a <span class="token operator">=</span> a<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">var</span> bar <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">foo</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span> bar<span class="token punctuation">.</span>a <span class="token comment">// 2</span> |
Khi ta gọi 1 function với từ khóa new
thì các điều sau sẽ được thực hiện:
- Tạo một object mới.
- Link object mới này với một object khác.
this
được ràng buộc với object mới tạo ở bước 1.- Trả về
this
nếu hàm không trả về object.
1 2 3 4 5 6 7 8 9 10 11 | <span class="token keyword">function</span> <span class="token function">foo</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>a <span class="token operator">=</span> a<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">var</span> bar <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">foo</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span> bar<span class="token punctuation">.</span>a <span class="token comment">// undefined</span> <span class="token comment">// Do hàm trả về một object nên this ở đây là foo,</span> <span class="token comment">// không phải là object bar vừa tạo nên kết quả là undefined</span> |
Quy tắc 2 – Explicit binding (Ràng buộc rõ ràng):
this
là một object được chỉ định rõ. Hàm có được gọi cùng với call, apply hoặc bind không?
1 2 3 4 5 6 7 8 9 10 11 | <span class="token keyword">function</span> <span class="token function">foo</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>a<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">var</span> obj <span class="token operator">=</span> <span class="token punctuation">{</span> a<span class="token punctuation">:</span> <span class="token number">2</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment">// this được chỉ định rõ là obj bằng từ khóa `call`</span> <span class="token keyword">var</span> bar <span class="token operator">=</span> foo<span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span>obj<span class="token punctuation">)</span><span class="token punctuation">;</span> bar<span class="token punctuation">.</span>a<span class="token punctuation">;</span> <span class="token comment">//2</span> |
Phân biệt call, appy và bind
- call: gọi hàm ngay lập tức và cho phép pass từng arguments một.1234567891011<span class="token keyword">var</span> member <span class="token operator">=</span> <span class="token punctuation">{</span>name<span class="token punctuation">:</span> <span class="token string">'Quan Tien'</span><span class="token punctuation">}</span><span class="token punctuation">;</span><span class="token keyword">function</span> <span class="token function">greeting</span><span class="token punctuation">(</span>text1<span class="token punctuation">,</span> text2<span class="token punctuation">)</span> <span class="token punctuation">{</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>text1<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>name<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">. </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>text2<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">.`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span>greeting<span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span>member<span class="token punctuation">,</span> <span class="token string">'Hello'</span><span class="token punctuation">,</span> <span class="token string">'Nice to meet you'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// Hello, Quan Tien. Nice to meet you.</span>
- apply: gọi hàm ngay lập tức như call, chỉ khác là apply cho phép pass một array có một hoặc nhiều elements.1234567891011<span class="token keyword">var</span> member <span class="token operator">=</span> <span class="token punctuation">{</span>name<span class="token punctuation">:</span> <span class="token string">'Quan Tien'</span><span class="token punctuation">}</span><span class="token punctuation">;</span><span class="token keyword">function</span> <span class="token function">greeting</span><span class="token punctuation">(</span>text1<span class="token punctuation">,</span> text2<span class="token punctuation">)</span> <span class="token punctuation">{</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>text1<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>name<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">. </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>text2<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">.`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span>greeting<span class="token punctuation">.</span><span class="token function">apply</span><span class="token punctuation">(</span>member<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token string">'Hello'</span><span class="token punctuation">,</span> <span class="token string">'Nice to meet you'</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// Hello, Quan Tien. Nice to meet you.</span>
- bind: không gọi hàm ngay mà trả về một hàm mới.123456789101112var member = {name: 'Quan Tien'};function greeting(text1, text2) {console.log(`${text1}, ${this.name}. ${text2}.`);}// `bind` trả về một function. Gán function này với sayHi.var sayHi = greeting.bind(member, 'Hello', 'Nice to meet you');sayHi(); // Hello, Quan Tien. Nice to meet you.
Quy tắc 3 – Implicit binding (Ràng buộc ẩn):
1 2 3 4 5 6 7 8 9 10 | <span class="token keyword">function</span> <span class="token function">foo</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>a<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">var</span> obj <span class="token operator">=</span> <span class="token punctuation">{</span> a<span class="token punctuation">:</span> <span class="token number">2</span><span class="token punctuation">,</span> foo<span class="token punctuation">:</span> foo <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">var</span> bar <span class="token operator">=</span> obj<span class="token punctuation">.</span><span class="token function">foo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> bar<span class="token punctuation">;</span> <span class="token comment">//2</span> |
Điều đầu tiên cần chú ý đó là function foo() được khởi tạo, sau đó được tham chiếu bởi một thuộc tính trong obj ta hiểu là hàm bị bao bởi một object (trong trường hợp này là obj)
Call site sử dụng obj để tham chiếu đến hàm, ta hiểu rule này là obj sẽ được tham chiếu đến this khi hàm foo được gọi, hay có thể hiểu this.a ở đây chính là obj.a
Cũng cần nhớ rằng chỉ có obj cuối cùng trong chuỗi gọi sẽ được tham chiếu đến như trong ví dụ sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <span class="token keyword">function</span> <span class="token function">foo</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>a<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">var</span> obj2 <span class="token operator">=</span> <span class="token punctuation">{</span> a<span class="token punctuation">:</span> <span class="token number">42</span><span class="token punctuation">,</span> foo<span class="token punctuation">:</span> foo <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">var</span> obj1 <span class="token operator">=</span> <span class="token punctuation">{</span> a<span class="token punctuation">:</span> <span class="token number">2</span><span class="token punctuation">,</span> obj2<span class="token punctuation">:</span> obj2 <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">var</span> bar <span class="token operator">=</span> obj1<span class="token punctuation">.</span>obj2<span class="token punctuation">.</span><span class="token function">foo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> bar<span class="token punctuation">;</span> <span class="token comment">//42 this sẽ tham chiếu đến obj2 thay vì là obj1</span> |
Quy tắc 4 – Default Binding (Ràng buộc mặc định):
this
là window object (browser) hoặc global object (nodejs) hoặc undefined (use strict).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <span class="token comment">// Xét TH 1: Dùng var</span> <span class="token keyword">function</span> <span class="token function">foo</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>a<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">var</span> a <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span> <span class="token function">foo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//2</span> <span class="token comment">// `this` được trỏ tới global/window object.</span> global <span class="token operator">===</span> <span class="token keyword">this</span><span class="token punctuation">;</span> <span class="token comment">// true</span> <span class="token comment">// Dùng var thì biến name sẽ được thêm vào properties của global/window object.</span> global<span class="token punctuation">.</span>a <span class="token operator">===</span> <span class="token keyword">this</span><span class="token punctuation">.</span>a<span class="token punctuation">;</span> <span class="token comment">// true</span> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <span class="token comment">// Xét TH 1: Dùng let</span> <span class="token keyword">function</span> <span class="token function">foo</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>a<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">let</span> a <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span> <span class="token function">foo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//2</span> <span class="token comment">// `this` được trỏ tới global/window object.</span> global <span class="token operator">===</span> <span class="token keyword">this</span><span class="token punctuation">;</span> <span class="token comment">// true</span> <span class="token comment">// Dùng `let` thì biến `name` sẽ không được thêm vào properties của `global/window object`.</span> global<span class="token punctuation">.</span>a <span class="token operator">===</span> <span class="token keyword">this</span><span class="token punctuation">.</span>a<span class="token punctuation">;</span> <span class="token comment">// false</span> |
1 2 3 4 5 6 7 8 9 10 11 | <span class="token comment">// Xét TH 3: strict mode</span> <span class="token keyword">function</span> <span class="token function">foo</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token string">"use strict"</span><span class="token punctuation">;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>a<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">var</span> a <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span> <span class="token function">foo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// TypeError: Cannot read property 'a' of undefined</span> <span class="token comment">// Nếu có `use strict` thì giá trị của `this` sẽ là undefined</span> |
Tổng kết
this
binding phụ thuộc vào ngữ cảnh (context).Call site
là nơi hàm được gọi.Call stack
là một khái niệm chỉ vị trí của thread khi chương trình đang thực thi (execution).Bốn quy tắc
theo thứ tự ưu tiên xác định this:- Từ khóa
new
. Explicit binding
(ràng buộc rõ ràng): this là object được gọi cụ thể cùng với call, apply và bind.Implicit binding
(ràng buộc ẩn): this là object chứa context.Default Binding
: mặc định this là global/window object hoặc là undefined nếu có use strict.
- Từ khóa