Việc tìm ra giá trị của this
có thể khó khăn trong JavaScript, sau đây là cách thực hiện…
this
của JavaScript là chủ đề của nhiều câu chuyện cười, và đó là vì nó khá phức tạp.
Tuy nhiên, tôi đã thấy các nhà phát triển làm những việc phức tạp hơn nhiều và dành riêng cho một lĩnh vực để tránh xử lý this
này. Nếu bạn không chắc về this
, hy vọng thông tin này sẽ giúp ích cho bạn. Đây là hướng dẫn của tôi về this
.
Tôi sẽ bắt đầu với trường hợp cụ thể nhất và kết thúc bằng trường hợp ít cụ thể nhất. Bài viết này giống như một if (…) … else if () … else if (…) …
lớn, vì vậy, bạn có thể chuyển thẳng đến phần đầu tiên khớp với mã bạn đang xem.
- Nếu hàm được xác định là hàm mũi tên
- Ngoài ra, nếu hàm/lớp được gọi bằng
new
- Ngoài ra, nếu hàm có giá trị
this
"giới hạn" - Nếu không, nếu
this
được đặt tại thời điểm gọi - Ngược lại, nếu hàm được gọi thông qua đối tượng mẹ (
parent.func()
) - Ngược lại, nếu hàm hoặc phạm vi mẹ ở chế độ nghiêm ngặt
- Nếu không
Nếu hàm được xác định là hàm mũi tên:
const arrowFunction = () => {
console.log(this);
};
Trong trường hợp này, giá trị của this
luôn giống với this
trong phạm vi mẹ:
const outerThis = this;
const arrowFunction = () => {
// Always logs `true`:
console.log(this === outerThis);
};
Hàm mũi tên rất hữu ích vì giá trị bên trong của this
không thể thay đổi, giá trị này luôn giống với this
bên ngoài.
Ví dụ khác
Với các hàm mũi tên, bạn không thể thay đổi giá trị của this
bằng bind
:
// Logs `true` - bound `this` value is ignored:
arrowFunction.bind({foo: 'bar'})();
Với các hàm mũi tên, bạn không thể thay đổi giá trị của this
bằng call
hoặc apply
:
// Logs `true` - called `this` value is ignored:
arrowFunction.call({foo: 'bar'});
// Logs `true` - applied `this` value is ignored:
arrowFunction.apply({foo: 'bar'});
Với các hàm mũi tên, bạn không thể thay đổi giá trị của this
bằng cách gọi hàm dưới dạng thành viên của một đối tượng khác:
const obj = {arrowFunction};
// Logs `true` - parent object is ignored:
obj.arrowFunction();
Với các hàm mũi tên, bạn không thể thay đổi giá trị của this
bằng cách gọi hàm dưới dạng hàm khởi tạo:
// TypeError: arrowFunction is not a constructor
new arrowFunction();
Phương thức thực thể "Bound"
Với các phương thức thực thể, nếu bạn muốn đảm bảo this
luôn tham chiếu đến thực thể lớp, cách tốt nhất là sử dụng các hàm mũi tên và trường lớp:
class Whatever {
someMethod = () => {
// Always the instance of Whatever:
console.log(this);
};
}
Mẫu này rất hữu ích khi sử dụng các phương thức thực thể làm trình nghe sự kiện trong các thành phần (chẳng hạn như thành phần React hoặc thành phần web).
Phần trên có vẻ như đang vi phạm quy tắc "this
sẽ giống với this
trong phạm vi mẹ", nhưng điều này sẽ bắt đầu có ý nghĩa nếu bạn coi các trường lớp là cú pháp đơn giản để thiết lập các mục trong hàm khởi tạo:
class Whatever {
someMethod = (() => {
const outerThis = this;
return () => {
// Always logs `true`:
console.log(this === outerThis);
};
})();
}
// …is roughly equivalent to:
class Whatever {
constructor() {
const outerThis = this;
this.someMethod = () => {
// Always logs `true`:
console.log(this === outerThis);
};
}
}
Các mẫu thay thế liên quan đến việc liên kết một hàm hiện có trong hàm khởi tạo hoặc gán hàm trong hàm khởi tạo. Nếu bạn không thể sử dụng các trường lớp vì lý do nào đó, việc chỉ định hàm trong hàm khởi tạo là một giải pháp thay thế hợp lý:
class Whatever {
constructor() {
this.someMethod = () => {
// …
};
}
}
Nếu không, nếu hàm/lớp được gọi bằng new
:
new Whatever();
Mã trên sẽ gọi Whatever
(hoặc hàm khởi tạo của lớp nếu đó là một lớp) với this
được đặt thành kết quả của Object.create(Whatever.prototype)
.
class MyClass {
constructor() {
console.log(
this.constructor === Object.create(MyClass.prototype).constructor,
);
}
}
// Logs `true`:
new MyClass();
Điều này cũng đúng với các hàm khởi tạo kiểu cũ:
function MyClass() {
console.log(
this.constructor === Object.create(MyClass.prototype).constructor,
);
}
// Logs `true`:
new MyClass();
Ví dụ khác
Khi được gọi bằng new
, giá trị của this
không thể được thay đổi bằng bind
:
const BoundMyClass = MyClass.bind({foo: 'bar'});
// Logs `true` - bound `this` value is ignored:
new BoundMyClass();
Khi được gọi bằng new
, giá trị của this
không thể được thay đổi bằng cách gọi hàm dưới dạng thành viên của một đối tượng khác:
const obj = {MyClass};
// Logs `true` - parent object is ignored:
new obj.MyClass();
Nếu không, nếu hàm có giá trị this
"giới hạn":
function someFunction() {
return this;
}
const boundObject = {hello: 'world'};
const boundFunction = someFunction.bind(boundObject);
Bất cứ khi nào boundFunction
được gọi, giá trị this
của đối tượng này sẽ là đối tượng được truyền đến bind
(boundObject
).
// Logs `false`:
console.log(someFunction() === boundObject);
// Logs `true`:
console.log(boundFunction() === boundObject);
Ví dụ khác
Khi gọi một hàm liên kết, bạn không thể thay đổi giá trị của this
bằng call
hoặc apply
:
// Logs `true` - called `this` value is ignored:
console.log(boundFunction.call({foo: 'bar'}) === boundObject);
// Logs `true` - applied `this` value is ignored:
console.log(boundFunction.apply({foo: 'bar'}) === boundObject);
Khi gọi một hàm liên kết, bạn không thể thay đổi giá trị của this
bằng cách gọi hàm dưới dạng một thành viên của đối tượng khác:
const obj = {boundFunction};
// Logs `true` - parent object is ignored:
console.log(obj.boundFunction() === boundObject);
Nếu không, nếu this
được đặt tại thời điểm gọi:
function someFunction() {
return this;
}
const someObject = {hello: 'world'};
// Logs `true`:
console.log(someFunction.call(someObject) === someObject);
// Logs `true`:
console.log(someFunction.apply(someObject) === someObject);
Giá trị của this
là đối tượng được truyền đến call
/apply
.
Rất tiếc, this
được đặt thành một số giá trị khác bởi các trình nghe sự kiện DOM và việc sử dụng giá trị này có thể dẫn đến mã khó hiểu:
element.addEventListener('click', function (event) {
// Logs `element`, since the DOM spec sets `this` to
// the element the handler is attached to.
console.log(this);
});
Tôi tránh sử dụng this
trong các trường hợp như trên và thay vào đó:
element.addEventListener('click', (event) => {
// Ideally, grab it from a parent scope:
console.log(element);
// But if you can't do that, get it from the event object:
console.log(event.currentTarget);
});
Nếu không, nếu hàm được gọi thông qua đối tượng mẹ (parent.func()
):
const obj = {
someMethod() {
return this;
},
};
// Logs `true`:
console.log(obj.someMethod() === obj);
Trong trường hợp này, hàm được gọi là thành viên của obj
, vì vậy this
sẽ là obj
. Điều này xảy ra tại thời điểm gọi, vì vậy, đường liên kết sẽ bị hỏng nếu hàm được gọi mà không có đối tượng mẹ hoặc với một đối tượng mẹ khác:
const {someMethod} = obj;
// Logs `false`:
console.log(someMethod() === obj);
const anotherObj = {someMethod};
// Logs `false`:
console.log(anotherObj.someMethod() === obj);
// Logs `true`:
console.log(anotherObj.someMethod() === anotherObj);
someMethod() === obj
là false vì someMethod
không được gọi là thành viên của obj
. Bạn có thể gặp phải vấn đề này khi thử làm như sau:
const $ = document.querySelector;
// TypeError: Illegal invocation
const el = $('.some-element');
Điều này xảy ra vì việc triển khai querySelector
xem xét giá trị this
của chính nó và dự kiến giá trị đó sẽ là một nút DOM của các loại, và điều trên sẽ phá vỡ kết nối đó. Để thực hiện chính xác những điều trên:
const $ = document.querySelector.bind(document);
// Or:
const $ = (...args) => document.querySelector(...args);
Thông tin thú vị: Không phải tất cả API đều sử dụng this
trong nội bộ. Các phương thức của bảng điều khiển như console.log
đã được thay đổi để tránh tham chiếu this
, vì vậy, log
không cần phải liên kết với console
.
Nếu không, nếu hàm hoặc phạm vi mẹ ở chế độ nghiêm ngặt:
function someFunction() {
'use strict';
return this;
}
// Logs `true`:
console.log(someFunction() === undefined);
Trong trường hợp này, giá trị của this
là không xác định. Bạn không cần 'use strict'
trong hàm nếu phạm vi mẹ ở chế độ nghiêm ngặt (và tất cả các mô-đun đều ở chế độ nghiêm ngặt).
Nếu không thì:
function someFunction() {
return this;
}
// Logs `true`:
console.log(someFunction() === globalThis);
Trong trường hợp này, giá trị của this
giống với globalThis
.
Chà!
Chỉ vậy thôi! Đó là tất cả những gì tôi biết về this
. Bạn có câu hỏi? Tôi có bỏ lỡ điều gì không? Bạn có thể twitt về tôi.
Cảm ơn Mathias Bynens, Ingvar Stepanyan và Thomas Steiner đã xem xét.