Zoeken…
Opmerkingen
Scope is de context waarin variabelen leven en toegankelijk zijn voor andere code in dezelfde scope. Omdat JavaScript grotendeels kan worden gebruikt als een functionele programmeertaal, is het belangrijk om de reikwijdte van variabelen en functies te kennen, omdat het helpt bij het voorkomen van bugs en onverwacht gedrag tijdens runtime.
Verschil tussen var en let
(Opmerking: alle voorbeelden met let
zijn ook geldig voor const
)
var
is beschikbaar in alle versies van JavaScript, terwijl let
en const
deel uitmaken van ECMAScript 6 en alleen beschikbaar in sommige nieuwere browsers .
var
valt onder de bevattende functie of de globale ruimte, afhankelijk van wanneer het wordt aangegeven:
var x = 4; // global scope
function DoThings() {
var x = 7; // function scope
console.log(x);
}
console.log(x); // >> 4
DoThings(); // >> 7
console.log(x); // >> 4
Dat betekent dat het "ontsnapt" if
statements en alle vergelijkbare blokconstructies:
var x = 4;
if (true) {
var x = 7;
}
console.log(x); // >> 7
for (var i = 0; i < 4; i++) {
var j = 10;
}
console.log(i); // >> 4
console.log(j); // >> 10
Ter vergelijking: let
we blokkeren:
let x = 4;
if (true) {
let x = 7;
console.log(x); // >> 7
}
console.log(x); // >> 4
for (let i = 0; i < 4; i++) {
let j = 10;
}
console.log(i); // >> "ReferenceError: i is not defined"
console.log(j); // >> "ReferenceError: j is not defined"
Merk op dat i
en j
alleen in de for
lus worden aangegeven en daarom daarbuiten niet worden aangegeven.
Er zijn verschillende andere cruciale verschillen:
Globale variabele verklaring
In het bovenste bereik (buiten alle functies en blokken) plaatsen var
declaraties een element in het globale object. let
niet:
var x = 4;
let y = 7;
console.log(this.x); // >> 4
console.log(this.y); // >> undefined
Re-verklaring
Twee keer een variabele declareren met var
levert geen fout op (hoewel het equivalent is van één keer declareren):
var x = 4;
var x = 7;
Met let
veroorzaakt dit een fout:
let x = 4;
let x = 7;
TypeError: Identifier
x
is al gedeclareerd
Hetzelfde geldt wanneer y
wordt verklaard met var
:
var y = 4;
let y = 7;
TypeError: Identifier
y
is al gedeclareerd
Variabelen die met let zijn gedeclareerd, kunnen echter opnieuw worden gebruikt (niet opnieuw) in een genest blok
let i = 5;
{
let i = 6;
console.log(i); // >> 6
}
console.log(i); // >> 5
Binnen het blok is de buitenste i
toegankelijk, maar als het binnenste blok een let
verklaring voor i
, is de buitenste i
niet toegankelijk en zal deze een ReferenceError
gooien indien gebruikt voordat de tweede is gedeclareerd.
let i = 5;
{
i = 6; // outer i is unavailable within the Temporal Dead Zone
let i;
}
ReferenceError: i is niet gedefinieerd
Hijsen
Variabelen die zijn opgegeven met var
en let
worden gehesen . Het verschil is dat naar een variabele die met var
is gedeclareerd, naar zijn eigen toewijzing kan worden verwezen, omdat deze automatisch wordt toegewezen (met undefined
als zijn waarde), maar let
niet - het vereist specifiek dat de variabele wordt gedeclareerd voordat deze wordt aangeroepen:
console.log(x); // >> undefined
console.log(y); // >> "ReferenceError: `y` is not defined"
//OR >> "ReferenceError: can't access lexical declaration `y` before initialization"
var x = 4;
let y = 7;
Het gebied tussen het begin van een blok en een let
of const
declaratie staat bekend als de Temporal Dead Zone en verwijzingen naar de variabele in dit gebied veroorzaken een ReferenceError
. Dit gebeurt zelfs als de variabele wordt toegewezen voordat deze wordt gedeclareerd :
y=7; // >> "ReferenceError: `y` is not defined"
let y;
Als u in een niet-strikte modus een waarde toewijst aan een variabele zonder enige declaratie, wordt de variabele automatisch gedeclareerd in het globale bereik . In dit geval, in plaats van y
automatisch in het globale bereik wordt verklaard, let
reserves naam van de variabele ( y
) en geen enkele toegang of opdracht tot het voor de lijn waar het wordt gedeclareerd / geïnitialiseerd mogelijk te maken.
sluitingen
Wanneer een functie wordt gedeclareerd, worden variabelen in de context van de declaratie in het bereik vastgelegd. In de onderstaande code is de variabele x
bijvoorbeeld gebonden aan een waarde in het buitenbereik en wordt de verwijzing naar x
vastgelegd in de context van bar
:
var x = 4; // declaration in outer scope
function bar() {
console.log(x); // outer scope is captured on declaration
}
bar(); // prints 4 to console
Voorbeelduitvoer:
4
Dit concept van het "vastleggen" bereik is interessant omdat we variabelen van een buitenbereik kunnen gebruiken en wijzigen, zelfs nadat het buitenbereik is verlaten. Overweeg bijvoorbeeld het volgende:
function foo() {
var x = 4; // declaration in outer scope
function bar() {
console.log(x); // outer scope is captured on declaration
}
return bar;
// x goes out of scope after foo returns
}
var barWithX = foo();
barWithX(); // we can still access x
Voorbeelduitvoer:
4
In het bovenstaande voorbeeld, wanneer foo
heet, de context wordt meegenomen in de functie bar
. Dus zelfs nadat het is teruggekeerd, kan de bar
de variabele x
nog steeds openen en wijzigen. De functie foo
, waarvan de context is vastgelegd in een andere functie, zou een sluiting zijn .
Privé gegevens
Dit laat ons enkele interessante dingen doen, zoals het definiëren van "private" variabelen die alleen zichtbaar zijn voor een specifieke functie of set functies. Een gekunsteld (maar populair) voorbeeld:
function makeCounter() {
var counter = 0;
return {
value: function () {
return counter;
},
increment: function () {
counter++;
}
};
}
var a = makeCounter();
var b = makeCounter();
a.increment();
console.log(a.value());
console.log(b.value());
Voorbeeld output:
1 0
Wanneer makeCounter()
wordt aangeroepen, wordt een momentopname van de context van die functie opgeslagen. Alle code in makeCounter()
zal die momentopname gebruiken in hun uitvoering. Twee aanroepen van makeCounter()
zullen dus twee verschillende snapshots maken, met hun eigen exemplaar van counter
.
Onmiddellijk opgeroepen functie-expressies (IIFE)
Sluitingen worden ook gebruikt om mondiale vervuiling van de naamruimte te voorkomen, vaak door het gebruik van onmiddellijk opgeroepen functie-uitdrukkingen.
Direct opgeroepen functie-uitdrukkingen (of, misschien meer intuïtief, zelfuitvoerende anonieme functies ) zijn in wezen sluitingen die direct na verklaring worden genoemd. Het algemene idee met IIFE's is om het neveneffect van het creëren van een afzonderlijke context die alleen toegankelijk is voor de code binnen de IIFE te gebruiken.
Stel dat we naar jQuery
willen verwijzen met $
. Overweeg de naïeve methode, zonder een IIFE te gebruiken:
var $ = jQuery;
// we've just polluted the global namespace by assigning window.$ to jQuery
In het volgende voorbeeld wordt een IIFE gebruikt om ervoor te zorgen dat de $
alleen aan jQuery
is gebonden in de context die door de afsluiting is gecreëerd:
(function ($) {
// $ is assigned to jQuery here
})(jQuery);
// but window.$ binding doesn't exist, so no pollution
Zie het canonieke antwoord op Stackoverflow voor meer informatie over sluitingen.
Hijsen
Wat is hijsen?
Hijsen is een mechanisme dat alle variabele en functieverklaringen naar de top van hun bereik verplaatst. Variabele toewijzingen vinden echter nog steeds plaats waar ze oorspronkelijk waren.
Overweeg bijvoorbeeld de volgende code:
console.log(foo); // → undefined
var foo = 42;
console.log(foo); // → 42
De bovenstaande code is hetzelfde als:
var foo; // → Hoisted variable declaration
console.log(foo); // → undefined
foo = 42; // → variable assignment remains in the same place
console.log(foo); // → 42
Merk op dat vanwege het hijsen bovenstaande undefined
niet hetzelfde is als het not defined
resultaat van het uitvoeren:
console.log(foo); // → foo is not defined
Een soortgelijk principe is van toepassing op functies. Wanneer functies worden toegewezen aan een variabele (dat wil zeggen een functie-uitdrukking ), wordt de declaratie van de variabele opgeheven terwijl de toewijzing op dezelfde plaats blijft. De volgende twee codefragmenten zijn equivalent.
console.log(foo(2, 3)); // → foo is not a function
var foo = function(a, b) {
return a * b;
}
var foo;
console.log(foo(2, 3)); // → foo is not a function
foo = function(a, b) {
return a * b;
}
Bij het declareren van functie-instructies doet zich een ander scenario voor. In tegenstelling tot functieverklaringen, worden functieverklaringen boven aan het bereik gehesen. Overweeg de volgende code:
console.log(foo(2, 3)); // → 6
function foo(a, b) {
return a * b;
}
De bovenstaande code is hetzelfde als het volgende codefragment vanwege het hijsen:
function foo(a, b) {
return a * b;
}
console.log(foo(2, 3)); // → 6
Hier zijn enkele voorbeelden van wat wel en niet hijst:
// Valid code:
foo();
function foo() {}
// Invalid code:
bar(); // → TypeError: bar is not a function
var bar = function () {};
// Valid code:
foo();
function foo() {
bar();
}
function bar() {}
// Invalid code:
foo();
function foo() {
bar(); // → TypeError: bar is not a function
}
var bar = function () {};
// (E) valid:
function foo() {
bar();
}
var bar = function(){};
foo();
Beperkingen van hijsen
Het initialiseren van een variabele kan niet worden gehesen of in eenvoudige JavaScript-takels declaraties niet initialisatie.
Bijvoorbeeld: de onderstaande scripts geven verschillende uitvoer.
var x = 2;
var y = 4;
alert(x + y);
Dit geeft je een output van 6. Maar dit ...
var x = 2;
alert(x + y);
var y = 4;
Dit geeft u een uitvoer van NaN. Omdat we de waarde van y initialiseren, gebeurt het JavaScript-hijsen niet, dus de y-waarde is niet gedefinieerd. Het JavaScript zal overwegen dat y nog niet is aangegeven.
Het tweede voorbeeld is dus hetzelfde als hieronder.
var x = 2;
var y;
alert(x + y);
y = 4;
Dit geeft u een uitvoer van NaN.
Let-in-loops gebruiken in plaats van var (voorbeeld van klikhandlers)
Laten we zeggen dat we een knop moeten toevoegen voor elk stuk loadedData
array (bijvoorbeeld, elke knop moet een schuifregelaar zijn die de gegevens toont; voor de eenvoud zullen we alleen een bericht waarschuwen). Je kunt zoiets proberen:
for(var i = 0; i < loadedData.length; i++)
jQuery("#container").append("<a class='button'>"+loadedData[i].label+"</a>")
.children().last() // now let's attach a handler to the button which is a child
.on("click",function() { alert(loadedData[i].content); });
Maar in plaats van te waarschuwen, veroorzaakt elke knop de
TypeError: loadedData [i] is niet gedefinieerd
fout. Dit komt omdat het bereik van i
het globale bereik (of een functiebereik) is en na de lus i == 3
. Wat we nodig hebben is niet om "de staat van i
te onthouden". Dit kan gedaan worden met let
:
for(let i = 0; i < loadedData.length; i++)
jQuery("#container").append("<a class='button'>"+loadedData[i].label+"</a>")
.children().last() // now let's attach a handler to the button which is a child
.on("click",function() { alert(loadedData[i].content); });
Een voorbeeld van loadedData
die met deze code moeten worden getest:
var loadedData = [
{ label:"apple", content:"green and round" },
{ label:"blackberry", content:"small black or blue" },
{ label:"pineapple", content:"weird stuff.. difficult to explain the shape" }
];
Een viool om dit te illustreren
Aanroep van methode
Het aanroepen van een functie als methode van een object, de waarde this
is dat object.
var obj = {
name: "Foo",
print: function () {
console.log(this.name)
}
}
We kunnen nu afdrukken gebruiken als een methode van obj. this
zal obj zijn
obj.print();
Dit houdt dus in:
Foo
Anonieme aanroep
Het aanroepen van een functie als anonieme functie, this
is het globale object ( self
in de browser).
function func() {
return this;
}
func() === window; // true
In de strikte modus van ECMAScript 5 is this
niet undefined
als de functie anoniem wordt aangeroepen.
(function () {
"use strict";
func();
}())
Dit wordt uitgevoerd
undefined
Aanroeper van de constructeur
Wanneer een functie wordt aangeroepen als constructor met het new
trefwoord this
neemt this
de waarde van het object dat wordt geconstrueerd
function Obj(name) {
this.name = name;
}
var obj = new Obj("Foo");
console.log(obj);
Dit zal loggen
{name: "Foo"}
Aanroep pijlfunctie
Bij het gebruik van pijlfuncties neemt this
de waarde van de omsluitende uitvoeringscontext, this
(dat wil zeggen, this
in pijlfuncties heeft een lexicale scope in plaats van de gebruikelijke dynamische scope). In globale code (code die bij geen enkele functie hoort) zou dit het globale object zijn. En zo blijft het, zelfs als u de functie met de pijlnotatie oproept uit een van de andere methoden die hier worden beschreven.
var globalThis = this; //"window" in a browser, or "global" in Node.js
var foo = (() => this);
console.log(foo() === globalThis); //true
var obj = { name: "Foo" };
console.log(foo.call(obj) === globalThis); //true
Kijk hoe this
de context overneemt in plaats van te verwijzen naar het object waarop de methode is ingeschakeld.
var globalThis = this;
var obj = {
withoutArrow: function() {
return this;
},
withArrow: () => this
};
console.log(obj.withoutArrow() === obj); //true
console.log(obj.withArrow() === globalThis); //true
var fn = obj.withoutArrow; //no longer calling withoutArrow as a method
var fn2 = obj.withArrow;
console.log(fn() === globalThis); //true
console.log(fn2() === globalThis); //true
Toepassen en oproepsyntaxis en aanroep.
De apply
en de call
methoden in elke functie laat het een aangepaste waarde te bieden voor this
.
function print() {
console.log(this.toPrint);
}
print.apply({ toPrint: "Foo" }); // >> "Foo"
print.call({ toPrint: "Foo" }); // >> "Foo"
Je merkt misschien dat de syntaxis voor beide hierboven gebruikte aanroepen hetzelfde is. dwz De handtekening lijkt op elkaar.
Maar er is een klein verschil in hun gebruik, omdat we te maken hebben met functies en hun bereik wijzigen, moeten we nog steeds de oorspronkelijke argumenten aan de functie behouden. Beide zijn van apply
en call
ondersteuning door als volgt argumenten voor de doelfunctie:
function speak() {
var sentences = Array.prototype.slice.call(arguments);
console.log(this.name+": "+sentences);
}
var person = { name: "Sunny" };
speak.apply(person, ["I", "Code", "Startups"]); // >> "Sunny: I Code Startups"
speak.call(person, "I", "<3", "Javascript"); // >> "Sunny: I <3 Javascript"
Merk op dat je met de apply
een Array
of het arguments
(matrixachtig) kunt doorgeven als de lijst met argumenten, terwijl voor call
elk argument afzonderlijk moet worden doorgegeven.
Deze twee methoden geven u de vrijheid om zo chique te worden als u wilt, zoals het implementeren van een slechte versie van de native bind
van ECMAScript om een functie te maken die altijd als methode voor een object uit een originele functie wordt aangeroepen.
function bind (func, obj) {
return function () {
return func.apply(obj, Array.prototype.slice.call(arguments, 1));
}
}
var obj = { name: "Foo" };
function print() {
console.log(this.name);
}
printObj = bind(print, obj);
printObj();
Dit zal loggen
"Foo"
De bind
functie heeft veel te doen
-
obj
zal als de waardethis
worden gebruikt - stuur de argumenten door naar de functie
- en retourneer vervolgens de waarde
Gebonden aanroeping
Met de bind
van elke functie kunt u een nieuwe versie van die functie maken met de context strikt gebonden aan een specifiek object. Het is vooral handig om een functie te dwingen om als methode van een object te worden aangeroepen.
var obj = { foo: 'bar' };
function foo() {
return this.foo;
}
fooObj = foo.bind(obj);
fooObj();
Dit zal loggen:
bar