This trong Javascript là gì

1. Mở đầu

Trước khi học hiểu về thisbạn nên học trước vềscope và closure

Trong js, từ khóa thislà thứ rất hay nhưng cũng là thứ gây ra bao rắc rối cho nhiều người, nhất là đối với những người đi từ ngôn ngữ lập trình khác sang js.

Lý do lớn nhất khiến thisgây hiểu nhầm cho bao nhiêu người chính là vì ý nghĩa từ điểncủa chính từ this

Khi bạn bắt gặp từ thistrong lập trình, gần như chắc chắn bạn sẽ nghĩ tới nó chính là tham chiếu tới instancehiện tại hoặc nội hàmhiện tại, và đó cũng là lý do khiến nhiều người hiểu nhầm từ thistrong js.

Có 2 chú ý khi bạn bắt gặp thismà cần phải nhớ đó là:

  • thischính là bối cảnh(context) của nơi mà hàm chứa từ thisđược gọi. Bạn hãy nhớ từ thistham chiếu tới cái vùng không gian mà hàm chứa từ thisđược gọi.
  • Chỉ có 2 loại contextđối với thislà objectchứa method được gọi hoặc global, ngoài ra không có loại khác.
  • Khi gặp từ this, chỉ quan tâm tới cái nơi gọi hàm chứa nó chứ không được dịch thislà nội hàm hiện tại.
Chính vì thế, nếu bạn gặp this, đừng có dịch nó là cái nàymà hãy dịch nó thành bối cảnh hay nơi gọi tao(context)

2. thiskhông phải là tham chiếu tới chính function.

function foo(num) { console.log("foo: " + num); //keep track of how many times `foo` is called this.count++; } foo.count = 0; var i; for (i=0; i<5; i++) { foo(i); } console.log(foo.count);

Nếu dịch thislà nội hàm thì nhiều người sẽ nghĩ đoạn code trên cho ra kết quả:

0
1
2
3
4
5 <= result of console.log(foo.count)

Nhưng không phải, kết quả in ra là:

0
1
2
3
4
0 <= result of console.log(foo.count)

Như nói ở trên, ta cần quan tâm tới việc hàm foo()được gọi ở đâu. Trong TH này foo()được gọi tại câu lệnh số 10 bằng cách gọi hàm trực tiếp(xem Function Invocation phía dưới) nên contextở đây chính là global. Vì là globalnên this.countở dòng 4 sẽ làundefineddẫn tớithis.count++trả về NaN. Câu lệnh 13 in ra giá trị 0 vìfoo.countở scope hiện tại được khai báo bằng 0.

Nếu bạn muốn tham chiếu tới chính object footrong function thì sửa thành như sau:

function foo(num) { console.log("foo: " + num); //keep track of how many times `foo` is called foo.count++; } foo.count = 0; var i; for (i=0; i<5; i++) { foo(i); } console.log(foo.count);​

Nó sẽ in ra kết quả đúng cho bạn.

3. thistrong Function Invocation

Xét ví dụ:

var value = 500; //Global variable var obj = { value: 0, increment: function() { this.value++; var innerFunction = function() { console.log(this.value); } innerFunction(); //Function invocation pattern } } obj.increment(); //Method invocation pattern

Gọi hàm kiểu Function invocation pattern (gọi trực tiếp bằng cách thêm dấu () ) thì từ khóa this trong hàm đó luôn là global object (window)

Vì vậy đoạn code trên sẽ in ra 500 chứ không phải 1.

4. thistrong callback của hàm setTimeout

this trong hàm ẩn danh(anonymous function) luôn là global

  • thistrong callback của setTimeout luôn là global object
  • Không thể tham chiếu tới chính function trong callback của hàm setTimeout vì nó là anonymous function.
var a = 10; setTimeout( function(){ // anonymous function (no name), cannot // refer to itself var a = 20; console.log(this.a); // 10 }, 1000);

5. thistrong Method Invocation.

thistrong Method Invocation chính là contextcủa object gọi tới method đó.

var value = 500; //Global variable var obj = { value: 0, increment: function() { this.value++; console.log(this.value) } } obj.increment(); //Method invocation pattern

Kết quả trả về là 1

Nhưng nếu là gọi trong setTimeout thì thisluôn là global:

function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; var a = 100; setTimeout( obj.foo, 1000 ); // 100

6. thiskhi gọi với từ khóa new

function foo(a) { this.a = a; } var a = 10; var bar = new foo( 2 ); console.log( bar.a ); // 2

Khác với Function invocation,khi khai báo bằng từ khóa newphía trước, một object sẽ được khởi tạo và trả về object đó nên contextở đây sẽ chính là object được khởi tạo.

7. thistrong eval()

  • thistrong direct evalcontextcủa nơi gọi hàm
  • thistrong indirect evalcontextcủa global

Đọc thêm vền direct eval và indirect eval tại đây

x = 10; (function foo() { var x = 20; (function bar(){ var x = 30; eval("test()"); // 10 var indirectEval = eval; indirectEval("test()"); // 10 var obj = { x: 40, test: test }; eval("obj.test()"); // 40 indirectEval("obj.test()"); // lỗi vì ở global ko có biến obj })(); })(); function test() { var x = 100; console.log(this.x) }

Kết quả in ra là:

10
10
40
error

8. thistrong các hàm đặc biệt của js

Như nói ở trên, khi gọi hàm bằng method invocationthì thislà contextcủa object. Nhưng trừ 8 hàm đặc biệt sau:

  • Function.prototype.apply( thisArg, argArray )
  • Function.prototype.call( thisArg [ , arg1 [ , arg2, ... ] ] )
  • Function.prototype.bind( thisArg [ , arg1 [ , arg2, ... ] ] )
  • Array.prototype.every( callbackfn [ , thisArg ] )
  • Array.prototype.some( callbackfn [ , thisArg ] )
  • Array.prototype.forEach( callbackfn [ , thisArg ] )
  • Array.prototype.map( callbackfn [ , thisArg ] )
  • Array.prototype.filter( callbackfn [ , thisArg ] )

Đối với trường hợp Function.prototype: contextsẽ là thisArg chứ không phải là object

Đối với trường hợp Array.prototype:contextsẽ là thisArg nếu được truyền vào, nếu không thì là global

9. thistrong DOM event handler

Xem thêm về cách gọi hàm trong event handler tạiđây

thiskhi event được trigger chính là button chứa event đó.

Nhưng nếu bạn khai báo một hàm trong event handler thì thissẽ là global(window trong browser) vì thisđã nằm trong hàm ẩn danh.

hoặc

10. thistrong ES6

arrow function

Chỉ nên dùng arrow function với các hàm không phải là method của object

var obj = { i: 10, b: () => console.log(this.i, this), // hoặc b: () => {console.log(this.i, this)}, c: function() { console.log(this.i, this); } } obj.b(); // prints undefined, Window {...} (or the global object) obj.c(); // prints 10, Object {...}

Bản thân arrow function không tự tạo ra thisrồi truyền vào cho lệnh thực thi mà thực chất arrow function không được khai báo theo kiểu truyền thống function(){}nên sẽ không có thischo arrow function, thisđược sử dụng ở đây chính là thiscủa contextmà nơi chứa arrow functionđược gọi.

Nên ở trên obj.b()mới có thislà global(Window)

Cũng chính vì lý do trên mà các hàm đặc biệt như bind, callsẽ không hoạt động với arrow function

var globalObject = this; var foo = (() => this); // hoặc var foo = (() => {return this}); console.log(foo() === globalObject); // true // Call as a method of an object var obj = {func: foo}; console.log(obj.func() === globalObject); // true // Attempt to set this using call console.log(foo.call(obj) === globalObject); // true // Attempt to set this using bind foo = foo.bind(obj); console.log(foo() === globalObject); // true

class trong ES6

Xét ví dụ một đoạn tutorial trong document của react:

class Toggle extends React.Component { constructor(props) { super(props); console.log("1: ", this) this.state = {isToggleOn: true}; } handleClick() { console.log("2: ", this) this.setState(prevState => ({ isToggleOn: !prevState.isToggleOn })); } render() { console.log("3: ", this) return (
); } } ReactDOM.render( , document.getElementById('root') );

Các bước thực thi như sau:

  • Đầu tiên là tìm tới nơi bắt đầu gọi hàm ở đâu? Nó là đoạn ReactDOM.render(...
  • Khi gọi trong đó, nó sẽ khởi tạo một object Toggle
  • Khi object Toggle được khởi tạo, hàm constructorđược thực thi, thistrong hàm này chính là object củaToggle(xem với từ khóa newở phía trên)
  • Sau khi constructor được gọi xong, hàm renderđược gọi thực thi, thisở đây vẫn là object của class Toggle

Bạn để ý trong hàm rendercó đoạn ); }

Một số ví dụ kiểm tra

VD1:

var obj = { someData: "a string" }; function myFun() { console.log(this); } obj.staticFunction = myFun; obj.staticFunction(); //=======result==========// // this is obj // //=======================//

VD2:

var obj = { myMethod : function () { console.log(this); } }; var myFun = obj.myMethod; myFun(); //=========result==========// // this is global(window) // //=========================//

VD3:

function myFun() { console.log(this); } var obj = { myMethod : function () { eval("myFun()"); } }; obj.myMethod(); //=========result==========// // this is global(window) // //=========================//

VD4:

function myFun() { console.log(this); } var obj = { someData: "a string" }; myFun.call(obj); //=========result==========// // this is obj // //=========================//

VD5:

function Person(){ var age = 10; setTimeout(function(){ this.age++; console.log(this.age); }, 1000); } var p = Person(); //=========result==========// // NaN // //=========================// var q = new Person(); //=========result==========// // NaN // //=========================//

VD6:

function Person(){ this.age = 10; setTimeout(function(){ this.age++; console.log(this.age); }, 1000); } var p = Person(); //=========result==========// // 11 // //=========================//

nhưng:

function Person(){ this.age = 10; setTimeout(function(){ this.age++; console.log(this.age); }, 1000); } var p = new Person(); //=========result==========// // NaN // //=========================//

https://github.com/getify/You-Dont-Know-JS/blob/master/this%20%26%20object%20prototypes/ch2.md

https://github.com/getify/You-Dont-Know-JS/blob/master/this%20%26%20object%20prototypes/ch2.md

https://stackoverflow.com/questions/3127429/how-does-the-this-keyword-work/3127440#3127440

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions

https://reactjs.org/docs/handling-events.html