Sök…
Anmärkningar
Omfattning är det sammanhang där variabler lever och kan nås med annan kod i samma omfattning. Eftersom JavaScript till stor del kan användas som ett funktionellt programmeringsspråk, är det viktigt att känna till omfattningen av variabler och funktioner eftersom det hjälper till att förhindra buggar och oväntat beteende vid körning.
Skillnad mellan var och låt
(Obs! Alla exempel som använder let
är också giltiga för const
)
var
är tillgängligt i alla versioner av JavaScript, medan let
och const
ingår i ECMAScript 6 och endast tillgängligt i vissa nyare webbläsare .
var
scoped till den innehållande funktionen eller det globala utrymmet, beroende på när det deklareras:
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
Det betyder att det "flyr" if
uttalanden och alla liknande blockkonstruktioner:
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
Som jämförelse är let
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"
Observera att i
och j
endast deklareras i for
loopen och därför inte är deklarerade utanför den.
Det finns flera andra avgörande skillnader:
Global variabel deklaration
I toppområdet (utanför alla funktioner och block) sätter var
deklarationer ett element i det globala objektet. let
inte:
var x = 4;
let y = 7;
console.log(this.x); // >> 4
console.log(this.y); // >> undefined
Re-deklaration
Att förklara en variabel två gånger med var
producerar inte ett fel (även om det motsvarar att deklarera en gång):
var x = 4;
var x = 7;
Med let
skapar detta ett fel:
let x = 4;
let x = 7;
TypeError: Identifieraren
x
har redan deklarerats
Detsamma gäller när y
deklareras med var
:
var y = 4;
let y = 7;
TypeError: Identifierare
y
har redan deklarerats
Men variabler som deklarerats med let kan återanvändas (inte deklareras) i ett kapslat block
let i = 5;
{
let i = 6;
console.log(i); // >> 6
}
console.log(i); // >> 5
Inom blocket ytter i
kan nås, men om inom blocket har en let
deklaration för i
den yttre i
kan inte nås och kommer att kasta en ReferenceError
om de används innan andra deklareras.
let i = 5;
{
i = 6; // outer i is unavailable within the Temporal Dead Zone
let i;
}
ReferenceError: i är inte definierad
lyft
Variabler som deklareras både med var
och let
hissas . Skillnaden är att en variabel som deklarerats med var
kan refereras före sin egen tilldelning, eftersom den automatiskt tilldelas (med undefined
som dess värde), men let
inte kan - det kräver specifikt att variabeln deklareras innan den åberopas:
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;
Området mellan början av ett block och en let
eller const
deklaration kallas Temporal Dead Zone , och alla referenser till variabeln i detta område kommer att orsaka en ReferenceError
. Detta händer även om variabeln tilldelas innan den deklareras :
y=7; // >> "ReferenceError: `y` is not defined"
let y;
I icke-strikt läge, tilldela ett värde till en variabel utan någon deklaration, deklarerar automatiskt variabeln i det globala omfånget . I detta fall, istället för att y
automatiskt deklareras i det globala omfånget, let
reservera variabelns namn ( y
) och tillåter ingen åtkomst eller tilldelning till den innan den rad där den deklareras / initieras.
nedläggningar
När en funktion deklareras fångas variabler i samband med dess deklaration inom dess omfattning. Till exempel, i koden nedan, är variabeln x
bunden till ett värde i det yttre omfånget, och sedan fångas referensen till x
i samband med bar
:
var x = 4; // declaration in outer scope
function bar() {
console.log(x); // outer scope is captured on declaration
}
bar(); // prints 4 to console
Provutgång:
4
Detta begrepp "fånga" omfattning är intressant eftersom vi kan använda och modifiera variabler från ett yttre omfång även efter det yttre omfånget har gått ut. Tänk till exempel på följande:
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
Provutgång:
4
I ovanstående exempel, när foo
anropas, är dess sammanhang fångas i bar
. Så även efter att den kommer tillbaka kan bar
fortfarande komma åt och ändra variabeln x
. Funktion foo
, vars sammanhang fångas i en annan funktion, sägs vara en nedläggning .
Privat data
Detta låter oss göra några intressanta saker, till exempel att definiera "privata" variabler som bara är synliga för en specifik funktion eller uppsättning funktioner. Ett förfalskat (men populärt) exempel:
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());
Provutgång:
1 0
När makeCounter()
anropas sparas en ögonblicksbild av sammanhanget för den funktionen. All kod inuti makeCounter()
kommer att använda den ögonblicksbilden i deras körning. Två samtal från makeCounter()
skapar därmed två olika ögonblicksbilder med en egen counter
.
Omedelbart åberopade funktionsuttryck (IIFE)
Stängningar används också för att förhindra global förorening av namnområdet, ofta genom att använda omedelbart åberopade funktionsuttryck.
Omedelbart åberopade funktionsuttryck (eller kanske mer intuitivt självutförande av anonyma funktioner ) är i huvudsak stängningar som kallas direkt efter deklarationen. Den allmänna tanken med IIFE: er att åberopa biverkningen av att skapa ett separat sammanhang som endast är tillgängligt för koden inom IIFE.
Anta att vi vill kunna referera till jQuery
med $
. Tänk på den naiva metoden utan att använda en IIFE:
var $ = jQuery;
// we've just polluted the global namespace by assigning window.$ to jQuery
I följande exempel används en IIFE för att säkerställa att $
är bundet till jQuery
i det sammanhang som skapats av nedläggningen:
(function ($) {
// $ is assigned to jQuery here
})(jQuery);
// but window.$ binding doesn't exist, so no pollution
Se det kanoniska svaret på Stackoverflow för mer information om stängningar.
lyft
Vad lyfter det?
Lyft är en mekanism som flyttar alla variabla och funktionsdeklarationer till toppen av deras omfattning. Men variabla tilldelningar sker fortfarande där de ursprungligen var.
Tänk till exempel på följande kod:
console.log(foo); // → undefined
var foo = 42;
console.log(foo); // → 42
Ovanstående kod är densamma som:
var foo; // → Hoisted variable declaration
console.log(foo); // → undefined
foo = 42; // → variable assignment remains in the same place
console.log(foo); // → 42
Observera att på grund av hissning är ovannämnda ovan undefined
inte detsamma som det not defined
följer av körning:
console.log(foo); // → foo is not defined
En liknande princip gäller för funktioner. När funktioner tilldelas en variabel (dvs. ett funktionsuttryck ) lyftes variabeldeklarationen medan tilldelningen förblir på samma plats. Följande två kodavsnitt är likvärdiga.
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;
}
När man deklarerar funktionsförklaringar inträffar ett annat scenario. Till skillnad från funktionsförklaringar hissas funktionsdeklarationer till toppen av deras omfattning. Tänk på följande kod:
console.log(foo(2, 3)); // → 6
function foo(a, b) {
return a * b;
}
Ovanstående kod är densamma som nästa kodavsnitt på grund av hissning:
function foo(a, b) {
return a * b;
}
console.log(foo(2, 3)); // → 6
Här är några exempel på vad som är och vad som inte lyfter:
// 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();
Begränsningar av hissning
Initiera en variabel kan inte hejas eller i enkla JavaScript-lyftdeklarationer inte initialisering.
Exempel: Skripten nedan ger olika utgångar.
var x = 2;
var y = 4;
alert(x + y);
Detta ger dig en produktion på 6. Men detta ...
var x = 2;
alert(x + y);
var y = 4;
Detta ger dig en utgång av NaN. Eftersom vi initialiserar värdet på y, sker inte JavaScript-lyftning, så y-värdet kommer att definieras. JavaScript kommer att överväga att y ännu inte har deklarerats.
Så det andra exemplet är samma som nedan.
var x = 2;
var y;
alert(x + y);
y = 4;
Detta ger dig en utgång av NaN.
Använda inlindningsslingor i stället för var (klickhanteraren exempel)
Låt oss säga att vi måste lägga till en knapp för varje bit av loadedData
matris (till exempel bör varje knapp vara en skjutreglage som visar data; för enkelhetens skull kommer vi bara att varna ett meddelande). Man kan prova något liknande:
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); });
Men istället för att varna kommer varje knapp att orsaka
TypeError: loadData [i] är odefinierad
fel. Detta beror på att omfånget för i
är det globala omfånget (eller ett funktionsomfång) och efter slingan, i == 3
. Det vi behöver är att inte "komma ihåg tillståndet i
". Detta kan göras med 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); });
Ett exempel på loadedData
som ska testas med denna kod:
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" }
];
En fiol för att illustrera detta
Metodkallelse
Att åberopa en funktion som en metod för ett objekt värdet på this
kommer att vara det objektet.
var obj = {
name: "Foo",
print: function () {
console.log(this.name)
}
}
Vi kan nu åberopa tryck som en metod för obj. this
kommer att vara obj
obj.print();
Detta kommer alltså att logga:
foo
Anonym anrop
Åkallar en funktion som en anonym funktion kommer this
att vara det globala objektet ( self
i webbläsaren).
function func() {
return this;
}
func() === window; // true
I ECMAScript 5: s strikta läge kommer this
att undefined
om funktionen anropas anonymt.
(function () {
"use strict";
func();
}())
Detta kommer att matas ut
undefined
Konstruktörsinvokation
När en funktion anropas som konstruktör med new
nyckelordet this
antar värdet av föremålet byggs
function Obj(name) {
this.name = name;
}
var obj = new Obj("Foo");
console.log(obj);
Detta kommer att logga
{name: "Foo"}
Pilfunktionskallning
Vid användning av pil funktioner this
tar värdet från exekverings omslutande sammanhang är this
(det vill säga, this
i pil funktioner har lexikal omfattning snarare än den vanliga dynamiska räckvidd). I global kod (kod som inte tillhör någon funktion) skulle det vara det globala objektet. Och det håller så, även om du åberopar den funktion som deklarerats med pilnotationen från någon av de andra metoderna som beskrivs här.
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
Se hur this
ärver sammanhanget snarare än att hänvisa till objektet som metoden kallades på.
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
Använd och ring syntax och kallelse.
apply
och call
i varje funktion gör det möjligt att ge ett anpassat värde för this
.
function print() {
console.log(this.toPrint);
}
print.apply({ toPrint: "Foo" }); // >> "Foo"
print.call({ toPrint: "Foo" }); // >> "Foo"
Du kanske märker att syntaxen för båda anropningarna som används ovan är densamma. dvs. signaturen ser liknande ut.
Men det är en liten skillnad i deras användning, eftersom vi har att göra med funktioner och ändra deras omfattning, måste vi fortfarande behålla de ursprungliga argumenten som har skickats till funktionen. Både apply
och call
support som överför argument till målfunktionen enligt följande:
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"
Meddelandet som apply
gör att du kan skicka en Array
eller arguments
(array-liknande) som en lista med argumenter, medan call
behöver att du skickar varje argument separat.
Dessa två metoder ger dig friheten att bli så snygg som du vill, som att implementera en dålig version av ECMAScripts ursprungliga bind
att skapa en funktion som alltid kommer att kallas som en metod för ett objekt från en originalfunktion.
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();
Detta kommer att logga
"Foo"
bind
har mycket på gång
-
obj
kommer att användas som värdet påthis
- vidarebefordra argumenten till funktionen
- och returnera sedan värdet
Begränsad invokation
bind
för varje funktion låter dig skapa en ny version av den funktionen med den sammanhang som är strikt bunden till ett specifikt objekt. Det är särskilt användbart att tvinga en funktion att kallas som en metod för ett objekt.
var obj = { foo: 'bar' };
function foo() {
return this.foo;
}
fooObj = foo.bind(obj);
fooObj();
Det här loggar:
bar