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.

ange bildbeskrivning här

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
5

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

6

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

  1. obj kommer att användas som värdet på this
  2. vidarebefordra argumenten till funktionen
  3. 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



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow