Artículos de Tecnología

Namespaces: cómo evitar conflictos en código JavaScript

Camila Pêssoa
Camila Pêssoa

Namespaces: cómo evitar conflictos en código JavaScript

imagen 1

Introducción

Imagina que tienes 3 hijas y has decidido ponerles los nombres Ana Clara, Ana Cristina y Ana Paula respectivamente. En cierto momento, las llamas:

-"¡Ana, ven a la sala!"

Pero, ¿a cuál Ana realmente quieres llamar? ¡Ciertamente habrá un conflicto ahí!

gif 1

Fuente : Tumblr

La mejor forma de evitar este conflicto será llamar especificando cuál "Ana":

"¡Ana Clara, ven a la sala!"

De esta manera, todas las hermanas y quienes estén presentes sabrán exactamente a quién te estás refiriendo.

Problemas como este también pueden surgir en programación cuando utilizamos identificadores iguales en variables, funciones y clases que presentan funcionalidades similares. Sin embargo, para resolver este y otros tipos de situaciones, existe un concepto muy popular llamado namespace (espacio de nombres).

Namespace, en términos literales, significa espacio nominal. Pero, ¿qué es en la práctica?

En primer lugar, necesitamos comprender que namespace es un concepto. Esto significa que no existe una única fórmula mágica para su aplicación en JavaScript (JS). Un ejemplo en JS sería crear una variable con un identificador generalista, como let name, en diferentes partes del código para describir, por ejemplo, el nombre de una persona en un formulario. No obstante, cuando trabajamos con bibliotecas externas, no podemos prever si alguna de ellas también va a utilizar alguna variable llamada name en su código interno. Para evitar estos conflictos,existe el concepto de namespaces.

A lo largo de este artículo, descubrirás cómo funcionan los namespaces y cómo aplicarlos en la práctica en tus códigos JavaScript.

¿Lo que son los namespaces?

En el mundo de la informática, los namespaces definen una práctica que consiste en crear un espacio declarativo. Este espacioproporciona un alcance a funciones, variables, clases, objetos, y demás. ¿Todavía aún no lo comprendes?

Un namespace funciona como un tipo de contenedor de identificadores, básicamente, es una manera de organizar tus códigos en grupos que tengan sentido para cualquier persona desarrolladora que los lea.

Dado que es un concepto, el namespace también se puede aplicar en otros contextos, como en sistemas operativos Linux, sistemas de orquestación de contenedores como Kubernetes, y similares. Sin embargo, en este artículo nos centraremos en su aplicación en lenguajes de programación, en particular en JavaScript.

El namespace es una solución que aborda uno de los desafíos más importantes: evitar los problemas de collision names (o en otras palabras, colisión de nombres). Esto se refiere a los conflictos entre elementos como variables, funciones, clases, etc., que comparten identificadores idénticos en su código.

¿Sabías que los conflictos entre nombres iguales son algo común cuando usamos bibliotecas externas o cuando un proyecto se vuelve muy grande?

Esto puede ocurrir cuando declaramos una variable, clase o función que carga el mismo identificador de una biblioteca externa o de nuestra propia base de código. Además, los proyectos crecen de una manera que a veces la creatividad para identificadores se va agotando, ¿verdad?

Esto puede ocurrir cuando declaras una variable, clase o función que tiene el mismo mismo identificador de una biblioteca externa o en nuestra propia base de código. Además, los proyectos crecen de tal manera que, en ocasiones, la creatividad para inventar nuevos identificadores empieza a escasear, ¿verdad?

imagen2

Fuente: Pinterest

De esta manera, si cuentas con una aplicación escalable, es posible que en algún momento necesites aplicar el concepto de namespacing. Especialmente si estás desarrollando el proyecto en colaboración con más de una persona o si planeas dar continuidad a un proyecto en desarrollo.

Sin embargo, además de evitar colisiones entre nombres idénticos, el namespacing es importante también para mantener tu código limpio y fácil de comprender. Esto se debe a que implica la creación de múltiples variables, funciones o clases con nombres distintos, así como la consideración de seleccionar identificadores únicos.

Las personas que desarrollan en C#, C++, PHP o Golang probablemente ya utilizan namespacing en su código. Principalmente porque estos lenguajes, entre otros, ofrecen esta funcionalidad de manera nativa a través de una palabra clave, como la reservada namespace en PHP, C# y C++.

Cuando analizamos el uso de namespaces en los lenguajes mencionados, observamos que su aplicación se da en el contexto de la orientación a objetos para la nomenclatura de clases. Esto significa que puedes nombrar clases con el mismo identificador.

Pero, es importante recordar que el uso del namespacing no está limitado exclusivamente a la programación orientada a objetos o a la nomenclatura de clases. Puede aplicarse también a funciones, variables, objetos, etc. Lo fundamental es mantener la idea central: permitir el uso de un mismo identificador a través de la creación de ámbitos o contextos específicos.

imagen 3

Fuente:Meme generator

El namespace funciona como un conjunto de instrucciones conservadas en una agrupación, y para acceder a ellas debemos utilizar primero el nombre del grupo y luego seleccionar la instrucción que deseamos. Esto constituye una forma de organización, es decir, agrupa instrucciones y las vincula con un identificador.

Hasta este punto, hemos adquirido un entendimiento conceptual de lo que son los namespaces y cómo se emplean en el código. También hemos hablado sobre qué lenguajes los presentan de forma nativa. Sin embargo, aún quedan algunas preguntas por responder, sobre todo la inquietud: "¿cómo puedo crear un namespace en la práctica?"

Y para responder a este tema, vamos a utilizar el lenguaje JavaScript.

gif 2

Fuente: Tenor

Además de ser uno de los lenguajes más populares en la web, escogemos JavaScript porque no tiene una palabra clave específica para la creación de namespaces, como ocurre en otros lenguajes. De esta manera, es posible incorporar y aplicar el concepto de contenerización de identificadores, similar al namespacing, en la práctica. Conozca algunas técnicas más adelante.

Namespaces en JavaScript

Ya comprendemos que el namespace implica una instrucción, como una variable o método, y la mantiene fuera del alcance global del código. El recurso creado como un namespace es invisible en el alcance global y, en contrapartida, permanece accesible solo para los procesos relacionados con su identificador, es decir, con su nombre.

En JavaScript, el namespace es muy interesante, especialmente debido a los temas que involucran el alcance global y el alcance de bloque con variables del tipo var, así como la reasignación de valores con let. Esto se debe a que variables con los mismos nombres pueden tener sus valores sobrescritos si son del tipo var, y dependiendo del alcance, pueden ocasionar un error en su código.

El libro "Eloquent JavaScript" presenta uno de los usos frecuentes de los namespaces que está integrado en el lenguaje. Podemos identificarlos durante la construcción de objetos que poseen recursos nativos, como el objeto Math. En otras palabras, es como si el objeto Math representara un conjunto mayor que contiene todos los métodos relacionados a él.

El Math funciona como una caja de herramientas para datos del tipo number, como el Math.max (máximo) o Math.min (mínimo). El container que agrupa estas funcionalidades del objeto Math proporciona un namespace para que los valores y funciones no se declaren como variables globales en el código. Pero, ¿qué significa eso? Comprenderemos mejor con el siguiente ejemplo:

Imagina que necesitas redondear números fraccionarios a un valor entero. El Math tiene un método para este fin, el round(), y puedes probarlo como se muestra en el siguiente código:

const round = Math.round(20.8);
console.log(round); //21

Sí, te diste cuenta de que no había conflictos entre el identificador round y el método round(). Esto significa que puedo utilizar el identificador round varias veces sin problemas.

¿Vamos a probar de otra forma? Analiza el próximo ejemplo:

const round = 20.8; // variable con identificador round

function round() { //función con identificador round
  return Math.round(round);
}
console.log(round);
console.log(round());

Cuando ejecutamos el código, la consola devuelve el error:

Uncaught SyntaxError: Identifier 'round' has already been declared

El error indica que existe una falla de sintaxis y que el identificador round ya ha sido creado. El conflicto se produce debido a que hemos declarado una variable y una función con el mismo identificador en el alcance global del código. Pero, ¿por qué se origina este problema? Si en el primer caso logramos trabajar con el método y crear una constante.

En la práctica, sabemos de manera intuitiva que es posible declarar una nueva variable en el código con el identificador round, max o min, ya que esto no generará conflictos. Y esto sucede porque los métodos round, floor, max, etc., están conservados dentro del objeto Math. En otras palabras, ¡no necesitamos preocuparnos por sobrescribir nuestros valores! ¿Habías pensado en esto antes?

Aunque JavaScript no proporciona el namespace como funcionalidad por defecto, es posible simular namespaces estáticos y dinámicos siguiendo esta línea de razonamiento.

Vamos a repasar algunas de las formas más conocidas de abordar el namespacing en JS, y esto es mediante la utilización de un prefijo en su identificador.

Namespaces con prefixos o prefix namespacing

La práctica de crear namespaces con prefijos resulta más sencilla en comparación con el uso de objetos o funciones. Además, se trata de un namespace estático, ya que siempre hará referencia a los mismos objetos.

Con el siguiente código, creamos un namespace lógico mediante el uso del mismo prefijo para variables o funciones dentro del mismo namespace*.

//agrega propriedades globales con un prefixo exclusivo
const miApp_digaHola = function () {
  console.log("íHola, Mundo!");
};
const miApp_digaAdios = function () {
  console.log("íAdios!");
};

// llame la función para utilizar las propriedades del namespace
miApp_digaHola();

Salida: "¡Hola, Mundo!"

Aquí la implementación resulta relativamente más sencilla, ya que el prefijo miApp es el encargado de crear el namespace. Una desventaja es que esto puede hacer que el código sea más extenso en palabras, pero soluciona el problema de posibles colisiones de nombres.

Además de esta opción, existen otras maneras de establecer un namespace estático. Una de ellas es a través de asignación utilizando objetos literales. Aunque puede ser un poco más compleja, contribuye a que el código esté más ordenado.

Notación del objeto literal

Otra manera de implementar el namespace es creando un objeto literal para "guardar" directamente funciones y variables que pertenezcan al namespace. Comprueba el código:

//namespace
const estudiante = {
    id: "1",
    nombre: "Carlos",
    situacionMatricula: () => {
        console.log("Alumno Matriculado");
    },
    //funcion
    get_Nota: () => {
        return "A";
    }
};
//imprimir detalles del namespace
console.log("Nombre:",estudiante.nombre);//"Nombre:" "Carlos"
console.log("ID:",estudiante.id);//"ID:" "1"
console.log("Nota:",estudiante.get_Nota());//"Nota:" "A"

Como es posible percibir, el nombre del objeto puede emplearse para acceder a los miembros que se encuentran en el namespace. En el ejemplo, el objeto estudiante es una única variable global y funciona como un namespace para todas sus propiedades.

Para acceder a las propiedades, utilizamos la notación de punto. Esto significa que para acceder a una variable utilizamos estudiante.nombre, y para ejecutar una función usamos estudiante.get_Nota().

También es posible implementar esto mediante la creación de un objeto vacío y después añadir los miembros del namespace a través de la asignación directa. Vamos a verificarlo en el código que se muestra a continuación:

// Objeto con namespace
const estudiante = {};

// Agrega miembros
estudiante.id = "1";
estudiante.nombre = "Carlos";

// Agrega función
estudiante.situacionMatricula = () => {
  console.log("Alumno Matriculado");
};

estudiante.get_Nota = () => {
  return "A";
};

En el código, los elementos se incorporan al objeto estudiante de manera gradual. La gran ventaja de esta práctica es que reduce la contaminación del código fuente a través de los objetos globales y mejora su legibilidad.

Un aspecto importante a destacar es que si llamamos a la función situacion de forma aislada, sin el namespace estudiante, esta no funcionará porque no está disponible en el resto del código, sino únicamente dentro de ese namespace. Además de eso, es posible utilizar el identificador situacion en otras partes del código.

Como podemos notar, lo que ocurre es la creación de un objeto literal con sus propiedades, algo común en JavaScript. Este recurso tiene la capacidad de simular un namespace en el sentido de aislar los nombres de las propiedades. A continuación, examinaremos otro patrón bastante común en JS, conocido como el patrón de módulo.

Namespaces con patrón de módulo o module pattern

En primer lugar, es importante señalar que el patrón de módulo no debe confundirse con los módulos en JavaScript. Entonces, ¿por qué deberías tener un mayor conocimiento acerca de este patrón si el Objeto Literal parece funcionar correctamente?

La notación con objetos literales es un poco más restrictiva, ya que permite agregar las propiedades directamente al objeto. Además, no es posible realizar referencias cruzadas con facilidad. Es en este contexto que se emplea el patrón de módulos, ya que no presenta estas limitaciones y añade la característica de mantener métodos y propiedades en modo privado. Puedes comprobar esto en el código que se presenta a continuación:

const persona = (function () {
  return {
    getNombre: function () {
    const nombre = "Camis";
    return nombre;
    },

    getIdade: function () {
    const idade = 34;
    return idade;
    }
  };
})();

console.log(persona.getNombre()); //”Camis”
console.log(persona.getIdade()); // 34

El patrón de módulo se caracteriza por ser autoejecutable, es lo que llamamos "Immediately Invoked Function Expressions" (en traducción libre, Expresiones de funciones invocadas inmediatamente), y son funciones ejecutadas de inmediato después de su declaración. Esta característica de las funciones IIFE se define mediante los paréntesis () al final de su construcción.

Otra característica relevante es que aunque JavaScript no incluya métodos privados de forma "built-in" (en traducción libre, incorporada), el patrón de módulo también posibilita la creación de métodos que funcionan de manera similar. Es una forma de evitar el escape del ámbito y conflictos de nombres.

Por lo tanto, la adopción de los patrones de módulo se torna más flexible en proyectos amplios, considerando el enfoque utilizado y garantizando una mayor seguridad en la información.

existe una práctica que genera controversia entre las personas desarrolladoras: la implementación de namespaces anidados. Profundizaremos más en este tema a continuación.

Namespaces anidados o nested namespaces

La creación de namespaces anidados es otra práctica común para extender el objeto que abarca el namespace. Básicamente, es un espacio de nombres dentro de otro. Comprueba a continuación:

// namespace
const simples_ns = simples_ns || {};
//creando un namespace anidado
simples_ns.anidado_ns= (function () {
    //objeto dientro del namespace anidado 
    const mas_anidado = {};
    mas_anidado.texto = "Este es un Namespace anidado";
    //definindo la función
    mas_anidado.iniciar = function () {
        console.log("Iniciando un Namespace más anidado");
   };
    //retorno del objeto
    return mas_anidado;
})();
//llamada del metodo por lo namespace anidado
simples_ns.anidado_ns.iniciar();
console.log("Nombre:",simples_ns.anidado_ns.texto)

En el código presentado, se identifica el namespace simples_ns, que puede recibir su propio valor o un objeto vacío. A continuación, está el namespace simples_ns.anidado_ns, que es una función que almacena otro namespace en su interior, llamado mas_anidado; finalmente, se devuelve su valor.

Algunas personas desarrolladoras comprenden los namespaces anidados como una mala práctica y cuestionan su uso, justificando que es una práctica compleja para evitar colisiones de identificadores. Justificación la utilización de los namespaces anidados, diciendo que es similar a una herencia del lenguaje Java, con sus convenciones de nomenclatura de los packages, más que una solución realmente pensada de acuerdo con la estructura de JavaScript. En este sentido, siempre vale la pena reflexionar sobre si una práctica en particular tiene sentido en tu proyecto, porque es común que una solución sencilla sea suficiente en algunos contextos.

gif3

Fuente: Tenor

Por otro lado, como ya hemos identificado la notación de objeto literal, hay maneras de utilizar la estructura de JavaScript para la creación de namespaces, y una práctica es utilizando el this, conprueba en el tópico adelante.

Namespaces y el this

En el código a continuación, identificaremos un namespace dinámico utilizando la palabra clave this y el método apply. Además de eso, lo que califica al namespace como dinámico es el hecho de ser referenciado dentro de un wrapper (envoltura, en traducción libre) de la función, eliminando la necesidad del return.

El uso del recurso this puede no parecer muy claro al principio, pero es muy útil para la aplicación de namespaces, ya que es un recurso propio del lenguaje y será utilizado de forma que fue proyectado. Comprobemos el código:

const miApp = {};
(function () {
  let id = 0;
  this.proxima = function () {
    return id++;
  };
  this.redefinir = function () {
    id = 0;
  };
}.apply(miApp));

console.log(
  miApp.proxima(),
  miApp.proxima(),
  miApp.redefinir(),
  miApp.proxima()
);

La salida será : //0,1,undefined,0

En general, el código define un contador simple que puede ser incrementado y asume el valor de cero utilizando las funciones proxima y redefinir en el objeto miApp. Pero, vamos a analizar paso a paso:

  1. El código tiene un único objeto global, miApp, que inicialmente es un objeto vacío.
  2. Se define y se invoca inmediatamente una función anónima utilizando el método apply. El método apply (nativo de JavaScript) permite que la función sea invocada con un valor this específico, en este caso, el objeto miApp.
  3. Dentro de la función anónima, se define una variable id con un valor de 0.
  4. Se definen y agregan dos funciones al objeto miApp: proxima y redefinir. La función proxima retorna el valor de id y lo incrementa en 1 cada vez que es llamada. La función redefinir redefine el valor de id a 0.
  5. La instrucción console.log al final del código llama a las funciones proxima y redefinir en el objeto miApp y registra los valores retornados.

Para profundizar en la comprensión del código, el método apply() funciona para llamar una función que presenta un valor this y argumentos como un array. La palabra clave this se aplica para hacer referencia al objeto al cual fue llamado en el contexto de ejecución. Y, en la coyuntura del código anterior, junto con el método apply, logramos crear un namespace. ¡Muy práctico, ¿verdad?!

Namespace vs Modulo

Aprendimos sobre diversas formas de crear namespaces para organizar el código. Sin embargo, al leer este texto, en algún momento puedes pensar: "¿Pero y los módulos? ¿No pueden funcionar como namespace?"

Este recurso bien característico del JavaScript parece tener mucha similitud con el concepto de namespace, pero no son lo mismo.

Un módulo es una forma de organizar el código en archivos separados y puedes ejecutarlos en un ámbito local. Para utilizar los módulos, JavaScript trabaja con las palabras clave import y export, y Node.JS también utiliza la función require() y el objeto global module.exports.

Según el libro "You Don't Know JavaScript", un módulo es una colección de archivos y funciones relacionadas, caracterizadas por la división entre detalles ocultos, privados o accesibles de forma pública, que son normalmente llamadas "API públicas". Además de eso, el módulo es stateful, lo que significa que mantiene algunas informaciones y posee la funcionalidad de accederlas y actualizarlas.

Por el contrario, los namespaces funcionan como un agrupamiento stateless (sin estado, es decir, recursos aislados), porque se trata de un conjunto de funciones sin datos. Los namespaces no hacen uso del encapsulamiento de la misma forma que los módulos.

También es posible usar los módulos ESM (es decir, import/export) para crear el llamado namespace import. Para ejemplificar, vamos a crear una calculadora con suma y resta en un archivo calculadora.js y exportar dos funciones con la palabra clave export. A continuación, el código:

// calculadora.js
export function sumar(a, b) {
  return a + b;
}

export function restar(a, b) {
  return a - b;
}

Después de eso, creamos otro archivo llamado main.js y importamos con la palabra clave import:

// main.js
import * as calculadora from './calculadora.js';

console.log(calculadora.sumar(1, 2)); // 3
console.log(calculadora.restar(1, 2)); // -1

En este ejemplo, tenemos un archivo llamado calculadora.js que exporta dos funciones: sumar y restar. En el archivo main.js, utilizamos la instrucción import para importar todas las exportaciones de calculadora.js y asignarlas a un objeto llamado calculadora. Así, podemos utilizar el objeto calculadora para acceder a las funciones importadas, como calculadora.sumar y calculadora.restar.

Es importante tener en cuenta que la instrucción de importación, import, debe estar en la parte superior del archivo, y la instrucción de exportación, export, debe estar en el archivo desde el cual se está exportando.

Conclusión

A lo largo de nuestra lectura, nos dimos cuenta de la utilidad de los namespaces para evitar colisiones de nombres y organizar nuestro código a partir del agrupamiento de miembros relacionados. Además de evitar posibles errores o la sobrescritura de información, esto es especialmente útil cuando trabajamos con un equipo grande o cuando la base de código tiene muchas bibliotecas.

Comprendemos que los namespaces permiten que utilicemos un mismo identificador para más de una clase, variable o función, lo que facilita la práctica del desarrollo, especialmente cuando hay códigos muy extensos.

También entendemos que aunque algunos lenguajes tengan el recurso del namespace de forma nativa, JavaScript no tiene este soporte por defecto. Sin embargo, es posible superar estas limitaciones utilizando los recursos presentes en el propio lenguaje, como el uso de objetos literales, IIFEs y módulos.

Ahora eres capaz de aplicar la práctica del namespace con JavaScript e incluso reconocer cuándo estás utilizando el concepto en tu código. Además, te has dado cuenta de que no hay una única forma de crear un namespace, y esto puede variar según tu proyecto, ya que la elección de enfoque garantizará un uso eficiente de los recursos del namespace.

¡Gracias por llegar hasta aquí! Continúa tu viaje de aprendizaje sobre los namespaces en JavaScript accediendo a las referencias, libros y artículos mencionados a lo largo de la lectura.

¡Buenos estudios y hasta luego!

autora

Camila Pessôa

¡Hola, soy Camila Pessôa! Tengo 33 años, soy madre y me adentré en el área de tecnología a través de la robótica educativa. Participé en el Bootcamp { Reprograma } con enfoque en Back-End / Node.js y estudio Sistema de Información. Actualmente, formo parte del Scuba-Team y siento una gran pasión por la educación y la tecnología, porque creo que esta combinación es ¡transformadora!

Artículos de Tecnología

En Alura encontrarás variados cursos sobre . ¡Comienza ahora!

Precios en:
USD
  • USD
  • BOB
  • CLP
  • COP
  • USD
  • PEN
  • MXN
  • UYU

Semestral

  • 274 cursos

    Cursos de Programación, Front End, Data Science, Innovación y Gestión.

  • Videos y actividades 100% en Español
  • Certificado de participación
  • Estudia las 24 horas, los 7 días de la semana
  • Foro y comunidad exclusiva para resolver tus dudas
  • Luri, la inteligencia artificial de Alura

    Luri es nuestra inteligencia artificial que resuelve dudas, da ejemplos prácticos y ayuda a profundizar aún más durante las clases. Puedes conversar con Luri hasta 100 mensajes por semana

  • Acceso a todo el contenido de la plataforma por 6 meses
US$ 65.90
un solo pago de US$ 65.90
¡QUIERO EMPEZAR A ESTUDIAR!

Paga en moneda local en los siguientes países

Anual

  • 274 cursos

    Cursos de Programación, Front End, Data Science, Innovación y Gestión.

  • Videos y actividades 100% en Español
  • Certificado de participación
  • Estudia las 24 horas, los 7 días de la semana
  • Foro y comunidad exclusiva para resolver tus dudas
  • Luri, la inteligencia artificial de Alura

    Luri es nuestra inteligencia artificial que resuelve dudas, da ejemplos prácticos y ayuda a profundizar aún más durante las clases. Puedes conversar con Luri hasta 100 mensajes por semana

  • Acceso a todo el contenido de la plataforma por 12 meses
US$ 99.90
un solo pago de US$ 99.90
¡QUIERO EMPEZAR A ESTUDIAR!

Paga en moneda local en los siguientes países

Bootcamp Back End

  • 274 cursos

    Cursos de Programación, Front End, Data Science, Innovación y Gestión.

  • Videos y actividades 100% en Español
  • Certificado de participación
  • Estudia las 24 horas, los 7 días de la semana
  • Foro y comunidad exclusiva para resolver tus dudas
  • Luri, la inteligencia artificial de Alura

    Luri es nuestra inteligencia artificial que resuelve dudas, da ejemplos prácticos y ayuda a profundizar aún más durante las clases. Puedes conversar con Luri hasta 100 mensajes por semana

  • Acceso a la Formación - Aprende Java con Orientación a Objetos - Bootcamp Back End Java

    Aprende a programar en uno de los principales lenguajes de programación para el back-end en el mercado: Java. En este módulo, tendrás la oportunidad de conocer las principales bibliotecas de Java y dominar el paradigma de la Orientación a Objetos.

  • Acceso a todo el contenido de la plataforma por 6 meses
US$ 149.90
un solo pago de US$ 149.90
¡QUIERO EMPEZAR A ESTUDIAR!

Paga en moneda local en los siguientes países

Acceso a todos
los cursos

Estudia las 24 horas,
dónde y cuándo quieras

Nuevos cursos
cada semana