Jak funguje „toto“ v JavaScriptu

this klíčové slovo může být velmi matoucí. Tento tutoriál vám pomůže pochopit, jak to funguje. Dozvíte se o tom, jak this funguje v různých kontextech a prostředích. Tyto kontexty zahrnují globální objekt, funkce, objektové a třídní metody a události. Dozvíte se také o globalThis , nová funkce přidána do ES2020.

Úvod

this je speciální klíčové slovo, které existuje nejen v JavaScriptu, ale i v jiných programovacích jazycích. V případě JavaScriptu se liší tím, že this se v různých režimech chová odlišně. V JavaScriptu existují dva režimy:přísný a nepřísný. Nepřísný režim je normální režim. Někdy se mu také říká „nedbalý“ režim.

Co this odkazuje vždy závisí na kontextu provádění, ve kterém je definován. Kontext provádění je aktuální prostředí nebo rozsah, ve kterém je deklarován řádek kódu, který se provádí. Během běhu JavaScript udržuje zásobník všech kontextů provádění.

Kontext provádění v horní části tohoto zásobníku je ten, který se provádí. Když se kontext provádění změní, hodnota this změny také. Pojďme se podívat na to, co this odkazuje v různých kontextech.

Poznámka k přísnému režimu

Přísný režim má za cíl pomoci vám čistit váš kód JavaScriptu. Činí tak tím, že stanoví některá zvláštní pravidla. Například všechny proměnné musí být explicitně deklarovány, než jim můžete přiřadit hodnotu. Vaše funkce musí být deklarovány v globálním rozsahu. Uvnitř funkce je zakázáno používat stejný název pro proměnnou i pro parametr funkce.

Je také zakázáno mazat neměnné vlastnosti a nekvalifikované identifikátory. Všechny tyto a mnoho dalších věcí vyvolá chybu. To je další rozdíl mezi přísným a nepřísným režimem. V nepřísném režimu je mnoho chyb tichých. V přísném, nejsou. Vše, co je v rozporu s pravidly přísného režimu, vyvolá chybu.

Kromě těchto pravidel přísný režim také mění způsob this se chová. Pro lepší přehlednost probereme jak v každém konkrétním kontextu. Poslední věc o přísném režimu. Pokud chcete přepnout do přísného režimu, přidejte 'use strict'; příkaz v horní části kódu.

Poznámka:Přísný režim můžete zapnout pro celý kód nebo pouze pro určitou funkci. Rozdíl je v tom, kde použijete 'use strict'; tvrzení. Použijte jej nahoře v globálním rozsahu a bude platit pro veškerý následující kód. Použijte jej v horní části funkce a bude se vztahovat pouze na kód, který následuje uvnitř této funkce.

„toto“ v globálním kontextu

V JavaScriptu, když this je definován v globálním kontextu this ve výchozím nastavení odkazuje na globální objekt. V případě prohlížeče je tento globální objekt window objekt. Tento globální objekt je obor nejvyšší úrovně. Přísný režim pro this nehraje žádnou roli v případě globálního kontextu. Ať už jste v přísném nebo nepřísném režimu, this se bude chovat stejně.

// global context and this in non-strict mode
console.log(this === window)
// true


// global context and this in strict mode
'use strict'

console.log(this === window)
// true

„toto“, globální kontext a Node.js

V případě Node.js neexistuje window objekt. V Node.js je globální objekt speciální objekt nazývaný globální. To znamená, že v globálním rozsahu this bude odkazovat na toto global . No, skoro. To platí pouze uvnitř samotného Node.js. Chcete-li to vyzkoušet, nejprve spusťte svou oblíbenou konzoli a zadejte node .

Tento příkaz zapne prostředí Node.js, takže s ním můžete přímo pracovat. Poté můžete otestovat co this odkazuje v globálním kontextu. Pokud na svém počítači nemáte Node.js, můžete jej získat z webu Node.js.

// In node environment
> console.log(this === global)
// true

> console.log(this)
// Object [global] {
//   global: [Circular],
//   clearInterval: [Function: clearInterval],
//   clearTimeout: [Function: clearTimeout],
//   setInterval: [Function: setInterval],
//   setTimeout: [Function: setTimeout] { [Symbol(util.promisify.custom)]: [Function] },
//   queueMicrotask: [Function: queueMicrotask],
//   clearImmediate: [Function: clearImmediate],
//   setImmediate: [Function: setImmediate] {
//     [Symbol(util.promisify.custom)]: [Function]
//   }
// }

Pokud svůj kód spustíte ze souboru JavaScript, výsledek bude jiný. Při práci se soubory JavaScriptu v Node.js je místní kód omezen na tento soubor. Všechno tam není globální, ale lokální. Výsledkem je this neodkazuje na global , ale na module.exports .

// In node environment, in JavaScript file
console.log(this === global)
// false

console.log(this === module.exports)
// true

Funkce a „toto“

V případě funkcí nejvyšší úrovně JavaScriptu záleží na režimu. Nejvyšší úrovní mám na mysli funkce deklarované v globálním rozsahu, nikoli uvnitř objektů nebo tříd. Pokud pracujete v nepřísném režimu, this bude odkazovat na globální objekt, window v případě prohlížeče.

// Function in a non-strict mode
function testThis() {
  console.log(this === window)
}

testThis()
// true

Přidejme use strict příkaz v horní části funkce pro zapnutí přísného režimu. Nyní bude výsledek jiný. this již nebude odkazovat na globální objekt, jako je window . Když se pokusíte získat hodnotu this JavaScript vrátí undefined . Důvodem je hodnota this není nyní nastaveno.

// Function in a non-strict mode
function testThis() {
  'use strict' // switch on strict mode for this function
  console.log(this === window)

  console.log(this)
}

testThis()
// false
// undefined

Funkce, this a call() a apply()

Existuje způsob, jak nastavit hodnotu this když vyvoláte funkci, tak to není undefined . K tomu můžete použít metody call(), apply() nebo bind(). Toto se nazývá „explicitní vazba funkce“. Když použijete jednu z těchto metod, předáte hodnotu this jako argument. První dva, call() a apply() jsou téměř stejné.

Rozdíl je v tom, že apply() přijímá seznam argumentů pomocí call() přijímá pole argumentů. apply() také umožňuje použít literál pole.

// Set value of this with apply()
function testThisWithApply() {
  'use strict'

  console.log('Value of this: ', this)
}

// set value of "this" to one
testThis.apply('one')
// 'Value of this: one'


// Set value of this with call()
function testThisWithCall() {
  'use strict'

  console.log('Value of this: ', this)
}

// set value of "this" to one
testThis.call('one')
// 'Value of this: one'

Funkce, this a bind()

bind() metoda je jiná. Tuto metodu nepoužíváte, když chcete vyvolat nebo zavolat funkci. Místo toho použijete bind() metoda k vytvoření nové „vázané“ funkce. Poté vyvoláte novou „svázanou“ funkci, nikoli původní. Nyní hodnota this bude takový, jaký jste ho chtěli mít.

// Set value of this with bind()
function testThisWithBind() {
  'use strict'

  console.log('Value of this: ', this)
}

// Create bound function and set value of "this" to "one"
const newTestThisWithBind = testThisWithBind.bind('one')

// Invoke new "bound" function "newTestThisWithBind"
newTestThisWithBind()
// 'Value of this:  one'


// Or, with reassigning the original function
function testThisWithBind() {
  'use strict'

  console.log('Value of this: ', this)
}

// Create bound function and set value of "this" to "reassigned!"
testThisWithBind = testThisWithBind.bind('reassigned!')

// Test: Invoke now "bound" function "testThisWithBind"
testThisWithBind()
// 'Value of this:  reassigned!'

Na bind() je jedna důležitá věc metoda k zapamatování. Funguje pouze jednou. Nemůžete použít bind() několikrát změnit hodnotu this „vázané“ funkce. Můžete jej však použít vícekrát s původní funkcí k vytvoření nových „svázaných“ funkcí.

// Doesn't work: Try to re-set this of bound function
function testThisWithBind() {
  'use strict'

  console.log('Value of this: ', this)
}

// Create bound function
// and set value of "this" to "one"
const newTestThisWithBind = testThisWithBind.bind('one')


// Test: Invoke new "bound" function "newTestThisWithBind"
newTestThisWithBind()
// The value of "this" is not correct
// 'Value of this:  one'


// Create another bound function
// using the bound function
// and try to change value of "this"
const newerTestThisWithBind = newTestThisWithBind.bind('two')

// Test: Invoke newer "bound" function "newerTestThisWithBind"
newerTestThisWithBind()
// The value of "this" is correct
// 'Value of this: one'


// Works: Create another bound function from the original
const brandNewThisWithBind = testThisWithBind.bind('two')

// Test: Invoke newer "bound" function "brandNewThisWithBind"
brandNewThisWithBind()
// The value of "this" is correct
// 'Value of this: two'



// Test: Invoke newer "bound" function "newerTestThisWithBind"
// The value of "this" is the same
newerTestThisWithBind()
// 'Value of this: one'

Poznámka:Toto je pro ty z vás, kteří znají komponenty React a třídy. Pravděpodobně poznáte něco jako this.myFunc = this.myFunc.bind(this) v constructor . To znamená, že vezme funkci a vytvoří vázanou funkci a vrátí ji a v podstatě přepíše originál.

V tomto případě hodnota this zde je this , to je samotná složka třídy. Další možnost změny vazby this v tomto případě by bylo použití funkce šipky.

Funkce šipek a „toto“

Funkce šipek zavedené v ES6 fungují jinak než normální funkce. Funkce šipek nemají vlastní this . Vždy používají stejnou hodnotu pro this jako jejich rodič, kontext provádění, ve kterém jsou deklarovány. Další důležitá věc u funkcí šipek je, že nemůžete nastavit jejich hodnoty this explicitně.

Když se pokusíte použít call() , apply() nebo bind() s funkcemi šipek se nic nestane. Funkce šipek budou tyto metody ignorovat.

// Arrow function inside an object
const user = {
  username: 'franky',
  email: '[email protected]',
  // Get data with arrow function
  getUserWithArrowFunction: () => {
    // This refers to global object, window
    // So, this.username is like window.username
    return `${this.username}, ${this.email}.`
  },
  // Get data with normal function
  getUserWithNormalFunction: function() {
    // This refers to myObj
    // So, this.username is like myObj.username
    return `${this.username}, ${this.email}.`
  }
}

// Test the arrow function
user.getUserWithArrowFunction()
// TypeError: Cannot read property 'title' of undefined

// Test the normal function
user.getUserWithNormalFunction()
// 'franky, [email protected].'


///
// Arrow functions and binding
let arrowFunctionWithBind = () => {
  'use strict'

  console.log('Value of this: ', this)
}

// Try to create bound function
// and set value of "this" to "arrow!"
arrowFunctionWithBind = arrowFunctionWithBind.bind('arrow!')


// Test: Invoke new "bound" function "arrowFunctionWithBind"
arrowFunctionWithBind()
// 'Value of this: undefined

Protože this funguje ve funkcích šipek, funkce šipek jsou dobrou volbou pro zpětná volání. Pamatujte, že funkce šipek vždy dědí this z jejich přiloženého kontextu provádění. Pomocí funkcí šipek můžete získat přístup k this v rámci zpětného volání, aniž byste se museli starat o to, co this je.

// Functions as callbacks
// Using normal function as a callback
const counter = {
  count: 0,
  addCount() {
    // Use normal function as a callback in setInterval
    setInterval(function() {
      // 'this' here is Global object
      // So, ++this.count is like ++window.count
      console.log(++this.count)
    }, 1000)
  }
}

// Invoke addCount() method
counter.addCount()
// NaN
// NaN
// NaN
// NaN
// NaN
// ...


// Using arrow function as a callback
const counter = {
  count: 0,
  addCount() {
    // Use arrow function as a callback in setInterval
    setInterval(() => {
      // 'this' here is the "counter" object
      // So, ++this.count is like ++counter.count
      console.log(++this.count)
    }, 1000)
  }
}

// Invoke addCount() method
counter.addCount()
// 1
// 2
// 3
// 4
// 5
// ...


///
// What "this" is
// Using normal function as a callback
const counter = {
  logThis() {
    // Use normal function as a callback in setInterval
    setInterval(function() {
      console.log(this)
    }, 1000)
  }
}

// Invoke logThis() method
counter.logThis()
// Window
// Window
// Window
// ...


// What "this" is
// Using arrow function as a callback
const counter = {
  logThis() {
    // Use normal function as a callback in setInterval
    setInterval(() => {
      console.log(this)
    }, 1000)
  }
}

// Invoke logThis() method
counter.logThis()
// { logThis: [Function: logThis] }
// { logThis: [Function: logThis] }
// { logThis: [Function: logThis] }
// ...

Metody objektů a „toto“

Řekněme, že používáte this uvnitř funkce, která je uvnitř objektu. V tomto případě hodnota this bude objekt, ve kterém je metoda deklarována. Toto je nezávislé na režimu JavaScript.

// Create object
const animal = {
  name: 'Cat',
  class: 'Mammalia',
  order: 'Carnivora',
  genus: 'Felis',
  logAnimal: function() {
    return this;
  }
}

// Call logAnimal() method
animal.logAnimal()
// {
//   name: 'Cat',
//   class: 'Mammalia',
//   order: 'Carnivora',
//   genus: 'Felis',
//   logAnimal: [Function: logAnimal]
// }

Nezáleží na tom, zda funkci deklarujete uvnitř objektu nebo mimo něj a připojíte ji.

// Create empty object
const thing = {}

// Add property to "thing" object
thing.itemName = 'Box'

// Add method to "thing" object
thing.getItemName = function() {
  return this.itemName
}

thing.returnThis = function() {
  return this
}

// Invoke getItemName() method
thing.getItemName()
// 'Box'

thing.returnThis()
// {
//   itemName: 'Box',
//   getItemName: [Function],
//   returnThis: [Function]
// }

Konstruktory funkcí a „toto“

Když použijete this v konstruktorech funkcí bude jeho hodnota vždy odkazovat na nový objekt vytvořený tímto konstruktorem.

// Create function constructor
function Phone(model, brand) {
  this.model = model
  this.brand = brand
  this.getModelAndBrand = function() {
    // "this" refers to new Phone object
    // created using "new" keyword
    return `Model: ${this.model}, brand: ${this.brand}`
  }
  this.returnThis = function() {
    return this
  }
}

// Create new Phone object using "new" keyword
const iPhoneX = new Phone('iPhone X', 'Apple')
// Here, "this" refers to "iPhoneX"

iPhoneX.getModelAndBrand()
// 'Model: iPhone X, brand: Apple'

iPhoneX.returnThis()
// Phone {
//   model: 'iPhone X',
//   brand: 'Apple',
//   getModelAndBrand: [Function],
//   returnThis: [Function]
// }

Metody tříd a „toto“

Když použijete this v metodách třídy bude odkazovat na instanci vytvořenou pomocí této třídy.

// Create new class with two properties
// add two methods
class Brain {
  constructor(numOfHemispheres, iq) {
    this.numOfHemispheres = numOfHemispheres
    this.iq = iq
  }

  getIQ() {
    // This refers to instance of Brain class
    return this.iq
  }

  learn() {
    // This refers to instance of Brain class
    this.iq += 1
  }

  watchTv() {
    // This refers to instance of Brain class
    this.iq -= 1
  }

  returnThis() {
    return this
  }
}

// Create instance of Brain class
// with 2 hemispheres and IQ of 180
const smartBrain = new Brain(2, 180)

// Log the IQ of smartBrain
smartBrain.getIQ()
// 180

// Learn something
smartBrain.learn()

// Log the IQ of smartBrain again
smartBrain.getIQ()
// 181

smartBrain.watchTv()

// Log the IQ of smartBrain again
smartBrain.getIQ()
// 180

smartBrain.returnThis()
// Brain { numOfHemispheres: 2, iq: 180 }

Události a „toto“

Když použijete this uvnitř obslužných rutin událostí bude odkazovat na prvek, ke kterému jste připojili posluchač události.

Vytvořte jednoduchý button prvek.

<!-- Create button -->
<button class="btn">Click</button>

Připojte eventListener na button prvek.

// Create event handler function
handleButtonClick function() {
  console.log(this)
}

// Find the button in the DOM,
// attach event listener to it
// and pass the handler function as an argument
document.querySelector('.btn').addEventListener('click', handleButtonClick)

Když nyní kliknete na tlačítko, uvidíte [object HTMLButtonElement] a spoustu dat. Toto je prvek tlačítka spolu se všemi jeho vlastnostmi a metodami.

Funkce událostí, „toto“ a šipky

Pokud použijete funkci šipky jako zpětné volání pro obsluhu události, získáte jiný výsledek. Tentokrát nedostanete [object HTMLButtonElement] a jeho vlastnosti a metody. Místo toho dostanete [object Window] , globální window objekt. Pokud tedy chcete použít this, použijte normální funkci pro přístup k prvku, na kterém byla událost spuštěna.

Pokud z jakéhokoli důvodu stále chcete používat funkci šipky, existuje způsob. Přidejte event jako parametr vaší funkce šipky. Pak v této funkci šipky použijte event.target , event.currentTarget , pro přístup k prvku. V případě tlačítka získáte [object HTMLButtonElement] .

// Create handler function, now arrow function
// and specify parameter for event
const handleButtonClick = (event) => {
  // Access the value passed as event, not "this"
  console.log(event)
}


// Find the button in the DOM,
// attach event listener to it
// and pass the handler function as an argument
document.querySelector('.btn').addEventListener('click', handleButtonClick)

globálněToto

globalThis je jednou z funkcí přidaných v ES2020. Tato funkce má za cíl usnadnit práci s globálním this . Tedy s window , self , this nebo frame objektů v prohlížeči a global nebo this v Node.js. Pokud pracujete s multiplatformním JavaScriptem, už se nebudete muset starat o použití správného objektu.

Místo toho můžete použít nově přidaný globalThis . S globalThis , vždy automaticky vyberete správný globální objekt bez ohledu na platformu. To znamená globalThis by se nemělo zneužívat. Stále byste měli co nejvíce svého kódu uchovávat mimo globální rozsah, uvnitř funkcí a bloků kódu.

globalThis by měl být používán hlavně pro věci, jako jsou polyfilly a podložky. globalThis může být také použita detekce funkcí pro zjištění, které funkce JavaScriptu jsou podporovány v konkrétním prohlížeči nebo prostředí.

// In the browser
globalThis === window
// true

const obj = {
  name: 'foo',
  getThis: function() {
    return this
  },
  getGlobalThis = function() {
    return globalThis
  }
}

obj.getThis()
// {name: "foo", getThis: ƒ}

obj.getGlobalThis()
// Window { ... }


// In Node.js
globalThis === global
// true

Závěr:Jak „toto“ v JavaScriptu funguje

Právě jste se dostali na konec tohoto tutoriálu. Doufám, že sis to užil. Také doufám, že vám to pomohlo pochopit, jak this Klíčová slova fungují a jak je používat. this klíčové slovo může být velmi matoucí a jeho pochopení může chvíli trvat. Za ten čas to však stojí. Když to pochopíte, budete také lépe rozumět samotnému JavaScriptu.


No