Buscar..


Observaciones

Ámbito es el contexto en el que las variables viven y pueden ser accedidas por otro código en el mismo ámbito. Debido a que JavaScript se puede usar en gran medida como un lenguaje de programación funcional, conocer el alcance de las variables y funciones es importante ya que ayuda a prevenir errores y comportamientos inesperados en el tiempo de ejecución.

Diferencia entre var y let

(Nota: todos los ejemplos que usan let también son válidos para const )

var está disponible en todas las versiones de JavaScript, mientras que let y const son parte de ECMAScript 6 y solo están disponibles en algunos navegadores más nuevos .

var está dentro del alcance de la función contenedora o del espacio global, dependiendo de cuándo se declara:

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

Eso significa que se "escapa" if declaraciones y todas las construcciones de bloques similares:

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

En comparación, let ámbito del bloque:

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"

Tenga en cuenta que i y j solo se declaran en el bucle for y, por lo tanto, no se declaran fuera de él.

Hay varias otras diferencias cruciales:

Declaración de variable global

En el ámbito superior (fuera de cualquier función y bloque), las declaraciones var ponen un elemento en el objeto global. no lo let

var x = 4;
let y = 7;

console.log(this.x); // >> 4
console.log(this.y); // >> undefined

Re-declaración

Declarar una variable dos veces usando var no produce un error (aunque es equivalente a declararlo una vez):

var x = 4;
var x = 7;

Con let , esto produce un error:

let x = 4;
let x = 7;

TypeError: el identificador x ya ha sido declarado

Lo mismo es cierto cuando y se declara con var :

var y = 4;
let y = 7;

TypeError: el identificador y ya ha sido declarado

Sin embargo, las variables declaradas con let se pueden reutilizar (no volver a declarar) en un bloque anidado

let i = 5;    
{
   let i = 6;
   console.log(i); // >> 6
}
console.log(i); // >> 5

Dentro del bloque se puede acceder a la i externa, pero si el bloque interior tiene una declaración de let para i , no se puede acceder a la i externa y lanzará un ReferenceError si se usa antes de que se declare la segunda.

let i = 5;
{
    i = 6;  // outer i is unavailable within the Temporal Dead Zone
    let i;
}

Error de referencia: no está definido

Levantamiento

Variables declaradas ambas con var y let son izadas . La diferencia es que se puede hacer referencia a una variable declarada con var antes de su propia asignación, ya que se asigna automáticamente (con undefined como su valor), pero let puede: requiere específicamente que se declare la variable antes de ser invocada:

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;

El área entre el inicio de un bloque y una declaración let o const se conoce como la Zona Muerta Temporal , y cualquier referencia a la variable en esta área causará un ReferenceError . Esto sucede incluso si la variable se asigna antes de ser declarada :

y=7; // >> "ReferenceError: `y` is not defined"
let y;

En el modo no estricto, al asignar un valor a una variable sin ninguna declaración, se declara automáticamente la variable en el ámbito global . En este caso, en lugar de que y se declare automáticamente en el alcance global, let reserve el nombre de la variable ( y ) y no permita ningún acceso o asignación a ella antes de la línea donde se declara / inicializa.

Cierres

Cuando se declara una función, las variables en el contexto de su declaración se capturan en su alcance. Por ejemplo, en el código a continuación, la variable x está vinculada a un valor en el ámbito externo, y luego la referencia a x se captura en el contexto de la bar :

var x = 4; // declaration in outer scope

function bar() {
    console.log(x); // outer scope is captured on declaration
}

bar(); // prints 4 to console

Salida de muestra: 4

Este concepto de "capturar" el alcance es interesante porque podemos usar y modificar variables desde un alcance externo incluso después de que el alcance externo finalice. Por ejemplo, considere lo siguiente:

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

Salida de muestra: 4

En el ejemplo anterior, cuando se llama a foo , su contexto se captura en la bar funciones. Así que incluso después de que regrese, la bar todavía puede acceder y modificar la variable x . La función foo , cuyo contexto se captura en otra función, se dice que es un cierre .

Datos privados

Esto nos permite hacer algunas cosas interesantes, como definir variables "privadas" que son visibles solo para una función específica o un conjunto de funciones. Un ejemplo artificial (pero popular):

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());

Salida de muestra:

1
0

Cuando se llama a makeCounter() , se guarda una instantánea del contexto de esa función. Todo el código dentro de makeCounter() usará esa instantánea en su ejecución. Dos llamadas de makeCounter() crearán dos instantáneas diferentes, con su propia copia del counter .

Expresiones de función invocadas de inmediato (IIFE)

Los cierres también se usan para prevenir la contaminación global del espacio de nombres, a menudo mediante el uso de expresiones de función invocadas de inmediato.

Las expresiones de función invocadas de inmediato (o, quizás de manera más intuitiva, las funciones anónimas de ejecución automática ) son esencialmente cierres que se llaman inmediatamente después de la declaración. La idea general con IIFE es invocar el efecto secundario de crear un contexto separado que sea accesible solo para el código dentro de IIFE.

Supongamos que queremos poder hacer referencia a jQuery con $ . Considere el método ingenuo, sin utilizar un IIFE:

var $ = jQuery;
// we've just polluted the global namespace by assigning window.$ to jQuery

En el siguiente ejemplo, se utiliza un IIFE para garantizar que el $ esté vinculado a jQuery solo en el contexto creado por el cierre:

(function ($) {
    // $ is assigned to jQuery here
})(jQuery);
// but window.$ binding doesn't exist, so no pollution

Consulte la respuesta canónica en Stackoverflow para obtener más información sobre los cierres.

Levantamiento

¿Qué es la elevación?

La elevación es un mecanismo que mueve todas las declaraciones de variables y funciones a la parte superior de su alcance. Sin embargo, las asignaciones de variables todavía ocurren donde estaban originalmente.

Por ejemplo, considere el siguiente código:

console.log(foo);  // → undefined
var foo = 42;
console.log(foo);  // → 42

El código anterior es el mismo que:

var foo;             // → Hoisted variable declaration
console.log(foo);    // → undefined
foo = 42;            // → variable assignment remains in the same place
console.log(foo);    // → 42

Tenga en cuenta que debido a la elevación de lo anterior, undefined no es lo mismo que el not defined resultante de la ejecución:

console.log(foo);    // → foo is not defined 

Un principio similar se aplica a las funciones. Cuando las funciones se asignan a una variable (es decir, una expresión de función ), la declaración de la variable se eleva mientras la asignación permanece en el mismo lugar. Los siguientes dos fragmentos de código son equivalentes.

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;
}

Al declarar declaraciones de funciones , se produce un escenario diferente. A diferencia de las declaraciones de función, las declaraciones de función se elevan a la parte superior de su alcance. Considere el siguiente código:

console.log(foo(2, 3));  // → 6
function foo(a, b) {
    return a * b;
}

El código anterior es el mismo que el siguiente fragmento de código debido a la elevación:

function foo(a, b) {
    return a * b;
}

console.log(foo(2, 3));  // → 6

Aquí hay algunos ejemplos de lo que es y lo que no es la elevación:

// 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();

Limitaciones del Levantamiento

La inicialización de una variable no puede ser Elevada o En JavaScript simple. Las declaraciones de Ascensores no se inicializan.

Por ejemplo: Los siguientes scripts darán diferentes salidas.

var x = 2; 
var y = 4; 
alert(x + y);

Esto te dará una salida de 6. Pero esto ...

var x = 2; 
alert(x + y);
var y = 4; 

Esto te dará una salida de NaN. Dado que estamos inicializando el valor de y, el Aumento de JavaScript no está ocurriendo, por lo que el valor de y no estará definido. El JavaScript considerará que y aún no está declarado.

Así que el segundo ejemplo es el mismo que el de abajo.

var x = 2; 
var y;
alert(x + y);
y = 4; 

Esto te dará una salida de NaN.

introduzca la descripción de la imagen aquí

Usar let in loops en lugar de var (ejemplo de controladores de clic)

Digamos que necesitamos agregar un botón para cada parte de la matriz de loadedData (por ejemplo, cada botón debe ser un control deslizante que muestre los datos; por simplicidad, solo alertaremos un mensaje). Uno puede intentar algo como esto:

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); });

Pero en lugar de alertar, cada botón causará

TypeError: loadedData [i] no está definido

error. Esto se debe a que el alcance de i es el alcance global (o un alcance de función) y después del bucle, i == 3 . Lo que necesitamos no es "recordar el estado de i ". Esto se puede hacer usando 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); });

Un ejemplo de loadedData para ser probado con este código:

    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" }
    ];

Un violín para ilustrar esto.

Invocación de método

La invocación de una función como un método de un objeto el valor de this será ese objeto.

var obj = {
    name: "Foo",
    print: function () {
        console.log(this.name)
    }
}

Ahora podemos invocar la impresión como un método de obj. this será obj

obj.print();

Esto registrará así:

Foo

Invocación anónima

Al invocar una función como una función anónima, this será el objeto global ( self en el navegador).

function func() {
    return this;
}

func() === window; // true
5

En el modo estricto de ECMAScript 5 , this quedará undefined si la función se invoca de forma anónima.

(function () {
    "use strict";
    func();
}())

Esto dará salida

undefined

Invocación del constructor

Cuando se invoca una función como un constructor con la new palabra clave this toma el valor del objeto que se construido

function Obj(name) {
    this.name = name;
}

var obj = new Obj("Foo");

console.log(obj);

Esto se registrará

{nombre: "Foo"}

Invocación de la función de flecha

6

Al utilizar las funciones de flecha this toma el valor del contexto de ejecución de cerramiento es this (es decir, this en funciones de flecha tiene ámbito léxico en lugar del alcance dinámico de costumbre). En el código global (código que no pertenece a ninguna función) sería el objeto global. Y sigue siendo así, incluso si invoca la función declarada con la notación de flecha de cualquiera de los otros métodos descritos aquí.

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

Vea cómo this hereda el contexto en lugar de referirse al objeto en el que se invocó el método.

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

Aplicar y llamar sintaxis e invocación.

Los métodos de apply y call en cada función le permiten proporcionar un valor personalizado para this .

function print() {
    console.log(this.toPrint);
}

print.apply({ toPrint: "Foo" }); // >> "Foo"
print.call({ toPrint: "Foo" }); // >> "Foo"

Puede observar que la sintaxis de las dos invocaciones utilizadas anteriormente es la misma. Es decir, la firma se ve similar.

Pero hay una pequeña diferencia en su uso, ya que estamos tratando con funciones y cambiando sus ámbitos, todavía necesitamos mantener los argumentos originales pasados ​​a la función. Tanto la apply como la call admiten el paso de argumentos a la función de destino de la siguiente manera:

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"

El aviso de que se apply permite pasar una Array o el objeto de arguments (similar a una matriz) como la lista de argumentos, mientras que, la call necesita que usted pase cada argumento por separado.

Estos dos métodos le dan la libertad de obtener la fantasía que desea, como implementar una versión deficiente del bind nativo de ECMAScript para crear una función que siempre se llamará como método de un objeto desde una función original.

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();

Esto se registrará

"Foo"


La función de bind tiene mucho que hacer

  1. obj se utilizará como el valor de this
  2. reenviar los argumentos a la función
  3. y luego devolver el valor

Invocación encuadernada

El método de bind de cada función le permite crear una nueva versión de esa función con el contexto vinculado estrictamente a un objeto específico. Es especialmente útil forzar una función para que sea llamada como un método de un objeto.

var obj = { foo: 'bar' };

function foo() {
    return this.foo;
}

fooObj = foo.bind(obj);

fooObj();

Esto registrará:

bar



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow