costaricajs / Fundamentos-de-JavaScript

:seedling: Libro de Fundamentos de JavaScript
http://bit.ly/fundamentos-javascript
15 stars 8 forks source link

Ambito y Contexto de Ejecución #19

Closed josoroma-zz closed 8 years ago

josoroma-zz commented 9 years ago

Hice como una recopilacion de un poco de conceptos de varios lados y los pude transformar en algo como:

Ambito y Contexto de Ejecución

Es muy fácil confundir el ámbito con el contexto, pero no son lo mismo. Para ser justos, el tema es menos turbio si partimos y escarbamos cada concepto por separado.

Básicamente, por cada invocación a una función se asocia un:

El ámbito es un acceso único a la variable de una Función cuando esta se invoca.

Contexto

El contexto siempre es el valor de this, que es una referencia al objeto que "posee" el código que se esté ejecutando en el momento.

Ambiente de Variables

Una variable se puede definir en cualquier ámbito:

esto establece el acceso a las variables en diferentes ámbitos durante el tiempo de ejecución.

Globales

Cualquier variable global declarada fuera del cuerpo de una función vivirá durante todo el tiempo de ejecución y puede ser accesada y alterada desde cualquier ámbito.

Locales

Las variables locales sólo existen dentro del cuerpo de la función en que hayan sido declaradas y siempre tendrán un ámbito diferente por cada invocación que se realice a la función donde viven. Por lo tanto, sólo pueden ser recuperadas, asignadas y manipuladas dentro de esa llamada y no son accesibles fuera de ese ámbito.

Ambito de Bloque / "Block Scope"

Es la capacidad de definir una variable para el ámbito de un bloque:

Motores de JavaScript compatibles con el estándar ECMAScript 5 no son amigables con el concepto de "Ambito de Bloque". Sin embargo el estándar ECMAScript 2015 (ES6) sí lo soporta con la introducción y el uso de la sentencia let.

Dentro de la Compatibilidad con el estándar ECMAScript 5 y usando var permite que las variables definidas dentro de un bloque sean accesibles fuera del bloque.

Brendan Eich: 10 días no dejaron tiempo para habilitar el ámbito de bloque. También muchos "lenguajes de script" de a mediados de los años 90's con costos tenían ámbito, además más tarde crecieron.

var soyGlobal = 'Vivo en el ámbito global';

if (true) {
  soyGlobal = 'Sigo viviendo en el ámbito global.'
  var soyDeBloque = 'Soy global, pero nací dentro de un bloque.';
}

soyDeBloque = 'Siempre he sido global.'

console.log(soyGlobal);    // Sigo viviendo en el ámbito global.
console.log(soyDeBloque);  // Siempre he sido global.

Dentro de la Compatibilidad con el estándar ECMAScript 2015 (ES6) y usando let cualquier variable local con ámbito de bloque es imposible de acceder desde afuera de la apertura y el cierre de llaves de un bloque. Por el momento no vamos a abarcar las bondades de ES6 pero para muestra tenemos un botón:

let soyGlobal = 'Vivo en el ámbito global';

if (true) {
  // var soyGlobal; // Duplicate declaration "soyGlobal".
  let soyGlobal = 'Vivo en el ámbito local a un bloque y soy diferente al global.'
  let soyDeBloque = 'Vivo en el ámbito local a un bloque.';
}

console.log(soyGlobal);    // Vivo en el ámbito global.

soyDeBloque = 'Yo no estoy seguro de nada.'

console.log(soyDeBloque);  // soyDeBloque is not defined.

Peculiaridades

Javascript es un lenguaje bastante interesante, siempre y cuando seamos conscientes de sus peculiaridades.

var clan = ['Hattori Hanzō', 'Fūma Kotarō', 'Fujibayashi Nagato'];

for(var i = 0; i < clan.length; i++) {
  var ninja = clan[i];
  console.log(ninja);
}
// Hattori Hanzō.
// Fūma Kotarō.
// Fujibayashi Nagato.

Funciones de Primer Orden / "First Class Citizens"

El siguiente fragmento de código utiliza: Funciones de Primer Orden. Las funciones son valores que se pueden asignar o empujar en una variable, incluyendo los arreglos también.

var clan = ['Hattori Hanzō', 'Fūma Kotarō', 'Fujibayashi Nagato'];
var arregloDeFuncionesDePrimeraClase = [];

for (var i = 0; i < clan.length; i++) {
  var ninja = clan[i];

  /**
   * Se construye un arreglo llamado arregloDeFuncionesDePrimeraClase con funciones adentro
   * que imprimen un elemento de la lista.
   */
  arregloDeFuncionesDePrimeraClase.push(function() {
    console.log('ninja => ', ninja);
  });
}

/**
 * Mediante `arregloDeFuncionesDePrimeraClase` es que podremos elegir e
 * invocar cualquierade las funciones del arreglo construido en el bucle for.
 *
 * ¿En el fondo esto tiene buena pinta?
 */

// arregloDeFuncionesDePrimeraClase =>  [ [Function], [Function], [Function] ]
console.log('arregloDeFuncionesDePrimeraClase => ', arregloDeFuncionesDePrimeraClase);

// arregloDeFuncionesDePrimeraClase.length =>  3
console.log('arregloDeFuncionesDePrimeraClase.length => ', arregloDeFuncionesDePrimeraClase.length);

// Invoca la primera función del arreglo de funciones.
arregloDeFuncionesDePrimeraClase[0](); // ninja =>  Fujibayashi Nagato.

// Invoca la segunda función.
arregloDeFuncionesDePrimeraClase[1](); // ninja =>  Fujibayashi Nagato.

// Invoca la última.
arregloDeFuncionesDePrimeraClase[2](); // ninja =>  Fujibayashi Nagato.

// Invoca una función inexistente dentro del arreglo de funciones.
arregloDeFuncionesDePrimeraClase[3]();
// TypeError: arregloDeFuncionesDePrimeraClase[3] is not a function.

Hmm, todas las funciones invocadas imprimieron el valor: Fujibayashi Nagato.

Ambito Artificial

Implica crear una nueva función que debe ser automáticamente invocada de inmediato, también conocida como una función anónima auto-invocada.

var ninjas = ['Hattori Hanzō', 'Fūma Kotarō', 'Fujibayashi Nagato'];
var clan = [];

for (var i = 0; i < ninjas.length; i++) {
  /**
  * Define una función anónima e inmediatamente la invoca.
  */
  (function() {
    var ninja = ninjas[i];
    clan.push(function() {
      console.log(ninja);
    });
  }());
}

// Sólo una alternativa que sirve para optimizar el bucle for.
for (var i = 0, l = ninjas.length; i !== l; i++) {
  clan[i]();
}
// Hattori Hanzō.
// Fūma Kotarō.
// Fujibayashi Nagato.

Lo que sucedió es que por cada iteración del bucle for se utiliza un ámbito nuevo, que contiene una variable ninja propia, lo que permite que las funciones referencien a un ninja diferente. Por esta razón, al invocar clan[2]() se obtiene el resultado pensado.

El comportamiento de "this"

En general el contexto se determina por cómo se invoca una función. Cuando una función se invoca como un método de un objeto, this referencia al objeto del método del cual es llamado.

Un objeto literal es una lista que envuelve entre llaves una colección de pares de nombre:valor separados por coma.

var autor = {
  nombre: 'Édith Piaf',
  cancion: 'La Vie En Rose',
  pais: 'Francia'
}

Los valores de las propiedades de un objeto literal pueden ser de cualquier tipo de datos, por ejemplo: arreglos, funciones, incluso otros objetos literales anidados.

var unObjetoLiteral = {
  compare: function(){
    console.log(this === unObjetoLiteral);    
  }
};

unObjetoLiteral.compare(); // true

El mismo principio se aplica al invocar una función con el operador new que sirve para crear una nueva instancia de un objeto. Cuando se invoca de esta manera, el valor de this dentro del ámbito de la función referencia a la instancia recién creada:

function invocar() {
  console.log(' => ', this);
}

invocar()       // => Window Object. Undefined en modo 'strict'.

new invocar()   // => invocar {}.

Pila de Contextos de Ejecución

JavaScript es un lenguaje que corre sobre un único hilo, es decir, sólo una tarea a la vez se puede correr. Cuando el motor de JavaScript corre código, entra por primera vez a un contexto de ejecución global. Cada invocación de una función a partir de este punto dará lugar a la creación de un nuevo contexto de ejecución.

Conceptos clave sobre la Pila de Contextos de Ejecución:

// Contexto de Ejecucion Global.

function user() {          // Nuevo Contexto de Ejecución.

  function email() {       // Nuevo Contexto de Ejecución.
    // ...
  }

  function geolocation() { // Nuevo Contexto de Ejecución.
    // ...
  }
}

El término "contexto de ejecución" en realidad se refiere más al ámbito y no al contexto.

+------------------------------+
| contexto de ejecución actual |
+------------------------------+
| contexto de ejecución n + 2  |
+------------------------------+
| contexto de ejecución n + 1  |
+------------------------------+
| contexto de ejecución global |
+------------------------------+

Pila de Ejecución

Cada vez que un nuevo contexto de ejecución se crea, este se agrega a la parte superior de la pila de ejecución. Nuestro navegador Web siempre ejecutará el contexto de ejecución actual, el que esté en la cima de la pila de ejecución. Una vez completado, se retira de la parte superior de la pila y el control volverá al contexto de ejecución siguiente.

El contexto de ejecución se puede dividir en dos fases:

Objeto de Activación [OA] / Variable de Objecto [VO]

En la fase de creación, el intérprete de JavaScript lo primero que hace es crear una variable de objeto, también llamada objeto de activación, que se compone de todas las variables, declaraciones de funciones y argumentos definidos dentro de ese contexto de ejecución. A partir de ahí la cadena de ámbito/scope chain se inicializa, por último se determina el valor de this. Seguidamente, en la fase de ejecución, el código es interpretado y ejecutado.

La cadena de ámbito

Para cada contexto de ejecución hay una cadena de ámbito junto con este. La cadena de ámbito contiene la variable objeto por cada contexto de ejecución en la pila de ejecución. Se utiliza para determinar el acceso a variables y para la resolución de identificadores. Por ejemplo:

var cero = 0;

function primero() {
  var uno = 1;
  segundo();

  function segundo() {
    var dos = 2;
    tercero();

    function tercero() {
      var tres = 3;
      var uno = 'uno';
      cuarto();

      /**
       * cuarto() tiene acceso a todas las variables globales y también a
       * todas las variables definidas dentro de todas las funciones anteriores.
       */
      function cuarto() {
        console.log(cero);  // 0
        console.log(uno);   // uno
        console.log(dos);   // 2
        console.log(tres);  // 3
      }
    }
  }   
}

primero();

Cada vez que se intente acceder a una variable desde el contexto de ejecución de una función, el proceso de consulta siempre comenzará con su propia variable objeto. Si el identificador no se encuentra en su propia variable objeto, la búsqueda continúa en la cadena de ámbito. Sube por la cadena de ámbito y examina la variable objeto de cada contexto de ejecución en busca de una coincidencia con el nombre de la variable.

Espero no estar "batiando" mucho (y).

josoroma-zz commented 9 years ago

Creo que de vez en cuando podria ser apropiado colar algun ejemplo de ES6, ver una comparacion/contraste podria ser bastante interesante o inspirador para ir traveseando cosas "nuevas".

josoroma-zz commented 9 years ago

A veces cuando robo, re-uso o transformo buenas ideas o un concepto de otro lado me siento medio "guilty" de hacerlo sin dar credito, en agradeciemiento a fuentes de inspiracion "gugliadas" podriamos colocar al final de cada tema algo como recursos o fuentes o algo asi.

gaboesquivel commented 9 years ago

gracias @josoroma, necesitamos organizar esta información,

uno de los objetivos del libro es que sea un guía práctica concisa con ejemplos en ES6 y ES5

Por favor organiza la información dentro de la categorización con la cual hemos iniciado o propone cambios a la categorización. Haz un Pull Request por cambio lógico siguiendo el github flow así podemos discutir cambios a la redacción y otros detalles cada pull request.

gaboesquivel commented 8 years ago

ver pull requests