Skip to content

Instantly share code, notes, and snippets.

@MinusFour
Last active August 10, 2021 23:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MinusFour/a4a3143edc0f04cc5bd57fe500fd20ba to your computer and use it in GitHub Desktop.
Save MinusFour/a4a3143edc0f04cc5bd57fe500fd20ba to your computer and use it in GitHub Desktop.

Las expliaciones de cada código a continuación:

1

(function test(){ console.log('ok'); })
test();

La primera linea, al ser usada dentro de los parentesís deja de ser una declaración de una función y en su lugar se vuelve una expresión de una función. El nombre dado a la expresión, a diferencia de una declaración, solo puede ser alcanzado dentro del cuuerpo de la función. Es por eso que la llamada a test falla en la segunda linea, no puede alcanzar el identificador.

2

test()
function test(){ console.log('ok'); }

En este caso, tenemos una declaración de una función. Las declaraciones de funciones son "elevadas" al principio de su entorno léxico correspondiente. Esto quiere decir que la función es instanciada ANTES que el código empiece a correr (no confundir instanciación con invocación) con su respectiva variable creada también.

3

(function test(t = eval('var t = 1;')){
    console.log(t); 
})();

Uno de los requisitos cuando tenemos inicializadores o propiedades computadas en los parametros (en el caso de destructurar objetos) es que los parametros son inicializados en un entorno léxico nuevo. eval también tiene sus reglas especiales dependiendo de que tipo de llamada sea. En este caso, tenemos una llamada directa, no estricta a eval. Lo que significa que eval terminará usando el entorno léxico de las declaraciones variables de la función (el entorno léxico donde comunmente van las declaraciones con var y function). El resultado de estas dos variables es muy parecido a esto:

{
    let t; //declaración de parametro
    {
        var t; //declaración de eval
    }
}

Lo que termina provocando un error porque la variable se repite. Más acerca de esto en un ejemplo adelante.

4

(function test(t = eval('var c = 1;')){
    console.log(c);
    var c = 0;
})();

Este ejemplo, es muy parecido al anterior. En este caso la variable declarada en el eval directo es diferente al nombre del parametro por lo que no existe el error que tenía el ejemplo anterior. Uno podría pensar que el entorno léxico comparatido por eval y la función es el mismo. Realmente lo que ocurre es que al usar tanto propiedades computadas o inicializadores se crea nuevamente OTRO entorno léxico para las declaraciones de las variables. El resultado es que la variable c dentro de la función es diferente y oculta a la variable c declarada por eval. Lo que significa que la variable c dentro de la función no ha sido inicializada a la hora de imprimir la variable. Por ende, el resultado es undefined. Comprobable mediante el siguiente código:

(function f(t = eval('var c = 1;')){
  eval("var c;");
  c = 2;
  console.log(c); //2
  delete c;
  console.log(c); //1
}())

Aunque la prueba en sí merece su propia explicación. Quizás lo explicare en otro post.

5

(function test(){
    x();
    function x(){ console.log(1); };
    function x(){ console.log(2); };
})();

En este ejemplo las dos declaraciones de función tienen el mismo nombre. Javascript inicializa la primera declaración solo para que sea sobreescrita una vez que inicializa la segunda declaración. Ambas declaraciones son "elevadas" por lo que no hay un error a la hora de invocar x() pero esta imprime 2 porque la segunda declaración sobreescribe la primera.

6

(function test(){
   x();
   function x(){ console.log(1); };
   {
       x();
       function x(){ console.log(2); };
   }
   x();
})();

Este es también un ejemplo especial también y dependiendo de que motor de javascript estes usando puedes obtener resultados diferentes. La razón de esto es que antes de EcmaScript 6 la especificación no contemplaba declaraciones de funciones dentro de bloques, es decir no estaba especificado. Los navegadores entonces extendieron el lenguaje para poder utilizar declaraciones de funciones dentro de bloques. Sin embargo, las implementaciones divergieron en las semanticas de las declaraciones lo que significa que a pesar de utilizar la misma syntaxis se comportaban de diferente manera.

EcmaScript 6 contempla este problema en el anexo B como medio de compatibilidad entre las diferentes implementaciones y solo cuando el código no esta en su modo estricto. Si el código es estricto entonces se utilizan las reglas semanticas introducidas en EcmaScript 6 para declaraciones dentro de bloques.

En este caso, el código no es estricto, así que se utilizan las reglas de compatibilidad. Esto significa que la declaración dentro del bloque sobreescribe la declaración fuera del bloque una vez que sea evaluada. La segunda llamada a x usa la segunda declaración porque la función es "elevada" dentro del mismo bloque (aunque esto ocurre también en modo estricto).

7

new class {
    constructor() {
        x();
        function x(){ console.log(1); }
        function x(){ console.log(2); }
    }
}

Para este ejemplo, utilice una clase porque el código dentro de una clase está en modo estricto por defecto. En esta ocasión, las funciones están dentro del cuerpo de la función constructor lo que significa que se aplican las mismas reglas de siempre, lo que significa que acaba sobreescribiendo la primera declaración de x por su segunda declaración como en un ejemplo anterior. El que sea código estricto no produce ningún cambio.

8

new class {
    constructor() {
        function x(){ console.log(1); }
        {
            function x(){ console.log(2); }
        }
        x();
    }
}

En este ejemplo, la segunda declaración de x no sobreescribe la primera declaración por estar en modo estricto. La segunda declaración solo es alcanzable desde el bloque que fue definido. La llamada al final usa la función de la primera declaración.

9

new class {
    constructor() {
        {
            function x(){ console.log('ok'); }
        }
        x();
    }
}

Aquí la variable no puede alcanzar la declaración dentro del bloque y no existe otra declaración para x por lo tanto es un error.

10

new class {
    constructor() {
        {
            function x(){ console.log(1); }
            function x(){ console.log(2); }
            x();
        }
    }
}

En este ejemplo podemos ver un comportamiento diferente de las funciones declaradas en el entorno léxico superior de la función y los subsequentes (dentro de bloques). Dentro de un bloque y en modo estricto, las declaraciones son consideradas declaraciones léxicas (más acerca de ellas adelante) lo que significa que no pueden existir dos variables del mismo nombre.

11

(function test(c){
   var c;
   console.log('ok');
})();

Este es un ejemplo muy común, en un pasado no existía forma de establecer valores por defectos a un argumento. Entonces era muy común hacer:

var c = c === undefined ? 'valor por defecto' : c;

La declaración no era necesaría pero sin embargo se volvio un patrón. Lo que ocurre aquí es que se comparte el mismo entorno léxico para las variables y parametros al no tener expresiones dentro de los parametros (como en los 3er y 4to ejemplos con eval). Esto significa que la segunda declaración no hace nada puesto que la variable ya existe (no es considerada una declaración lexica asi que no es un error). Si hubiese una asignación esta sobreescribiría el parametro.

12

(function test(c){
   let c;
   console.log('ok');
})();

Es un caso similar, mismo entorno léxico. Solo que en este caso, si estamos usando una declaración léxica. Y una declaración léxica no puede tener el mismo nombre que cualquier otra variable (de que cualquier tipo) dentro del mismo entorno léxico.

13

(function test(){
   var c;
   var c;
   console.log('ok');
})();

Es más de lo mismo. Podemos escribir la misma declaración cuantas veces querramos, esto no arroja un error porque no es una declaración léxica. La declaración no sobreescribe la siguiente, simplemente ambas declaraciones producen el mismo resultado (la creación de la variable c).

14

(function test(c, c){
   console.log(c);
})(1,2);

En este ejemplo, el segundo parametro c sobreescribe al primer parametro c. Para ser precisos, solo tenemos una variable c pero una vez invocada la función, los argumentos son pasados a sus respectivos parametros. Esto quiere decir que c es 1 primeramente y después es asginado 2 por el segundo parametro.

15

(function test(c, c = 2){
   console.log(c);
})(1);

A diferencia del ejemplo anterior, se incluye un inicializador en el segundo parametro. Si una función contiene parametros complejos (como el operador para el resto de parametros, inicializadores o destructurar un parametro) o esta en modo stricto, tener parametros duplicados resulta en un error. Lo que significa que este código arroja un error por tener el inicializador.

16

(function test(){
   console.log(arguments);
})();

No hay nada de especial en este ejemplo aunque quizás algunos no sepan acerca de la variable arguments dentro de la función. Su propósito es basicamente de proveer los argumentos usados durante la invocación de la función. Esta tarea es precisamente la de los parametros pero es posible que no exista un parametro en concreto para el argumento dado. Através de arguments uno puede acceder a cualquier valor sin importar que exista el parametro o no.

17

(() => console.log(arguments))();

Esta es una función flecha y esta no tiene una variable arguments. La razón de esto es simple, con la llegada de EcmaScript 6 realmente no hay necesidad de usar la variable arguments ya que podemos usar el operador rest para obtener todos los argumentos. Por ejemplo:

((...arguments) => console.log(arguments))();

Aunque es cierto que perdemos acceso a otras facilidades provisionadas por el objeto especial de arguments (que realmente tienen sus propios problemas).

18

(function test(arguments){
   console.log(arguments);
})();

Una de las condiciones para crear el objeto de arguments es que no exista un parametro bajo ese nombre. En este caso, el valor de arguments es el argumento que pasemos atraves de la llamada (undefined si no recibe ningún argumento).

19

(function test(...c){
   console.log(arguments);
})();

Este es un caso especial de arguments, este incluye todos los argumentos recibidos pero es un objeto diferente en esta ocasión, mucho más sencillo. Intentar usar arguments.callee por ejemplo resulta en un error. Cada argumento es grabado en este objeto solo una vez mientras que el otro objeto arguments puede "actualizar" los valores del objeto si algún parametro llegara a cambiar.

20

(function test(){
    let arguments;
    console.log(arguments);
})();

Al igual que tener un parametro con el nombre arguments, tener una declaración lexica evita la construcción del objeto arguments.

21

(function test(){
   var arguments;
   console.log(arguments);
})();

En este caso, arguments se contruye normalmente. La declaración de arguments no hace nada en este caso.

22

(function test(){
   var c;
   let c;
})();

Esto es un error porque no puede exister una declaración léxica con el mismo nombre que cualquier otro tipo de declaración (en el mismo contexto léxico)

23

(function test(){
   var c;
   {
       let c;
   }
})();

Aquí no hay ningún error porque la declaración léxica esta en su propio entorno y no hay ningúna otra declaración con el mismo nombre.

24

(function test(){
   let c;
   {
       var c;
   }
})();

Aquí es necesario entender que a pesar de estar en un { } separado la declaración var c; usa el mismo contexto léxico que let c;. La especificación basicamente hace una distinción entre dos tipos de declaraciónes. Declaraciones lexicas y declaraciones var (quizás podriamos decirles no lexicas o variables pero no tiene mucho sentido en mi opinión). Las declaraciones lexicas pueden estar en cualquier contexto léxico, generalmente delimitado por { }. Entre ellas incluyen, let, const, class, function (dentro de bloques estrictos). Declaraciones var solo tenemos var y function. Estas declaraciones a pesar de estar lexicamente dentro de otros { } son siempre parte del contexto léxico superior de funciones y de scripts (delimitado por el exterior del primer bloque). Por lo tanto var c; está dentro del mismo entorno léxico que let c; y eso no puede ocurrir.

Otra regla importante es que a pesar que las declaraciones lexicas esten en su respectivo contexto léxico libres de colisión, no pueden tomar el mismo nombre que una declaración variable en un bloque subsequente. Es decir:

24

(function test(){
   var c;
   {
       let c;
       {
           var c;
       }
   }
})();

Esto también provoca un error debido a que existe una declaración var en un bloque subsequente.

25

(function test(){
   let c;
   if(false){
       var c;
   }
})();

Una extensión del último ejemplo. Realmente no importa si la declaración está dentro de un bloque dentro de un if, la variable pertenece siempre al contexto léxico superior de la función (sin embargo, si fuera un eval esto no sería un error porque no se evaluaría hasta llegar a ese punto, lo cual nunca ocurre).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment