Hiểu điều này, ràng buộc, gọi và áp dụng trong JavaScript
Tác giả đã chọn Open Internet / Free Speech Fund để nhận khoản đóng góp như một phần của chương trình Viết cho DO donate .
Từ khóa this
là một khái niệm rất quan trọng trong JavaScript, và cũng là một khái niệm đặc biệt khó hiểu đối với cả các nhà phát triển mới và những người đã có kinh nghiệm về các ngôn ngữ lập trình khác. Trong JavaScript, this
là một tham chiếu đến một đối tượng. Đối tượng mà this
đề cập đến có thể thay đổi, hoàn toàn dựa trên việc nó là toàn cục, trên một đối tượng hay trong một phương thức khởi tạo và cũng có thể thay đổi một cách rõ ràng dựa trên việc sử dụng các phương thức nguyên mẫu của Function
bind
, call
và apply
.
Mặc dù this
là một chủ đề hơi phức tạp, nhưng nó cũng là một chủ đề xuất hiện ngay khi bạn bắt đầu viết các chương trình JavaScript đầu tiên của bạn . Cho dù bạn đang cố gắng truy cập một phần tử hoặc sự kiện trong Mô hình Đối tượng Tài liệu (DOM) , xây dựng các lớp để viết theo phong cách lập trình hướng đối tượng hay sử dụng các thuộc tính và phương thức của các đối tượng thông thường, bạn sẽ gặp phải this
.
Trong bài viết này, bạn sẽ tìm hiểu những gì this
đề cập đến ngầm dựa trên ngữ cảnh và bạn sẽ học cách sử dụng các phương thức bind
, call
và apply
để xác định rõ ràng giá trị của this
.
Ngữ cảnh ngầm định
Có bốn bối cảnh chính trong đó giá trị của this
có thể được suy ra ngầm:
- bối cảnh global
- như một phương thức trong một đối tượng
- như một hàm tạo trên một hàm hoặc lớp
- như một trình xử lý sự kiện DOM
Global
Trong bối cảnh global , this
đề cập đến đối tượng toàn cục . Khi bạn đang làm việc trong một trình duyệt, bối cảnh chung sẽ là window
. Khi bạn đang làm việc trong Node.js, bối cảnh chung là global
.
Lưu ý: Nếu bạn chưa quen với khái niệm phạm vi trong JavaScript, vui lòng xem lại Tìm hiểu về Biến, Phạm vi và Lưu trữ trong JavaScript .
Đối với các ví dụ, bạn sẽ thực hành mã trong console Công cụ dành cho nhà phát triển của trình duyệt. Đọc Cách sử dụng Control panel dành cho nhà phát triển JavaScript nếu bạn không quen với việc chạy mã JavaScript trong trình duyệt.
Nếu bạn ghi lại giá trị của mã this
mà không có bất kỳ mã nào khác, bạn sẽ thấy đối tượng this
đề cập đến.
console.log(this)
OutputWindow {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
Bạn có thể thấy rằng this
là window
, là đối tượng chung của trình duyệt.
Trong Tìm hiểu về Biến, Phạm vi và Lưu trữ trong JavaScript , bạn đã biết rằng các hàm có ngữ cảnh riêng cho các biến. Bạn có thể bị cám dỗ khi nghĩ rằng this
sẽ tuân theo các luật tương tự bên trong một hàm, nhưng không phải vậy. Một hàm cấp cao nhất sẽ vẫn giữ nguyên tham chiếu this
của đối tượng toàn cục.
Bạn viết một hàm cấp cao nhất hoặc một hàm không được liên kết với bất kỳ đối tượng nào, như sau:
function printThis() {
console.log(this)
}
printThis()
OutputWindow {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
Ngay cả trong một hàm, this
vẫn đề cập đến window
hoặc đối tượng toàn cục.
Tuy nhiên, khi sử dụng chế độ nghiêm ngặt , ngữ cảnh của this
trong một hàm trên ngữ cảnh chung sẽ undefined
được undefined
.
'use strict'
function printThis() {
console.log(this)
}
printThis()
Outputundefined
Nói chung, nó là an toàn hơn để sử dụng chế độ nghiêm ngặt để giảm xác suất this
có một phạm vi bất ngờ. Hiếm khi ai đó muốn tham chiếu đến đối tượng window
bằng cách sử dụng this
.
Để biết thêm thông tin về chế độ nghiêm ngặt và những thay đổi mà nó thực hiện liên quan đến các lỗi và bảo mật, hãy đọc tài liệu về Chế độ nghiêm ngặt trên MDN.
Một phương pháp đối tượng
Phương thức là một chức năng trên một đối tượng hoặc một tác vụ mà một đối tượng có thể thực hiện. Một phương thức sử dụng this
để tham chiếu đến các thuộc tính của đối tượng.
const america = {
name: 'The United States of America',
yearFounded: 1776,
describe() {
console.log(`${this.name} was founded in ${this.yearFounded}.`)
},
}
america.describe()
Output"The United States of America was founded in 1776."
Trong ví dụ này, this
giống với america
.
Trong một đối tượng lồng nhau, this
đề cập đến phạm vi đối tượng hiện tại của phương thức. Trong ví dụ sau, this.symbol
trong đối tượng details
tham chiếu đến details.symbol
.
const america = {
name: 'The United States of America',
yearFounded: 1776,
details: {
symbol: 'eagle',
currency: 'USD',
printDetails() {
console.log(`The symbol is the ${this.symbol} and the currency is ${this.currency}.`)
},
},
}
america.details.printDetails()
Output"The symbol is the eagle and the currency is USD."
Một cách khác để nghĩ về nó là điều this
đề cập đến đối tượng ở phía bên trái của dấu chấm khi gọi một phương thức.
Một hàm tạo
Khi bạn sử dụng từ khóa new
, nó sẽ tạo ra một thể hiện của một hàm hoặc lớp phương thức khởi tạo. Hàm tạo là cách tiêu chuẩn để khởi tạo đối tượng do user xác định trước khi cú pháp class
được giới thiệu trong bản cập nhật ECMAScript 2015 cho JavaScript. Trong phần Hiểu các lớp trong JavaScript , bạn sẽ học cách tạo một hàm tạo hàm và một hàm tạo lớp tương đương.
function Country(name, yearFounded) {
this.name = name
this.yearFounded = yearFounded
this.describe = function() {
console.log(`${this.name} was founded in ${this.yearFounded}.`)
}
}
const america = new Country('The United States of America', 1776)
america.describe()
Output"The United States of America was founded in 1776."
Trong bối cảnh này, this
bây giờ bị ràng buộc với ví dụ của Country
, được chứa trong hằng số america
.
A Class Constructor
Một hàm tạo trên một lớp hoạt động giống như một hàm tạo trên một hàm. Đọc thêm về những điểm giống và khác nhau giữa hàm tạo hàm và các lớp ES6 trong Tìm hiểu các lớp trong JavaScript .
class Country {
constructor(name, yearFounded) {
this.name = name
this.yearFounded = yearFounded
}
describe() {
console.log(`${this.name} was founded in ${this.yearFounded}.`)
}
}
const america = new Country('The United States of America', 1776)
america.describe()
this
trong phương thức describe
đề cập đến trường hợp của Country
, là america
.
Output"The United States of America was founded in 1776."
Trình xử lý sự kiện DOM
Trong trình duyệt, có một ngữ cảnh đặc biệt this
dành cho các trình xử lý sự kiện. Trong một trình xử lý sự kiện được gọi bởi addEventListener
, this
sẽ tham chiếu đến event.currentTarget
. Thông thường, các nhà phát triển sẽ chỉ sử dụng event.target
hoặc event.currentTarget
khi cần thiết để truy cập các phần tử trong DOM, nhưng vì tham chiếu this
thay đổi trong ngữ cảnh này nên điều quan trọng là phải biết.
Trong ví dụ sau, ta sẽ tạo một nút, thêm văn bản vào đó và nối nó vào DOM . Khi ta ghi giá trị của giá trị this
trong trình xử lý sự kiện, nó sẽ in ra mục tiêu.
const button = document.createElement('button')
button.textContent = 'Click me'
document.body.append(button)
button.addEventListener('click', function(event) {
console.log(this)
})
Output<button>Click me</button>
Sau khi dán file này vào trình duyệt của bạn , bạn sẽ thấy một nút được nối vào trang có nội dung “Nhấp vào tôi”. Nếu bạn nhấp vào nút, bạn sẽ thấy <button>Click me</button>
xuất hiện trong console của bạn, khi nhấp vào nút ghi lại phần tử, chính là nút. Do đó, như bạn thấy , this
đề cập đến phần tử được nhắm đến , là phần tử ta đã thêm trình nghe sự kiện vào.
Bối cảnh rõ ràng
Trong tất cả các ví dụ trước, giá trị của this
được xác định bởi ngữ cảnh của nó — cho dù nó là toàn cục, trong một đối tượng, trong một hàm hoặc lớp được xây dựng hay trên một trình xử lý sự kiện DOM. Tuy nhiên, bằng cách sử dụng call
, apply
hoặc bind
, bạn có thể xác định rõ ràng điều this
nên tham chiếu đến.
Rất khó để xác định chính xác thời điểm sử dụng call
, apply
hoặc bind
, vì nó sẽ phụ thuộc vào ngữ cảnh của chương trình của bạn. bind
có thể đặc biệt hữu ích khi bạn muốn sử dụng các sự kiện để truy cập các thuộc tính của một lớp trong một lớp khác. Ví dụ: nếu bạn viết một trò chơi đơn giản, bạn có thể tách giao diện user và I / O thành một lớp, logic và trạng thái của trò chơi thành một lớp khác. Vì logic trò chơi cần truy cập đầu vào, chẳng hạn như nhấn và nhấp phím, bạn cần bind
các sự kiện để truy cập giá trị this
của lớp logic trò chơi.
Phần quan trọng là biết cách xác định đối tượng this
đề cập đến, điều mà bạn có thể thực hiện ngầm với những gì bạn đã học trong các phần trước hoặc rõ ràng với ba phương pháp bạn sẽ học tiếp theo.
Gọi và đăng ký
call
và apply
rất giống nhau — chúng gọi một hàm với ngữ cảnh cụ thể this
và các đối số tùy chọn. Sự khác biệt duy nhất giữa call
và call
apply
là call
yêu cầu các đối số phải được chuyển từng cái một và apply
nhận các đối số dưới dạng một mảng.
Trong ví dụ này, ta sẽ tạo một đối tượng và tạo một hàm tham chiếu đến this
nhưng không có ngữ cảnh this
.
const book = {
title: 'Brave New World',
author: 'Aldous Huxley',
}
function summary() {
console.log(`${this.title} was written by ${this.author}.`)
}
summary()
Output"undefined was written by undefined"
Vì summary
và book
không có mối liên hệ nào nên việc gọi summary
sẽ chỉ in ra undefined
, vì nó đang tìm kiếm các thuộc tính đó trên đối tượng toàn cục.
Lưu ý: Cố gắng này trong chế độ nghiêm ngặt sẽ cho kết quả Uncaught TypeError: Cannot read property 'title' of undefined
, như this
tự nó sẽ được undefined
.
Tuy nhiên, bạn có thể sử dụng call
và apply
để gọi this
bối cảnh của book
về chức năng.
summary.call(book)
// or:
summary.apply(book)
Output"Brave New World was written by Aldous Huxley."
Hiện có mối liên hệ giữa book
và summary
khi các phương pháp này được áp dụng. Hãy xác nhận chính xác this
là gì.
function printThis() {
console.log(this)
}
printThis.call(book)
// or:
whatIsThis.apply(book)
Output{title: "Brave New World", author: "Aldous Huxley"}
Trong trường hợp này, this
thực sự trở thành đối tượng được truyền dưới dạng đối số.
Đây là cách call
và apply
giống nhau, nhưng có một điểm khác biệt nhỏ. Ngoài việc có thể chuyển ngữ cảnh this
làm đối số đầu tiên, bạn cũng có thể chuyển các đối số bổ sung qua.
function longerSummary(genre, year) {
console.log(
`${this.title} was written by ${this.author}. It is a ${genre} novel written in ${year}.`
)
}
Với call
mỗi giá trị bổ sung bạn muốn chuyển được gửi dưới dạng đối số bổ sung.
longerSummary.call(book, 'dystopian', 1932)
Output"Brave New World was written by Aldous Huxley. It is a dystopian novel written in 1932."
Nếu bạn cố gắng gửi các đối số giống hệt nhau với apply
, thì đây là điều sẽ xảy ra:
longerSummary.apply(book, 'dystopian', 1932)
OutputUncaught TypeError: CreateListFromArrayLike called on non-object at <anonymous>:1:15
Thay vào đó, để apply
, bạn phải chuyển tất cả các đối số trong một mảng.
longerSummary.apply(book, ['dystopian', 1932])
Output"Brave New World was written by Aldous Huxley. It is a dystopian novel written in 1932."
Sự khác biệt giữa việc truyền các đối số riêng lẻ hoặc trong một mảng là rất nhỏ, nhưng điều quan trọng là bạn cần lưu ý. Nó có thể đơn giản hơn và thuận tiện hơn để sử dụng apply
, vì nó sẽ không yêu cầu thay đổi lệnh gọi hàm nếu một số chi tiết tham số thay đổi.
Trói buộc
Cả hai call
và apply
đều là các phương thức sử dụng một lần — nếu bạn gọi phương thức với ngữ cảnh this
nó sẽ có nó, nhưng chức năng ban đầu sẽ không thay đổi.
Đôi khi, bạn có thể cần phải sử dụng một phương pháp lặp đi lặp với this
bối cảnh của đối tượng khác, và trong trường hợp đó bạn có thể sử dụng các bind
phương pháp để tạo ra một chức năng hoàn toàn mới với một ràng buộc rõ ràng this
.
const braveNewWorldSummary = summary.bind(book)
braveNewWorldSummary()
Output"Brave New World was written by Aldous Huxley"
Trong ví dụ này, mỗi khi bạn gọi braveNewWorldSummary
, nó sẽ luôn trả về giá trị ban đầu this
giá trị this
gắn với nó. Cố gắng liên kết một ngữ cảnh mới this
với nó sẽ không thành công, vì vậy bạn luôn có thể tin tưởng một hàm ràng buộc để trả về giá trị this
mà bạn mong đợi.
const braveNewWorldSummary = summary.bind(book)
braveNewWorldSummary() // Brave New World was written by Aldous Huxley.
const book2 = {
title: '1984',
author: 'George Orwell',
}
braveNewWorldSummary.bind(book2)
braveNewWorldSummary() // Brave New World was written by Aldous Huxley.
Mặc dù ví dụ này cố gắng ràng buộc braveNewWorldSummary
, nhưng nó vẫn giữ nguyên ngữ cảnh this
từ lần đầu tiên được ràng buộc.
Hàm mũi tên
Các hàm mũi tên không có ràng buộc this
của riêng chúng. Thay vào đó, họ chuyển sang cấp độ thực thi tiếp theo.
const whoAmI = {
name: 'Leslie Knope',
regularFunction: function() {
console.log(this.name)
},
arrowFunction: () => {
console.log(this.name)
},
}
whoAmI.regularFunction() // "Leslie Knope"
whoAmI.arrowFunction() // undefined
Có thể hữu ích khi sử dụng hàm mũi tên trong trường hợp bạn thực sự muốn hàm this
tham chiếu đến ngữ cảnh bên ngoài. Ví dụ: nếu bạn có một trình nghe sự kiện bên trong một lớp, bạn có thể cần this
tham chiếu đến một số giá trị trong lớp.
Trong ví dụ này, bạn sẽ tạo và nối nút vào DOM như trước đây, nhưng lớp sẽ có trình xử lý sự kiện sẽ thay đổi giá trị văn bản của nút khi được nhấp vào.
const button = document.createElement('button')
button.textContent = 'Click me'
document.body.append(button)
class Display {
constructor() {
this.buttonText = 'New text'
button.addEventListener('click', event => {
event.target.textContent = this.buttonText
})
}
}
new Display()
Nếu bạn nhấp vào nút, nội dung văn bản sẽ thay đổi thành giá trị của buttonText
. Nếu bạn không sử dụng hàm mũi tên ở đây, hàm this
sẽ tương đương với event.currentTarget
và bạn sẽ không thể sử dụng nó để truy cập một giá trị trong lớp mà không ràng buộc nó một cách rõ ràng. Chiến thuật này thường được sử dụng trên các phương thức lớp trong các khuôn khổ như React.
Kết luận
Trong bài viết này, bạn đã tìm hiểu về this
trong JavaScript và nhiều giá trị khác nhau mà nó có thể có dựa trên ràng buộc thời gian chạy ngầm định và ràng buộc rõ ràng thông qua bind
, call
và apply
. Bạn cũng đã tìm hiểu về cách có thể sử dụng sự thiếu ràng buộc this
trong các hàm mũi tên để tham chiếu đến một ngữ cảnh khác. Với kiến thức này, bạn có thể xác định giá trị của this
trong các chương trình của bạn.
Các tin liên quan
Sử dụng phương pháp cắt chuỗi trong JavaScript2019-09-16
Những lý do tại sao bạn không bao giờ nên sử dụng eval () trong JavaScript
2019-08-26
Toàn cầu mới Thuộc tính JavaScript này
2019-08-08
Vẽ hình với API Canvas JavaScript
2019-08-05
clientWidth và clientHeight trong JavaScript
2019-07-24
Các phương pháp hay nhất để gỡ lỗi mã JavaScript trong trình duyệt
2019-07-05
Tối ưu hóa Tuyên bố chuyển đổi trong JavaScript
2019-06-18
Làm việc với Singletons trong JavaScript
2019-04-19
Cách sử dụng Axios với JavaScript
2019-04-05
Giới thiệu về Lặp lại và Trình lặp trong JavaScript
2019-03-13