de Lukas Gisder-Dubé

Cum să înțelegeți cuvântul cheie și contextul în JavaScript

Cum sa intelegeti cuvantul cheie acesta si contextul in JavaScript
Fotografie de fiară. pe Unsplash

După cum sa menționat în unul dintre articolele mele anterioare, stăpânirea JavaScript completă poate fi o călătorie lungă. S-ar putea să fi dat peste this în călătoria dvs. ca dezvoltator JavaScript. Când am început, am văzut-o prima dată când foloseam eventListeners și cu jQuery. Mai târziu, a trebuit să-l folosesc des cu React și sunt sigur că și tu ai făcut-o. Asta nu înseamnă că am înțeles cu adevărat ce este și cum să-l controlez pe deplin.

Cu toate acestea, este foarte util să stăpânești conceptul din spatele său și, atunci când este abordat cu o minte limpede, nici nu este foarte dificil.

Săpând în asta

Explicând this poate duce la o mulțime de confuzie, pur și simplu prin denumirea cuvântului cheie.

this este strâns legat de contextul în care vă aflați, în programul dvs. Să începem până la capăt. În browserul nostru, dacă tastați doar this în consolă, veți obține window-obiect, cel mai exterior context pentru JavaScript. În Node.js, dacă facem:

console.log(this)

ajungem cu {}, un obiect gol. Acest lucru este puțin ciudat, dar se pare că Node.js se comportă așa. Dacă faci

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

cu toate acestea, veți primi global obiect, contextul cel mai exterior. În acest context setTimeout , setInterval , sunt stocate. Simțiți-vă liber să jucați puțin cu el pentru a vedea ce puteți face cu el. De aici, nu există aproape nicio diferență între Node.js și browser. Voi folosi window. Amintiți-vă doar că în Node.js va fi global obiect, dar nu prea face diferența.

1612122550 423 Cum sa intelegeti cuvantul cheie acesta si contextul in JavaScript
Fotografie de Chor Hung Tsang pe Unsplash

Rețineți: contextul are sens numai în interiorul funcțiilor

Imaginați-vă că scrieți un program fără să cuibăriți nimic în funcții. Ați scrie pur și simplu o linie după alta, fără a coborî structuri specifice. Asta înseamnă că nu trebuie să țineți evidența locului în care vă aflați. Ești întotdeauna la același nivel.

Când începeți să aveți funcții, este posibil să aveți diferite niveluri ale programului și this reprezintă unde ești, ce obiect numit funcție.

Urmărirea obiectului apelantului

Să aruncăm o privire la următorul exemplu și să vedem cum this modificări în funcție de context:

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

coffee.info() // The coffee is strong

Deoarece numim o funcție care este declarată în interiorul coffee obiect, contextul nostru se schimbă exact în acel obiect. Acum putem accesa toate proprietățile acelui obiect prin this . În exemplul nostru de mai sus, am putea, de asemenea, să îl referim direct făcând coffee.strong . Devine mai interesant, atunci când nu știm în ce context, în ce obiect ne aflăm sau când lucrurile devin pur și simplu un pic mai complexe. Aruncați o privire la următorul exemplu:

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()

Clase și instanțe

Clasele pot fi folosite pentru a vă abstra codul și pentru a partaja comportamentul. Repetând întotdeauna info declarația funcției din ultimul exemplu nu este bună. Deoarece clasele și instanțele lor sunt de fapt obiecte, ele se comportă în același mod. Un lucru de remarcat este însă declararea this în constructor este de fapt o predicție pentru viitor, când va exista o instanță.

Hai să aruncăm o privire:

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

Capcană: apeluri funcționale imbricate perfect

Uneori, ajungem într-un context la care nu ne așteptam cu adevărat. Acest lucru se poate întâmpla atunci când apelăm fără să știm funcția în contextul unui alt obiect. Un exemplu foarte comun este atunci când se utilizează setTimeout sau setInterval :

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

coffee.drink()

Ce crezi coffee.amount este?

..

.

Încă e 120 . În primul rând, am fost în interiorul coffee obiect, din moment ce drink metoda este declarată în interiorul acesteia. Tocmai am făcut-o setTimeout si nimic altceva. Exact asta este.

După cum am explicat mai devreme, setTimeout metoda este de fapt declarată în window obiect. Când îl apelăm, trecem de fapt la context window din nou. Asta înseamnă că instrucțiunile noastre au încercat efectiv să se schimbe window.amount, dar a ajuns să nu facă nimic din cauza if-afirmație. Pentru a avea grijă de asta, trebuie bind funcțiile noastre (vezi mai jos).

Reacţiona

Folosind React, sperăm că acest lucru va fi în curând trecut, datorită Hooks. În acest moment, trebuie încă bind totul (mai multe despre asta mai târziu) într-un fel sau altul. Când am început, nu aveam idee de ce o fac, dar în acest moment, ar trebui să știți deja de ce este necesar.

Să aruncăm o privire la două componente simple ale clasei React:

// 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,
    })
  }
}

Când facem clic pe butonul redat de Child , vom primi o eroare. De ce? Deoarece React ne-a schimbat contextul atunci când apelam _getCoffee metodă.

Presupun că React numește metoda de redare a componentelor noastre într-un alt context, prin clase de asistență sau similare (chiar dacă ar trebui să sap mai adânc pentru a afla sigur). Prin urmare, this.state este nedefinit și încercăm să accesăm this.state.coffeeCount . Ar trebui să primești ceva de genul Cannot read property coffeeCount of undefined .

Pentru a rezolva problema, trebuie bind (vom ajunge acolo) metodele din clasele noastre, de îndată ce le trecem din componenta în care sunt definite.

1612122551 563 Cum sa intelegeti cuvantul cheie acesta si contextul in JavaScript
Câte cafele ai băut până acum? / Fotografie de Ozgu Ozden pe Unsplash

Să aruncăm o privire la încă un exemplu generic:

// 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])

Trecem pe lângă increaseCount de la o clasă la alta. Când apelăm la increaseCount metoda în Viking, am schimbat deja contextul și this indică de fapt către Viking , ceea ce înseamnă că increaseCount metoda nu va funcționa conform așteptărilor.

Soluție – legare

Cea mai simplă soluție pentru noi este să bind metodele care vor fi transmise din obiectul sau clasa noastră originală. Există diferite moduri în care puteți lega funcții, dar cea mai comună (de asemenea, în React) este să o legați în constructor. Deci ar trebui să adăugăm această linie în Battle constructor înainte de linia 18:

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

Puteți lega orice funcție de orice context. Acest lucru nu înseamnă că trebuie întotdeauna să legați funcția de contextul în care este declarat (acesta este totuși cel mai frecvent caz). În schimb, l-ai putea lega de un alt context. Cu bind , tu mereu setați contextul pentru o declarație de funcție. Aceasta înseamnă că toate apelurile pentru acea funcție vor primi contextul legat ca this . Există alți doi asistenți pentru stabilirea contextului.

Funcțiile săgeată `() => {}` leagă automat funcția de contextul declarației

1612122551 212 Cum sa intelegeti cuvantul cheie acesta si contextul in JavaScript
Fotografie de Mario Klassen pe Unsplash

Aplicați și sunați

Amândoi fac practic același lucru, doar că sintaxa este diferită. Pentru ambele, treceți contextul ca prim argument. apply ia o matrice pentru celelalte argumente, cu call puteți separa alte argumente prin virgulă. Acum ce fac? Ambele metode stabilesc contextul pentru un apel funcțional specific. Când apelați funcția fără call , contextul este setat la contextul implicit (sau chiar un context legat). Iată un exemplu:

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

Poți ghici care este contextul ultimului showType() apel este?

..

.

Ai dreptate, este scopul cel mai exterior, window . Prin urmare, type este undefined, nu este window.type

Asta este, sperăm că acum aveți o înțelegere clară despre cum să utilizați this în JavaScript. Nu ezitați să lăsați sugestii pentru următorul articol în comentarii.

Despre autor: Lukas Gisder-Dubé a cofondat și a condus o startup ca CTO timp de 1 1/2 ani, construind echipa tehnologică și arhitectura. După ce a părăsit startup-ul, a predat programarea în calitate de instructor principal la Ironhack și acum construiește o agenție de startup și consultanță la Berlin. Verifică dube.io pentru a afla mai multe.

Cum sa intelegeti cuvantul cheie acesta si contextul in JavaScript