Suche…
Bemerkungen
Bereich ist der Kontext, in dem Variablen leben und auf den mit anderem Code im selben Bereich zugegriffen werden kann. Da JavaScript weitgehend als funktionale Programmiersprache verwendet werden kann, ist es wichtig, den Umfang der Variablen und Funktionen zu kennen, da Fehler und unerwartetes Verhalten zur Laufzeit vermieden werden.
Unterschied zwischen var und let
(Hinweis: Alle Beispiele, die let
gelten auch für const
)
var
ist in allen JavaScript-Versionen verfügbar, während let
und const
Teil von ECMAScript 6 und nur in einigen neueren Browsern verfügbar sind .
var
ist abhängig von der Deklaration auf die enthaltende Funktion oder den globalen Raum beschränkt:
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
Das heißt, es "entgeht", if
Anweisungen und alle ähnlichen Blockkonstrukte:
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
Zum Vergleich: let
is block scoped:
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"
Beachten Sie, dass i
und j
nur in der for
Schleife deklariert sind und daher außerhalb von ihr nicht deklariert werden.
Es gibt einige andere entscheidende Unterschiede:
Globale Variablendeklaration
Im oberen Bereich (außerhalb von Funktionen und Blöcken) var
Deklarationen ein Element in das globale Objekt ein. let
nicht:
var x = 4;
let y = 7;
console.log(this.x); // >> 4
console.log(this.y); // >> undefined
Re-Deklaration
Wenn Sie eine Variable zweimal mit var
deklarieren, wird kein Fehler ausgegeben (obwohl sie gleichbedeutend ist, wenn Sie sie einmal deklarieren):
var x = 4;
var x = 7;
Mit let
erzeugt dies einen Fehler:
let x = 4;
let x = 7;
TypeError: Bezeichner
x
wurde bereits deklariert
Dasselbe gilt, wenn y
mit var
deklariert wird:
var y = 4;
let y = 7;
TypeError: Identifier
y
wurde bereits deklariert
Mit let deklarierte Variablen können jedoch in einem geschachtelten Block wiederverwendet (nicht erneut deklariert) werden
let i = 5;
{
let i = 6;
console.log(i); // >> 6
}
console.log(i); // >> 5
Innerhalb des Blocks kann auf das äußere i
zugegriffen werden. Wenn der innere Block jedoch eine let
Deklaration für i
, kann auf das äußere i
nicht zugegriffen werden, und es wird ein ReferenceError
wenn der zweite Block vor der Deklaration verwendet wird.
let i = 5;
{
i = 6; // outer i is unavailable within the Temporal Dead Zone
let i;
}
ReferenceError: i ist nicht definiert
Heben
Variablen, die sowohl mit var
als auch let
deklariert wurden let
werden angehoben . Der Unterschied besteht darin, dass eine mit var
deklarierte Variable vor ihrer eigenen Zuweisung referenziert werden kann, da sie automatisch zugewiesen wird (mit undefined
als Wert). let
jedoch nicht, da die Variable vor dem Aufruf deklariert werden muss.
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;
Der Bereich zwischen dem Beginn eines Blocks und einer let
oder const
Deklaration wird als temporale Totzone bezeichnet. Verweise auf die Variable in diesem Bereich führen zu einem ReferenceError
. Dies geschieht auch, wenn die Variable vor der Deklaration zugewiesen wird :
y=7; // >> "ReferenceError: `y` is not defined"
let y;
Wenn Sie einer Variablen ohne Deklaration einen Wert zuweisen, wird die Variable automatisch im globalen Bereich deklariert . In diesem Fall wird anstelle von y
automatisch im globalen Bereich erklärt werden, let
Reserven den Namen der Variablen ( y
) und erlauben keinen Zugriff oder Zuweisung , um es vor der Zeile , wo sie deklariert / initialisiert wird.
Verschlüsse
Wenn eine Funktion deklariert wird, werden Variablen im Kontext ihrer Deklaration in ihrem Gültigkeitsbereich erfasst. Im folgenden Code wird beispielsweise die Variable x
an einen Wert im äußeren Bereich gebunden, und dann wird der Verweis auf x
im Kontext von bar
erfasst:
var x = 4; // declaration in outer scope
function bar() {
console.log(x); // outer scope is captured on declaration
}
bar(); // prints 4 to console
Musterausgabe:
4
Dieses Konzept des "Erfassungsbereichs" ist interessant, da wir auch nach dem Beenden des äußeren Bereichs Variablen aus einem äußeren Bereich verwenden und modifizieren können. Betrachten Sie zum Beispiel Folgendes:
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
Musterausgabe:
4
In dem obigen Beispiel, wenn foo
genannt wird , wird ihr Kontext in der Funktion erfaßt bar
. bar
kann also auch nach der Rückkehr auf die Variable x
zugreifen und diese ändern. Die Funktion foo
, deren Kontext in einer anderen Funktion erfasst wird, wird als Schließung bezeichnet .
Private Daten
Dadurch können wir einige interessante Dinge tun, beispielsweise die Definition "privater" Variablen, die nur für eine bestimmte Funktion oder eine Gruppe von Funktionen sichtbar sind. Ein erfundenes (aber beliebtes) Beispiel:
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());
Beispielausgabe:
1 0
Wenn makeCounter()
aufgerufen wird, wird eine Momentaufnahme des Kontextes dieser Funktion gespeichert. Der gesamte Code in makeCounter()
verwendet diesen Snapshot bei der Ausführung. Bei zwei Aufrufen von makeCounter()
werden somit zwei verschiedene Momentaufnahmen mit einer eigenen Kopie des counter
.
Sofort aufgerufene Funktionsausdrücke (IIFE)
Closures werden auch verwendet, um die Verschmutzung des globalen Namensraums zu verhindern, häufig durch die Verwendung von sofort aufgerufenen Funktionsausdrücken.
Sofort aufgerufene Funktionsausdrücke (oder, intuitiver, anonyme Funktionen , die sich selbst ausführen lassen ) sind im Wesentlichen Schließungen, die direkt nach der Deklaration aufgerufen werden. Die allgemeine Idee bei IIFE ist, den Nebeneffekt der Erstellung eines separaten Kontexts aufzurufen, auf den nur der Code innerhalb der IIFE zugreifen kann.
Nehmen wir an, wir möchten jQuery
mit $
referenzieren. Betrachten Sie die naive Methode, ohne einen IIFE zu verwenden:
var $ = jQuery;
// we've just polluted the global namespace by assigning window.$ to jQuery
Im folgenden Beispiel wird ein IIFE verwendet, um sicherzustellen, dass $
nur in dem durch die Schließung erstellten Kontext an jQuery
gebunden ist:
(function ($) {
// $ is assigned to jQuery here
})(jQuery);
// but window.$ binding doesn't exist, so no pollution
Weitere Informationen zu Schließungen finden Sie in der kanonischen Antwort zu Stackoverflow .
Heben
Was ist das Heben?
Beim Hochziehen handelt es sich um einen Mechanismus, mit dem alle Variablen- und Funktionsdeklarationen ganz nach oben verschoben werden. Variable Zuweisungen finden jedoch weiterhin dort statt, wo sie ursprünglich waren.
Betrachten Sie zum Beispiel den folgenden Code:
console.log(foo); // → undefined
var foo = 42;
console.log(foo); // → 42
Der obige Code ist derselbe wie:
var foo; // → Hoisted variable declaration
console.log(foo); // → undefined
foo = 42; // → variable assignment remains in the same place
console.log(foo); // → 42
Beachten Sie, dass aufgrund des Anhebens das obige undefined
nicht dasselbe ist wie das not defined
Ergebnis des Laufens:
console.log(foo); // → foo is not defined
Ein ähnliches Prinzip gilt für Funktionen. Wenn Funktionen einer Variablen zugewiesen werden (dh einem Funktionsausdruck ), wird die Variablendeklaration angehoben, während die Zuweisung an derselben Stelle bleibt. Die folgenden zwei Codeausschnitte sind gleichwertig.
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;
}
Bei der Deklaration von Funktionsanweisungen tritt ein anderes Szenario auf. Im Gegensatz zu Funktionsanweisungen werden Funktionsdeklarationen ganz oben in ihren Geltungsbereich gehoben. Betrachten Sie den folgenden Code:
console.log(foo(2, 3)); // → 6
function foo(a, b) {
return a * b;
}
Der obige Code ist derselbe wie der nächste Codeausschnitt aufgrund des Hochziehens:
function foo(a, b) {
return a * b;
}
console.log(foo(2, 3)); // → 6
Hier sind einige Beispiele von was ist und was nicht hebt:
// 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();
Einschränkungen beim Anheben
Das Initialisieren einer Variablen kann nicht angehoben werden oder In einfachen JavaScript-Deklinationen kann Hoists nicht initialisiert werden.
Zum Beispiel: Die folgenden Skripte liefern unterschiedliche Ausgaben.
var x = 2;
var y = 4;
alert(x + y);
Dies ergibt eine Ausgabe von 6. Aber das ...
var x = 2;
alert(x + y);
var y = 4;
Dadurch erhalten Sie eine Ausgabe von NaN. Da wir den Wert von y initialisieren, geschieht das JavaScript-Hoisting nicht, daher ist der y-Wert undefiniert. Das JavaScript berücksichtigt, dass y noch nicht deklariert ist.
Das zweite Beispiel ist also wie folgt.
var x = 2;
var y;
alert(x + y);
y = 4;
Dadurch erhalten Sie eine Ausgabe von NaN.
Verwenden von let in loops anstelle von var
Nehmen wir an, wir müssen für jedes loadedData
Array eine Schaltfläche hinzufügen (z. B. sollte jede Schaltfläche ein Schieberegler sein, der die Daten loadedData
; zur Vereinfachung wird eine Meldung loadedData
). Man kann so etwas versuchen:
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); });
Anstatt zu warnen, bewirkt jede Taste das
TypeError: loadedData [i] ist nicht definiert
Error. Dies liegt daran, dass der Gültigkeitsbereich von i
der globale Gültigkeitsbereich (oder ein Funktionsumfang) und nach der Schleife i == 3
. Was wir brauchen, ist nicht "sich an den Zustand von i
zu erinnern". Dies kann mit 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); });
Ein Beispiel für loadedData
, das mit diesem Code getestet werden soll:
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" }
];
Eine Geige, um das zu veranschaulichen
Methodenaufruf
Wenn Sie eine Funktion als Methode eines Objekts aufrufen, wird der Wert this
Objekts dieses Objekt sein.
var obj = {
name: "Foo",
print: function () {
console.log(this.name)
}
}
Wir können jetzt print als Methode von obj aufrufen. this
wird obj sein
obj.print();
Dies wird also protokollieren:
Foo
Anonymer Aufruf
Unter Berufung auf eine Funktion als eine anonyme Funktion, this
wird das globale Objekt ( self
im Browser).
function func() {
return this;
}
func() === window; // true
Im strikten Modus von ECMAScript 5 ist this
undefined
wenn die Funktion anonym aufgerufen wird.
(function () {
"use strict";
func();
}())
Dies wird ausgegeben
undefined
Aufruf des Konstruktors
Wenn eine Funktion als Konstruktor aufgerufen wird , mit dem new
Schlüsselwort this
nimmt den Wert des Objekts ausgebildet ist
function Obj(name) {
this.name = name;
}
var obj = new Obj("Foo");
console.log(obj);
Dies wird protokollieren
{name: "foo"}
Aufruf der Pfeilfunktion
Wenn Sie Pfeilfunktionen verwenden, übernimmt this
den Wert aus dem umgebenden Ausführungskontext. this
dass this
Funktion in Pfeilfunktionen einen lexikalischen Bereich und nicht den üblichen dynamischen Bereich hat. Im globalen Code (Code, der zu keiner Funktion gehört) wäre es das globale Objekt. Dies bleibt auch dann der Fall, wenn Sie die mit der Pfeilnotation deklarierte Funktion aus einer der anderen hier beschriebenen Methoden aufrufen.
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
Sehen Sie, wie this
den Kontext erbt, anstatt sich auf das Objekt zu beziehen, für das die Methode aufgerufen wurde.
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
Übernehmen und Aufruf von Syntax und Aufruf
Die apply
und call
Methoden in jeder Funktion ermöglicht es , einen eigenen Wert zu liefern this
.
function print() {
console.log(this.toPrint);
}
print.apply({ toPrint: "Foo" }); // >> "Foo"
print.call({ toPrint: "Foo" }); // >> "Foo"
Möglicherweise stellen Sie fest, dass die Syntax für die beiden oben verwendeten Aufrufe dieselbe ist. dh Die Signatur sieht ähnlich aus.
Es gibt jedoch einen kleinen Unterschied in ihrer Verwendung, da wir uns mit Funktionen und deren Änderung befassen, müssen wir die ursprünglichen Argumente beibehalten, die an die Funktion übergeben werden. apply
und call
Unterstützung für die Weitergabe von Argumenten an die Zielfunktion auf:
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"
Beachten Sie, dass apply
es Ihnen ermöglicht, ein Array
oder das arguments
(array-like) als Liste von Argumenten zu übergeben, wohingegen Sie beim call
jedes Argument separat übergeben müssen.
Diese beiden Methoden geben Ihnen die Freiheit, so schick zu werden, wie Sie möchten, z. B. die Implementierung einer schlechten Version des nativen ECMAScript- bind
, um eine Funktion zu erstellen, die immer als Methode eines Objekts aus einer Originalfunktion aufgerufen wird.
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();
Dies wird protokollieren
"Foo"
Die bind
hat viel zu tun
-
obj
wird als Wert verwendet werden ,this
- leiten Sie die Argumente an die Funktion weiter
- und dann den Wert zurückgeben
Gebundene Anrufung
Mit der bind
jeder Funktion können Sie eine neue Version dieser Funktion erstellen, deren Kontext streng an ein bestimmtes Objekt gebunden ist. Es ist besonders nützlich, eine Funktion als Methode eines Objekts aufzurufen.
var obj = { foo: 'bar' };
function foo() {
return this.foo;
}
fooObj = foo.bind(obj);
fooObj();
Dies wird protokollieren:
Bar