Variables estáticas en JavaScript
¿Cómo puedo crear variables estáticas en Javascript?
Si proviene de un lenguaje orientado a objetos, de tipo estático y basado en clases (como Java, C++ o C#), supongo que está intentando crear una variable o método asociado a un "tipo" pero no a una instancia.
Un ejemplo que utiliza un enfoque "clásico", con funciones de constructor, tal vez podría ayudarle a comprender los conceptos de JavaScript OO básico:
function MyClass () { // constructor function
var privateVariable = "foo"; // Private variable
this.publicVariable = "bar"; // Public variable
this.privilegedMethod = function () { // Public Method
alert(privateVariable);
};
}
// Instance method will be available to all instances but only load once in memory
MyClass.prototype.publicMethod = function () {
alert(this.publicVariable);
};
// Static variable shared by all instances
MyClass.staticProperty = "baz";
var myInstance = new MyClass();
staticProperty
está definido en el objeto MyClass (que es una función) y no tiene nada que ver con sus instancias creadas, JavaScript trata las funciones como objetos de primera clase , por lo que al ser un objeto, puedes asignar propiedades a una función.
ACTUALIZACIÓN: ES6 introdujo la capacidad de declarar clases mediante la class
palabra clave. Es azúcar de sintaxis sobre la herencia basada en prototipos existente.
La static
palabra clave le permite definir fácilmente propiedades o métodos estáticos en una clase.
Veamos el ejemplo anterior implementado con clases de ES6:
class MyClass {
// class constructor, equivalent to
// the function body of a constructor
constructor() {
const privateVariable = 'private value'; // Private variable at the constructor scope
this.publicVariable = 'public value'; // Public property
this.privilegedMethod = function() {
// Public Method with access to the constructor scope variables
console.log(privateVariable);
};
}
// Prototype methods:
publicMethod() {
console.log(this.publicVariable);
}
// Static properties shared by all instances
static staticProperty = 'static value';
static staticMethod() {
console.log(this.staticProperty);
}
}
// We can add properties to the class prototype
MyClass.prototype.additionalMethod = function() {
console.log(this.publicVariable);
};
var myInstance = new MyClass();
myInstance.publicMethod(); // "public value"
myInstance.additionalMethod(); // "public value"
myInstance.privilegedMethod(); // "private value"
MyClass.staticMethod(); // "static value"
Podrías aprovechar el hecho de que las funciones JS también son objetos, lo que significa que pueden tener propiedades.
Por ejemplo, citando el ejemplo dado en el artículo (ahora desaparecido) Variables estáticas en Javascript :
function countMyself() {
// Check to see if the counter has been initialized
if ( typeof countMyself.counter == 'undefined' ) {
// It has not... perform the initialization
countMyself.counter = 0;
}
// Do something stupid to indicate the value
alert(++countMyself.counter);
}
Si llamas a esa función varias veces, verás que el contador se incrementa.
Y esta es probablemente una solución mucho mejor que contaminar el espacio de nombres global con una variable global.
Aquí hay otra posible solución, basada en un cierre: Truco para usar variables estáticas en javascript :
var uniqueID = (function() {
var id = 0; // This is the private persistent value
// The outer function returns a nested function that has access
// to the persistent value. It is this nested function we're storing
// in the variable uniqueID above.
return function() { return id++; }; // Return and increment
})(); // Invoke the outer function after defining it.
Lo que le da el mismo tipo de resultado, excepto que esta vez se devuelve el valor incrementado, en lugar de mostrarse.
Lo haces a través de un IIFE (expresión de función inmediatamente invocada):
var incr = (function () {
var i = 1;
return function () {
return i++;
}
})();
incr(); // returns 1
incr(); // returns 2
He visto un par de respuestas similares, pero me gustaría mencionar que esta publicación las describe mejor, así que me gustaría compartirlas con ustedes.
Aquí hay un código extraído de él, que modifiqué para obtener un ejemplo completo que, con suerte, beneficiará a la comunidad porque puede usarse como plantilla de diseño para clases.
También responde a tu pregunta:
function Podcast() {
// private variables
var _somePrivateVariable = 123;
// object properties (read/write)
this.title = 'Astronomy Cast';
this.description = 'A fact-based journey through the galaxy.';
this.link = 'http://www.astronomycast.com';
// for read access to _somePrivateVariable via immutableProp
this.immutableProp = function() {
return _somePrivateVariable;
}
// object function
this.toString = function() {
return 'Title: ' + this.title;
}
};
// static property
Podcast.FILE_EXTENSION = 'mp3';
// static function
Podcast.download = function(podcast) {
console.log('Downloading ' + podcast + ' ...');
};
Dado ese ejemplo, puede acceder a las propiedades/funciones estáticas de la siguiente manera:
// access static properties/functions
console.log(Podcast.FILE_EXTENSION); // 'mp3'
Podcast.download('Astronomy cast'); // 'Downloading Astronomy cast ...'
Y las propiedades/funciones del objeto simplemente como:
// access object properties/functions
var podcast = new Podcast();
podcast.title = 'The Simpsons';
console.log(podcast.toString()); // Title: The Simpsons
console.log(podcast.immutableProp()); // 123
Tenga en cuenta que en podcast.immutableProp() tenemos un cierre : la referencia a _somePrivateVariable se mantiene dentro de la función.
Incluso puedes definir captadores y definidores . Eche un vistazo a este fragmento de código (donde d
está el prototipo del objeto para el que desea declarar una propiedad, y
es una variable privada no visible fuera del constructor):
// getters and setters
var d = Date.prototype;
Object.defineProperty(d, "year", {
get: function() {return this.getFullYear() },
set: function(y) { this.setFullYear(y) }
});
Define la propiedad d.year
mediante get
y set
funciones: si no especifica set
, entonces la propiedad es de solo lectura y no se puede modificar (tenga en cuenta que no recibirá un error si intenta configurarla, pero no tiene ningún efecto). Cada propiedad tiene los atributos writable
( configurable
permite cambiar después de la declaración) y enumerable
(permite usarlo como enumerador), que son por defecto false
. Puede configurarlos mediante defineProperty
el tercer parámetro, por ejemplo enumerable: true
.
Lo que también es válido es esta sintaxis:
// getters and setters - alternative syntax
var obj = { a: 7,
get b() {return this.a + 1;},
set c(x) {this.a = x / 2}
};
que define una propiedad de lectura/escritura a
, una propiedad de solo lectura b
y una propiedad de solo escritura c
, a través de las cuales a
se puede acceder a la propiedad.
Uso:
console.log(obj.a); console.log(obj.b); // output: 7, 8
obj.c=40;
console.log(obj.a); console.log(obj.b); // output: 20, 21
Notas:
Para evitar comportamientos inesperados en caso de que haya olvidado la new
palabra clave, le sugiero que agregue lo siguiente a la función Podcast
:
// instantiation helper
function Podcast() {
if(false === (this instanceof Podcast)) {
return new Podcast();
}
// [... same as above ...]
};
Ahora las dos instancias siguientes funcionarán como se esperaba:
var podcast = new Podcast(); // normal usage, still allowed
var podcast = Podcast(); // you can omit the new keyword because of the helper
La declaración 'nueva' crea un nuevo objeto y copia todas las propiedades y métodos, es decir
var a=new Podcast();
var b=new Podcast();
a.title="a"; b.title="An "+b.title;
console.log(a.title); // "a"
console.log(b.title); // "An Astronomy Cast"
Tenga en cuenta también que en algunas situaciones puede resultar útil utilizar la return
declaración en la función constructora Podcast
para devolver un objeto personalizado que protege las funciones en las que la clase depende internamente pero que deben exponerse. Esto se explica con más detalle en el capítulo 2 (Objetos) de la serie de artículos.
Puedes decir eso a
y b
heredar de Podcast
. Ahora, ¿qué sucede si desea agregar un método a Podcast que se aplique a todos ellos después de a
haber b
sido instanciados? En este caso, utilice lo .prototype
siguiente:
Podcast.prototype.titleAndLink = function() {
return this.title + " [" + this.link + "]";
};
Ahora llama a
y b
otra vez:
console.log(a.titleAndLink()); // "a [http://www.astronomycast.com]"
console.log(b.titleAndLink()); // "An Astronomy Cast [http://www.astronomycast.com]"
Puede encontrar más detalles sobre los prototipos aquí . Si desea hacer más herencia, le sugiero que investigue esto .
Es muy recomendable leer la serie de artículos que he mencionado anteriormente , ya que también incluyen los siguientes temas:
- Funciones
- Objetos
- Prototipos
- Aplicación de funciones nuevas en el constructor
- Izar
- Inserción automática de punto y coma
- Propiedades y métodos estáticos
Tenga en cuenta que la "función" de inserción automática de punto y coma de JavaScript (como se menciona en 6.) es muy a menudo responsable de causar problemas extraños en su código. Por lo tanto, prefiero considerarlo como un error que como una característica.
Si desea leer más, aquí hay un artículo de MSDN bastante interesante sobre estos temas, algunos de los que se describen allí brindan aún más detalles.
Lo que también es interesante de leer (que también cubren los temas mencionados anteriormente) son los artículos de la Guía de JavaScript de MDN :
- Una reintroducción a JavaScript
- Trabajar con objetos
Si desea saber cómo emular parámetros de C#out
(como en DateTime.TryParse(str, out result)
) en JavaScript, puede encontrar un código de muestra aquí.
Aquellos de ustedes que trabajan con IE (que no tiene consola para JavaScript a menos que abran las herramientas de desarrollador F12y abran la pestaña de la consola) pueden encontrar útil el siguiente fragmento. Le permite utilizarlo console.log(msg);
como se utiliza en los ejemplos anteriores. Simplemente insértelo antes de la Podcast
función.
Para su comodidad, aquí está el código anterior en un único fragmento de código completo:
Mostrar fragmento de código
Notas:
Puede encontrar algunos buenos consejos, sugerencias y recomendaciones sobre la programación de JavaScript en general aquí (mejores prácticas de JavaScript) y allá ('var' versus 'let') . También se recomienda este artículo sobre encasillamientos implícitos (coerción) .
Una forma conveniente de utilizar clases y compilarlas en JavaScript es TypeScript. Aquí tienes un área de juegos donde puedes encontrar algunos ejemplos que te muestran cómo funciona. Incluso si no estás usando TypeScript en este momento, puedes echarle un vistazo porque puedes comparar TypeScript con el resultado de JavaScript en una vista lado a lado. La mayoría de los ejemplos son simples, pero también hay un ejemplo de Raytracer que puedes probar al instante. Recomiendo especialmente mirar los ejemplos de "Uso de clases", "Uso de herencia" y "Uso de genéricos" seleccionándolos en el cuadro combinado; estas son buenas plantillas que puedes usar instantáneamente en JavaScript. Typecript se utiliza con Angular.
Para lograr la encapsulación de variables locales, funciones, etc. en JavaScript, sugiero usar un patrón como el siguiente (JQuery usa la misma técnica):
<html>
<head></head>
<body><script>
'use strict';
// module pattern (self invoked function)
const myModule = (function(context) {
// to allow replacement of the function, use 'var' otherwise keep 'const'
// put variables and function with local module scope here:
var print = function(str) {
if (str !== undefined) context.document.write(str);
context.document.write("<br/><br/>");
return;
}
// ... more variables ...
// main method
var _main = function(title) {
if (title !== undefined) print(title);
print("<b>last modified: </b>" + context.document.lastModified + "<br/>");
// ... more code ...
}
// public methods
return {
Main: _main
// ... more public methods, properties ...
};
})(this);
// use module
myModule.Main("<b>Module demo</b>");
</script></body>
</html>
Por supuesto, puede (y debe) colocar el código del script en un *.js
archivo separado; esto está escrito en línea para que el ejemplo sea breve.
Las funciones de autoinvocación (también conocidas como IIFE = expresión de función invocada inmediatamente) se describen con más detalle aquí .
puedes usar arguments.callee para almacenar variables "estáticas" (esto también es útil en funciones anónimas):
function () {
arguments.callee.myStaticVar = arguments.callee.myStaticVar || 1;
arguments.callee.myStaticVar++;
alert(arguments.callee.myStaticVar);
}