HomeLập trìnhJavaScriptCách hiểu từ...

Cách hiểu từ khóa này và ngữ cảnh trong JavaScript


bởi Lukas Gisder-Dubé

1*4Ufc1CWbaLhjEMhPgVV3Qw
Ảnh của Beasty. trên Bapt

Như đã đề cập trong một trong những bài viết trước đây của tôi, việc thành thạo JavaScript hoàn toàn có thể là một hành trình dài. bạn có thể đã đi qua this trên hành trình của bạn với tư cách là Nhà phát triển JavaScript. Khi tôi bắt đầu, lần đầu tiên tôi nhìn thấy nó khi sử dụng eventListeners và với jQuery. Sau này, tôi phải sử dụng nó thường xuyên với React và tôi chắc rằng bạn cũng vậy. Điều đó không có nghĩa là tôi thực sự hiểu nó là gì và làm thế nào để hoàn toàn kiểm soát nó.

Tuy nhiên, sẽ rất hữu ích nếu nắm vững khái niệm đằng sau nó và khi tiếp cận với một tâm trí trong sáng, điều đó cũng không khó lắm.

Đi sâu vào điều này

giải thích this có thể dẫn đến rất nhiều nhầm lẫn, chỉ đơn giản bằng cách đặt tên cho từ khóa.

this được kết hợp chặt chẽ với bối cảnh bạn đang ở, trong chương trình của bạn. Hãy bắt đầu từ đầu. Trong trình duyệt của chúng tôi, nếu bạn chỉ gõ this trong bảng điều khiển, bạn sẽ nhận được window-object, bối cảnh ngoài cùng cho JavaScript của bạn. Trong Node.js, nếu chúng ta làm:

console.log(this)

chúng tôi kết thúc với {}, một đối tượng rỗng. Điều này hơi kỳ lạ, nhưng có vẻ như Node.js hoạt động theo cách đó. Nếu bạn làm

(function() {
  console.log(this);
})();

tuy nhiên, bạn sẽ nhận được global đối tượng, bối cảnh bên ngoài. Trong bối cảnh đó setTimeout , setInterval , được cất giữ. Vui lòng chơi xung quanh một chút với nó để xem bạn có thể làm gì với nó. Kể từ đây, hầu như không có sự khác biệt giữa Node.js và trình duyệt. tôi sẽ sử dụng window. Chỉ cần nhớ rằng trong Node.js, nó sẽ là global đối tượng, nhưng nó không thực sự tạo ra sự khác biệt.

1*ucforPRGm6kR3bdZMHzHKQ
Ảnh của Chor Hung Tsang trên Bapt

Hãy nhớ rằng: Ngữ cảnh chỉ có ý nghĩa bên trong các chức năng

Hãy tưởng tượng bạn viết một chương trình mà không lồng bất cứ thứ gì vào hàm. Bạn chỉ cần viết hết dòng này đến dòng khác mà không cần đi sâu vào cấu trúc cụ thể. Điều đó có nghĩa là bạn không cần phải theo dõi xem bạn đang ở đâu. Bạn luôn ở cùng cấp độ.

Khi bạn bắt đầu có các chức năng, bạn có thể có các cấp độ khác nhau của chương trình và this đại diện cho bạn đang ở đâu, đối tượng nào được gọi là chức năng.

Đọc thêm  Hướng dẫn biểu đồ đường – Cách tạo biểu đồ đường trong JavaScript

Theo dõi đối tượng người gọi

Hãy xem ví dụ sau và xem cách this thay đổi tùy theo ngữ cảnh:

const coffee = {
  strong: true,
  info: function() {
    console.log(`The coffee is ${this.strong ? '' : 'not '}strong`)
  },
}

coffee.info() // The coffee is strong

Vì chúng ta gọi một hàm được khai báo bên trong coffee đối tượng, bối cảnh của chúng tôi thay đổi chính xác đối tượng đó. Bây giờ chúng ta có thể truy cập tất cả các thuộc tính của đối tượng đó thông qua this . Trong ví dụ của chúng tôi ở trên, chúng tôi cũng có thể chỉ tham chiếu trực tiếp bằng cách thực hiện coffee.strong . Nó trở nên thú vị hơn, khi chúng ta không biết mình đang ở trong bối cảnh nào, đối tượng nào, hoặc khi mọi thứ trở nên phức tạp hơn một chút. Hãy xem ví dụ sau:

const drinks = [
  {
    name: 'Coffee',
    addictive: true,
    info: function() {
      console.log(`${this.name} is ${this.addictive ? '' : 'not '} addictive.`)
    },
  },
  {
    name: 'Celery Juice',
    addictive: false,
    info: function() {
      console.log(`${this.name} is ${this.addictive ? '' : 'not '} addictive.`)
    },
  },
]

function pickRandom(arr) {
  return arr[Math.floor(Math.random() * arr.length)]
}

pickRandom(drinks).info()

Các lớp và trường hợp

Các lớp có thể được sử dụng để tóm tắt mã của bạn và chia sẻ hành vi. Luôn luôn lặp đi lặp lại info khai báo hàm trong ví dụ trước là không tốt. Vì các lớp và các thể hiện của chúng thực tế là các đối tượng, nên chúng hoạt động theo cùng một cách. Tuy nhiên, một điều cần lưu ý là việc khai báo this trong hàm tạo thực sự là một dự đoán cho tương lai, khi sẽ có một thể hiện.

Hãy cùng xem:

class Coffee {
  constructor(strong) {
    this.strong = !!strong
  }
  info() {
    console.log(`This coffee is ${this.strong ? '' : 'not '}strong`)
  }
}

const strongCoffee = new Coffee(true)
const normalCoffee = new Coffee(false)

strongCoffee.info() // This coffee is strong
normalCoffee.info() // This coffee is not strong

Cạm bẫy: các lệnh gọi hàm lồng nhau liền mạch

Đôi khi, chúng tôi kết thúc trong một bối cảnh mà chúng tôi không thực sự mong đợi. Điều này có thể xảy ra khi chúng ta vô tình gọi hàm bên trong ngữ cảnh đối tượng khác. Một ví dụ rất phổ biến là khi sử dụng setTimeout hoặc setInterval :

// BAD EXAMPLE
const coffee = {
  strong: true,
  amount: 120,
  drink: function() {
    setTimeout(function() {
      if (this.amount) this.amount -= 10
    }, 10)
  },
}

coffee.drink()

Bạn nghĩ sao coffee.amount Là?

..

.

nó vẫn còn 120 . Đầu tiên, chúng tôi đã ở trong coffee đối tượng, kể từ khi drink phương thức được khai báo bên trong nó. chúng tôi vừa mới làm setTimeout và không có gì khác. Chính xác là vậy.

Như tôi đã giải thích trước đó, các setTimeout phương pháp thực sự được khai báo trong window vật. Khi gọi nó, chúng tôi thực sự chuyển ngữ cảnh sang window lần nữa. Điều đó có nghĩa là hướng dẫn của chúng tôi thực sự đã cố gắng thay đổi window.amountnhưng nó đã kết thúc không làm gì vì if-bản tường trình. Để giải quyết vấn đề đó, chúng ta phải bind chức năng của chúng tôi (xem bên dưới).

Đọc thêm  Hoisting trong JavaScript là gì?

Phản ứng

Sử dụng React, hy vọng điều này sẽ sớm trở thành quá khứ, nhờ Hooks. Hiện tại, chúng tôi vẫn phải bind mọi thứ (sẽ nói thêm về điều đó sau) bằng cách này hay cách khác. Khi mới bắt đầu, tôi không biết tại sao mình lại làm việc đó, nhưng đến thời điểm này, bạn hẳn đã biết tại sao nó lại cần thiết.

Chúng ta hãy xem hai Thành phần lớp React đơn giản:

// BAD EXAMPLE
import React from 'react'

class Child extends React.Component {
  render() {
    return <button onClick = {
      this.props.getCoffee
    } > Get some Coffee! < /button>
  }
}

class Parent extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      coffeeCount: 0,
    }
    // change to turn into good example – normally we would do:
    // this._getCoffee = this._getCoffee.bind(this)
  }
  render() {
    return ( <
      React.Fragment >
      <
      Child getCoffee = {
        this._getCoffee
      }
      /> < /
      React.Fragment >
    )
  }

  _getCoffee() {
    this.setState({
      coffeeCount: this.state.coffeeCount + 1,
    })
  }
}

Bây giờ khi chúng ta nhấp vào nút được hiển thị bởi Child , chúng tôi sẽ nhận được lỗi. Tại sao? Bởi vì React đã thay đổi ngữ cảnh của chúng ta khi gọi phương thức _getCoffee phương pháp.

Tôi cho rằng React gọi phương thức kết xuất của các Thành phần của chúng ta trong ngữ cảnh khác, thông qua các lớp trợ giúp hoặc tương tự (mặc dù tôi sẽ phải tìm hiểu sâu hơn để tìm hiểu chắc chắn). Vì vậy, this.state không được xác định và chúng tôi đang cố gắng truy cập this.state.coffeeCount . Bạn sẽ nhận được một cái gì đó như Cannot read property coffeeCount of undefined .

Để giải quyết vấn đề, bạn phải bind (chúng ta sẽ đến đó) các phương thức trong các lớp của chúng ta, ngay sau khi chúng ta chuyển chúng ra khỏi thành phần nơi chúng được định nghĩa.

1*nkgR5atcQtIeHQLeY8XMRQ
Bạn đã uống bao nhiêu cà phê cho đến nay? / Ảnh của Ozgu Ozden trên Bapt

Chúng ta hãy xem một ví dụ chung hơn:

// BAD EXAMPLE
class Viking {
  constructor(name) {
    this.name = name
  }

  prepareForBattle(increaseCount) {
    console.log(`I am ${this.name}! Let's go fighting!`)
    increaseCount()
  }
}

class Battle {
  constructor(vikings) {
    this.vikings = vikings
    this.preparedVikingsCount = 0

    this.vikings.forEach(viking => {
      viking.prepareForBattle(this.increaseCount)
    })
  }

  increaseCount() {
    this.preparedVikingsCount++
    console.log(`${this.preparedVikingsCount} vikings are now ready to fight!`)
  }
}

const vikingOne = new Viking('Olaf')
const vikingTwo = new Viking('Odin')

new Battle([vikingOne, vikingTwo])

Chúng tôi đang vượt qua increaseCount từ lớp này sang lớp khác. Khi chúng tôi gọi increaseCount phương pháp trong Vikingchúng tôi đã thay đổi ngữ cảnh và this thực sự chỉ ra Viking nghĩa là chúng ta increaseCount phương pháp sẽ không hoạt động như mong đợi.

Giải pháp – ràng buộc

Giải pháp đơn giản nhất cho chúng tôi là bind các phương thức sẽ được chuyển ra khỏi đối tượng hoặc lớp ban đầu của chúng ta. Có nhiều cách khác nhau để bạn có thể liên kết các hàm, nhưng cách phổ biến nhất (cũng trong React) là liên kết nó trong hàm tạo. Vì vậy, chúng tôi sẽ phải thêm dòng này vào Battle hàm tạo trước dòng 18:

this.increaseCount = this.increaseCount.bind(this)

Bạn có thể liên kết bất kỳ chức năng nào với bất kỳ ngữ cảnh nào. Điều này không có nghĩa là bạn luôn phải liên kết hàm với ngữ cảnh mà nó được khai báo (tuy nhiên đây là trường hợp phổ biến nhất). Thay vào đó, bạn có thể liên kết nó với một ngữ cảnh khác. Với bind bạn luôn luôn đặt ngữ cảnh cho khai báo hàm. Điều này có nghĩa là tất cả các cuộc gọi cho chức năng đó sẽ nhận được ngữ cảnh ràng buộc như this . Có hai người trợ giúp khác để thiết lập bối cảnh.

Hàm mũi tên `() => {}` tự động liên kết hàm với ngữ cảnh khai báo

1*acg_8b0T63Aiv8TAbtn5_w
Ảnh của Mario Klassen trên Bapt

Áp dụng và gọi

Cả hai đều làm về cơ bản giống nhau, chỉ là cú pháp là khác nhau. Đối với cả hai, bạn chuyển ngữ cảnh làm đối số đầu tiên. apply lấy một mảng cho các đối số khác, với call bạn chỉ có thể tách các đối số khác bằng dấu phẩy. Bây giờ họ làm gì? Cả hai phương pháp này đều thiết lập bối cảnh cho một cuộc gọi chức năng cụ thể. Khi gọi hàm mà không có call , bối cảnh được đặt thành bối cảnh mặc định (hoặc thậm chí là bối cảnh bị ràng buộc). Đây là một ví dụ:

class Salad {
  constructor(type) {
    this.type = type
  }
}

function showType() {
  console.log(`The context's type is ${this.type}`)
}

const fruitSalad = new Salad('fruit')
const greekSalad = new Salad('greek')

showType.call(fruitSalad) // The context's type is fruit
showType.call(greekSalad) // The context's type is greek

showType() // The context's type is undefined

Bạn có thể đoán bối cảnh cuối cùng là gì không showType() gọi là?

Đọc thêm  Async Await JavaScript Tutorial – Cách đợi một chức năng kết thúc trong JS

..

.

Bạn nói đúng, đó là phạm vi ngoài cùng, window . Vì vậy, typeundefinedkhông có window.type

Đây là nó, hy vọng bây giờ bạn đã hiểu rõ về cách sử dụng this trong JavaScript. Vui lòng để lại đề xuất cho bài viết tiếp theo trong phần bình luận.

Giới thiệu về tác giả: Lukas Gisder-Dubé đồng sáng lập và lãnh đạo một công ty khởi nghiệp với tư cách là CTO trong 1 năm rưỡi, xây dựng đội ngũ công nghệ và kiến ​​trúc. Sau khi rời công ty khởi nghiệp, anh ấy đã dạy mã hóa với tư cách là Người hướng dẫn chính tại Ironhack và hiện đang xây dựng Cơ quan tư vấn & Đại lý khởi nghiệp ở Berlin. Kiểm tra dube.io để tìm hiểu thêm.

1*p-l0Cee1IHvX0RQkVTOceQ



Zik.vn – Biên dịch & Biên soạn Lại

spot_img

Create a website from scratch

Just drag and drop elements in a page to get started with Newspaper Theme.

Buy Now ⟶

Bài viết liên quang

DMCA.com Protection Status