de Lukas Gisder-Dubé
Conţinut
Cum să înțelegeți cuvântul cheie și contextul în JavaScript
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.
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.
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
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 #să #înțelegeți #cuvântul #cheie #acesta #și #contextul #în #JavaScript
Cum să înțelegeți cuvântul cheie acesta și contextul în JavaScript