Thứ hai, 30/09/2019 | 00:00 GMT+7

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 , callapply .

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 , callapply để 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)
Output
Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}

Bạn có thể thấy rằng thiswindow , 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()
Output
Window {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()
Output
undefined

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ý

callapply 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 callcall applycall 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"

summarybook 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 callapply để 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 booksummary 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 callapply 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)
Output
Uncaught 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 callapply đề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 , callapply . 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.


Tags:

Các tin liên quan

Sử dụng phương pháp cắt chuỗi trong JavaScript
2019-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