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.
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" }
];
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
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
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
-
obj
se utilizará como el valor dethis
- reenviar los argumentos a la función
- 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