Buscar..
Sintaxis
- clase Foo {}
- clase Foo extiende Bar {}
- clase Foo {constructor () {}}
- clase Foo {myMethod () {}}
- clase Foo {get myProperty () {}}
- clase Foo {set myProperty (newValue) {}}
- clase Foo {estática myStaticMethod () {}}
- clase Foo {static get myStaticProperty () {}}
- const Foo = clase Foo {};
- const Foo = clase {};
Observaciones
class
soporte de class
solo se agregó a JavaScript como parte del estándar 2015 es6 .
Las clases de Javascript son azúcar sintáctica sobre la herencia basada en un prototipo ya existente de JavaScript. Esta nueva sintaxis no introduce un nuevo modelo de herencia orientado a objetos a JavaScript, solo una forma más sencilla de tratar con los objetos y la herencia. Una declaración de class
es esencialmente una abreviatura para definir manualmente una function
constructor y agregar propiedades al prototipo del constructor. Una diferencia importante es que las funciones se pueden llamar directamente (sin la new
palabra clave), mientras que una clase llamada directamente generará una excepción.
class someClass {
constructor () {}
someMethod () {}
}
console.log(typeof someClass);
console.log(someClass);
console.log(someClass === someClass.prototype.constructor);
console.log(someClass.prototype.someMethod);
// Output:
// function
// function someClass() { "use strict"; }
// true
// function () { "use strict"; }
Si está utilizando una versión anterior de JavaScript, necesitará un transpiler como babel o google- closing -compiler para compilar el código en una versión que la plataforma de destino pueda entender.
Clase constructor
La parte fundamental de la mayoría de las clases es su constructor, que configura el estado inicial de cada instancia y maneja todos los parámetros que se pasaron al llamar new
.
Se define en un bloque de class
como si estuviera definiendo un método llamado constructor
, aunque en realidad se trata como un caso especial.
class MyClass {
constructor(option) {
console.log(`Creating instance using ${option} option.`);
this.option = option;
}
}
Ejemplo de uso:
const foo = new MyClass('speedy'); // logs: "Creating instance using speedy option"
Una pequeña cosa a tener en cuenta es que un constructor de clase no puede hacerse estático a través de la palabra clave static
, como se describe a continuación para otros métodos.
Métodos estáticos
Los métodos y las propiedades estáticas se definen en la clase / el constructor en sí , no en los objetos de instancia. Estos se especifican en una definición de clase utilizando la palabra clave static
.
class MyClass {
static myStaticMethod() {
return 'Hello';
}
static get myStaticProperty() {
return 'Goodbye';
}
}
console.log(MyClass.myStaticMethod()); // logs: "Hello"
console.log(MyClass.myStaticProperty); // logs: "Goodbye"
Podemos ver que las propiedades estáticas no están definidas en instancias de objetos:
const myClassInstance = new MyClass();
console.log(myClassInstance.myStaticProperty); // logs: undefined
Sin embargo, se definen en subclases:
class MySubClass extends MyClass {};
console.log(MySubClass.myStaticMethod()); // logs: "Hello"
console.log(MySubClass.myStaticProperty); // logs: "Goodbye"
Hechiceros y Setters
Getters y setters le permiten definir un comportamiento personalizado para leer y escribir una propiedad determinada en su clase. Para el usuario, parecen lo mismo que cualquier propiedad típica. Sin embargo, internamente una función personalizada que usted proporciona se usa para determinar el valor cuando se accede a la propiedad (el captador), y para realizar cualquier cambio necesario cuando se asigna la propiedad (el establecedor).
En una definición de class
, un captador se escribe como un método sin argumentos prefijado por la palabra clave get
. Un definidor es similar, excepto que acepta un argumento (se asigna el nuevo valor) y en su lugar se usa la palabra clave set
.
Aquí hay una clase de ejemplo que proporciona un getter y setter para su propiedad .name
. Cada vez que se asigna, registraremos el nuevo nombre en una matriz interna .names_
. Cada vez que se accede, devolveremos el último nombre.
class MyClass {
constructor() {
this.names_ = [];
}
set name(value) {
this.names_.push(value);
}
get name() {
return this.names_[this.names_.length - 1];
}
}
const myClassInstance = new MyClass();
myClassInstance.name = 'Joe';
myClassInstance.name = 'Bob';
console.log(myClassInstance.name); // logs: "Bob"
console.log(myClassInstance.names_); // logs: ["Joe", "Bob"]
Si solo define un definidor, intentar acceder a la propiedad siempre se devolverá undefined
.
const classInstance = new class {
set prop(value) {
console.log('setting', value);
}
};
classInstance.prop = 10; // logs: "setting", 10
console.log(classInstance.prop); // logs: undefined
Si solo define un captador, intentar asignar la propiedad no tendrá ningún efecto.
const classInstance = new class {
get prop() {
return 5;
}
};
classInstance.prop = 10;
console.log(classInstance.prop); // logs: 5
Herencia de clase
La herencia funciona igual que en otros lenguajes orientados a objetos: los métodos definidos en la superclase son accesibles en la subclase extendida.
Si la subclase declara su propio constructor, entonces debe invocar al constructor padre a través de super()
antes de poder acceder a this
.
class SuperClass {
constructor() {
this.logger = console.log;
}
log() {
this.logger(`Hello ${this.name}`);
}
}
class SubClass extends SuperClass {
constructor() {
super();
this.name = 'subclass';
}
}
const subClass = new SubClass();
subClass.log(); // logs: "Hello subclass"
Miembros privados
JavaScript no es técnicamente compatible con miembros privados como una función de idioma. La privacidad, descrita por Douglas Crockford , se emula a través de cierres (alcance de función preservada) que se generarán con cada llamada de instanciación de una función de constructor.
El ejemplo de Queue
muestra cómo, con las funciones de constructor, el estado local se puede preservar y hacer accesible también a través de métodos privilegiados.
class Queue {
constructor () { // - does generate a closure with each instantiation.
const list = []; // - local state ("private member").
this.enqueue = function (type) { // - privileged public method
// accessing the local state
list.push(type); // "writing" alike.
return type;
};
this.dequeue = function () { // - privileged public method
// accessing the local state
return list.shift(); // "reading / writing" alike.
};
}
}
var q = new Queue; //
//
q.enqueue(9); // ... first in ...
q.enqueue(8); //
q.enqueue(7); //
//
console.log(q.dequeue()); // 9 ... first out.
console.log(q.dequeue()); // 8
console.log(q.dequeue()); // 7
console.log(q); // {}
console.log(Object.keys(q)); // ["enqueue","dequeue"]
Con cada instanciación de un tipo de Queue
el constructor genera un cierre.
Por lo tanto, los dos métodos propios en enqueue
de un tipo de Queue
y dequeue
(ver Object.keys(q)
) todavía tienen acceso a la list
que continúa viviendo en su ámbito de distribución que, en el momento de la construcción, se ha conservado.
Haciendo uso de este patrón (emulando miembros privados a través de métodos públicos privilegiados), se debe tener en cuenta que, con cada instancia, se consumirá memoria adicional para cada método de propiedad propia (ya que es un código que no se puede compartir / reutilizar). Lo mismo es cierto para la cantidad / tamaño del estado que se va a almacenar dentro de dicho cierre.
Nombres de métodos dinámicos
También existe la capacidad de evaluar expresiones al nombrar métodos similares a cómo puede acceder a las propiedades de un objeto con []
. Esto puede ser útil para tener nombres de propiedades dinámicos, sin embargo, a menudo se usa junto con los símbolos.
let METADATA = Symbol('metadata');
class Car {
constructor(make, model) {
this.make = make;
this.model = model;
}
// example using symbols
[METADATA]() {
return {
make: this.make,
model: this.model
};
}
// you can also use any javascript expression
// this one is just a string, and could also be defined with simply add()
["add"](a, b) {
return a + b;
}
// this one is dynamically evaluated
[1 + 2]() {
return "three";
}
}
let MazdaMPV = new Car("Mazda", "MPV");
MazdaMPV.add(4, 5); // 9
MazdaMPV[3](); // "three"
MazdaMPV[METADATA](); // { make: "Mazda", model: "MPV" }
Métodos
Los métodos se pueden definir en clases para realizar una función y, opcionalmente, devolver un resultado.
Pueden recibir argumentos de la persona que llama.
class Something {
constructor(data) {
this.data = data
}
doSomething(text) {
return {
data: this.data,
text
}
}
}
var s = new Something({})
s.doSomething("hi") // returns: { data: {}, text: "hi" }
Gestionando datos privados con clases
Uno de los obstáculos más comunes en el uso de clases es encontrar el enfoque adecuado para manejar estados privados. Hay 4 soluciones comunes para manejar estados privados:
Usando simbolos
Los símbolos son nuevos tipos primitivos introducidos en ES2015, como se define en MDN
Un símbolo es un tipo de datos único e inmutable que se puede usar como un identificador para las propiedades del objeto.
Cuando se usa el símbolo como una clave de propiedad, no es enumerable.
Como tal, no se revelarán utilizando for var in
o Object.keys
.
Así podemos usar símbolos para almacenar datos privados.
const topSecret = Symbol('topSecret'); // our private key; will only be accessible on the scope of the module file
export class SecretAgent{
constructor(secret){
this[topSecret] = secret; // we have access to the symbol key (closure)
this.coverStory = 'just a simple gardner';
this.doMission = () => {
figureWhatToDo(topSecret[topSecret]); // we have access to topSecret
};
}
}
Debido a que los symbols
son únicos, debemos hacer referencia al símbolo original para acceder a la propiedad privada.
import {SecretAgent} from 'SecretAgent.js'
const agent = new SecretAgent('steal all the ice cream');
// ok lets try to get the secret out of him!
Object.keys(agent); // ['coverStory'] only cover story is public, our secret is kept.
agent[Symbol('topSecret')]; // undefined, as we said, symbols are always unique, so only the original symbol will help us to get the data.
Pero no es 100% privado; vamos a romper ese agente! Podemos usar el método Object.getOwnPropertySymbols
para obtener los símbolos del objeto.
const secretKeys = Object.getOwnPropertySymbols(agent);
agent[secretKeys[0]] // 'steal all the ice cream' , we got the secret.
Usando WeakMaps
WeakMap
es un nuevo tipo de objeto que se ha agregado para es6.
Como se define en MDN
El objeto WeakMap es una colección de pares clave / valor en los que las claves tienen una referencia débil. Las claves deben ser objetos y los valores pueden ser valores arbitrarios.
Otra característica importante de WeakMap
es, como se define en MDN .
La clave en un mapa débil se mantiene débilmente. Lo que esto significa es que, si no hay otras referencias sólidas a la clave, el recolector de basura eliminará toda la entrada del WeakMap.
La idea es utilizar WeakMap, como un mapa estático para toda la clase, para mantener cada instancia como clave y mantener los datos privados como un valor para esa clave de instancia.
Por lo tanto, solo dentro de la clase tendremos acceso a la colección WeakMap
.
Probemos a nuestro agente con WeakMap
:
const topSecret = new WeakMap(); // will hold all private data of all instances.
export class SecretAgent{
constructor(secret){
topSecret.set(this,secret); // we use this, as the key, to set it on our instance private data
this.coverStory = 'just a simple gardner';
this.doMission = () => {
figureWhatToDo(topSecret.get(this)); // we have access to topSecret
};
}
}
Debido a que const topSecret
se define dentro del cierre de nuestro módulo y como no lo vinculamos a nuestras propiedades de instancia, este enfoque es totalmente privado y no podemos llegar al agente topSecret
.
Definir todos los métodos dentro del constructor.
La idea aquí es simplemente definir todos nuestros métodos y miembros dentro del constructor y usar el cierre para acceder a miembros privados sin asignarlos a this
.
export class SecretAgent{
constructor(secret){
const topSecret = secret;
this.coverStory = 'just a simple gardner';
this.doMission = () => {
figureWhatToDo(topSecret); // we have access to topSecret
};
}
}
También en este ejemplo, los datos son 100% privados y no se pueden localizar fuera de clase, por lo que nuestro agente está a salvo.
Usando convenciones de nomenclatura
Decidiremos que cualquier propiedad privada será prefijada con _
.
Tenga en cuenta que para este enfoque los datos no son realmente privados.
export class SecretAgent{
constructor(secret){
this._topSecret = secret; // it private by convention
this.coverStory = 'just a simple gardner';
this.doMission = () => {
figureWhatToDo(this_topSecret);
};
}
}
Enlace de nombre de clase
El nombre de ClassDeclaration está vinculado de diferentes maneras en diferentes ámbitos:
- El ámbito en el que se define la clase -
let
vinculante - El alcance de la clase en sí, dentro de
{
y}
en laclass {}
- vinculaciónconst
class Foo {
// Foo inside this block is a const binding
}
// Foo here is a let binding
Por ejemplo,
class A {
foo() {
A = null; // will throw at runtime as A inside the class is a `const` binding
}
}
A = null; // will NOT throw as A here is a `let` binding
Esto no es lo mismo para una función -
function A() {
A = null; // works
}
A.prototype.foo = function foo() {
A = null; // works
}
A = null; // works